import 'dart:convert'; import 'package:ainoval/models/ai_request_models.dart'; import 'package:ainoval/models/context_selection_models.dart'; import 'package:ainoval/utils/logger.dart'; /// 场景节拍组件数据模型 /// 存储在Quill文档中的自包含配置数据 class SceneBeatData { /// AI请求的完整配置,序列化为JSON字符串 /// 这是配置的"快照",包含模型、参数、上下文等所有信息 final String requestData; /// AI最后生成的内容,存储为Quill的Delta JSON字符串 /// 以便在内部的子编辑器中显示富文本 final String generatedContentDelta; /// (可选) 为了UI方便,记录上次加载的预设ID /// 这样在下次打开编辑弹窗时,可以高亮显示对应的预设 /// **注意:此字段仅用于UI展示,不参与AI请求逻辑** final String? lastUsedPresetId; /// 🚀 新增:选中的统一模型ID(用于UI状态恢复) final String? selectedUnifiedModelId; /// 🚀 新增:选中的字数长度('200', '400', '600' 或自定义值) final String? selectedLength; /// 🚀 新增:温度参数(0.0-2.0) final double temperature; /// 🚀 新增:Top-P参数(0.0-1.0) final double topP; /// 🚀 新增:是否启用智能上下文 final bool enableSmartContext; /// 🚀 新增:选中的提示词模板ID final String? selectedPromptTemplateId; /// 🚀 新增:上下文选择数据(序列化为JSON字符串) final String? contextSelectionsData; /// 组件创建时间 final DateTime createdAt; /// 组件最后更新时间 final DateTime updatedAt; /// 组件状态 final SceneBeatStatus status; /// 生成进度(0.0-1.0) final double progress; SceneBeatData({ required this.requestData, this.generatedContentDelta = '[{"insert":"\\n"}]', // 默认为空文档 this.lastUsedPresetId, this.selectedUnifiedModelId, this.selectedLength, this.temperature = 0.7, this.topP = 0.9, this.enableSmartContext = true, this.selectedPromptTemplateId, this.contextSelectionsData, DateTime? createdAt, DateTime? updatedAt, this.status = SceneBeatStatus.draft, this.progress = 0.0, }) : createdAt = createdAt ?? DateTime.now(), updatedAt = updatedAt ?? DateTime.now(); /// 从存储在Quill Delta中的JSON字符串反序列化 factory SceneBeatData.fromJson(String jsonString) { try { final map = jsonDecode(jsonString); return SceneBeatData( requestData: map['requestData'] as String? ?? '{}', generatedContentDelta: map['generatedContentDelta'] as String? ?? '[{"insert":"\\n"}]', lastUsedPresetId: map['lastUsedPresetId'] as String?, selectedUnifiedModelId: map['selectedUnifiedModelId'] as String?, selectedLength: map['selectedLength'] as String?, temperature: (map['temperature'] as num? ?? 0.7).toDouble(), topP: (map['topP'] as num? ?? 0.9).toDouble(), enableSmartContext: map['enableSmartContext'] as bool? ?? true, selectedPromptTemplateId: map['selectedPromptTemplateId'] as String?, contextSelectionsData: map['contextSelectionsData'] as String?, createdAt: map['createdAt'] != null ? DateTime.parse(map['createdAt'] as String) : DateTime.now(), updatedAt: map['updatedAt'] != null ? DateTime.parse(map['updatedAt'] as String) : DateTime.now(), status: SceneBeatStatus.values.firstWhere( (s) => s.name == (map['status'] as String? ?? 'draft'), orElse: () => SceneBeatStatus.draft, ), progress: (map['progress'] as num? ?? 0.0).toDouble(), ); } catch (e) { AppLogger.e('SceneBeatData', '解析SceneBeatData失败: $e'); // 如果解析失败,返回一个安全的默认值 return SceneBeatData( requestData: '{}', generatedContentDelta: '[{"insert":"\\n"}]', ); } } /// 序列化为JSON字符串,以存储在Quill Delta中 String toJson() { return jsonEncode({ 'requestData': requestData, 'generatedContentDelta': generatedContentDelta, 'lastUsedPresetId': lastUsedPresetId, 'selectedUnifiedModelId': selectedUnifiedModelId, 'selectedLength': selectedLength, 'temperature': temperature, 'topP': topP, 'enableSmartContext': enableSmartContext, 'selectedPromptTemplateId': selectedPromptTemplateId, 'contextSelectionsData': contextSelectionsData, 'createdAt': createdAt.toIso8601String(), 'updatedAt': updatedAt.toIso8601String(), 'status': status.name, 'progress': progress, }); } /// 一个方便的getter,用于获取反序列化后的请求对象 UniversalAIRequest? get parsedRequest { try { if (requestData.isEmpty || requestData == '{}') { return null; } final requestJson = jsonDecode(requestData); // 🚀 兼容性处理:将旧的 NOVEL_GENERATION 类型转换为 SCENE_BEAT_GENERATION if (requestJson['requestType'] == 'NOVEL_GENERATION' && requestJson['metadata'] != null && requestJson['metadata']['action'] == 'scene_beat') { requestJson['requestType'] = 'SCENE_BEAT_GENERATION'; AppLogger.d('SceneBeatData', '自动将旧版场景节拍请求类型更新为 SCENE_BEAT_GENERATION'); } return UniversalAIRequest.fromJson(requestJson); } catch (e) { AppLogger.e('SceneBeatData', '解析UniversalAIRequest失败: $e'); return null; } } /// 更新请求数据 SceneBeatData updateRequestData(UniversalAIRequest request) { return SceneBeatData( requestData: jsonEncode(request.toApiJson()), generatedContentDelta: generatedContentDelta, lastUsedPresetId: lastUsedPresetId, selectedUnifiedModelId: selectedUnifiedModelId, selectedLength: selectedLength, temperature: temperature, topP: topP, enableSmartContext: enableSmartContext, selectedPromptTemplateId: selectedPromptTemplateId, contextSelectionsData: contextSelectionsData, createdAt: createdAt, updatedAt: DateTime.now(), status: status, progress: progress, ); } /// 更新生成的内容 SceneBeatData updateGeneratedContent(String deltaJson) { return SceneBeatData( requestData: requestData, generatedContentDelta: deltaJson, lastUsedPresetId: lastUsedPresetId, selectedUnifiedModelId: selectedUnifiedModelId, selectedLength: selectedLength, temperature: temperature, topP: topP, enableSmartContext: enableSmartContext, selectedPromptTemplateId: selectedPromptTemplateId, contextSelectionsData: contextSelectionsData, createdAt: createdAt, updatedAt: DateTime.now(), status: status == SceneBeatStatus.draft ? SceneBeatStatus.generated : status, progress: progress, ); } /// 更新状态和进度 SceneBeatData updateStatus(SceneBeatStatus newStatus, {double? newProgress}) { return SceneBeatData( requestData: requestData, generatedContentDelta: generatedContentDelta, lastUsedPresetId: lastUsedPresetId, selectedUnifiedModelId: selectedUnifiedModelId, selectedLength: selectedLength, temperature: temperature, topP: topP, enableSmartContext: enableSmartContext, selectedPromptTemplateId: selectedPromptTemplateId, contextSelectionsData: contextSelectionsData, createdAt: createdAt, updatedAt: DateTime.now(), status: newStatus, progress: newProgress ?? progress, ); } /// 复制数据 SceneBeatData copyWith({ String? requestData, String? generatedContentDelta, String? lastUsedPresetId, String? selectedUnifiedModelId, String? selectedLength, double? temperature, double? topP, bool? enableSmartContext, String? selectedPromptTemplateId, String? contextSelectionsData, DateTime? createdAt, DateTime? updatedAt, SceneBeatStatus? status, double? progress, }) { return SceneBeatData( requestData: requestData ?? this.requestData, generatedContentDelta: generatedContentDelta ?? this.generatedContentDelta, lastUsedPresetId: lastUsedPresetId ?? this.lastUsedPresetId, selectedUnifiedModelId: selectedUnifiedModelId ?? this.selectedUnifiedModelId, selectedLength: selectedLength ?? this.selectedLength, temperature: temperature ?? this.temperature, topP: topP ?? this.topP, enableSmartContext: enableSmartContext ?? this.enableSmartContext, selectedPromptTemplateId: selectedPromptTemplateId ?? this.selectedPromptTemplateId, contextSelectionsData: contextSelectionsData ?? this.contextSelectionsData, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, status: status ?? this.status, progress: progress ?? this.progress, ); } /// 创建默认的场景节拍数据 factory SceneBeatData.createDefault({ required String userId, required String novelId, String? initialPrompt, }) { // 创建默认的AI请求配置 final defaultRequest = UniversalAIRequest( requestType: AIRequestType.sceneBeat, userId: userId, novelId: novelId, prompt: initialPrompt ?? '续写故事。', instructions: '一个关键时刻,重要的事情发生改变,推动故事发展。', enableSmartContext: true, parameters: { 'length': '400', 'temperature': 0.7, 'topP': 0.9, 'maxTokens': 4000, }, metadata: { 'action': 'scene_beat', 'source': 'scene_beat_component', 'featureType': 'SCENE_BEAT_GENERATION', }, ); return SceneBeatData( requestData: jsonEncode(defaultRequest.toApiJson()), generatedContentDelta: '[{"insert":"\\n"}]', selectedLength: '400', temperature: 0.7, topP: 0.9, enableSmartContext: true, status: SceneBeatStatus.draft, progress: 0.0, ); } /// 🚀 新增:获取解析后的上下文选择数据 ContextSelectionData? get parsedContextSelections { if (contextSelectionsData == null || contextSelectionsData!.isEmpty) { return null; } try { final map = jsonDecode(contextSelectionsData!); final selectedItems = {}; final availableItems = []; final flatItems = {}; // 解析选中的项目 final selectedList = map['selectedItems'] as List? ?? []; for (final itemData in selectedList) { final item = ContextSelectionItem( id: itemData['id'] as String, title: itemData['title'] as String, type: ContextSelectionType.values.firstWhere( (type) => type.value == itemData['type'], // 🚀 修复:使用API值而不是displayName orElse: () => ContextSelectionType.fullNovelText, ), metadata: Map.from(itemData['metadata'] ?? {}), selectionState: SelectionState.fullySelected, ); selectedItems[item.id] = item; availableItems.add(item); flatItems[item.id] = item; } return ContextSelectionData( novelId: map['novelId'] as String? ?? 'scene_beat', selectedItems: selectedItems, availableItems: availableItems, flatItems: flatItems, ); } catch (e) { AppLogger.e('SceneBeatData', '解析上下文选择数据失败: $e'); return null; } } /// 🚀 新增:更新上下文选择数据 SceneBeatData updateContextSelections(ContextSelectionData? contextData) { String? serializedData; if (contextData != null && contextData.selectedCount > 0) { // 序列化选中的项目 final selectedList = contextData.selectedItems.values.map((item) => { 'id': item.id, 'title': item.title, 'type': item.type.value, // 🚀 修复:使用API值而不是displayName 'metadata': item.metadata, }).toList(); serializedData = jsonEncode({ 'novelId': contextData.novelId, 'selectedItems': selectedList, }); } return copyWith( contextSelectionsData: serializedData, updatedAt: DateTime.now(), ); } /// 🚀 新增:更新UI配置(不更新请求数据) SceneBeatData updateUIConfig({ String? selectedUnifiedModelId, String? selectedLength, double? temperature, double? topP, bool? enableSmartContext, String? selectedPromptTemplateId, ContextSelectionData? contextSelections, }) { String? serializedContextData = this.contextSelectionsData; if (contextSelections != null) { final selectedList = contextSelections.selectedItems.values.map((item) => { 'id': item.id, 'title': item.title, 'type': item.type.value, // 🚀 修复:使用API值而不是displayName 'metadata': item.metadata, }).toList(); serializedContextData = jsonEncode({ 'novelId': contextSelections.novelId, 'selectedItems': selectedList, }); } return copyWith( selectedUnifiedModelId: selectedUnifiedModelId, selectedLength: selectedLength, temperature: temperature, topP: topP, enableSmartContext: enableSmartContext, selectedPromptTemplateId: selectedPromptTemplateId, contextSelectionsData: serializedContextData, updatedAt: DateTime.now(), ); } /// 轻量级占位实例:折叠状态下仅存最小信息、避免占用大量内存 /// 注意:当面板真正展开时请调用 `createDefault` 或相应的 update* 方法替换掉该实例 static SceneBeatData get empty => SceneBeatData(requestData: '{}'); } /// 场景节拍状态枚举 enum SceneBeatStatus { /// 草稿状态 - 刚创建,还未生成内容 draft, /// 生成中 - 正在进行AI生成 generating, /// 已生成 - AI生成完成 generated, /// 已应用 - 生成的内容已被用户接受并应用 applied, /// 错误状态 - 生成过程中发生错误 error, } extension SceneBeatStatusExtension on SceneBeatStatus { /// 获取状态的显示名称 String get displayName { switch (this) { case SceneBeatStatus.draft: return '草稿'; case SceneBeatStatus.generating: return '生成中'; case SceneBeatStatus.generated: return '已生成'; case SceneBeatStatus.applied: return '已应用'; case SceneBeatStatus.error: return '错误'; } } /// 获取状态的图标 String get icon { switch (this) { case SceneBeatStatus.draft: return '📝'; case SceneBeatStatus.generating: return '⚡'; case SceneBeatStatus.generated: return '✅'; case SceneBeatStatus.applied: return '🎯'; case SceneBeatStatus.error: return '❌'; } } /// 是否可以编辑 bool get canEdit { return this != SceneBeatStatus.generating; } /// 是否可以生成 bool get canGenerate { return this != SceneBeatStatus.generating; } /// 是否可以应用 bool get canApply { return this == SceneBeatStatus.generated; } }