马良AI写作初始化仓库

This commit is contained in:
邓滨杰
2025-09-10 00:07:52 +08:00
parent 3c06bb1a03
commit 39c0f8840f
1309 changed files with 318528 additions and 0 deletions

View File

@@ -0,0 +1,459 @@
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 = <String, ContextSelectionItem>{};
final availableItems = <ContextSelectionItem>[];
final flatItems = <String, ContextSelectionItem>{};
// 解析选中的项目
final selectedList = map['selectedItems'] as List<dynamic>? ?? [];
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<String, dynamic>.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;
}
}