import 'package:ainoval/utils/logger.dart'; /// 小说模型 class Novel { Novel({ required this.id, required this.title, this.coverUrl= '', required this.createdAt, required this.updatedAt, this.acts = const [], this.lastEditedChapterId, this.author, this.wordCount = 0, this.readTime = 0, this.version = 1, this.contributors = const [], }); /// 从JSON创建Novel实例 factory Novel.fromJson(Map json) { AppLogger.v( 'NovelModel', 'Parsing Novel from JSON: ${json['id']}'); // 添加日志确认进入 try { // --- 这是关键部分 --- List parsedActs = []; // 处理acts数据 - 优先检查structure.acts路径 if (json.containsKey('structure') && json['structure'] is Map) { final structure = json['structure'] as Map; if (structure.containsKey('acts') && structure['acts'] is List) { AppLogger.v('NovelModel', 'Found "structure.acts" list with ${(structure['acts'] as List).length} items.'); parsedActs = (structure['acts'] as List) .map((actJson) { if (actJson is Map) { // 对列表中的每个元素调用 Act.fromJson return Act.fromJson(actJson); } else { // 处理无效数据项 AppLogger.w('NovelModel', 'Invalid item in "structure.acts" list: $actJson'); return null; // 返回null让whereType过滤掉 } }) .whereType() // 过滤掉可能的 null 值 .toList(); AppLogger.v('NovelModel', 'Successfully parsed ${parsedActs.length} acts from structure.acts.'); } else { AppLogger.w('NovelModel', '"structure.acts" field is missing, null, or not a list in JSON for Novel ${json['id']}'); } } // 如果在structure中没有找到有效的acts,尝试直接从json的acts字段读取 else if (json.containsKey('acts') && json['acts'] is List) { AppLogger.v('NovelModel', 'Found direct "acts" list with ${(json['acts'] as List).length} items.'); parsedActs = (json['acts'] as List) .map((actJson) { if (actJson is Map) { return Act.fromJson(actJson); } else { AppLogger.w('NovelModel', 'Invalid item in direct "acts" list: $actJson'); return null; } }) .whereType() .toList(); AppLogger.v('NovelModel', 'Successfully parsed ${parsedActs.length} acts from direct acts field.'); } else { AppLogger.w('NovelModel', 'No valid acts field found in JSON for Novel ${json['id']}'); } // --- 关键部分结束 --- // 解析元数据 final metadata = json['metadata'] as Map? ?? {}; final wordCount = metadata['wordCount'] is int ? metadata['wordCount'] as int : 0; final readTime = metadata['readTime'] is int ? metadata['readTime'] as int : 0; final version = metadata['version'] is int ? metadata['version'] as int : 1; // 处理contributors列表 List contributors = []; if (metadata.containsKey('contributors') && metadata['contributors'] is List) { // 尝试转换每个元素为String for (var item in metadata['contributors'] as List) { if (item is String) { contributors.add(item); } } } // 解析日期 DateTime createdAt; DateTime updatedAt; try { createdAt = json.containsKey('createdAt') && json['createdAt'] is String ? DateTime.parse(json['createdAt'] as String) : DateTime.now(); } catch (e) { AppLogger.w('NovelModel', '解析createdAt失败,使用当前时间', e); createdAt = DateTime.now(); } try { updatedAt = json.containsKey('updatedAt') && json['updatedAt'] is String ? DateTime.parse(json['updatedAt'] as String) : DateTime.now(); } catch (e) { AppLogger.w('NovelModel', '解析updatedAt失败,使用当前时间', e); updatedAt = DateTime.now(); } // 处理封面URL字段 String coverUrl = ''; if (json.containsKey('coverUrl') && json['coverUrl'] is String) { coverUrl = json['coverUrl'] as String; } else if (json.containsKey('coverImage') && json['coverImage'] is String) { // 兼容后端可能使用coverImage字段 coverUrl = json['coverImage'] as String; } // 创建Novel对象 return Novel( id: json['id'] as String? ?? 'unknown_${DateTime.now().millisecondsSinceEpoch}', title: json['title'] as String? ?? '无标题', coverUrl: coverUrl, createdAt: createdAt, updatedAt: updatedAt, acts: parsedActs, lastEditedChapterId: json['lastEditedChapterId'] as String?, author: json['author'] != null ? Author.fromJson(json['author'] as Map) : null, wordCount: wordCount, readTime: readTime, version: version, contributors: contributors, ); } catch (e, stackTrace) { AppLogger.e('NovelModel', 'Error parsing Novel from JSON: ${json['id']}', e, stackTrace); // 返回一个基本的空Novel对象,避免应用崩溃 return Novel( id: json['id'] as String? ?? 'error_${DateTime.now().millisecondsSinceEpoch}', title: '解析错误 - ${json['title'] ?? '无标题'}', coverUrl: '', createdAt: DateTime.now(), updatedAt: DateTime.now(), acts: [], wordCount: 0, ); } } final String id; final String title; final String coverUrl; final DateTime createdAt; final DateTime updatedAt; final List acts; final String? lastEditedChapterId; // 上次编辑的章节ID final Author? author; // 作者信息 final int wordCount; // 总字数(来自元数据) final int readTime; // 估计阅读时间(分钟) final int version; // 文档版本号 final List contributors; // 贡献者列表 /// 计算小说总字数(如果需要动态计算) int calculateWordCount() { int totalWordCount = 0; for (final act in acts) { for (final chapter in act.chapters) { for (final scene in chapter.scenes) { totalWordCount += scene.wordCount; } } } return totalWordCount; } /// 计算小说总场景数(考虑 sceneIds 字段) int getSceneCount() { int totalSceneCount = 0; //AppLogger.d('Novel', '开始计算场景总数'); for (final act in acts) { int actSceneCount = 0; for (final chapter in act.chapters) { // 使用 sceneCount 属性,它会返回 scenes 和 sceneIds 中的较大值 int chapterSceneCount = chapter.sceneCount; actSceneCount += chapterSceneCount; //AppLogger.d('Novel', '章节 ${chapter.id} 场景数: scenes=${chapter.scenes.length}, sceneIds=${chapter.sceneIds.length}, 取较大值=${chapterSceneCount}'); } totalSceneCount += actSceneCount; //AppLogger.d('Novel', '卷 ${act.id} 场景总数: $actSceneCount'); } //AppLogger.d('Novel', '小说场景总数: $totalSceneCount'); return totalSceneCount; } /// 计算小说总章节数 int getChapterCount() { int totalChapterCount = 0; for (final act in acts) { totalChapterCount += act.chapters.length; } return totalChapterCount; } /// 计算小说总卷数 int getActCount() { return acts.length; } /// 转换为JSON Map toJson() { return { 'id': id, 'title': title, 'coverUrl': coverUrl, 'createdAt': createdAt.toIso8601String(), 'updatedAt': updatedAt.toIso8601String(), 'acts': acts.map((act) => act.toJson()).toList(), 'lastEditedChapterId': lastEditedChapterId, 'author': author?.toJson(), 'metadata': { 'wordCount': wordCount, 'readTime': readTime, 'version': version, 'contributors': contributors, }, }; } /// 创建Novel的副本 Novel copyWith({ String? id, String? title, String? coverUrl, DateTime? createdAt, DateTime? updatedAt, List? acts, String? lastEditedChapterId, Author? author, int? wordCount, int? readTime, int? version, List? contributors, }) { return Novel( id: id ?? this.id, title: title ?? this.title, coverUrl: coverUrl?? this.coverUrl, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, acts: acts ?? this.acts, lastEditedChapterId: lastEditedChapterId ?? this.lastEditedChapterId, author: author ?? this.author, wordCount: wordCount ?? this.wordCount, readTime: readTime ?? this.readTime, version: version ?? this.version, contributors: contributors ?? this.contributors, ); } /// 创建一个空的小说结构 static Novel createEmpty(String id, String title) { final now = DateTime.now(); return Novel( id: id, title: title, createdAt: now, updatedAt: now, acts: [], ); } /// 添加一个新的Act Novel addAct(String title) { final newAct = Act( id: 'act_${DateTime.now().millisecondsSinceEpoch}', title: title, order: acts.length + 1, chapters: [], ); return copyWith( acts: [...acts, newAct], updatedAt: DateTime.now(), ); } /// 获取指定Act Act? getAct(String actId) { try { return acts.firstWhere((act) => act.id == actId); } catch (e) { return null; } } /// 获取指定Chapter Chapter? getChapter(String actId, String chapterId) { final act = getAct(actId); if (act == null) return null; try { return act.chapters.firstWhere((chapter) => chapter.id == chapterId); } catch (e) { return null; } } /// 根据章节ID直接获取章节,不需要知道Act ID Chapter? getChapterById(String chapterId) { for (final act in acts) { try { final chapter = act.chapters.firstWhere((chapter) => chapter.id == chapterId); return chapter; } catch (e) { // 继续查找下一个act } } return null; } /// 获取指定Scene Scene? getScene(String actId, String chapterId, {String? sceneId}) { final chapter = getChapter(actId, chapterId); if (chapter == null) return null; if (sceneId != null) { // 如果提供了sceneId,则获取特定Scene return chapter.getScene(sceneId); } else if (chapter.scenes.isNotEmpty) { // 否则返回第一个Scene return chapter.scenes.first; } return null; } /// 获取上下文章节(前后n章) List getContextChapters(String chapterId, int n) { // 提取所有章节 List allChapters = []; for (final act in acts) { allChapters.addAll(act.chapters); } // 按order排序 allChapters.sort((a, b) => a.order.compareTo(b.order)); // 找到当前章节的索引 int currentIndex = allChapters.indexWhere((chapter) => chapter.id == chapterId); if (currentIndex == -1) { // 如果找不到当前章节,返回前n章 return allChapters.take(n).toList(); } // 计算前后n章的范围 int startIndex = (currentIndex - n) < 0 ? 0 : (currentIndex - n); int endIndex = (currentIndex + n) >= allChapters.length ? allChapters.length - 1 : (currentIndex + n); // 提取前后n章 return allChapters.sublist(startIndex, endIndex + 1); } /// 更新最后编辑的章节ID Novel updateLastEditedChapter(String chapterId) { return copyWith( lastEditedChapterId: chapterId, updatedAt: DateTime.now(), ); } } /// 幕模型(如Act 1, Act 2等) class Act { Act({ required this.id, required this.title, required this.order, this.chapters = const [], }); /// 从JSON创建Act实例 factory Act.fromJson(Map json) { List parsedChapters = []; if (json['chapters'] != null && json['chapters'] is List) { parsedChapters = (json['chapters'] as List) .map((chapterJson) => Chapter.fromJson(chapterJson as Map)) .toList(); } return Act( id: json['id'] as String, title: json['title'] as String, order: json['order'] as int, chapters: parsedChapters, // 使用解析后的列表 ); } final String id; final String title; final int order; final List chapters; /// 计算Act的总字数 int get wordCount { return chapters.fold(0, (sum, chapter) => sum + chapter.wordCount); } /// 转换为JSON Map toJson() { return { 'id': id, 'title': title, 'order': order, 'chapters': chapters.map((chapter) => chapter.toJson()).toList(), }; } /// 创建Act的副本 Act copyWith({ String? id, String? title, int? order, List? chapters, }) { return Act( id: id ?? this.id, title: title ?? this.title, order: order ?? this.order, chapters: chapters ?? this.chapters, ); } /// 添加一个新的Chapter Act addChapter(String title) { // 创建一个默认的Scene final defaultScene = Scene.createEmpty(); final newChapter = Chapter( id: 'chapter_${DateTime.now().millisecondsSinceEpoch}', title: title, order: chapters.length + 1, scenes: [defaultScene], // 包含一个默认的Scene ); return copyWith( chapters: [...chapters, newChapter], ); } /// 获取指定Chapter Chapter? getChapter(String chapterId) { try { return chapters.firstWhere((chapter) => chapter.id == chapterId); } catch (e) { return null; } } } /// 章节模型 class Chapter { Chapter({ required this.id, required this.title, required this.order, this.scenes = const [], this.sceneIds = const [], // 添加 sceneIds 字段 }); /// 从JSON创建Chapter实例 factory Chapter.fromJson(Map json) { List parsedScenes = []; List parsedSceneIds = []; // 解析场景列表 if (json['scenes'] != null && json['scenes'] is List) { parsedScenes = (json['scenes'] as List) .map((sceneJson) => Scene.fromJson(sceneJson as Map)) .toList(); } // 解析场景ID列表 if (json['sceneIds'] != null && json['sceneIds'] is List) { parsedSceneIds = (json['sceneIds'] as List) .map((id) => id.toString()) .toList(); } return Chapter( id: json['id'] as String, title: json['title'] as String, order: json['order'] as int, scenes: parsedScenes, sceneIds: parsedSceneIds, // 保存场景ID列表 ); } final String id; final String title; final int order; final List scenes; final List sceneIds; // 保存从后端返回的场景ID列表 /// 计算章节的总字数 int get wordCount { return scenes.fold(0, (sum, scene) => sum + scene.wordCount); } /// 获取场景总数(scenes列表或sceneIds列表中的较大值) int get sceneCount { int scenesLength = scenes.length; int sceneIdsLength = sceneIds.length; int result = scenesLength > sceneIdsLength ? scenesLength : sceneIdsLength; //AppLogger.d('Chapter', '章节 $id 场景计数: scenes=$scenesLength, sceneIds=$sceneIdsLength, 取较大值=$result'); return result; } /// 转换为JSON Map toJson() { return { 'id': id, 'title': title, 'order': order, 'scenes': scenes.map((scene) => scene.toJson()).toList(), 'sceneIds': sceneIds, // 添加场景ID列表 }; } /// 创建Chapter的副本 Chapter copyWith({ String? id, String? title, int? order, List? scenes, List? sceneIds, // 添加sceneIds参数 }) { return Chapter( id: id ?? this.id, title: title ?? this.title, order: order ?? this.order, scenes: scenes ?? this.scenes, sceneIds: sceneIds ?? this.sceneIds, // 设置sceneIds ); } /// 添加一个新的Scene void addScene(Scene newScene) { scenes.add(newScene); } /// 获取指定Scene Scene? getScene(String sceneId) { try { return scenes.firstWhere((scene) => scene.id == sceneId); } catch (e) { return null; } } /// 更新指定Scene Chapter updateScene(String sceneId, Scene updatedScene) { final updatedScenes = scenes.map((scene) { if (scene.id == sceneId) { return updatedScene; } return scene; }).toList(); return copyWith(scenes: updatedScenes); } } /// 场景模型 class Scene { Scene({ required this.id, required this.content, required this.wordCount, required this.summary, required this.lastEdited, this.title = '', this.actId = '', this.chapterId = '', this.version = 1, this.history = const [], }); /// 从JSON创建Scene实例 factory Scene.fromJson(Map json) { // 创建安全的Summary对象 Summary summaryObj; try { // 处理summary字段 - 可能是字符串(后端)或对象(前端) if (json.containsKey('summary')) { final summaryData = json['summary']; if (summaryData is Map) { // 如果是对象格式,直接解析 summaryObj = Summary.fromJson(summaryData); } else if (summaryData is String) { // 如果是字符串格式(后端发送的),创建Summary对象 final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(); summaryObj = Summary( id: '${sceneId}_summary', content: summaryData, ); } else { // 其他格式,创建默认Summary final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(); summaryObj = Summary( id: '${sceneId}_summary', content: '', ); AppLogger.w('Scene.fromJson', '场景 $sceneId 的摘要字段类型不支持: ${summaryData.runtimeType}'); } } else { // 创建默认Summary final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(); summaryObj = Summary( id: '${sceneId}_summary', content: '', ); AppLogger.w('Scene.fromJson', '场景 $sceneId 缺少摘要字段,已创建默认摘要'); } } catch (e) { // 处理任何异常,创建默认Summary final sceneId = json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(); summaryObj = Summary( id: '${sceneId}_summary', content: '', ); AppLogger.e('Scene.fromJson', '解析场景 $sceneId 的摘要时出错', e); } // 安全解析lastEdited字段,支持多种日期格式 DateTime lastEditedDate; try { if (json.containsKey('lastEdited') && json['lastEdited'] != null) { final lastEditedStr = json['lastEdited'].toString(); lastEditedDate = _parseDateTime(lastEditedStr); } else if (json.containsKey('updatedAt') && json['updatedAt'] != null) { // 兼容后端可能使用updatedAt字段 final updatedAtStr = json['updatedAt'].toString(); lastEditedDate = _parseDateTime(updatedAtStr); } else { lastEditedDate = DateTime.now(); AppLogger.w('Scene.fromJson', '场景 ${json['id']} 缺少时间字段,使用当前时间'); } } catch (e) { lastEditedDate = DateTime.now(); AppLogger.w('Scene.fromJson', '解析场景 ${json['id']} 的时间字段失败,使用当前时间', e); } return Scene( id: json['id'] ?? DateTime.now().millisecondsSinceEpoch.toString(), content: json['content'] ?? '', wordCount: json['wordCount'] ?? 0, summary: summaryObj, lastEdited: lastEditedDate, title: json['title'] ?? '', actId: json['actId'] ?? '', chapterId: json['chapterId'] ?? '', version: json['version'] ?? 1, history: [], ); } final String id; final String content; final int wordCount; final Summary summary; final DateTime lastEdited; final String title; final String actId; final String chapterId; final int version; final List history; /// 解析多种日期格式的工具方法 static DateTime _parseDateTime(String dateTimeStr) { if (dateTimeStr.isEmpty) { return DateTime.now(); } try { // 尝试标准ISO格式 return DateTime.parse(dateTimeStr); } catch (e1) { try { // 尝试处理带毫秒的格式 "yyyy-MM-dd'T'HH:mm:ss.SSS" if (dateTimeStr.contains('T') && dateTimeStr.contains('.')) { // 如果包含时区信息,先移除 String cleanStr = dateTimeStr; if (cleanStr.endsWith('Z')) { cleanStr = cleanStr.substring(0, cleanStr.length - 1); } if (cleanStr.contains('+') || cleanStr.lastIndexOf('-') > 10) { // 移除时区偏移 final timeZoneIndex = cleanStr.lastIndexOf('+') > cleanStr.lastIndexOf('-') ? cleanStr.lastIndexOf('+') : cleanStr.lastIndexOf('-'); if (timeZoneIndex > 10) { cleanStr = cleanStr.substring(0, timeZoneIndex); } } return DateTime.parse(cleanStr); } // 尝试其他常见格式 // 格式:yyyy-MM-dd HH:mm:ss if (dateTimeStr.contains(' ') && !dateTimeStr.contains('T')) { final parts = dateTimeStr.split(' '); if (parts.length == 2) { final datePart = parts[0]; final timePart = parts[1]; final isoStr = '${datePart}T$timePart'; return DateTime.parse(isoStr); } } throw e1; // 如果都失败了,抛出原始异常 } catch (e2) { AppLogger.w('Scene._parseDateTime', '无法解析日期格式: $dateTimeStr,使用当前时间'); return DateTime.now(); } } } /// 转换为JSON Map toJson() { return { 'id': id, 'content': content, 'wordCount': wordCount, 'summary': summary.toJson(), 'lastEdited': lastEdited.toIso8601String(), 'title': title, 'actId': actId, 'chapterId': chapterId, 'version': version, 'history': history.map((entry) => entry.toJson()).toList(), }; } /// 创建Scene的副本 Scene copyWith({ String? id, String? content, int? wordCount, Summary? summary, DateTime? lastEdited, String? title, String? actId, String? chapterId, int? version, List? history, }) { return Scene( id: id ?? this.id, content: content ?? this.content, wordCount: wordCount ?? this.wordCount, summary: summary ?? this.summary, lastEdited: lastEdited ?? this.lastEdited, title: title ?? this.title, actId: actId ?? this.actId, chapterId: chapterId ?? this.chapterId, version: version ?? this.version, history: history ?? this.history, ); } /// 创建一个空的场景 static Scene createEmpty() { const defaultContent = '{"ops":[{"insert":"\\n"}]}'; // <-- 确保是这个值 final now = DateTime.now(); return Scene( id: DateTime.now().millisecondsSinceEpoch.toString(), content: defaultContent, wordCount: 0, summary: Summary( id: '${DateTime.now().millisecondsSinceEpoch}_summary', content: '', ), lastEdited: now, title: '', actId: '', chapterId: '', version: 1, history: [], ); } /// 创建一个默认的场景 static Scene createDefault(String sceneIdBase) { // 使用正确Quill Delta格式包含ops对象的内容 const defaultContent = '{"ops":[{"insert":"\\n"}]}'; final now = DateTime.now(); return Scene( id: sceneIdBase, content: defaultContent, wordCount: 0, summary: Summary( id: '${sceneIdBase}_summary', content: '', ), lastEdited: now, title: '新场景', actId: '', chapterId: '', version: 1, history: [], ); } } /// 摘要模型 class Summary { Summary({ required this.id, required this.content, }); /// 从JSON创建Summary实例 factory Summary.fromJson(Map json) { return Summary( id: json['id'] as String, content: json['content'] as String, ); } final String id; final String content; /// 转换为JSON Map toJson() { return { 'id': id, 'content': content, }; } /// 创建Summary的副本 Summary copyWith({ String? id, String? content, }) { return Summary( id: id ?? this.id, content: content ?? this.content, ); } /// 创建一个空的摘要 static Summary createEmpty() { return Summary( id: 'summary_${DateTime.now().millisecondsSinceEpoch}', content: '', ); } } class HistoryEntry { HistoryEntry({ this.content, required this.updatedAt, required this.updatedBy, required this.reason, }); factory HistoryEntry.fromJson(Map json) { DateTime updatedAt; try { updatedAt = DateTime.parse(json['updatedAt']); } catch (e) { updatedAt = DateTime.now(); } return HistoryEntry( content: json['content'], updatedAt: updatedAt, updatedBy: json['updatedBy'] ?? 'unknown', reason: json['reason'] ?? '', ); } final String? content; final DateTime updatedAt; final String updatedBy; final String reason; Map toJson() => { 'content': content, 'updatedAt': updatedAt.toIso8601String(), 'updatedBy': updatedBy, 'reason': reason, }; } /// 作者信息模型 class Author { Author({ required this.id, required this.username, }); /// 从JSON创建Author实例 factory Author.fromJson(Map json) { return Author( id: json['id'] ?? '', username: json['username'] ?? '未知作者', ); } final String id; final String username; /// 转换为JSON Map toJson() { return { 'id': id, 'username': username, }; } /// 创建Author的副本 Author copyWith({ String? id, String? username, }) { return Author( id: id ?? this.id, username: username ?? this.username, ); } }