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

680 lines
21 KiB
Dart
Raw Permalink 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 '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,
};
}
}