Files
MaliangAINovalWriter/AINoval/lib/models/context_selection_models.dart
2025-09-10 00:07:52 +08:00

1193 lines
42 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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<ContextSelectionItem> children;
/// 父项ID用于扁平化结构
final String? parentId;
/// 元数据(可存储字数、章节数等信息)
final Map<String, dynamic> metadata;
/// 选择状态
final SelectionState selectionState;
/// 排序顺序
final int order;
/// 创建副本
ContextSelectionItem copyWith({
String? id,
String? title,
ContextSelectionType? type,
String? subtitle,
List<ContextSelectionItem>? children,
String? parentId,
Map<String, dynamic>? 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<String, ContextSelectionItem> selectedItems;
/// 可用的选择项(树形结构)
final List<ContextSelectionItem> availableItems;
/// 扁平化的选择项映射 (itemId -> ContextSelectionItem)
final Map<String, ContextSelectionItem> flatItems;
/// 创建副本
ContextSelectionData copyWith({
String? novelId,
Map<String, ContextSelectionItem>? selectedItems,
List<ContextSelectionItem>? availableItems,
Map<String, ContextSelectionItem>? 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<String, ContextSelectionItem>.from(selectedItems);
final newFlatItems = Map<String, ContextSelectionItem>.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<String, ContextSelectionItem>.from(selectedItems);
final newFlatItems = Map<String, ContextSelectionItem>.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 = <String>{};
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<String, ContextSelectionItem> newFlatItems = flatItems.map(
(key, value) => MapEntry(key, value.copyWith(selectionState: SelectionState.unselected)),
);
final Map<String, ContextSelectionItem> newSelectedItems = {};
// 2) 单选分组去重:同一父为 contentFixedGroup/summaryFixedGroup 仅保留一个
final Map<String, String> singleSelectChosenByParent = {};
final List<String> 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<String, ContextSelectionItem> flatItems,
Map<String, ContextSelectionItem> 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<String, ContextSelectionItem> flatItems,
Map<String, ContextSelectionItem> 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<String, ContextSelectionItem> flatItems,
Map<String, ContextSelectionItem> 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<ContextSelectionItem> _rebuildTreeWithUpdatedStates(
Map<String, ContextSelectionItem> flatItems,
) {
// 递归更新树形结构中的所有项目状态
return availableItems.map((item) => _rebuildItemWithUpdatedState(item, flatItems)).toList();
}
/// 递归重建单个项目及其子项的状态
ContextSelectionItem _rebuildItemWithUpdatedState(
ContextSelectionItem item,
Map<String, ContextSelectionItem> 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<ContextSelectionItem> items = [];
final Map<String, ContextSelectionItem> flatItems = {};
// 顶部固定分组:内容/摘要
final contentGroupId = 'content_fixed_${novel.id}';
final summaryGroupId = 'summary_fixed_${novel.id}';
// 内容分组子项
final List<ContextSelectionItem> 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<ContextSelectionItem> 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 = <ContextSelectionItem>[];
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<int>(0, (sum, act) => sum + (act.metadata['chapterCount'] as int? ?? 0)),
},
order: 5,
);
items.add(actsItem);
flatItems[actsItem.id] = actsItem;
// 添加 Chapters 选项(扁平化显示所有章节)- 总是添加,即使为空
final allChapters = <ContextSelectionItem>[];
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<int>(0, (sum, chapter) => sum + (chapter.metadata['sceneCount'] as int? ?? 0)),
},
order: 6,
);
items.add(chaptersItem);
flatItems[chaptersItem.id] = chaptersItem;
// 添加 Scenes 选项(扁平化显示所有场景)- 总是添加,即使为空
final allScenes = <ContextSelectionItem>[];
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<int>(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<ContextSelectionItem> _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<ContextSelectionItem> _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<NovelSettingItem>? settings,
List<SettingGroup>? settingGroups,
List<NovelSnippet>? snippets,
}) {
final List<ContextSelectionItem> items = [];
final Map<String, ContextSelectionItem> 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<NovelSnippet> 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<ContextSelectionItem> _buildSettingsItems(
String novelId,
List<NovelSettingItem> settings,
List<SettingGroup> settingGroups,
) {
final List<ContextSelectionItem> 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<String, List<NovelSettingItem>> typeToItems = <String, List<NovelSettingItem>>{};
for (final s in settings) {
final String settingType = (s.type ?? 'unknown').toString();
typeToItems.putIfAbsent(settingType, () => <NovelSettingItem>[]).add(s);
}
final List<ContextSelectionItem> typeChildren = typeToItems.entries.map((entry) {
final String settingType = entry.key;
final List<NovelSettingItem> 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<String, dynamic>) {
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 '其他';
}
}