// import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:ainoval/models/novel_structure.dart'; import 'package:ainoval/models/novel_setting_item.dart'; import 'package:ainoval/models/novel_snippet.dart'; import 'package:ainoval/models/setting_group.dart'; import 'package:ainoval/models/setting_type.dart'; /// 上下文选择类型枚举 enum ContextSelectionType { fullNovelText('所有章节内容', Icons.menu_book), fullOutline('完整大纲', Icons.format_list_bulleted), novelBasicInfo('小说基本信息', Icons.info_outline), recentChaptersContent('最近5章内容', Icons.history_edu), recentChaptersSummary('最近5章摘要', Icons.summarize), // 固定分组(仅用于前端分组显示,不参与API传输) contentFixedGroup('内容分组', Icons.article_outlined), summaryFixedGroup('摘要分组', Icons.summarize), // 新增固定类型(内容/摘要) currentSceneContent('当前场景内容', Icons.movie_outlined), currentSceneSummary('当前场景摘要', Icons.summarize), currentChapterContent('当前章节内容', Icons.article_outlined), currentChapterSummaries('当前章节所有摘要', Icons.summarize), previousChaptersContent('之前所有章节内容', Icons.history_edu), previousChaptersSummary('之前所有章节摘要', Icons.summarize), acts('卷', Icons.bookmark_border), chapters('章节', Icons.article_outlined), scenes('场景', Icons.movie_outlined), snippets('片段', Icons.content_cut), settings('设定', Icons.settings_outlined), settingGroups('设定分组', Icons.folder_special_outlined), settingsByType('按设定类型', Icons.category_outlined), codexEntries('知识条目', Icons.library_books_outlined), entriesByType('按条目类型', Icons.category_outlined), entriesByDetail('按条目详情', Icons.info_outline), entriesByCategory('按条目分类', Icons.folder_outlined), entriesByTag('按条目标签', Icons.local_offer_outlined); const ContextSelectionType(this.displayName, this.icon); final String displayName; final IconData icon; } /// 上下文选择项 class ContextSelectionItem { const ContextSelectionItem({ required this.id, required this.title, required this.type, this.subtitle, this.children = const [], this.parentId, this.metadata = const {}, this.selectionState = SelectionState.unselected, this.order = 0, }); /// 唯一标识 final String id; /// 显示标题 final String title; /// 选择类型 final ContextSelectionType type; /// 副标题(可选) final String? subtitle; /// 子项列表 final List children; /// 父项ID(用于扁平化结构) final String? parentId; /// 元数据(可存储字数、章节数等信息) final Map metadata; /// 选择状态 final SelectionState selectionState; /// 排序顺序 final int order; /// 创建副本 ContextSelectionItem copyWith({ String? id, String? title, ContextSelectionType? type, String? subtitle, List? children, String? parentId, Map? metadata, SelectionState? selectionState, int? order, }) { return ContextSelectionItem( id: id ?? this.id, title: title ?? this.title, type: type ?? this.type, subtitle: subtitle ?? this.subtitle, children: children ?? this.children, parentId: parentId ?? this.parentId, metadata: metadata ?? this.metadata, selectionState: selectionState ?? this.selectionState, order: order ?? this.order, ); } /// 是否有子项 bool get hasChildren => children.isNotEmpty; /// 获取显示的子标题信息 String get displaySubtitle { if (subtitle != null && subtitle!.isNotEmpty) { return subtitle!; } // 根据类型和元数据生成子标题 switch (type) { case ContextSelectionType.scenes: final wordCount = metadata['wordCount'] ?? 0; return wordCount > 0 ? '$wordCount 词' : '无内容'; case ContextSelectionType.chapters: final sceneCount = metadata['sceneCount'] ?? 0; final wordCount = metadata['wordCount'] ?? 0; if (sceneCount > 0 && wordCount > 0) { return '$sceneCount 个场景,$wordCount 词'; } else if (sceneCount > 0) { return '$sceneCount 个场景'; } else if (wordCount > 0) { return '$wordCount 词'; } return '无内容'; case ContextSelectionType.acts: final chapterCount = metadata['chapterCount'] ?? 0; final sceneCount = metadata['sceneCount'] ?? 0; if (chapterCount > 0 && sceneCount > 0) { return '$chapterCount 个章节,$sceneCount 个场景'; } else if (chapterCount > 0) { return '$chapterCount 个章节'; } else if (sceneCount > 0) { return '$sceneCount 个场景'; } return '无内容'; case ContextSelectionType.snippets: final wordCount = metadata['wordCount'] ?? 0; final itemCount = metadata['itemCount'] ?? 0; if (itemCount > 0 && wordCount > 0) { return '$itemCount 个片段,$wordCount 词'; } else if (itemCount > 0) { return '$itemCount 个片段'; } else if (wordCount > 0) { return '$wordCount 词'; } return '无片段'; case ContextSelectionType.settings: final itemCount = metadata['itemCount'] ?? 0; return itemCount > 0 ? '$itemCount 个设定' : '无设定'; case ContextSelectionType.settingGroups: // 顶级容器显示组数量,个别组显示设定数量 final groupCount = metadata['groupCount']; final itemCount = metadata['itemCount']; if (groupCount != null) { return groupCount > 0 ? '$groupCount 个分组' : '无分组'; } else if (itemCount != null) { return itemCount > 0 ? '$itemCount 个设定' : '无设定'; } return ''; case ContextSelectionType.settingsByType: // 父容器:显示类型数量 final groupCount = metadata['groupCount']; if (groupCount != null) { return groupCount > 0 ? '$groupCount 个类型' : '无类型'; } // 子项:显示该类型下的条目数 final itemCount = metadata['itemCount'] ?? 0; final settingType = metadata['settingType']; if (settingType != null) { final String zhType = _resolveSettingTypeZh(settingType); return itemCount > 0 ? '$zhType($itemCount 项)' : '$zhType(无条目)'; } return itemCount > 0 ? '$itemCount 项' : '无条目'; case ContextSelectionType.fullNovelText: final wordCount = metadata['wordCount'] ?? 0; return wordCount > 0 ? '$wordCount 词' : '无内容'; case ContextSelectionType.currentSceneContent: return '当前场景文本内容'; case ContextSelectionType.currentSceneSummary: return '当前场景摘要'; case ContextSelectionType.currentChapterContent: final wordCount2 = metadata['wordCount'] ?? 0; return wordCount2 > 0 ? '当前章节内容 · $wordCount2 词' : '当前章节内容'; case ContextSelectionType.currentChapterSummaries: final count = metadata['summaryCount'] ?? 0; return count > 0 ? '当前章节摘要 · $count 条' : '当前章节摘要'; case ContextSelectionType.previousChaptersContent: final prevCount = metadata['chapterCount'] ?? 0; final totalWords2 = metadata['totalWords'] ?? 0; if (prevCount == 0) return '无之前章节'; return totalWords2 > 0 ? '之前$prevCount章内容,共$totalWords2词' : '之前$prevCount章内容'; case ContextSelectionType.previousChaptersSummary: final prevSumCount = metadata['chapterCount'] ?? 0; final summaryCount2 = metadata['summaryCount'] ?? 0; if (prevSumCount == 0) return '无之前章节'; return summaryCount2 > 0 ? '之前$prevSumCount章摘要,共$summaryCount2条' : '之前$prevSumCount章摘要'; case ContextSelectionType.contentFixedGroup: case ContextSelectionType.summaryFixedGroup: return ''; // 🚀 新增:基本信息和前五章相关类型的子标题 case ContextSelectionType.novelBasicInfo: return '小说的基本信息,包括标题、作者、简介等'; case ContextSelectionType.recentChaptersContent: final chapterCount = metadata['chapterCount'] ?? 5; final totalWords = metadata['totalWords'] ?? 0; return totalWords > 0 ? '最近$chapterCount章内容,共$totalWords词' : '最近$chapterCount章内容'; case ContextSelectionType.recentChaptersSummary: final chapterCount = metadata['chapterCount'] ?? 5; final summaryCount = metadata['summaryCount'] ?? 0; return summaryCount > 0 ? '最近$chapterCount章摘要,共$summaryCount条' : '最近$chapterCount章摘要'; default: return ''; } } } /// 选择状态枚举 enum SelectionState { /// 未选中 unselected, /// 部分选中(有子项被选中) partiallySelected, /// 完全选中 fullySelected; /// 获取对应的图标 IconData? get icon { switch (this) { case SelectionState.fullySelected: return Icons.check_circle; case SelectionState.partiallySelected: return Icons.circle; case SelectionState.unselected: return null; } } /// 是否为选中状态(包括部分选中) bool get isSelected => this != SelectionState.unselected; } /// 上下文选择数据 class ContextSelectionData { const ContextSelectionData({ required this.novelId, this.selectedItems = const {}, this.availableItems = const [], this.flatItems = const {}, }); /// 小说ID final String novelId; /// 已选择的项目 (itemId -> ContextSelectionItem) final Map selectedItems; /// 可用的选择项(树形结构) final List availableItems; /// 扁平化的选择项映射 (itemId -> ContextSelectionItem) final Map flatItems; /// 创建副本 ContextSelectionData copyWith({ String? novelId, Map? selectedItems, List? availableItems, Map? flatItems, }) { return ContextSelectionData( novelId: novelId ?? this.novelId, selectedItems: selectedItems ?? this.selectedItems, availableItems: availableItems ?? this.availableItems, flatItems: flatItems ?? this.flatItems, ); } /// 选择项目 ContextSelectionData selectItem(String itemId, {bool selectChildren = false}) { final item = flatItems[itemId]; if (item == null) { if (kDebugMode) debugPrint('🚨 selectItem: 项目不存在 $itemId'); return this; } if (kDebugMode) debugPrint('🚀 selectItem: 开始选择项目 ${item.title} (${item.id})${selectChildren ? ' 及其子项' : ''}'); final newSelectedItems = Map.from(selectedItems); final newFlatItems = Map.from(flatItems); // 🚦 单选分组:如果属于 内容/摘要 固定分组,则取消同组其他子项的选择 final String? parentId = item.parentId; if (parentId != null) { final ContextSelectionItem? parent = newFlatItems[parentId] ?? flatItems[parentId]; if (parent != null && (parent.type == ContextSelectionType.contentFixedGroup || parent.type == ContextSelectionType.summaryFixedGroup)) { // 取消同组其他子项 final siblingIds = newFlatItems.values .where((i) => i.parentId == parent.id) .map((i) => i.id) .toList(); for (final sibId in siblingIds) { if (sibId == item.id) continue; final sib = newFlatItems[sibId]; if (sib != null && sib.selectionState.isSelected) { newSelectedItems.remove(sibId); newFlatItems[sibId] = sib.copyWith(selectionState: SelectionState.unselected); } } } } // 添加到选中列表 newSelectedItems[itemId] = item.copyWith(selectionState: SelectionState.fullySelected); // 更新扁平化映射中的状态 newFlatItems[itemId] = item.copyWith(selectionState: SelectionState.fullySelected); // 🚀 新增:如果需要选择子项,递归选择所有子项 if (selectChildren) { _selectAllChildren(item, newFlatItems, newSelectedItems); } if (kDebugMode) debugPrint(' ✅ 已更新选中列表和扁平化映射'); // 更新父项的选择状态 _updateParentSelectionState(item, newFlatItems, newSelectedItems); if (kDebugMode) debugPrint(' ✅ 已更新父项选择状态'); // 重新构建树形结构 final newAvailableItems = _rebuildTreeWithUpdatedStates(newFlatItems); if (kDebugMode) debugPrint(' ✅ 已重建树形结构'); if (kDebugMode) debugPrint('🚀 selectItem: 完成,当前选中项目数: ${newSelectedItems.length}'); return copyWith( selectedItems: newSelectedItems, availableItems: newAvailableItems, flatItems: newFlatItems, ); } /// 取消选择项目 ContextSelectionData deselectItem(String itemId) { final newSelectedItems = Map.from(selectedItems); final newFlatItems = Map.from(flatItems); // 从选中列表移除 newSelectedItems.remove(itemId); // 更新扁平化映射中的状态 final item = newFlatItems[itemId]; if (item != null) { newFlatItems[itemId] = item.copyWith(selectionState: SelectionState.unselected); // 如果是固定分组子项,取消选择就是简单恢复未选状态 // 父组状态由后续 _updateParentSelectionState 统一更新 // 递归取消选择所有子项 _deselectAllChildren(item, newFlatItems, newSelectedItems); // 更新父项的选择状态 _updateParentSelectionState(item, newFlatItems, newSelectedItems); } // 重新构建树形结构 final newAvailableItems = _rebuildTreeWithUpdatedStates(newFlatItems); return copyWith( selectedItems: newSelectedItems, availableItems: newAvailableItems, flatItems: newFlatItems, ); } /// 获取选中项的数量 int get selectedCount => selectedItems.length; /// 🚀 根据预设的上下文选择来更新当前选择状态 /// 保持当前的菜单结构,根据预设中的具体项目ID来精确匹配并选择对应项目 ContextSelectionData applyPresetSelections(ContextSelectionData presetSelections) { if (kDebugMode) debugPrint('🚀 [ContextSelectionData] 开始应用预设上下文选择'); // 收集预设里选中的具体项目ID final presetSelectedIds = {}; for (final presetItem in presetSelections.selectedItems.values) { presetSelectedIds.add(presetItem.id); if (kDebugMode) debugPrint('🚀 [ContextSelectionData] 预设选择项目: ${presetItem.title} (${presetItem.id})'); } if (kDebugMode) debugPrint('🚀 [ContextSelectionData] 预设共选择了 ${presetSelectedIds.length} 个具体项目'); // 1) 清空现有选择,全部置为未选 final Map newFlatItems = flatItems.map( (key, value) => MapEntry(key, value.copyWith(selectionState: SelectionState.unselected)), ); final Map newSelectedItems = {}; // 2) 单选分组去重:同一父为 contentFixedGroup/summaryFixedGroup 仅保留一个 final Map singleSelectChosenByParent = {}; final List finalIds = []; for (final id in presetSelectedIds) { final item = newFlatItems[id]; if (item == null) continue; final parentId = item.parentId; if (parentId != null) { final parent = newFlatItems[parentId]; if (parent != null && (parent.type == ContextSelectionType.contentFixedGroup || parent.type == ContextSelectionType.summaryFixedGroup)) { if (singleSelectChosenByParent.containsKey(parentId)) { // 已有同组选择,跳过后续同组项 continue; } singleSelectChosenByParent[parentId] = id; } } finalIds.add(id); } // 3) 一次性标记选中项 for (final id in finalIds) { final item = newFlatItems[id]; if (item == null) continue; newSelectedItems[id] = item.copyWith(selectionState: SelectionState.fullySelected); newFlatItems[id] = item.copyWith(selectionState: SelectionState.fullySelected); } // 4) 更新所有相关父项的选择状态(自底向上) for (final id in finalIds) { final item = newFlatItems[id]; if (item != null) { _updateParentSelectionState(item, newFlatItems, newSelectedItems); } } // 5) 重建树形结构一次 final newAvailableItems = _rebuildTreeWithUpdatedStates(newFlatItems); final updatedData = copyWith( selectedItems: newSelectedItems, availableItems: newAvailableItems, flatItems: newFlatItems, ); if (kDebugMode) debugPrint('🚀 [ContextSelectionData] 应用后总选择数: ${updatedData.selectedCount}'); return updatedData; } /// 🚀 合并两个上下文选择数据 /// 保留当前的所有选择,并添加新数据中未被选择的项目 /// 这与 applyPresetSelections 不同,后者会清除现有选择后重新应用 ContextSelectionData mergeSelections(ContextSelectionData newSelections) { debugPrint('🚀 [ContextSelectionData] 开始合并上下文选择'); debugPrint('🚀 [ContextSelectionData] 当前选择数: ${selectedCount}'); debugPrint('🚀 [ContextSelectionData] 新增选择数: ${newSelections.selectedCount}'); ContextSelectionData merged = this; int addedCount = 0; // 遍历新选择的项目,将尚未选择的项目添加到当前选择中 for (final newItem in newSelections.selectedItems.values) { if (!merged.selectedItems.containsKey(newItem.id)) { // 检查当前数据中是否存在对应的项目 if (merged.flatItems.containsKey(newItem.id)) { merged = merged.selectItem(newItem.id); addedCount++; debugPrint('🚀 [ContextSelectionData] 添加新选择: ${newItem.title} (${newItem.type.displayName})'); } else { debugPrint('⚠️ [ContextSelectionData] 跳过不存在的项目: ${newItem.title} (${newItem.id})'); } } else { debugPrint('🔄 [ContextSelectionData] 项目已存在,跳过: ${newItem.title}'); } } debugPrint('🚀 [ContextSelectionData] 合并完成,新增了 $addedCount 个选择'); debugPrint('🚀 [ContextSelectionData] 合并后总选择数: ${merged.selectedCount}'); return merged; } /// 更新父项的选择状态 void _updateParentSelectionState( ContextSelectionItem item, Map flatItems, Map selectedItems, ) { if (item.parentId == null) return; final parent = flatItems[item.parentId]; if (parent == null) return; // 计算父项的子项选择状态 final childrenIds = flatItems.values .where((i) => i.parentId == parent.id) .map((i) => i.id) .toList(); final selectedChildrenCount = childrenIds .where((id) => flatItems[id]?.selectionState.isSelected == true) .length; SelectionState newParentState; if (selectedChildrenCount == 0) { newParentState = SelectionState.unselected; selectedItems.remove(parent.id); } else if (selectedChildrenCount == childrenIds.length) { newParentState = SelectionState.fullySelected; // 🚀 修复:即使所有子项都被选中,也不自动将父项添加到选中列表 // 只有用户明确选择父项本身时,父项才会被添加到选中列表 selectedItems.remove(parent.id); } else { newParentState = SelectionState.partiallySelected; // 对于部分选中的父项,只更新其状态但不加入 selectedItems, // 这样在 UI 标签列表中只会显示实际被选中的叶子节点,避免重复显示。 selectedItems.remove(parent.id); } flatItems[parent.id] = parent.copyWith(selectionState: newParentState); // 递归更新上级父项 _updateParentSelectionState(parent, flatItems, selectedItems); } /// 🚀 新增:递归选择所有子项 void _selectAllChildren( ContextSelectionItem item, Map flatItems, Map selectedItems, ) { final childrenIds = flatItems.values .where((i) => i.parentId == item.id) .map((i) => i.id) .toList(); for (final childId in childrenIds) { final child = flatItems[childId]; if (child != null) { selectedItems[childId] = child.copyWith(selectionState: SelectionState.fullySelected); flatItems[childId] = child.copyWith(selectionState: SelectionState.fullySelected); _selectAllChildren(child, flatItems, selectedItems); } } } /// 递归取消选择所有子项 void _deselectAllChildren( ContextSelectionItem item, Map flatItems, Map selectedItems, ) { final childrenIds = flatItems.values .where((i) => i.parentId == item.id) .map((i) => i.id) .toList(); for (final childId in childrenIds) { selectedItems.remove(childId); final child = flatItems[childId]; if (child != null) { flatItems[childId] = child.copyWith(selectionState: SelectionState.unselected); _deselectAllChildren(child, flatItems, selectedItems); } } } /// 重新构建树形结构 List _rebuildTreeWithUpdatedStates( Map flatItems, ) { // 递归更新树形结构中的所有项目状态 return availableItems.map((item) => _rebuildItemWithUpdatedState(item, flatItems)).toList(); } /// 递归重建单个项目及其子项的状态 ContextSelectionItem _rebuildItemWithUpdatedState( ContextSelectionItem item, Map flatItems, ) { // 获取更新后的项目状态 final updatedItem = flatItems[item.id] ?? item; // 检查状态是否有变化 if (updatedItem.selectionState != item.selectionState) { if (kDebugMode) debugPrint(' 🔄 状态更新: ${item.title} ${item.selectionState} → ${updatedItem.selectionState}'); } // 如果有子项,递归更新子项状态 if (item.children.isNotEmpty) { final updatedChildren = item.children.map((child) => _rebuildItemWithUpdatedState(child, flatItems) ).toList(); return updatedItem.copyWith(children: updatedChildren); } return updatedItem; } } /// 上下文选择数据构建器 class ContextSelectionDataBuilder { /// 从小说结构构建上下文选择数据 static ContextSelectionData fromNovel(Novel novel) { final List items = []; final Map flatItems = {}; // 顶部固定分组:内容/摘要 final contentGroupId = 'content_fixed_${novel.id}'; final summaryGroupId = 'summary_fixed_${novel.id}'; // 内容分组子项 final List contentChildren = [ ContextSelectionItem( id: 'current_scene_content_${novel.id}', title: '当前场景内容', type: ContextSelectionType.currentSceneContent, parentId: contentGroupId, order: 0, ), ContextSelectionItem( id: 'current_chapter_content_${novel.id}', title: '当前章节内容', type: ContextSelectionType.currentChapterContent, parentId: contentGroupId, metadata: {'wordCount': 0}, order: 1, ), ContextSelectionItem( id: 'previous_chapters_content_${novel.id}', title: '之前所有章节内容', type: ContextSelectionType.previousChaptersContent, parentId: contentGroupId, metadata: { 'chapterCount': novel.getChapterCount() > 0 ? (novel.getChapterCount() - 1) : 0, 'totalWords': 0, }, order: 2, ), ContextSelectionItem( id: 'recent_chapters_content_${novel.id}', title: '最近5章内容', type: ContextSelectionType.recentChaptersContent, parentId: contentGroupId, metadata: { 'chapterCount': 5, 'totalWords': _calculateRecentChaptersWords(novel, 5), 'includesCurrent': true, }, order: 3, ), ContextSelectionItem( id: 'full_novel_${novel.id}', title: '所有章节内容', type: ContextSelectionType.fullNovelText, parentId: contentGroupId, subtitle: '包含所有小说文本,这将产生费用', metadata: {'wordCount': novel.wordCount}, order: 4, ), ]; // 摘要分组子项 final List summaryChildren = [ ContextSelectionItem( id: 'current_scene_summary_${novel.id}', title: '当前场景摘要', type: ContextSelectionType.currentSceneSummary, parentId: summaryGroupId, order: 0, ), ContextSelectionItem( id: 'current_chapter_summaries_${novel.id}', title: '当前章节所有摘要', type: ContextSelectionType.currentChapterSummaries, parentId: summaryGroupId, metadata: {'summaryCount': 0}, order: 1, ), ContextSelectionItem( id: 'previous_chapters_summary_${novel.id}', title: '之前所有章节摘要', type: ContextSelectionType.previousChaptersSummary, parentId: summaryGroupId, metadata: {'chapterCount': 0, 'summaryCount': 0}, order: 2, ), ContextSelectionItem( id: 'recent_chapters_summary_${novel.id}', title: '最近5章摘要', type: ContextSelectionType.recentChaptersSummary, parentId: summaryGroupId, metadata: {'chapterCount': 5, 'summaryCount': _calculateRecentChaptersSummaryCount(novel, 5)}, order: 3, ), ]; final contentGroup = ContextSelectionItem( id: contentGroupId, title: '内容', type: ContextSelectionType.contentFixedGroup, children: contentChildren, order: 0, ); final summaryGroup = ContextSelectionItem( id: summaryGroupId, title: '摘要', type: ContextSelectionType.summaryFixedGroup, children: summaryChildren, order: 1, ); items.addAll([contentGroup, summaryGroup]); // 将分组与子项加入flat映射,便于父子/同级联动 flatItems[contentGroup.id] = contentGroup; flatItems[summaryGroup.id] = summaryGroup; for (final child in contentChildren) { flatItems[child.id] = child; } for (final child in summaryChildren) { flatItems[child.id] = child; } // 🚀 新增:添加小说基本信息选项 final novelBasicInfoItem = ContextSelectionItem( id: 'novel_basic_info_${novel.id}', title: '小说基本信息', type: ContextSelectionType.novelBasicInfo, subtitle: '包含小说标题、作者、简介、类型等基本信息', metadata: { 'hasTitle': novel.title.isNotEmpty, 'hasAuthor': novel.author?.username.isNotEmpty ?? false, 'hasDescription': false, // Novel类暂时没有description字段 'hasGenre': false, // Novel类暂时没有genre字段 }, order: 2, ); items.add(novelBasicInfoItem); flatItems[novelBasicInfoItem.id] = novelBasicInfoItem; // 添加 Acts 选项(层级化结构)- 总是添加,即使为空 final actsChildren = []; if (novel.acts.isNotEmpty) { for (final act in novel.acts) { final chapterChildren = _buildChapterItems(act, act.id); final actItem = ContextSelectionItem( id: act.id, // 移除 'act_' 前缀,因为act.id本来就有前缀 title: act.title.isNotEmpty ? act.title : '第${act.order}卷', type: ContextSelectionType.acts, parentId: 'acts_${novel.id}', metadata: { 'chapterCount': act.chapters.length, 'wordCount': act.wordCount, }, order: act.order, children: chapterChildren, ); actsChildren.add(actItem); // 添加到扁平化映射 flatItems[actItem.id] = actItem; // 添加章节到扁平化映射 for (final chapterItem in actItem.children) { flatItems[chapterItem.id] = chapterItem; // 添加场景到扁平化映射 for (final sceneItem in chapterItem.children) { flatItems[sceneItem.id] = sceneItem; } } } } final actsItem = ContextSelectionItem( id: 'acts_${novel.id}', title: '卷', type: ContextSelectionType.acts, children: actsChildren, metadata: { 'chapterCount': actsChildren.fold(0, (sum, act) => sum + (act.metadata['chapterCount'] as int? ?? 0)), }, order: 5, ); items.add(actsItem); flatItems[actsItem.id] = actsItem; // 添加 Chapters 选项(扁平化显示所有章节)- 总是添加,即使为空 final allChapters = []; if (novel.acts.isNotEmpty) { for (final act in novel.acts) { for (final chapter in act.chapters) { final sceneChildren = _buildSceneItems(chapter, 'flat_${chapter.id}'); final chapterItem = ContextSelectionItem( id: 'flat_${chapter.id}', // 保留flat_前缀避免与层级结构中的chapter.id冲突 title: chapter.title.isNotEmpty ? chapter.title : '第${chapter.order}章', type: ContextSelectionType.chapters, parentId: 'chapters_${novel.id}', metadata: { 'sceneCount': chapter.sceneCount, 'wordCount': chapter.wordCount, 'actTitle': act.title.isNotEmpty ? act.title : '第${act.order}卷', }, order: chapter.order, children: sceneChildren, ); allChapters.add(chapterItem); // 添加到扁平化映射 flatItems[chapterItem.id] = chapterItem; // 添加场景到扁平化映射 for (final sceneItem in chapterItem.children) { flatItems[sceneItem.id] = sceneItem; } } } } final chaptersItem = ContextSelectionItem( id: 'chapters_${novel.id}', title: '章节', type: ContextSelectionType.chapters, children: allChapters, metadata: { 'sceneCount': allChapters.fold(0, (sum, chapter) => sum + (chapter.metadata['sceneCount'] as int? ?? 0)), }, order: 6, ); items.add(chaptersItem); flatItems[chaptersItem.id] = chaptersItem; // 添加 Scenes 选项(扁平化显示所有场景)- 总是添加,即使为空 final allScenes = []; if (novel.acts.isNotEmpty) { for (final act in novel.acts) { for (final chapter in act.chapters) { for (final scene in chapter.scenes) { final sceneItem = ContextSelectionItem( id: 'flat_${scene.id}', // 保留flat_前缀避免与层级结构中的scene.id冲突 title: scene.title.isNotEmpty ? scene.title : '新场景', type: ContextSelectionType.scenes, parentId: 'scenes_${novel.id}', metadata: { 'wordCount': scene.wordCount, 'chapterTitle': chapter.title.isNotEmpty ? chapter.title : '第${chapter.order}章', 'actTitle': act.title.isNotEmpty ? act.title : '第${act.order}卷', }, order: chapter.scenes.indexOf(scene), ); allScenes.add(sceneItem); // 添加到扁平化映射 flatItems[sceneItem.id] = sceneItem; } } } } final scenesItem = ContextSelectionItem( id: 'scenes_${novel.id}', title: '场景', type: ContextSelectionType.scenes, children: allScenes, metadata: { 'wordCount': allScenes.fold(0, (sum, scene) => sum + (scene.metadata['wordCount'] as int? ?? 0)), }, order: 7, ); items.add(scenesItem); flatItems[scenesItem.id] = scenesItem; // TODO: 添加其他类型的选项(Snippets, Codex Entries等) return ContextSelectionData( novelId: novel.id, availableItems: items, flatItems: flatItems, ); } /// 构建章节选择项 static List _buildChapterItems(Act act, String parentId) { return act.chapters.map((chapter) { return ContextSelectionItem( id: chapter.id, title: chapter.title.isNotEmpty ? chapter.title : '第${chapter.order}章', type: ContextSelectionType.chapters, parentId: parentId, metadata: { 'sceneCount': chapter.sceneCount, 'wordCount': chapter.wordCount, }, order: chapter.order, children: _buildSceneItems(chapter, chapter.id), ); }).toList(); } /// 构建场景选择项 static List _buildSceneItems(Chapter chapter, String parentId) { return chapter.scenes.map((scene) { return ContextSelectionItem( id: scene.id, title: scene.title.isNotEmpty ? scene.title : '新场景', type: ContextSelectionType.scenes, parentId: parentId, metadata: { 'wordCount': scene.wordCount, }, order: chapter.scenes.indexOf(scene), ); }).toList(); } /// 从小说结构、设定和片段构建完整的上下文选择数据 static ContextSelectionData fromNovelWithContext( Novel novel, { List? settings, List? settingGroups, List? snippets, }) { final List items = []; final Map flatItems = {}; // 首先添加基础的小说结构项(Full Novel Text, Full Outline, Acts, Chapters, Scenes) final baseData = fromNovel(novel); items.addAll(baseData.availableItems); flatItems.addAll(baseData.flatItems); // 添加片段选项 if (snippets != null) { final snippetsItem = _buildSnippetsItem(novel.id, snippets); items.add(snippetsItem); flatItems[snippetsItem.id] = snippetsItem; // 添加片段子项到扁平化映射 for (final child in snippetsItem.children) { flatItems[child.id] = child; } } // 添加设定选项 if (settings != null || settingGroups != null) { final settingsItems = _buildSettingsItems(novel.id, settings ?? [], settingGroups ?? []); items.addAll(settingsItems); // 添加设定项到扁平化映射 for (final item in settingsItems) { flatItems[item.id] = item; for (final child in item.children) { flatItems[child.id] = child; // 如果有孙子项也要添加 for (final grandChild in child.children) { flatItems[grandChild.id] = grandChild; } } } } return ContextSelectionData( novelId: novel.id, availableItems: items, flatItems: flatItems, ); } /// 构建片段选择项 static ContextSelectionItem _buildSnippetsItem(String novelId, List snippets) { final snippetChildren = snippets.map((snippet) { return ContextSelectionItem( id: 'snippet_${snippet.id}', title: snippet.title, type: ContextSelectionType.snippets, parentId: 'snippets_$novelId', subtitle: snippet.content.length > 50 ? '${snippet.content.substring(0, 50)}...' : snippet.content, metadata: { 'wordCount': snippet.metadata.wordCount, 'isFavorite': snippet.isFavorite, 'createdAt': snippet.createdAt.toIso8601String(), }, ); }).toList(); return ContextSelectionItem( id: 'snippets_$novelId', title: '片段', type: ContextSelectionType.snippets, children: snippetChildren, metadata: { 'itemCount': snippets.length, }, order: 8, ); } /// 构建设定选择项 static List _buildSettingsItems( String novelId, List settings, List settingGroups, ) { final List items = []; // 添加设定组选项 if (settingGroups.isNotEmpty) { final groupChildren = settingGroups.map((group) { final groupSettings = settings.where((s) => group.itemIds?.contains(s.id) == true ).toList(); final settingChildren = groupSettings.map((setting) { return _buildSettingItem(setting, 'setting_group_${group.id}'); }).toList(); return ContextSelectionItem( id: 'setting_group_${group.id}', title: group.name, type: ContextSelectionType.settingGroups, parentId: 'setting_groups_$novelId', subtitle: group.description, children: settingChildren, metadata: { 'itemCount': groupSettings.length, 'isActive': group.isActiveContext, }, ); }).toList(); final settingGroupsItem = ContextSelectionItem( id: 'setting_groups_$novelId', title: '设定分组', type: ContextSelectionType.settingGroups, children: groupChildren, metadata: { 'groupCount': settingGroups.length, }, order: 9, ); items.add(settingGroupsItem); } // 添加所有设定选项(直接列出所有设定,不再按类型分组) if (settings.isNotEmpty) { final settingChildren = settings.map((setting) { return _buildSettingItem(setting, 'settings_$novelId'); }).toList(); final settingsItem = ContextSelectionItem( id: 'settings_$novelId', title: '设定', type: ContextSelectionType.settings, children: settingChildren, metadata: { 'itemCount': settings.length, }, order: 10, ); items.add(settingsItem); } // 🚀 新增:按设定类型分组(Settings by Type) if (settings.isNotEmpty) { // 统计各类型及其条目 final Map> typeToItems = >{}; for (final s in settings) { final String settingType = (s.type ?? 'unknown').toString(); typeToItems.putIfAbsent(settingType, () => []).add(s); } final List typeChildren = typeToItems.entries.map((entry) { final String settingType = entry.key; final List itemsOfType = entry.value; return ContextSelectionItem( id: 'type_$settingType', title: settingType, type: ContextSelectionType.settingsByType, parentId: 'settings_by_type_$novelId', metadata: { 'itemCount': itemsOfType.length, 'settingType': settingType, }, ); }).toList(); final settingsByTypeItem = ContextSelectionItem( id: 'settings_by_type_$novelId', title: '设定类型', type: ContextSelectionType.settingsByType, children: typeChildren, metadata: { 'groupCount': typeChildren.length, }, order: 11, ); items.add(settingsByTypeItem); } return items; } /// 构建单个设定项 static ContextSelectionItem _buildSettingItem(NovelSettingItem setting, String parentId) { return ContextSelectionItem( id: setting.id ?? '', title: setting.name, type: ContextSelectionType.settings, parentId: parentId, subtitle: setting.description, metadata: { 'type': setting.type ?? 'unknown', 'hasContent': setting.content?.isNotEmpty ?? false, 'priority': setting.priority ?? 0, }, ); } /// 获取设定类型的显示名称 // static String _getSettingTypeDisplayName(String type) { return type; } // 🚀 新增:计算前N章的总字数 static int _calculateRecentChaptersWords(Novel novel, int chapterCount) { int totalWords = 0; int processedChapters = 0; // 遍历所有卷和章节,取前N章 outer: for (final act in novel.acts) { for (final chapter in act.chapters) { if (processedChapters >= chapterCount) { break outer; } totalWords += chapter.wordCount; processedChapters++; } } return totalWords; } // 🚀 新增:计算前N章的摘要数量 static int _calculateRecentChaptersSummaryCount(Novel novel, int chapterCount) { int summaryCount = 0; int processedChapters = 0; // 遍历所有卷和章节,取前N章 outer: for (final act in novel.acts) { for (final chapter in act.chapters) { if (processedChapters >= chapterCount) { break outer; } // 检查章节是否有场景(有场景就认为有可能有摘要) summaryCount += chapter.scenes.length; processedChapters++; } } return summaryCount; } } /// 将设定类型解析为中文显示名(兼容字符串、枚举和Map) String _resolveSettingTypeZh(dynamic rawType) { if (rawType == null) return '其他'; try { if (rawType is SettingType) { return rawType.displayName; } if (rawType is Map) { final SettingType t = SettingType.fromJson(rawType); return t.displayName; } final String value = rawType.toString(); final SettingType t = SettingType.fromValue(value); return t.displayName; } catch (_) { return '其他'; } }