马良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,680 @@
import 'package:ainoval/models/context_selection_models.dart';
import 'package:ainoval/models/user_ai_model_config_model.dart';
import 'package:ainoval/utils/date_time_parser.dart';
/// AI请求类型枚举
enum AIRequestType {
chat('AI_CHAT', '聊天对话'),
expansion('TEXT_EXPANSION', '扩写文本'),
summary('TEXT_SUMMARY', '缩写文本'),
sceneSummary('SCENE_TO_SUMMARY', '场景摘要'),
refactor('TEXT_REFACTOR', '重构文本'),
generation('NOVEL_GENERATION', '内容生成'),
sceneBeat('SCENE_BEAT_GENERATION', '场景节拍生成'),
novelCompose('NOVEL_COMPOSE', '设定编排');
const AIRequestType(this.value, this.displayName);
final String value;
final String displayName;
}
/// 通用AI请求模型
class UniversalAIRequest {
const UniversalAIRequest({
required this.requestType,
required this.userId,
this.sessionId,
this.novelId,
this.chapterId,
this.sceneId,
this.settingSessionId,
this.modelConfig,
this.prompt,
this.instructions,
this.selectedText,
this.contextSelections,
this.enableSmartContext = false,
this.parameters = const {},
this.metadata = const {},
});
/// 请求类型
final AIRequestType requestType;
/// 用户ID
final String userId;
/// 会话ID聊天对话时必填
final String? sessionId;
/// 小说ID
final String? novelId;
/// 章节ID用于上下文提供器
final String? chapterId;
/// 场景ID用于上下文提供器
final String? sceneId;
/// 设定生成会话ID用于设定编排/写作编排场景)
final String? settingSessionId;
/// 模型配置
final UserAIModelConfigModel? modelConfig;
/// 主要提示内容(用户输入的消息或待处理的文本)
final String? prompt;
/// 指令内容AI执行任务的具体指导
final String? instructions;
/// 选中的文本(扩写、缩写、重构时使用)
final String? selectedText;
/// 上下文选择数据
final ContextSelectionData? contextSelections;
/// 是否启用智能上下文RAG检索
final bool enableSmartContext;
/// 请求参数温度、最大token等
final Map<String, dynamic> parameters;
/// 元数据(其他附加信息)
final Map<String, dynamic> metadata;
/// 复制方法
UniversalAIRequest copyWith({
AIRequestType? requestType,
String? userId,
String? sessionId,
String? novelId,
String? chapterId,
String? sceneId,
String? settingSessionId,
UserAIModelConfigModel? modelConfig,
String? prompt,
String? instructions,
String? selectedText,
ContextSelectionData? contextSelections,
bool? enableSmartContext,
Map<String, dynamic>? parameters,
Map<String, dynamic>? metadata,
}) {
return UniversalAIRequest(
requestType: requestType ?? this.requestType,
userId: userId ?? this.userId,
sessionId: sessionId ?? this.sessionId,
novelId: novelId ?? this.novelId,
chapterId: chapterId ?? this.chapterId,
sceneId: sceneId ?? this.sceneId,
settingSessionId: settingSessionId ?? this.settingSessionId,
modelConfig: modelConfig ?? this.modelConfig,
prompt: prompt ?? this.prompt,
instructions: instructions ?? this.instructions,
selectedText: selectedText ?? this.selectedText,
contextSelections: contextSelections ?? this.contextSelections,
enableSmartContext: enableSmartContext ?? this.enableSmartContext,
parameters: parameters ?? this.parameters,
metadata: metadata ?? this.metadata,
);
}
/// 转换为API请求的JSON格式
Map<String, dynamic> toApiJson() {
final Map<String, dynamic> json = {
'requestType': requestType.value,
'userId': userId,
'enableSmartContext': enableSmartContext,
};
// 添加可选字段
if (sessionId != null) json['sessionId'] = sessionId;
if (novelId != null) json['novelId'] = novelId;
if (chapterId != null) json['chapterId'] = chapterId;
if (sceneId != null) json['sceneId'] = sceneId;
if (settingSessionId != null) json['settingSessionId'] = settingSessionId;
if (prompt != null) json['prompt'] = prompt;
if (instructions != null) json['instructions'] = instructions;
if (selectedText != null) json['selectedText'] = selectedText;
// 模型配置
if (modelConfig != null) {
json['modelName'] = modelConfig!.modelName;
json['modelProvider'] = modelConfig!.provider;
final bool isPublic = metadata['isPublicModel'] == true;
// 仅在私有模型时发送 modelConfigId避免公共模型被误判为私有配置查询
if (!isPublic) {
json['modelConfigId'] = modelConfig!.id;
}
// 🚀 明确标识是否为公共模型并传递公共配置ID
if (isPublic) {
json['isPublicModel'] = true;
if (metadata.containsKey('publicModelConfigId') && metadata['publicModelConfigId'] != null) {
// 优先使用 publicModelConfigId与后端期望一致
json['publicModelConfigId'] = metadata['publicModelConfigId'];
}
if (metadata.containsKey('publicModelId') && metadata['publicModelId'] != null) {
json['publicModelId'] = metadata['publicModelId']; // 兼容旧字段
}
print('🔧 [UniversalAIRequest.toApiJson] 公共模型请求 - 模型: ${modelConfig!.modelName}, 提供商: ${modelConfig!.provider}, 公共模型ID: ${metadata['publicModelId'] ?? metadata['publicModelConfigId']}');
} else {
json['isPublicModel'] = false;
print('🔧 [UniversalAIRequest.toApiJson] 私有模型请求 - 模型: ${modelConfig!.modelName}, 提供商: ${modelConfig!.provider}, 配置ID: ${modelConfig!.id}');
}
}
// 上下文选择
if (contextSelections != null && contextSelections!.selectedCount > 0) {
final contextList = contextSelections!.selectedItems.values
.map((item) => {
'id': item.id,
'title': item.title,
'type': item.type.value, // 🚀 修复使用API值而不是displayName
'metadata': item.metadata,
})
.toList();
json['contextSelections'] = contextList;
// 🚀 添加调试日志
print('🔧 [UniversalAIRequest.toApiJson] 添加上下文选择: ${contextList.length}个项目');
for (var item in contextList) {
print(' - ${item['type']}:${item['id']} (${item['title']})');
}
} else {
print('🔧 [UniversalAIRequest.toApiJson] 没有上下文选择数据');
}
// 请求参数
json['parameters'] = {
'temperature': parameters['temperature'] ?? 0.7,
'maxTokens': parameters['maxTokens'] ?? 2000,
'enableSmartContext': enableSmartContext, // 🚀 确保enableSmartContext也在parameters中
...parameters,
};
// 元数据
if (metadata.isNotEmpty) {
json['metadata'] = metadata;
}
return json;
}
/// 从JSON创建请求对象
factory UniversalAIRequest.fromJson(Map<String, dynamic> json) {
// 🚀 处理contextSelections字段
ContextSelectionData? contextSelections;
if (json['contextSelections'] != null) {
final contextList = json['contextSelections'] as List<dynamic>;
print('🔧 [UniversalAIRequest.fromJson] 解析contextSelections: ${contextList.length}个项目');
// 🚀 新增:检查是否需要过滤预设模板上下文
final isPresetTemplate = json['metadata']?['isPresetTemplate'] == true ||
json['source'] == 'preset_template' ||
contextList.any((item) => item['metadata']?['isHardcoded'] == true);
if (isPresetTemplate) {
print('🔧 [UniversalAIRequest.fromJson] 检测到预设模板,启用上下文过滤');
}
// 将已选择的项目转换为ContextSelectionItem并标记为已选择
final selectedItems = <String, ContextSelectionItem>{};
final availableItems = <ContextSelectionItem>[];
final flatItems = <String, ContextSelectionItem>{};
for (var itemData in contextList) {
final contextType = itemData['type'] as String?;
// 🚀 预设模板上下文过滤:只保留硬编码的上下文类型
if (isPresetTemplate && !_isHardcodedContextType(contextType)) {
print(' 🚫 过滤掉非硬编码上下文: $contextType');
continue;
}
final item = ContextSelectionItem(
id: itemData['id'] ?? '',
title: itemData['title'] ?? '',
type: ContextSelectionType.values.firstWhere(
(type) => type.value == 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;
print('${item.type.displayName}:${item.id} (${item.title})');
}
// 创建ContextSelectionData包含选择状态
contextSelections = ContextSelectionData(
novelId: json['novelId'] ?? '',
selectedItems: selectedItems,
availableItems: availableItems,
flatItems: flatItems,
);
if (isPresetTemplate) {
print('🔧 [UniversalAIRequest.fromJson] 预设模板上下文过滤完成: ${contextSelections.selectedCount}个硬编码项目');
} else {
print('🔧 [UniversalAIRequest.fromJson] 创建ContextSelectionData: ${contextSelections.selectedCount}个已选择项目');
}
}
// 🚀 智能获取enableSmartContext优先从顶级字段获取回退到parameters中获取
final Map<String, dynamic> parameters = Map<String, dynamic>.from(json['parameters'] ?? {});
bool enableSmartContext = json['enableSmartContext'] ??
parameters['enableSmartContext'] ??
false;
return UniversalAIRequest(
requestType: AIRequestType.values.firstWhere(
(type) => type.value == json['requestType'],
orElse: () => AIRequestType.chat,
),
userId: json['userId'] ?? '',
sessionId: json['sessionId'],
novelId: json['novelId'],
chapterId: json['chapterId'],
sceneId: json['sceneId'],
settingSessionId: json['settingSessionId'],
prompt: json['prompt'],
instructions: json['instructions'],
selectedText: json['selectedText'],
contextSelections: contextSelections,
enableSmartContext: enableSmartContext,
parameters: parameters,
metadata: Map<String, dynamic>.from(json['metadata'] ?? {}),
);
}
/// 🚀 新增:判断是否为硬编码的预设模板上下文类型
static bool _isHardcodedContextType(String? contextType) {
if (contextType == null) return false;
// 定义预设模板允许的硬编码上下文类型
const hardcodedTypes = {
// 核心文本上下文
'full_novel_text', // 全文文本
'full_outline', // 完整大纲
'novel_basic_info', // 基本信息
// 前五章相关
'recent_chapters_content', // 前五章内容
'recent_chapters_summary', // 前五章摘要
// 结构化上下文
'settings', // 设定
'snippets', // 片段
// 当前上下文
'chapters', // 章节(当前章节)
'scenes', // 场景(当前场景)
// 世界观相关
'setting_groups', // 设定组
'codex_entries', // 词条
};
return hardcodedTypes.contains(contextType);
}
}
/// AI响应模型
class UniversalAIResponse {
const UniversalAIResponse({
required this.id,
required this.requestType,
required this.content,
this.finishReason,
this.tokenUsage,
this.model,
this.createdAt,
this.metadata = const {},
});
/// 响应ID
final String id;
/// 对应的请求类型
final AIRequestType requestType;
/// 生成的内容
final String content;
/// 完成原因
final String? finishReason;
/// Token使用情况
final TokenUsage? tokenUsage;
/// 使用的模型
final String? model;
/// 创建时间
final DateTime? createdAt;
/// 元数据
final Map<String, dynamic> metadata;
/// 从JSON创建响应对象
factory UniversalAIResponse.fromJson(Map<String, dynamic> json) {
return UniversalAIResponse(
id: json['id'] ?? '',
requestType: AIRequestType.values.firstWhere(
(type) => type.value == json['requestType'],
orElse: () => AIRequestType.chat,
),
content: json['content'] ?? '',
finishReason: json['finishReason'],
tokenUsage: json['tokenUsage'] != null
? TokenUsage.fromJson(json['tokenUsage'])
: null,
model: json['model'],
createdAt: json['createdAt'] != null
? parseBackendDateTime(json['createdAt'])
: null,
metadata: Map<String, dynamic>.from(json['metadata'] ?? {}),
);
}
/// 转换为JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'requestType': requestType.value,
'content': content,
'finishReason': finishReason,
'tokenUsage': tokenUsage?.toJson(),
'model': model,
'createdAt': createdAt?.toIso8601String(),
'metadata': metadata,
};
}
}
/// Token使用情况
class TokenUsage {
const TokenUsage({
this.promptTokens = 0,
this.completionTokens = 0,
this.totalTokens = 0,
});
final int promptTokens;
final int completionTokens;
final int totalTokens;
/// 从JSON创建Token使用情况
factory TokenUsage.fromJson(Map<String, dynamic> json) {
return TokenUsage(
promptTokens: json['promptTokens'] ?? 0,
completionTokens: json['completionTokens'] ?? 0,
totalTokens: json['totalTokens'] ?? 0,
);
}
/// 转换为JSON
Map<String, dynamic> toJson() {
return {
'promptTokens': promptTokens,
'completionTokens': completionTokens,
'totalTokens': totalTokens,
};
}
}
/// 通用AI预览响应模型
class UniversalAIPreviewResponse {
const UniversalAIPreviewResponse({
required this.preview,
required this.systemPrompt,
required this.userPrompt,
this.context,
this.estimatedTokens,
this.modelName,
this.modelProvider,
this.modelConfigId,
});
/// 预览内容(完整的提示词)
final String preview;
/// 系统提示词
final String systemPrompt;
/// 用户提示词
final String userPrompt;
/// 上下文信息
final String? context;
/// 估计的Token数量
final int? estimatedTokens;
/// 将要使用的模型名称
final String? modelName;
/// 将要使用的模型提供商
final String? modelProvider;
/// 模型配置ID
final String? modelConfigId;
/// 从JSON创建预览响应
factory UniversalAIPreviewResponse.fromJson(Map<String, dynamic> json) {
return UniversalAIPreviewResponse(
preview: json['preview'] ?? '',
systemPrompt: json['systemPrompt'] ?? '',
userPrompt: json['userPrompt'] ?? '',
context: json['context'],
estimatedTokens: json['estimatedTokens'],
modelName: json['modelName'],
modelProvider: json['modelProvider'],
modelConfigId: json['modelConfigId'],
);
}
/// 转换为JSON
Map<String, dynamic> toJson() {
return {
'preview': preview,
'systemPrompt': systemPrompt,
'userPrompt': userPrompt,
'context': context,
'estimatedTokens': estimatedTokens,
'modelName': modelName,
'modelProvider': modelProvider,
'modelConfigId': modelConfigId,
};
}
/// 计算系统提示词的字数
int get systemPromptWordCount => _countWords(systemPrompt);
/// 计算用户提示词的字数
int get userPromptWordCount => _countWords(userPrompt);
/// 计算上下文的字数
int get contextWordCount => context != null ? _countWords(context!) : 0;
/// 计算总字数
int get totalWordCount => systemPromptWordCount + userPromptWordCount + contextWordCount;
/// 计算字数的辅助方法
static int _countWords(String text) {
if (text.isEmpty) return 0;
// 简单的字数计算:按空格分割英文单词,中文字符直接计数
int wordCount = 0;
int chineseCharCount = 0;
// 分割文本按空格
final words = text.split(RegExp(r'\s+'));
for (String word in words) {
if (word.trim().isEmpty) continue;
// 计算中文字符
for (int i = 0; i < word.length; i++) {
final charCode = word.codeUnitAt(i);
if (charCode >= 0x4e00 && charCode <= 0x9fff) {
chineseCharCount++;
}
}
// 移除中文字符后计算英文单词
final nonChineseWord = word.replaceAll(RegExp(r'[\u4e00-\u9fff]'), '');
if (nonChineseWord.trim().isNotEmpty) {
wordCount++;
}
}
// 中文字符每个算一个词,英文单词按原数量
return wordCount + chineseCharCount;
}
}
/// 扩展上下文选择类型枚举添加value字段用于API传输
extension ContextSelectionTypeApi on ContextSelectionType {
String get value {
switch (this) {
case ContextSelectionType.fullNovelText:
return 'full_novel_text';
case ContextSelectionType.fullOutline:
return 'full_outline';
case ContextSelectionType.novelBasicInfo:
return 'novel_basic_info';
case ContextSelectionType.recentChaptersContent:
return 'recent_chapters_content';
case ContextSelectionType.recentChaptersSummary:
return 'recent_chapters_summary';
case ContextSelectionType.currentSceneContent:
return 'current_scene_content';
case ContextSelectionType.currentSceneSummary:
return 'current_scene_summary';
case ContextSelectionType.currentChapterContent:
return 'current_chapter_content';
case ContextSelectionType.currentChapterSummaries:
return 'current_chapter_summary';
case ContextSelectionType.previousChaptersContent:
return 'previous_chapters_content';
case ContextSelectionType.previousChaptersSummary:
return 'previous_chapters_summary';
case ContextSelectionType.contentFixedGroup:
case ContextSelectionType.summaryFixedGroup:
return 'group';
case ContextSelectionType.acts:
return 'acts';
case ContextSelectionType.chapters:
return 'chapters';
case ContextSelectionType.scenes:
return 'scenes';
case ContextSelectionType.snippets:
return 'snippets';
case ContextSelectionType.settings:
return 'settings';
case ContextSelectionType.settingGroups:
return 'setting_groups';
case ContextSelectionType.settingsByType:
return 'settings_by_type';
case ContextSelectionType.codexEntries:
return 'codex_entries';
case ContextSelectionType.entriesByType:
return 'entries_by_type';
case ContextSelectionType.entriesByDetail:
return 'entries_by_detail';
case ContextSelectionType.entriesByCategory:
return 'entries_by_category';
case ContextSelectionType.entriesByTag:
return 'entries_by_tag';
}
}
}
/// 🚀 积分预估响应模型
class CostEstimationResponse {
const CostEstimationResponse({
required this.estimatedCost,
required this.success,
this.errorMessage,
this.estimatedInputTokens,
this.estimatedOutputTokens,
this.costMultiplier,
this.modelName,
this.modelProvider,
this.isPublicModel = false,
this.featureType,
});
/// 预估的积分成本
final int estimatedCost;
/// 是否成功
final bool success;
/// 错误信息
final String? errorMessage;
/// 预估输入Token数量
final int? estimatedInputTokens;
/// 预估输出Token数量
final int? estimatedOutputTokens;
/// 成本倍率
final double? costMultiplier;
/// 模型名称
final String? modelName;
/// 模型提供商
final String? modelProvider;
/// 是否为公共模型
final bool isPublicModel;
/// 功能类型
final String? featureType;
/// 从JSON创建积分预估响应
factory CostEstimationResponse.fromJson(Map<String, dynamic> json) {
return CostEstimationResponse(
estimatedCost: json['estimatedCost']?.toInt() ?? 0,
success: json['success'] ?? false,
errorMessage: json['errorMessage'],
estimatedInputTokens: json['estimatedInputTokens']?.toInt(),
estimatedOutputTokens: json['estimatedOutputTokens']?.toInt(),
costMultiplier: json['costMultiplier']?.toDouble(),
modelName: json['modelName'],
modelProvider: json['modelProvider'],
isPublicModel: json['isPublicModel'] ?? false,
featureType: json['featureType'],
);
}
/// 转换为JSON
Map<String, dynamic> toJson() {
return {
'estimatedCost': estimatedCost,
'success': success,
'errorMessage': errorMessage,
'estimatedInputTokens': estimatedInputTokens,
'estimatedOutputTokens': estimatedOutputTokens,
'costMultiplier': costMultiplier,
'modelName': modelName,
'modelProvider': modelProvider,
'isPublicModel': isPublicModel,
'featureType': featureType,
};
}
}