马良AI写作初始化仓库
This commit is contained in:
401
AINoval/lib/blocs/plan/plan_bloc.dart
Normal file
401
AINoval/lib/blocs/plan/plan_bloc.dart
Normal file
@@ -0,0 +1,401 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ainoval/models/novel_structure.dart' as novel_models;
|
||||
import 'package:ainoval/services/api_service/repositories/impl/editor_repository_impl.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../utils/logger.dart';
|
||||
|
||||
part 'plan_event.dart';
|
||||
part 'plan_state.dart';
|
||||
|
||||
class PlanBloc extends Bloc<PlanEvent, PlanState> {
|
||||
PlanBloc({
|
||||
required EditorRepositoryImpl repository,
|
||||
required this.novelId,
|
||||
}) : repository = repository,
|
||||
super(PlanInitial()) {
|
||||
on<LoadPlanContent>(_onLoadContent);
|
||||
on<UpdateActTitle>(_onUpdateActTitle);
|
||||
on<UpdateChapterTitle>(_onUpdateChapterTitle);
|
||||
on<UpdateSceneSummary>(_onUpdateSceneSummary);
|
||||
on<AddNewAct>(_onAddNewAct);
|
||||
on<AddNewChapter>(_onAddNewChapter);
|
||||
on<AddNewScene>(_onAddNewScene);
|
||||
on<MoveScene>(_onMoveScene);
|
||||
on<DeleteScene>(_onDeleteScene);
|
||||
}
|
||||
|
||||
final EditorRepositoryImpl repository;
|
||||
final String novelId;
|
||||
|
||||
Future<void> _onLoadContent(
|
||||
LoadPlanContent event, Emitter<PlanState> emit) async {
|
||||
emit(PlanLoading());
|
||||
|
||||
try {
|
||||
AppLogger.i('PlanBloc/_onLoadContent', '开始加载小说大纲数据');
|
||||
// 获取小说数据(带场景摘要)
|
||||
final novel = await repository.getNovelWithSceneSummaries(novelId);
|
||||
|
||||
if (novel == null) {
|
||||
emit(const PlanError(message: '无法加载小说大纲数据'));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(PlanLoaded(
|
||||
novel: novel,
|
||||
isDirty: false,
|
||||
isSaving: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(PlanError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUpdateActTitle(
|
||||
UpdateActTitle event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
// 更新标题逻辑
|
||||
final acts = currentState.novel.acts.map((act) {
|
||||
if (act.id == event.actId) {
|
||||
return act.copyWith(title: event.title);
|
||||
}
|
||||
return act;
|
||||
}).toList();
|
||||
|
||||
final updatedNovel = currentState.novel.copyWith(acts: acts);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isDirty: true,
|
||||
));
|
||||
|
||||
// 保存到服务器
|
||||
await repository.updateActTitle(
|
||||
novelId,
|
||||
event.actId,
|
||||
event.title,
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(isDirty: false));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
errorMessage: '更新Act标题失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUpdateChapterTitle(
|
||||
UpdateChapterTitle event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
// 更新标题逻辑
|
||||
final acts = currentState.novel.acts.map((act) {
|
||||
if (act.id == event.actId) {
|
||||
final chapters = act.chapters.map((chapter) {
|
||||
if (chapter.id == event.chapterId) {
|
||||
return chapter.copyWith(title: event.title);
|
||||
}
|
||||
return chapter;
|
||||
}).toList();
|
||||
return act.copyWith(chapters: chapters);
|
||||
}
|
||||
return act;
|
||||
}).toList();
|
||||
|
||||
final updatedNovel = currentState.novel.copyWith(acts: acts);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isDirty: true,
|
||||
));
|
||||
|
||||
// 保存到服务器
|
||||
await repository.updateChapterTitle(
|
||||
novelId,
|
||||
event.actId,
|
||||
event.chapterId,
|
||||
event.title,
|
||||
);
|
||||
|
||||
emit(currentState.copyWith(isDirty: false));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
errorMessage: '更新Chapter标题失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUpdateSceneSummary(
|
||||
UpdateSceneSummary event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
// 更新摘要逻辑
|
||||
bool updated = false;
|
||||
final acts = currentState.novel.acts.map((act) {
|
||||
if (act.id == event.actId) {
|
||||
final chapters = act.chapters.map((chapter) {
|
||||
if (chapter.id == event.chapterId) {
|
||||
final scenes = chapter.scenes.map((scene) {
|
||||
if (scene.id == event.sceneId) {
|
||||
updated = true;
|
||||
final updatedSummary = novel_models.Summary(
|
||||
id: scene.summary.id,
|
||||
content: event.summary,
|
||||
);
|
||||
return scene.copyWith(summary: updatedSummary);
|
||||
}
|
||||
return scene;
|
||||
}).toList();
|
||||
return chapter.copyWith(scenes: scenes);
|
||||
}
|
||||
return chapter;
|
||||
}).toList();
|
||||
return act.copyWith(chapters: chapters);
|
||||
}
|
||||
return act;
|
||||
}).toList();
|
||||
|
||||
if (!updated) {
|
||||
emit(currentState.copyWith(
|
||||
errorMessage: '未找到对应的场景',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
final updatedNovel = currentState.novel.copyWith(acts: acts);
|
||||
|
||||
// 先更新UI以立即反映更改
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isDirty: true,
|
||||
));
|
||||
|
||||
// 保存到服务器
|
||||
await repository.updateSummary(
|
||||
novelId,
|
||||
event.actId,
|
||||
event.chapterId,
|
||||
event.sceneId,
|
||||
event.summary,
|
||||
);
|
||||
|
||||
// 只更新isDirty标志,保持更新后的novel对象
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isDirty: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
errorMessage: '更新场景摘要失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onAddNewAct(
|
||||
AddNewAct event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
emit(currentState.copyWith(isSaving: true));
|
||||
|
||||
// 调用API创建新Act
|
||||
final updatedNovel = await repository.addNewAct(
|
||||
novelId,
|
||||
event.title,
|
||||
);
|
||||
|
||||
if (updatedNovel == null) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '添加新Act失败',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isSaving: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '添加新Act失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onAddNewChapter(
|
||||
AddNewChapter event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
emit(currentState.copyWith(isSaving: true));
|
||||
|
||||
// 调用API创建新Chapter
|
||||
final updatedNovel = await repository.addNewChapter(
|
||||
novelId,
|
||||
event.actId,
|
||||
event.title,
|
||||
);
|
||||
|
||||
if (updatedNovel == null) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '添加新Chapter失败',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isSaving: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '添加新Chapter失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onAddNewScene(
|
||||
AddNewScene event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
emit(currentState.copyWith(isSaving: true));
|
||||
|
||||
// 调用API创建新Scene
|
||||
final updatedNovel = await repository.addNewScene(
|
||||
novelId,
|
||||
event.actId,
|
||||
event.chapterId,
|
||||
);
|
||||
|
||||
if (updatedNovel == null) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '添加新Scene失败',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isSaving: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '添加新Scene失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onMoveScene(
|
||||
MoveScene event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
emit(currentState.copyWith(isSaving: true));
|
||||
|
||||
// 调用API移动Scene
|
||||
final updatedNovel = await repository.moveScene(
|
||||
novelId,
|
||||
event.sourceActId,
|
||||
event.sourceChapterId,
|
||||
event.sourceSceneId,
|
||||
event.targetActId,
|
||||
event.targetChapterId,
|
||||
event.targetIndex,
|
||||
);
|
||||
|
||||
if (updatedNovel == null) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '移动场景失败',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isSaving: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '移动场景失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onDeleteScene(
|
||||
DeleteScene event, Emitter<PlanState> emit) async {
|
||||
final currentState = state;
|
||||
if (currentState is PlanLoaded) {
|
||||
try {
|
||||
emit(currentState.copyWith(isSaving: true));
|
||||
|
||||
// 调用API删除场景
|
||||
final success = await repository.deleteScene(
|
||||
novelId,
|
||||
event.actId,
|
||||
event.chapterId,
|
||||
event.sceneId,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '删除场景失败',
|
||||
));
|
||||
return;
|
||||
}
|
||||
|
||||
// 从小说结构中删除场景
|
||||
final updatedActs = currentState.novel.acts.map((act) {
|
||||
if (act.id == event.actId) {
|
||||
final updatedChapters = act.chapters.map((chapter) {
|
||||
if (chapter.id == event.chapterId) {
|
||||
final updatedScenes = chapter.scenes
|
||||
.where((scene) => scene.id != event.sceneId)
|
||||
.toList();
|
||||
return chapter.copyWith(scenes: updatedScenes);
|
||||
}
|
||||
return chapter;
|
||||
}).toList();
|
||||
return act.copyWith(chapters: updatedChapters);
|
||||
}
|
||||
return act;
|
||||
}).toList();
|
||||
|
||||
final updatedNovel = currentState.novel.copyWith(acts: updatedActs);
|
||||
|
||||
emit(currentState.copyWith(
|
||||
novel: updatedNovel,
|
||||
isSaving: false,
|
||||
));
|
||||
} catch (e) {
|
||||
emit(currentState.copyWith(
|
||||
isSaving: false,
|
||||
errorMessage: '删除场景失败: ${e.toString()}',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
138
AINoval/lib/blocs/plan/plan_event.dart
Normal file
138
AINoval/lib/blocs/plan/plan_event.dart
Normal file
@@ -0,0 +1,138 @@
|
||||
part of 'plan_bloc.dart';
|
||||
|
||||
abstract class PlanEvent extends Equatable {
|
||||
const PlanEvent();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class LoadPlanContent extends PlanEvent {
|
||||
const LoadPlanContent();
|
||||
}
|
||||
|
||||
class UpdateActTitle extends PlanEvent {
|
||||
const UpdateActTitle({
|
||||
required this.actId,
|
||||
required this.title,
|
||||
});
|
||||
final String actId;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [actId, title];
|
||||
}
|
||||
|
||||
class UpdateChapterTitle extends PlanEvent {
|
||||
const UpdateChapterTitle({
|
||||
required this.actId,
|
||||
required this.chapterId,
|
||||
required this.title,
|
||||
});
|
||||
final String actId;
|
||||
final String chapterId;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [actId, chapterId, title];
|
||||
}
|
||||
|
||||
class UpdateSceneSummary extends PlanEvent {
|
||||
const UpdateSceneSummary({
|
||||
required this.novelId,
|
||||
required this.actId,
|
||||
required this.chapterId,
|
||||
required this.sceneId,
|
||||
required this.summary,
|
||||
});
|
||||
final String novelId;
|
||||
final String actId;
|
||||
final String chapterId;
|
||||
final String sceneId;
|
||||
final String summary;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [novelId, actId, chapterId, sceneId, summary];
|
||||
}
|
||||
|
||||
class AddNewAct extends PlanEvent {
|
||||
const AddNewAct({this.title = '新Act'});
|
||||
final String title;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [title];
|
||||
}
|
||||
|
||||
class AddNewChapter extends PlanEvent {
|
||||
const AddNewChapter({
|
||||
required this.novelId,
|
||||
required this.actId,
|
||||
this.title = '新章节',
|
||||
});
|
||||
final String novelId;
|
||||
final String actId;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [novelId, actId, title];
|
||||
}
|
||||
|
||||
class AddNewScene extends PlanEvent {
|
||||
const AddNewScene({
|
||||
required this.novelId,
|
||||
required this.actId,
|
||||
required this.chapterId,
|
||||
});
|
||||
final String novelId;
|
||||
final String actId;
|
||||
final String chapterId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [novelId, actId, chapterId];
|
||||
}
|
||||
|
||||
class MoveScene extends PlanEvent {
|
||||
const MoveScene({
|
||||
required this.novelId,
|
||||
required this.sourceActId,
|
||||
required this.sourceChapterId,
|
||||
required this.sourceSceneId,
|
||||
required this.targetActId,
|
||||
required this.targetChapterId,
|
||||
required this.targetIndex,
|
||||
});
|
||||
final String novelId;
|
||||
final String sourceActId;
|
||||
final String sourceChapterId;
|
||||
final String sourceSceneId;
|
||||
final String targetActId;
|
||||
final String targetChapterId;
|
||||
final int targetIndex;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
novelId,
|
||||
sourceActId,
|
||||
sourceChapterId,
|
||||
sourceSceneId,
|
||||
targetActId,
|
||||
targetChapterId,
|
||||
targetIndex,
|
||||
];
|
||||
}
|
||||
|
||||
class DeleteScene extends PlanEvent {
|
||||
const DeleteScene({
|
||||
required this.novelId,
|
||||
required this.actId,
|
||||
required this.chapterId,
|
||||
required this.sceneId,
|
||||
});
|
||||
final String novelId;
|
||||
final String actId;
|
||||
final String chapterId;
|
||||
final String sceneId;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [novelId, actId, chapterId, sceneId];
|
||||
}
|
||||
61
AINoval/lib/blocs/plan/plan_state.dart
Normal file
61
AINoval/lib/blocs/plan/plan_state.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
part of 'plan_bloc.dart';
|
||||
|
||||
abstract class PlanState extends Equatable {
|
||||
const PlanState();
|
||||
|
||||
@override
|
||||
List<Object?> get props => [];
|
||||
}
|
||||
|
||||
class PlanInitial extends PlanState {}
|
||||
|
||||
class PlanLoading extends PlanState {}
|
||||
|
||||
class PlanLoaded extends PlanState {
|
||||
|
||||
const PlanLoaded({
|
||||
required this.novel,
|
||||
this.isDirty = false,
|
||||
this.isSaving = false,
|
||||
this.lastSaveTime,
|
||||
this.errorMessage,
|
||||
});
|
||||
final novel_models.Novel novel;
|
||||
final bool isDirty;
|
||||
final bool isSaving;
|
||||
final DateTime? lastSaveTime;
|
||||
final String? errorMessage;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
novel,
|
||||
isDirty,
|
||||
isSaving,
|
||||
lastSaveTime,
|
||||
errorMessage,
|
||||
];
|
||||
|
||||
PlanLoaded copyWith({
|
||||
novel_models.Novel? novel,
|
||||
bool? isDirty,
|
||||
bool? isSaving,
|
||||
DateTime? lastSaveTime,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return PlanLoaded(
|
||||
novel: novel ?? this.novel,
|
||||
isDirty: isDirty ?? this.isDirty,
|
||||
isSaving: isSaving ?? this.isSaving,
|
||||
lastSaveTime: lastSaveTime ?? this.lastSaveTime,
|
||||
errorMessage: errorMessage ?? this.errorMessage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PlanError extends PlanState {
|
||||
const PlanError({required this.message});
|
||||
final String message;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [message];
|
||||
}
|
||||
Reference in New Issue
Block a user