314 lines
11 KiB
Dart
314 lines
11 KiB
Dart
import 'dart:convert';
|
||
|
||
import 'package:ainoval/models/context_selection_models.dart';
|
||
import 'package:ainoval/models/novel_structure.dart';
|
||
import 'package:ainoval/models/novel_setting_item.dart';
|
||
import 'package:ainoval/models/setting_group.dart';
|
||
import 'package:ainoval/models/novel_snippet.dart';
|
||
import 'package:ainoval/utils/logger.dart';
|
||
|
||
/// 上下文选择助手类
|
||
///
|
||
/// 提供统一的上下文选择管理方法,避免在不同组件中重复实现相同逻辑
|
||
class ContextSelectionHelper {
|
||
|
||
/// 初始化上下文选择数据
|
||
///
|
||
/// 根据提供的小说、设定、片段数据构建完整的上下文选择结构
|
||
static ContextSelectionData initializeContextData({
|
||
Novel? novel,
|
||
List<NovelSettingItem>? settings,
|
||
List<SettingGroup>? settingGroups,
|
||
List<NovelSnippet>? snippets,
|
||
ContextSelectionData? initialSelections,
|
||
}) {
|
||
//AppLogger.d('ContextSelectionHelper', '🔧 初始化上下文选择数据');
|
||
|
||
ContextSelectionData contextData;
|
||
|
||
if (novel != null) {
|
||
// 🚀 使用小说数据构建完整的上下文选择结构
|
||
contextData = ContextSelectionDataBuilder.fromNovelWithContext(
|
||
novel,
|
||
settings: settings ?? [],
|
||
settingGroups: settingGroups ?? [],
|
||
snippets: snippets ?? [],
|
||
);
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 从小说构建上下文数据成功: ${contextData.availableItems.length}个可选项');
|
||
} else {
|
||
// 🚀 创建演示数据作为回退
|
||
contextData = _createFallbackContextData();
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 创建回退上下文数据: ${contextData.availableItems.length}个可选项');
|
||
}
|
||
|
||
// 🚀 如果有初始选择,应用到构建的数据中
|
||
if (initialSelections != null && initialSelections.selectedCount > 0) {
|
||
contextData = contextData.applyPresetSelections(initialSelections);
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 应用初始选择: ${contextData.selectedCount}个已选项');
|
||
}
|
||
|
||
return contextData;
|
||
}
|
||
|
||
/// 处理上下文选择变化
|
||
///
|
||
/// 这是核心方法,用于正确处理级联菜单的选择变化
|
||
/// [currentData] 当前的上下文选择数据
|
||
/// [newData] 从下拉菜单组件返回的新选择数据
|
||
/// [isAddOperation] 是否为添加操作(true=添加,false=删除)
|
||
static ContextSelectionData handleSelectionChanged(
|
||
ContextSelectionData currentData,
|
||
ContextSelectionData newData, {
|
||
bool isAddOperation = true,
|
||
}) {
|
||
//AppLogger.d('ContextSelectionHelper', '🔄 处理上下文选择变化');
|
||
//AppLogger.d('ContextSelectionHelper', '当前选择数: ${currentData.selectedCount}');
|
||
//AppLogger.d('ContextSelectionHelper', '新数据选择数: ${newData.selectedCount}');
|
||
//AppLogger.d('ContextSelectionHelper', '操作类型: ${isAddOperation ? "添加" : "删除"}');
|
||
|
||
// 🚀 关键修复:直接使用新的选择数据,而不是合并
|
||
// 下拉菜单组件已经处理了选择/取消选择的逻辑,我们只需要接受结果
|
||
|
||
// 确保新数据具有完整的菜单结构
|
||
if (newData.availableItems.length < currentData.availableItems.length) {
|
||
// 如果新数据的菜单结构不完整,保持当前的菜单结构,只更新选择状态
|
||
//AppLogger.d('ContextSelectionHelper', '🔧 修复不完整的菜单结构');
|
||
|
||
// 重建具有完整结构的数据
|
||
final updatedData = currentData.copyWith(
|
||
selectedItems: {},
|
||
flatItems: currentData.flatItems.map(
|
||
(key, value) => MapEntry(key, value.copyWith(selectionState: SelectionState.unselected)),
|
||
),
|
||
);
|
||
|
||
// 应用新的选择
|
||
ContextSelectionData result = updatedData;
|
||
for (final selectedItem in newData.selectedItems.values) {
|
||
if (result.flatItems.containsKey(selectedItem.id)) {
|
||
result = result.selectItem(selectedItem.id);
|
||
}
|
||
}
|
||
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 选择处理完成: ${result.selectedCount}个已选项');
|
||
return result;
|
||
} else {
|
||
// 菜单结构完整,直接使用新数据
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 直接使用新选择数据: ${newData.selectedCount}个已选项');
|
||
return newData;
|
||
}
|
||
}
|
||
|
||
/// 从保存的上下文选择字符串恢复选择状态
|
||
///
|
||
/// [baseData] 基础的完整菜单结构数据
|
||
/// [savedContextSelectionsData] 保存的上下文选择JSON字符串
|
||
static ContextSelectionData restoreSelectionsFromSaved(
|
||
ContextSelectionData baseData,
|
||
String? savedContextSelectionsData,
|
||
) {
|
||
if (savedContextSelectionsData == null || savedContextSelectionsData.isEmpty) {
|
||
//AppLogger.d('ContextSelectionHelper', '📭 没有保存的上下文选择数据');
|
||
return baseData;
|
||
}
|
||
|
||
try {
|
||
// 🚀 解析保存的选择数据
|
||
final savedSelections = _parseSavedContextSelections(
|
||
savedContextSelectionsData,
|
||
baseData.novelId,
|
||
);
|
||
|
||
if (savedSelections.selectedCount > 0) {
|
||
// 应用保存的选择到基础数据
|
||
final restoredData = baseData.applyPresetSelections(savedSelections);
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 恢复上下文选择: ${restoredData.selectedCount}个已选项');
|
||
return restoredData;
|
||
}
|
||
} catch (e) {
|
||
AppLogger.e('ContextSelectionHelper', '恢复上下文选择失败', e);
|
||
}
|
||
|
||
return baseData;
|
||
}
|
||
|
||
/// 解析保存的上下文选择数据
|
||
static ContextSelectionData _parseSavedContextSelections(String savedData, String novelId) {
|
||
try {
|
||
// 🚀 解析JSON数据
|
||
final jsonData = jsonDecode(savedData) as Map<String, dynamic>;
|
||
|
||
// 检查是否有selectedItems字段
|
||
if (!jsonData.containsKey('selectedItems')) {
|
||
AppLogger.w('ContextSelectionHelper', '保存的数据中没有selectedItems字段');
|
||
return ContextSelectionData(novelId: novelId, availableItems: [], flatItems: {});
|
||
}
|
||
|
||
final contextList = jsonData['selectedItems'] as List<dynamic>;
|
||
//AppLogger.d('ContextSelectionHelper', '解析保存的上下文选择: ${contextList.length}个项目');
|
||
|
||
// 将已选择的项目转换为ContextSelectionItem
|
||
final selectedItems = <String, ContextSelectionItem>{};
|
||
final availableItems = <ContextSelectionItem>[];
|
||
final flatItems = <String, ContextSelectionItem>{};
|
||
|
||
for (var itemData in contextList) {
|
||
final item = ContextSelectionItem(
|
||
id: itemData['id'] ?? '',
|
||
title: itemData['title'] ?? '',
|
||
type: ContextSelectionType.values.firstWhere(
|
||
(type) => type.displayName == itemData['type'],
|
||
orElse: () => ContextSelectionType.fullNovelText,
|
||
),
|
||
metadata: Map<String, dynamic>.from(itemData['metadata'] ?? {}),
|
||
parentId: itemData['parentId'],
|
||
selectionState: SelectionState.fullySelected, // 标记为已选择
|
||
);
|
||
|
||
selectedItems[item.id] = item;
|
||
availableItems.add(item);
|
||
flatItems[item.id] = item;
|
||
|
||
//AppLogger.d('ContextSelectionHelper', ' ✅ ${item.type.displayName}:${item.id} (${item.title})');
|
||
}
|
||
|
||
return ContextSelectionData(
|
||
novelId: novelId,
|
||
selectedItems: selectedItems,
|
||
availableItems: availableItems,
|
||
flatItems: flatItems,
|
||
);
|
||
} catch (e) {
|
||
AppLogger.e('ContextSelectionHelper', '解析保存的上下文选择数据失败', e);
|
||
return ContextSelectionData(novelId: novelId, availableItems: [], flatItems: {});
|
||
}
|
||
}
|
||
|
||
/// 获取用于保存的上下文选择字符串
|
||
///
|
||
/// [contextData] 当前的上下文选择数据
|
||
static String? getSelectionsForSave(ContextSelectionData? contextData) {
|
||
if (contextData == null || contextData.selectedCount == 0) {
|
||
return null;
|
||
}
|
||
|
||
try {
|
||
return contextData.toSaveString();
|
||
} catch (e) {
|
||
AppLogger.e('ContextSelectionHelper', '序列化上下文选择失败', e);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// 清除所有选择
|
||
///
|
||
/// [currentData] 当前的上下文选择数据
|
||
static ContextSelectionData clearAllSelections(ContextSelectionData currentData) {
|
||
//AppLogger.d('ContextSelectionHelper', '🧹 清除所有上下文选择');
|
||
|
||
return currentData.copyWith(
|
||
selectedItems: {},
|
||
flatItems: currentData.flatItems.map(
|
||
(key, value) => MapEntry(key, value.copyWith(selectionState: SelectionState.unselected)),
|
||
),
|
||
);
|
||
}
|
||
|
||
/// 创建回退的上下文选择数据(用于没有小说数据的情况)
|
||
static ContextSelectionData _createFallbackContextData() {
|
||
final demoItems = [
|
||
ContextSelectionItem(
|
||
id: 'demo_full_novel',
|
||
title: 'Full Novel Text',
|
||
type: ContextSelectionType.fullNovelText,
|
||
subtitle: '包含所有小说文本,这将产生费用',
|
||
metadata: {'wordCount': 0},
|
||
),
|
||
ContextSelectionItem(
|
||
id: 'demo_full_outline',
|
||
title: 'Full Outline',
|
||
type: ContextSelectionType.fullOutline,
|
||
subtitle: '包含所有卷、章节和场景的完整大纲',
|
||
metadata: {'actCount': 0, 'chapterCount': 0, 'sceneCount': 0},
|
||
),
|
||
];
|
||
|
||
final flatItems = <String, ContextSelectionItem>{};
|
||
for (final item in demoItems) {
|
||
flatItems[item.id] = item;
|
||
}
|
||
|
||
return ContextSelectionData(
|
||
novelId: 'demo_novel',
|
||
availableItems: demoItems,
|
||
flatItems: flatItems,
|
||
);
|
||
}
|
||
|
||
/// 验证上下文选择数据的完整性
|
||
///
|
||
/// [contextData] 要验证的上下文选择数据
|
||
static bool validateContextData(ContextSelectionData? contextData) {
|
||
if (contextData == null) {
|
||
AppLogger.w('ContextSelectionHelper', '❌ 上下文数据为null');
|
||
return false;
|
||
}
|
||
|
||
if (contextData.availableItems.isEmpty) {
|
||
AppLogger.w('ContextSelectionHelper', '❌ 上下文数据无可用项目');
|
||
return false;
|
||
}
|
||
|
||
if (contextData.flatItems.isEmpty) {
|
||
AppLogger.w('ContextSelectionHelper', '❌ 上下文数据扁平化映射为空');
|
||
return false;
|
||
}
|
||
|
||
//AppLogger.d('ContextSelectionHelper', '✅ 上下文数据验证通过');
|
||
return true;
|
||
}
|
||
|
||
/// 获取上下文选择的统计信息
|
||
///
|
||
/// [contextData] 上下文选择数据
|
||
static Map<String, dynamic> getSelectionStats(ContextSelectionData? contextData) {
|
||
if (contextData == null) {
|
||
return {'totalItems': 0, 'selectedItems': 0, 'selectionTypes': []};
|
||
}
|
||
|
||
final selectedTypes = contextData.selectedItems.values
|
||
.map((item) => item.type.displayName)
|
||
.toSet()
|
||
.toList();
|
||
|
||
return {
|
||
'totalItems': contextData.availableItems.length,
|
||
'selectedItems': contextData.selectedCount,
|
||
'selectionTypes': selectedTypes,
|
||
'novelId': contextData.novelId,
|
||
};
|
||
}
|
||
}
|
||
|
||
/// 上下文选择数据扩展方法
|
||
extension ContextSelectionDataExt on ContextSelectionData {
|
||
|
||
/// 转换为保存字符串
|
||
String toSaveString() {
|
||
if (selectedCount == 0) return '';
|
||
|
||
final saveData = {
|
||
'novelId': novelId,
|
||
'selectedItems': selectedItems.values.map((item) => {
|
||
'id': item.id,
|
||
'title': item.title,
|
||
'type': item.type.displayName,
|
||
'metadata': item.metadata,
|
||
}).toList(),
|
||
};
|
||
|
||
return saveData.toString(); // 简化的序列化,可以根据需要使用 jsonEncode
|
||
}
|
||
|
||
|
||
} |