Files
MaliangAINovalWriter/AINoval/lib/screens/unified_management/widgets/preset_detail_view.dart
2025-09-10 00:07:52 +08:00

1792 lines
60 KiB
Dart
Raw 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 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:ainoval/blocs/preset/preset_bloc.dart';
import 'package:ainoval/blocs/preset/preset_state.dart';
import 'package:ainoval/blocs/preset/preset_event.dart';
import 'package:ainoval/blocs/prompt_new/prompt_new_bloc.dart';
import 'package:ainoval/blocs/prompt_new/prompt_new_event.dart';
import 'package:ainoval/models/preset_models.dart';
import 'package:ainoval/models/prompt_models.dart';
import 'package:ainoval/models/context_selection_models.dart';
import 'package:ainoval/models/ai_request_models.dart';
import 'package:ainoval/models/ai_feature_form_config.dart';
import 'package:ainoval/utils/web_theme.dart';
import 'package:ainoval/utils/logger.dart';
import 'package:ainoval/widgets/common/index.dart';
import 'package:ainoval/widgets/common/form_dialog_template.dart';
import 'package:ainoval/widgets/common/dynamic_form_field_widget.dart';
// 移除未使用的 multi_select 引用
/// 预设详情视图
/// 提供预设的查看和编辑功能,包含设置和预览两个标签页
class PresetDetailView extends StatefulWidget {
const PresetDetailView({super.key});
@override
State<PresetDetailView> createState() => _PresetDetailViewState();
}
class _PresetDetailViewState extends State<PresetDetailView>
with SingleTickerProviderStateMixin {
static const String _tag = 'PresetDetailView';
late TabController _tabController;
final TextEditingController _instructionsController = TextEditingController();
final TextEditingController _presetNameController = TextEditingController();
final TextEditingController _presetDescriptionController = TextEditingController();
final TextEditingController _tagsController = TextEditingController();
String? _selectedPromptTemplate;
bool _showInQuickAccess = false;
bool _enableSmartContext = true;
late ContextSelectionData _contextSelectionData;
double _temperature = 0.7; // 🚀 新增:温度参数
double _topP = 0.9; // 🚀 新增Top-P参数
AIPromptPreset? _editingPreset;
bool _hasUnsavedChanges = false;
// 🚀 新增:动态表单字段值映射表
final Map<AIFormFieldType, dynamic> _formValues = {};
// 🚀 新增:动态表单字段控制器映射表
final Map<AIFormFieldType, TextEditingController> _formControllers = {};
// 🚀 新增当前AI功能类型
AIFeatureType? _currentFeatureType;
@override
void initState() {
super.initState();
// 去掉“预览”页签,仅保留“设置”
_tabController = TabController(length: 1, vsync: this);
_contextSelectionData = FormFieldFactory.createPresetTemplateContextData();
// 🚀 初始化新的参数默认值
_temperature = 0.7;
_topP = 0.9;
// 🚀 初始化动态表单控制器
_initializeFormControllers();
}
/// 🚀 初始化动态表单控制器
void _initializeFormControllers() {
// 为需要文本控制器的字段类型创建控制器
final textFieldTypes = [
AIFormFieldType.instructions,
AIFormFieldType.length,
AIFormFieldType.style,
AIFormFieldType.memoryCutoff,
];
for (final type in textFieldTypes) {
_formControllers[type] = TextEditingController();
}
}
@override
void dispose() {
_tabController.dispose();
_instructionsController.dispose();
_presetNameController.dispose();
_presetDescriptionController.dispose();
_tagsController.dispose();
// 🚀 清理动态表单控制器
for (final controller in _formControllers.values) {
controller.dispose();
}
_formControllers.clear();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<PresetBloc, PresetState>(
builder: (context, state) {
// 🚀 修复:在状态变化时同步内部数据
if (!state.hasSelectedPreset) {
// 如果没有选中预设,清空表单
if (_editingPreset != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_clearForm();
});
}
return _buildEmptyState();
}
// 🚀 修复:检查是否需要加载新的预设数据
if (state.selectedPreset != _editingPreset) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadPresetData(state.selectedPreset);
});
}
return _buildDetailView(state.selectedPreset!);
},
);
}
void _loadPresetData(AIPromptPreset? preset) {
AppLogger.i(_tag, '🔄 开始加载预设数据: ${preset?.presetName ?? '空预设'}');
if (preset == null) {
_clearForm();
return;
}
_editingPreset = preset;
_presetNameController.text = preset.presetName ?? '';
_presetDescriptionController.text = preset.presetDescription ?? '';
_showInQuickAccess = preset.showInQuickAccess;
_tagsController.text = preset.tags.join(', ');
// 🚀 解析AI功能类型
try {
_currentFeatureType = AIFeatureTypeHelper.fromApiString(preset.aiFeatureType.toUpperCase());
AppLogger.i(_tag, '解析AI功能类型: $_currentFeatureType');
} catch (e) {
AppLogger.w(_tag, '无法解析AI功能类型: ${preset.aiFeatureType}', e);
_currentFeatureType = null;
}
// 🚀 修复:恢复关联的提示词模板
_selectedPromptTemplate = preset.templateId;
AppLogger.i(_tag, '恢复关联提示词模板: ${preset.templateId ?? "无关联模板"}');
// 🚀 确保提示词数据已加载(用于模板选择下拉框)
try {
final promptNewBloc = context.read<PromptNewBloc>();
if (promptNewBloc.state.promptPackages.isEmpty) {
AppLogger.i(_tag, '📢 触发提示词数据加载以支持模板选择');
promptNewBloc.add(const LoadAllPromptPackages());
}
} catch (e) {
AppLogger.w(_tag, '无法访问PromptNewBloc可能未注入到上下文中: $e');
}
final parsedRequest = preset.parsedRequest;
if (parsedRequest != null) {
AppLogger.i(_tag, '从预设解析出完整配置: ${preset.presetName}');
if (parsedRequest.instructions != null && parsedRequest.instructions!.isNotEmpty) {
_instructionsController.text = parsedRequest.instructions!;
} else {
_instructionsController.text = preset.effectiveUserPrompt;
}
if (parsedRequest.contextSelections != null && parsedRequest.contextSelections!.selectedCount > 0) {
// 🚀 修复:在预设管理模式下,使用硬编码的上下文数据
final originalContextData = parsedRequest.contextSelections!;
final filteredContextData = _filterPresetTemplateContextData(originalContextData);
_contextSelectionData = filteredContextData;
AppLogger.i(_tag, '应用上下文选择: 原始${originalContextData.selectedCount}个项目,过滤后${filteredContextData.selectedCount}个项目');
} else {
// 🚀 如果没有上下文数据,使用硬编码的预设模板上下文
_contextSelectionData = FormFieldFactory.createPresetTemplateContextData();
AppLogger.i(_tag, '使用硬编码的预设模板上下文数据');
}
if (parsedRequest.parameters.isNotEmpty) {
// 🚀 修复直接设置状态避免setState
_enableSmartContext = parsedRequest.enableSmartContext;
// 🚀 应用温度参数
final temperature = parsedRequest.parameters['temperature'];
if (temperature is double) {
_temperature = temperature;
AppLogger.i(_tag, '应用预设温度参数: $temperature');
} else if (temperature is num) {
_temperature = temperature.toDouble();
AppLogger.i(_tag, '应用预设温度参数: ${temperature.toDouble()}');
}
// 🚀 应用Top-P参数
final topP = parsedRequest.parameters['topP'];
if (topP is double) {
_topP = topP;
AppLogger.i(_tag, '应用预设Top-P参数: $topP');
} else if (topP is num) {
_topP = topP.toDouble();
AppLogger.i(_tag, '应用预设Top-P参数: ${topP.toDouble()}');
}
AppLogger.i(_tag, '应用参数设置: smartContext=$_enableSmartContext, temperature=$_temperature, topP=$_topP');
}
// 🚀 同步值到动态表单系统
_syncToFormValues(parsedRequest);
} else {
_instructionsController.text = preset.effectiveUserPrompt;
// 🚀 如果无法解析预设,使用硬编码的预设模板上下文
_contextSelectionData = FormFieldFactory.createPresetTemplateContextData();
AppLogger.i(_tag, '预设解析失败,使用硬编码的预设模板上下文数据');
}
_hasUnsavedChanges = false;
// 🚀 修复在方法最后统一触发UI更新
if (mounted) {
setState(() {
// 状态已经在上面设置好了,这里只是触发重建
});
}
}
/// 🚀 同步解析后的请求数据到动态表单值
void _syncToFormValues(UniversalAIRequest? request) {
if (request == null) return;
AppLogger.i(_tag, '🔄 同步解析请求数据到动态表单值');
// 同步指令
_formValues[AIFormFieldType.instructions] = request.instructions;
_formControllers[AIFormFieldType.instructions]?.text = request.instructions ?? '';
// 同步智能上下文
_formValues[AIFormFieldType.smartContext] = request.enableSmartContext;
// 同步温度
_formValues[AIFormFieldType.temperature] = _temperature;
// 同步Top-P
_formValues[AIFormFieldType.topP] = _topP;
// 同步快捷访问
_formValues[AIFormFieldType.quickAccess] = _showInQuickAccess;
// 同步提示词模板
_formValues[AIFormFieldType.promptTemplate] = _selectedPromptTemplate;
// 同步上下文选择
_formValues[AIFormFieldType.contextSelection] = _contextSelectionData;
// 根据不同功能类型同步特定字段
if (request.parameters.isNotEmpty) {
// 长度字段(用于扩写和缩写)
final length = request.parameters['length'] as String?;
if (length != null) {
_formValues[AIFormFieldType.length] = length;
_formControllers[AIFormFieldType.length]?.text = length;
}
// 样式字段(用于重构)
final style = request.parameters['style'] as String?;
if (style != null) {
_formValues[AIFormFieldType.style] = style;
_formControllers[AIFormFieldType.style]?.text = style;
}
// 记忆截断字段(用于聊天)
final memoryCutoff = request.parameters['memoryCutoff'];
if (memoryCutoff is int) {
_formValues[AIFormFieldType.memoryCutoff] = memoryCutoff;
_formControllers[AIFormFieldType.memoryCutoff]?.text = memoryCutoff.toString();
}
}
AppLogger.i(_tag, '✅ 动态表单值同步完成');
}
/// 🚀 新增:过滤预设模板上下文数据,只保留硬编码的上下文类型
ContextSelectionData _filterPresetTemplateContextData(ContextSelectionData originalData) {
// 定义硬编码的上下文类型
final hardcodedTypes = {
ContextSelectionType.fullNovelText,
ContextSelectionType.fullOutline,
ContextSelectionType.novelBasicInfo,
ContextSelectionType.recentChaptersContent,
ContextSelectionType.recentChaptersSummary,
ContextSelectionType.settings,
ContextSelectionType.snippets,
ContextSelectionType.chapters,
ContextSelectionType.scenes,
ContextSelectionType.settingGroups,
ContextSelectionType.codexEntries,
};
// 过滤已选择的项目,只保留硬编码类型
final filteredSelectedItems = <String, ContextSelectionItem>{};
for (final item in originalData.selectedItems.values) {
if (hardcodedTypes.contains(item.type) || item.metadata['isHardcoded'] == true) {
// 创建硬编码版本的项目,移除具体的小说关联信息
final hardcodedItem = _createHardcodedContextItem(item);
filteredSelectedItems[hardcodedItem.id] = hardcodedItem;
}
}
AppLogger.i(_tag, '上下文过滤: 原始${originalData.selectedCount}个 → 硬编码${filteredSelectedItems.length}');
// 如果过滤后没有项目,使用预设模板的硬编码上下文
if (filteredSelectedItems.isEmpty) {
AppLogger.i(_tag, '过滤后无有效上下文,使用预设模板硬编码上下文');
return FormFieldFactory.createPresetTemplateContextData();
}
// 获取硬编码的可用项目列表
final hardcodedAvailableItems = FormFieldFactory.createPresetTemplateContextData().availableItems;
final hardcodedFlatItems = FormFieldFactory.createPresetTemplateContextData().flatItems;
return ContextSelectionData(
novelId: 'preset_template', // 使用预设模板标识
selectedItems: filteredSelectedItems,
availableItems: hardcodedAvailableItems,
flatItems: hardcodedFlatItems,
);
}
/// 🚀 新增:创建硬编码版本的上下文项目
ContextSelectionItem _createHardcodedContextItem(ContextSelectionItem originalItem) {
// 根据类型生成硬编码的ID和标题
final hardcodedId = 'preset_${originalItem.type.displayName}';
final hardcodedTitle = originalItem.type.displayName;
// 移除具体的小说关联信息,只保留类型相关的元数据
final hardcodedMetadata = <String, dynamic>{
'isHardcoded': true,
'contextType': originalItem.type.displayName,
};
return ContextSelectionItem(
id: hardcodedId,
title: hardcodedTitle,
type: originalItem.type,
subtitle: _getHardcodedSubtitle(originalItem.type),
metadata: hardcodedMetadata,
selectionState: SelectionState.fullySelected,
);
}
/// 🚀 新增:获取硬编码上下文类型的子标题
String _getHardcodedSubtitle(ContextSelectionType type) {
switch (type) {
case ContextSelectionType.fullNovelText:
return '包含完整的小说文本内容';
case ContextSelectionType.fullOutline:
return '包含完整的小说大纲结构';
case ContextSelectionType.novelBasicInfo:
return '小说的基本信息(标题、作者、简介等)';
case ContextSelectionType.recentChaptersContent:
return '最近5章的内容';
case ContextSelectionType.recentChaptersSummary:
return '最近5章的摘要';
case ContextSelectionType.settings:
return '角色和世界观设定';
case ContextSelectionType.snippets:
return '参考片段和素材';
case ContextSelectionType.chapters:
return '当前章节内容';
case ContextSelectionType.scenes:
return '当前场景内容';
case ContextSelectionType.settingGroups:
return '设定组信息';
case ContextSelectionType.codexEntries:
return '词条和百科信息';
default:
return '硬编码上下文项目';
}
}
void _clearForm() {
AppLogger.i(_tag, '🧹 清空表单数据');
_editingPreset = null;
_presetNameController.clear();
_presetDescriptionController.clear();
_instructionsController.clear();
_selectedPromptTemplate = null;
_showInQuickAccess = false;
_enableSmartContext = true;
_contextSelectionData = FormFieldFactory.createPresetTemplateContextData();
_temperature = 0.7; // 🚀 新增:重置温度参数
_topP = 0.9; // 🚀 新增重置Top-P参数
_hasUnsavedChanges = false;
_tagsController.clear();
// 🚀 清空动态表单值和控制器
_formValues.clear();
for (final controller in _formControllers.values) {
controller.clear();
}
_currentFeatureType = null;
AppLogger.i(_tag, '🧹 表单清空完成 - 关联模板已重置为null');
}
/// 构建空状态视图
Widget _buildEmptyState() {
return Container(
color: WebTheme.getSurfaceColor(context),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: WebTheme.getPrimaryColor(context).withOpacity(0.06),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: WebTheme.getSecondaryBorderColor(context),
width: 1,
),
),
child: Icon(
Icons.settings_suggest_outlined,
size: 32,
color: WebTheme.getSecondaryTextColor(context),
),
),
const SizedBox(height: 20),
Text(
'选择一个预设',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: WebTheme.getTextColor(context),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'在左侧列表中选择一个预设进行查看或编辑',
style: TextStyle(
fontSize: 13,
color: WebTheme.getSecondaryTextColor(context),
),
textAlign: TextAlign.center,
),
],
),
),
);
}
/// 构建详情视图
Widget _buildDetailView(AIPromptPreset preset) {
return Container(
color: WebTheme.getSurfaceColor(context),
child: Column(
children: [
// 顶部操作栏
_buildTopActionBar(preset),
// 标签栏(仅“设置”)
_buildTabBar(),
// 标签页内容(仅“设置”)
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildSettingsTab(preset),
],
),
),
],
),
);
}
/// 构建顶部操作栏
Widget _buildTopActionBar(AIPromptPreset preset) {
return Container(
height: 52,
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(
color: WebTheme.getSurfaceColor(context),
border: Border(
bottom: BorderSide(
color: WebTheme.getSecondaryBorderColor(context),
width: 1.0,
),
),
),
child: Row(
children: [
// 预设类型图标
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: preset.isSystem
? (WebTheme.isDarkMode(context) ? WebTheme.darkGrey200 : WebTheme.grey100)
: WebTheme.getPrimaryColor(context),
borderRadius: BorderRadius.circular(6),
),
child: Icon(
preset.isSystem ? Icons.settings : Icons.person,
size: 16,
color: preset.isSystem
? WebTheme.getSecondaryTextColor(context)
: WebTheme.white,
),
),
const SizedBox(width: 10),
// 预设名称
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
preset.presetName ?? '未命名预设',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: WebTheme.getTextColor(context),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (preset.presetDescription != null && preset.presetDescription!.isNotEmpty)
Text(
preset.presetDescription!,
style: TextStyle(
fontSize: 11,
color: WebTheme.getSecondaryTextColor(context),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
// 状态指示器
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (_hasUnsavedChanges)
Container(
width: 6,
height: 6,
margin: const EdgeInsets.only(right: 6),
decoration: BoxDecoration(
color: WebTheme.getPrimaryColor(context),
shape: BoxShape.circle,
),
),
if (preset.showInQuickAccess)
Icon(
Icons.star,
size: 14,
color: Colors.amber,
),
],
),
const SizedBox(width: 8),
// 操作按钮组
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!preset.isSystem) ...[
_buildCompactActionButton(
icon: Icons.save,
tooltip: '保存',
onPressed: _hasUnsavedChanges ? () => _savePreset(preset) : null,
isDisabled: !_hasUnsavedChanges,
),
const SizedBox(width: 4),
],
_buildCompactActionButton(
icon: Icons.save_as,
tooltip: '另存为',
onPressed: () => _saveAsPreset(preset),
),
const SizedBox(width: 4),
_buildCompactActionButton(
icon: preset.showInQuickAccess ? Icons.star : Icons.star_outline,
tooltip: preset.showInQuickAccess ? '取消快捷访问' : '设为快捷访问',
onPressed: () => _toggleQuickAccess(preset),
),
if (!preset.isSystem) ...[
const SizedBox(width: 4),
_buildCompactActionButton(
icon: Icons.delete_outline,
tooltip: '删除',
onPressed: () => _deletePreset(preset),
isDestructive: true,
),
],
],
),
],
),
);
}
// 移除未使用的 _buildActionButton 以消除告警
/// 构建紧凑型操作按钮
Widget _buildCompactActionButton({
required IconData icon,
required String tooltip,
VoidCallback? onPressed,
bool isDestructive = false,
bool isDisabled = false,
}) {
final isDark = WebTheme.isDarkMode(context);
return Tooltip(
message: tooltip,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: isDisabled ? null : onPressed,
borderRadius: BorderRadius.circular(4),
child: Container(
width: 28,
height: 28,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: isDisabled
? (isDark ? WebTheme.darkGrey300 : WebTheme.grey300)
: (isDark ? WebTheme.darkGrey300 : WebTheme.grey300),
width: 1,
),
),
child: Icon(
icon,
size: 14,
color: isDisabled
? WebTheme.getSecondaryTextColor(context)
: isDestructive
? WebTheme.error
: WebTheme.getTextColor(context),
),
),
),
),
);
}
/// 构建标签栏
Widget _buildTabBar() {
return Container(
// 对齐提示词详情的标签栏样式
decoration: BoxDecoration(
color: WebTheme.getSurfaceColor(context),
border: Border(
bottom: BorderSide(
color: WebTheme.getSecondaryBorderColor(context),
width: 1.0,
),
),
),
child: TabBar(
controller: _tabController,
labelColor: WebTheme.getPrimaryColor(context),
unselectedLabelColor: WebTheme.getSecondaryTextColor(context),
indicatorColor: WebTheme.getPrimaryColor(context),
indicatorWeight: 3,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
unselectedLabelStyle: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 14,
),
dividerColor: Colors.transparent,
tabs: [
Tab(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.settings_outlined, size: 18),
const SizedBox(width: 8),
const Text('设置'),
],
),
),
],
),
);
}
/// 构建设置标签页
Widget _buildSettingsTab(AIPromptPreset preset) {
return Container(
color: WebTheme.getSurfaceColor(context),
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 基本信息
_buildCompactBasicInfoSection(preset),
const SizedBox(height: 20),
// 分割线
_buildDivider(),
const SizedBox(height: 20),
// 🚀 使用动态表单系统
..._buildDynamicFormFields(preset),
],
),
),
);
}
/// 区段标题(对齐 EditUserPresetDialog 的风格)
Widget _buildSectionHeader({
required String title,
Widget? trailing,
}) {
return Row(
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: WebTheme.getTextColor(context),
),
),
const Spacer(),
if (trailing != null) trailing,
],
);
}
void _showPromptHelper() {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: WebTheme.getSurfaceColor(context),
surfaceTintColor: Colors.transparent,
title: Text(
'提示词写作技巧',
style: TextStyle(
color: WebTheme.getTextColor(context),
),
),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
_buildPromptTip('优化建议', const [
'• 使用具体而非抽象的描述',
'• 明确定义期望的输出格式',
'• 提供具体的例子和情境',
'• 根据功能类型调整提示词风格',
]),
const SizedBox(height: 16),
_buildPromptTip('功能特定建议', const [
'聊天: 强调对话风格和个性',
'场景生成: 注重描述细节和氛围',
'续写: 保持风格一致性',
'总结: 明确长度和要点',
'大纲: 指定结构和层次',
]),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('知道了'),
),
],
),
);
}
Widget _buildPromptTip(String title, List<String> items) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
color: WebTheme.getTextColor(context),
),
),
const SizedBox(height: 8),
...items.map((item) => Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Text(
item,
style: TextStyle(
fontSize: 12,
color: WebTheme.getSecondaryTextColor(context),
),
),
)),
],
);
}
/// 🚀 构建动态表单字段
List<Widget> _buildDynamicFormFields(AIPromptPreset preset) {
if (_currentFeatureType == null) {
return [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
Icons.warning_outlined,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'无法识别的AI功能类型: ${preset.aiFeatureType}',
style: TextStyle(
color: Theme.of(context).colorScheme.error,
),
),
),
],
),
),
];
}
// 获取当前功能类型的表单配置
final formConfigs = AIFeatureFormConfig.getFormConfig(_currentFeatureType!);
if (formConfigs.isEmpty) {
return [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
Icons.info_outline,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'当前功能类型暂无配置的表单字段',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
],
),
),
];
}
// 对齐用户侧:分组渲染(指令区 / 上下文区 / 模板与参数区 / 其他)
final widgets = <Widget>[];
// 1) 指令相关
final instructionTypes = {
AIFormFieldType.instructions,
AIFormFieldType.length,
AIFormFieldType.style,
};
final instructionFields = formConfigs.where((c) => instructionTypes.contains(c.type)).toList();
if (instructionFields.isNotEmpty) {
widgets.add(_buildSectionHeader(title: '提示词配置', trailing: TextButton.icon(
onPressed: _showPromptHelper,
icon: const Icon(Icons.help_outline, size: 16),
label: const Text('写作技巧'),
)));
widgets.add(const SizedBox(height: 12));
widgets.addAll(_buildFieldList(preset, instructionFields));
widgets.add(const SizedBox(height: 20));
widgets.add(_buildDivider());
widgets.add(const SizedBox(height: 20));
}
// 2) 上下文相关
final contextTypes = {
AIFormFieldType.contextSelection,
AIFormFieldType.smartContext,
AIFormFieldType.memoryCutoff,
};
final contextFields = formConfigs.where((c) => contextTypes.contains(c.type)).toList();
if (contextFields.isNotEmpty) {
widgets.add(_buildSectionHeader(title: '上下文与记忆'));
widgets.add(const SizedBox(height: 12));
widgets.addAll(_buildFieldList(preset, contextFields));
widgets.add(const SizedBox(height: 20));
widgets.add(_buildDivider());
widgets.add(const SizedBox(height: 20));
}
// 3) 模板与参数
final templateAndParams = formConfigs.where((c) =>
c.type == AIFormFieldType.promptTemplate ||
c.type == AIFormFieldType.temperature ||
c.type == AIFormFieldType.topP
).toList();
if (templateAndParams.isNotEmpty) {
widgets.add(_buildSectionHeader(title: '模板与生成参数'));
widgets.add(const SizedBox(height: 12));
widgets.addAll(_buildFieldList(preset, templateAndParams));
widgets.add(const SizedBox(height: 20));
widgets.add(_buildDivider());
widgets.add(const SizedBox(height: 20));
}
// 4) 其他(快捷访问等)
final otherFields = formConfigs.where((c) =>
!instructionTypes.contains(c.type) &&
!contextTypes.contains(c.type) &&
c.type != AIFormFieldType.promptTemplate &&
c.type != AIFormFieldType.temperature &&
c.type != AIFormFieldType.topP
).toList();
if (otherFields.isNotEmpty) {
widgets.add(_buildSectionHeader(title: '其他设置'));
widgets.add(const SizedBox(height: 12));
widgets.addAll(_buildFieldList(preset, otherFields));
}
return widgets;
}
List<Widget> _buildFieldList(AIPromptPreset preset, List<FormFieldConfig> fields) {
final list = <Widget>[];
for (int i = 0; i < fields.length; i++) {
final config = fields[i];
list.add(
DynamicFormFieldWidget(
config: config,
values: _formValues,
onValueChanged: _handleDynamicFormValueChanged,
onReset: _handleDynamicFormFieldReset,
contextSelectionData: _contextSelectionData,
controllers: _formControllers,
aiFeatureType: preset.aiFeatureType,
isSystemPreset: preset.isSystem,
isPublicPreset: preset.isPublic,
),
);
if (i < fields.length - 1) {
list.add(const SizedBox(height: 16));
}
}
return list;
}
Widget _buildDivider() {
return Container(
height: 1,
color: WebTheme.getSecondaryBorderColor(context),
);
}
/// 构建紧凑型基本信息部分
Widget _buildCompactBasicInfoSection(AIPromptPreset preset) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.outlineVariant,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 标题
Text(
'基本信息',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: WebTheme.getTextColor(context),
),
),
const SizedBox(height: 12),
// 预设名称对齐用户对话框样式OutlineInputBorder、isDense、hint 颜色)
_buildCompactFormField(
label: '预设名称',
child: TextFormField(
controller: _presetNameController,
style: TextStyle(
fontSize: 13,
color: WebTheme.getTextColor(context),
),
decoration: WebTheme.getBorderedInputDecoration(
labelText: '预设名称',
hintText: '输入预设名称',
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
context: context,
),
enabled: !preset.isSystem,
onChanged: (_) => _markAsChanged(),
),
),
const SizedBox(height: 12),
// 预设描述(对齐用户对话框样式)
_buildCompactFormField(
label: '预设描述',
child: TextFormField(
controller: _presetDescriptionController,
maxLines: 2,
style: TextStyle(
fontSize: 13,
color: WebTheme.getTextColor(context),
),
decoration: WebTheme.getBorderedInputDecoration(
labelText: '预设描述',
hintText: '输入预设描述',
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
context: context,
),
enabled: !preset.isSystem,
onChanged: (_) => _markAsChanged(),
),
),
const SizedBox(height: 12),
// 标签(对齐用户侧:逗号分隔输入框)
_buildCompactFormField(
label: '标签',
child: TextFormField(
controller: _tagsController,
style: TextStyle(
fontSize: 13,
color: WebTheme.getTextColor(context),
),
decoration: WebTheme.getBorderedInputDecoration(
labelText: '标签',
hintText: '请输入标签,用逗号分隔',
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
context: context,
),
enabled: !preset.isSystem,
onChanged: (_) => _markAsChanged(),
),
),
const SizedBox(height: 12),
// 功能类型和状态信息(横向布局)
Row(
children: [
Expanded(
child: _buildCompactInfoItem(
label: 'AI功能',
value: _getFeatureDisplayName(preset.aiFeatureType),
),
),
const SizedBox(width: 16),
Expanded(
child: _buildCompactInfoItem(
label: '类型',
value: preset.isSystem ? '系统预设' : '用户预设',
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: _buildCompactInfoItem(
label: '使用次数',
value: '${preset.useCount}',
),
),
const SizedBox(width: 16),
Expanded(
child: _buildCompactInfoItem(
label: '快捷访问',
value: preset.showInQuickAccess ? '' : '',
),
),
],
),
// 标签
if (preset.tags.isNotEmpty) ...[
const SizedBox(height: 12),
_buildCompactFormField(
label: '标签',
child: Wrap(
spacing: 6,
runSpacing: 6,
children: preset.tags.map((tag) => _buildCompactTag(tag)).toList(),
),
),
],
],
),
);
}
/// 构建紧凑型表单字段
Widget _buildCompactFormField({
required String label,
required Widget child,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: WebTheme.getSecondaryTextColor(context),
),
),
const SizedBox(height: 6),
child,
],
);
}
/// 构建紧凑型信息项
Widget _buildCompactInfoItem({
required String label,
required String value,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 11,
color: WebTheme.getSecondaryTextColor(context),
),
),
const SizedBox(height: 2),
Text(
value,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: WebTheme.getTextColor(context),
),
),
],
);
}
/// 构建紧凑型标签
Widget _buildCompactTag(String text) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: WebTheme.isDarkMode(context) ? WebTheme.darkGrey200 : WebTheme.grey100,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: WebTheme.getSecondaryBorderColor(context),
width: 1,
),
),
child: Text(
text,
style: TextStyle(
fontSize: 11,
color: WebTheme.getTextColor(context),
),
),
);
}
/// 🚀 处理动态表单字段值变更
void _handleDynamicFormValueChanged(AIFormFieldType type, dynamic value) {
setState(() {
_formValues[type] = value;
// 同步到传统字段变量(保持兼容性)
switch (type) {
case AIFormFieldType.instructions:
_instructionsController.text = value as String? ?? '';
break;
case AIFormFieldType.smartContext:
_enableSmartContext = value as bool? ?? true;
break;
case AIFormFieldType.temperature:
_temperature = value as double? ?? 0.7;
break;
case AIFormFieldType.topP:
_topP = value as double? ?? 0.9;
break;
case AIFormFieldType.quickAccess:
_showInQuickAccess = value as bool? ?? false;
break;
case AIFormFieldType.promptTemplate:
_selectedPromptTemplate = value as String?;
break;
case AIFormFieldType.contextSelection:
if (value is ContextSelectionData) {
_contextSelectionData = value;
}
break;
default:
// 其他字段类型保存在_formValues中
break;
}
_markAsChanged();
});
AppLogger.i(_tag, '动态表单字段值已更改: $type = $value');
}
/// 🚀 处理动态表单字段重置
void _handleDynamicFormFieldReset(AIFormFieldType type) {
setState(() {
_formValues.remove(type);
_formControllers[type]?.clear();
// 重置传统字段变量(保持兼容性)
switch (type) {
case AIFormFieldType.instructions:
_instructionsController.clear();
break;
case AIFormFieldType.smartContext:
_enableSmartContext = true;
_formValues[type] = true;
break;
case AIFormFieldType.temperature:
_temperature = 0.7;
_formValues[type] = 0.7;
break;
case AIFormFieldType.topP:
_topP = 0.9;
_formValues[type] = 0.9;
break;
case AIFormFieldType.quickAccess:
_showInQuickAccess = false;
_formValues[type] = false;
break;
case AIFormFieldType.promptTemplate:
_selectedPromptTemplate = null;
break;
case AIFormFieldType.contextSelection:
_contextSelectionData = FormFieldFactory.createPresetTemplateContextData();
_formValues[type] = _contextSelectionData;
break;
default:
// 其他字段类型的默认重置逻辑
break;
}
_markAsChanged();
});
AppLogger.i(_tag, '动态表单字段已重置: $type');
}
// 移除未使用的 _buildBasicInfoSection 以消除告警
// 预览功能已移除
// 移除未使用的 _buildFormField
// 移除未使用的 _buildTag
// 移除未使用的 _buildAddTagButton
/// 获取指令预设列表
// 移除未使用的 _getInstructionPresets 以消除告警
/// 获取功能类型显示名称
String _getFeatureDisplayName(String featureType) {
try {
final type = AIFeatureTypeHelper.fromApiString(featureType.toUpperCase());
return type.displayName;
} catch (e) {
return featureType;
}
}
/// 将AIFeatureType映射到AIRequestType
AIRequestType _mapFeatureTypeToRequestType(AIFeatureType featureType) {
switch (featureType) {
case AIFeatureType.textExpansion:
return AIRequestType.expansion;
case AIFeatureType.textSummary:
return AIRequestType.summary;
case AIFeatureType.textRefactor:
return AIRequestType.refactor;
case AIFeatureType.aiChat:
return AIRequestType.chat;
case AIFeatureType.sceneToSummary:
return AIRequestType.sceneSummary;
case AIFeatureType.novelGeneration:
return AIRequestType.generation;
case AIFeatureType.novelCompose:
return AIRequestType.novelCompose;
default:
return AIRequestType.expansion; // 默认类型
}
}
/// 标记为已更改
void _markAsChanged() {
if (!_hasUnsavedChanges) {
setState(() {
_hasUnsavedChanges = true;
});
}
}
// 移除未使用的 handlers 以消除告警
/// 🚀 新增:处理温度参数变化
/// 🚀 新增:重置温度参数
/// 🚀 新增处理Top-P参数变化
/// 🚀 新增重置Top-P参数
// 操作方法
void _savePreset(AIPromptPreset preset) {
AppLogger.i(_tag, '💾 开始保存预设: ${preset.presetId}');
try {
// 🚀 使用当前编辑状态而不是传入参数
final currentPreset = _editingPreset ?? preset;
// 🚀 重新构建 requestData反映用户的所有修改
final updatedRequest = _buildUniversalAIRequestFromCurrentForm(currentPreset);
final newRequestData = updatedRequest != null
? jsonEncode(updatedRequest.toApiJson())
: currentPreset.requestData; // 如果构建失败,保持原数据
// 🚀 重新计算预设哈希
final newPresetHash = _generatePresetHash(newRequestData);
// 🚀 构建完整的更新对象(基于最新状态)
final normalizedTemplateId = _normalizeTemplateIdForSave(_selectedPromptTemplate);
final updatedPreset = AIPromptPreset(
presetId: currentPreset.presetId,
userId: currentPreset.userId,
presetName: _presetNameController.text.trim(),
presetDescription: _presetDescriptionController.text.trim().isNotEmpty
? _presetDescriptionController.text.trim()
: null,
presetTags: _parseTags(_tagsController.text),
isFavorite: currentPreset.isFavorite,
isPublic: currentPreset.isPublic,
useCount: currentPreset.useCount,
presetHash: newPresetHash,
requestData: newRequestData, // 🚀 使用重新构建的 requestData
systemPrompt: currentPreset.systemPrompt,
userPrompt: _instructionsController.text.trim(),
aiFeatureType: currentPreset.aiFeatureType,
customSystemPrompt: currentPreset.customSystemPrompt,
customUserPrompt: _instructionsController.text.trim().isNotEmpty
? _instructionsController.text.trim()
: null,
promptCustomized: _instructionsController.text.trim() != currentPreset.userPrompt,
templateId: normalizedTemplateId,
isSystem: currentPreset.isSystem,
showInQuickAccess: _showInQuickAccess,
createdAt: currentPreset.createdAt,
updatedAt: DateTime.now(),
lastUsedAt: currentPreset.lastUsedAt,
);
AppLogger.i(_tag, '📋 构建完整更新对象:');
AppLogger.i(_tag, ' - 预设名称: ${updatedPreset.presetName}');
AppLogger.i(_tag, ' - 预设描述: ${updatedPreset.presetDescription ?? ""}');
AppLogger.i(_tag, ' - 快捷访问: ${updatedPreset.showInQuickAccess}');
AppLogger.i(_tag, ' - 指令长度: ${_instructionsController.text.length}');
// 🚀 发送覆盖更新事件
context.read<PresetBloc>().add(OverwritePreset(preset: updatedPreset));
// 重置修改标记
setState(() {
_hasUnsavedChanges = false;
});
AppLogger.i(_tag, '✅ 覆盖更新请求已发送');
} catch (e) {
AppLogger.e(_tag, '❌ 构建保存请求失败', e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('保存失败: $e'),
backgroundColor: Colors.red,
),
);
}
}
void _saveAsPreset(AIPromptPreset preset) {
AppLogger.i(_tag, '📋 另存为预设: ${preset.presetId}');
_showSaveAsDialog(preset);
}
/// 显示另存为对话框
void _showSaveAsDialog(AIPromptPreset preset) {
final TextEditingController nameController = TextEditingController();
final TextEditingController descController = TextEditingController();
// 设置默认名称
nameController.text = '${_presetNameController.text.trim()} - 副本';
descController.text = _presetDescriptionController.text.trim();
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: WebTheme.getSurfaceColor(context),
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: WebTheme.getSecondaryBorderColor(context),
width: 1,
),
),
title: Text(
'另存为新预设',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: WebTheme.getTextColor(context),
),
),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
style: TextStyle(
fontSize: 13,
color: WebTheme.getTextColor(context),
),
decoration: WebTheme.getBorderedInputDecoration(
labelText: '新预设名称',
hintText: '输入新预设名称',
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
context: context,
),
autofocus: true,
),
const SizedBox(height: 12),
TextField(
controller: descController,
style: TextStyle(
fontSize: 13,
color: WebTheme.getTextColor(context),
),
decoration: WebTheme.getBorderedInputDecoration(
labelText: '描述(可选)',
hintText: '输入预设描述',
isDense: true,
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
context: context,
),
maxLines: 2,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
foregroundColor: WebTheme.getSecondaryTextColor(context),
textStyle: TextStyle(fontSize: 13),
),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
final name = nameController.text.trim();
if (name.isNotEmpty) {
Navigator.of(context).pop();
_performSaveAs(preset, name, descController.text.trim());
}
},
style: ElevatedButton.styleFrom(
backgroundColor: WebTheme.getPrimaryColor(context),
foregroundColor: WebTheme.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
textStyle: TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
),
child: const Text('另存为'),
),
],
),
);
}
/// 执行另存为操作
void _performSaveAs(AIPromptPreset preset, String newName, String newDescription) {
AppLogger.i(_tag, '🚀 开始执行另存为: $newName');
try {
// 构建新的UniversalAIRequest
final newRequest = _buildUniversalAIRequestFromCurrentForm(preset);
if (newRequest == null) {
throw Exception('无法构建有效的AI请求配置');
}
// 构建创建预设请求
final createRequest = CreatePresetRequest(
presetName: newName,
presetDescription: newDescription.isNotEmpty ? newDescription : null,
presetTags: _parseTags(_tagsController.text),
request: newRequest,
);
AppLogger.i(_tag, '📋 创建请求已构建:');
AppLogger.i(_tag, ' - 新预设名称: $newName');
AppLogger.i(_tag, ' - 新预设描述: ${newDescription.isNotEmpty ? newDescription : ""}');
AppLogger.i(_tag, ' - 功能类型: ${preset.aiFeatureType}');
AppLogger.i(_tag, ' - 指令长度: ${_instructionsController.text.length}');
AppLogger.i(_tag, ' - 上下文项目数: ${_contextSelectionData.selectedCount}');
AppLogger.i(_tag, ' - 关联模板ID: ${_selectedPromptTemplate ?? ""}');
// 发送创建事件到PresetBloc
context.read<PresetBloc>().add(CreatePreset(request: createRequest));
AppLogger.i(_tag, '✅ 另存为请求已发送');
} catch (e) {
AppLogger.e(_tag, '❌ 另存为操作失败', e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('另存为失败: $e'),
backgroundColor: Colors.red,
),
);
}
}
List<String>? _parseTags(String text) {
final parts = text
.split(',')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
return parts.isEmpty ? null : parts;
}
/// 从当前表单状态构建UniversalAIRequest
UniversalAIRequest? _buildUniversalAIRequestFromCurrentForm(AIPromptPreset preset) {
try {
// 解析AI功能类型
AIRequestType requestType;
try {
final featureType = AIFeatureTypeHelper.fromApiString(preset.aiFeatureType.toUpperCase());
requestType = _mapFeatureTypeToRequestType(featureType);
} catch (e) {
AppLogger.w(_tag, '无法解析功能类型: ${preset.aiFeatureType}', e);
requestType = AIRequestType.expansion; // 回退到默认类型
}
// 构建请求对象
final normalizedTemplateId = _normalizeTemplateIdForSave(_selectedPromptTemplate);
final request = UniversalAIRequest(
requestType: requestType,
userId: preset.userId,
novelId: 'preset_template', // 预设模板使用特殊的novelId
instructions: _instructionsController.text.trim().isNotEmpty
? _instructionsController.text.trim()
: null,
contextSelections: _contextSelectionData,
enableSmartContext: _enableSmartContext,
parameters: {
'enableSmartContext': _enableSmartContext,
'showInQuickAccess': _showInQuickAccess,
'associatedTemplateId': normalizedTemplateId,
'promptTemplateId': normalizedTemplateId,
'temperature': _temperature, // 🚀 新增:温度参数
'topP': _topP, // 🚀 新增Top-P参数
},
metadata: {
'source': 'preset_management',
'action': 'save_as',
'originalPresetId': preset.presetId,
'contextCount': _contextSelectionData.selectedCount,
'enableSmartContext': _enableSmartContext,
'showInQuickAccess': _showInQuickAccess,
'associatedTemplateId': normalizedTemplateId,
'promptTemplateId': normalizedTemplateId,
'temperature': _temperature, // 🚀 新增:温度参数
'topP': _topP, // 🚀 新增Top-P参数
},
);
AppLogger.i(_tag, '🔧 UniversalAIRequest构建成功:');
AppLogger.i(_tag, ' - requestType: ${request.requestType.value}');
AppLogger.i(_tag, ' - userId: ${request.userId}');
AppLogger.i(_tag, ' - novelId: ${request.novelId}');
AppLogger.i(_tag, ' - 指令: ${request.instructions?.substring(0, request.instructions!.length.clamp(0, 50)) ?? ""}...');
return request;
} catch (e) {
AppLogger.e(_tag, '❌ 构建UniversalAIRequest失败', e);
return null;
}
}
/// 规范化模板ID以用于保存
/// - public_ 前缀移除得到真实模板ID
/// - system_default_ 视为不关联返回null
String? _normalizeTemplateIdForSave(String? rawId) {
if (rawId == null || rawId.isEmpty) return null;
if (rawId.startsWith('public_')) return rawId.substring(7);
if (rawId.startsWith('system_default_')) return null;
return rawId;
}
void _toggleQuickAccess(AIPromptPreset preset) {
AppLogger.i(_tag, '⭐ 切换快捷访问状态: ${preset.presetId}');
AppLogger.i(_tag, ' - 当前状态: ${preset.showInQuickAccess ? "已启用" : "已禁用"}');
AppLogger.i(_tag, ' - 预设类型: ${preset.isSystem ? "系统预设" : "用户预设"}');
AppLogger.i(_tag, ' - 预设名称: ${preset.presetName}');
// 检查预设是否有效
if (preset.presetId.isEmpty) {
AppLogger.e(_tag, '❌ 预设ID为空无法切换快捷访问状态');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('操作失败预设ID无效'),
backgroundColor: Colors.red,
),
);
return;
}
try {
context.read<PresetBloc>().add(TogglePresetQuickAccess(presetId: preset.presetId));
AppLogger.i(_tag, '✅ 快捷访问切换请求已发送');
} catch (e) {
AppLogger.e(_tag, '❌ 发送快捷访问切换请求失败', e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('操作失败: $e'),
backgroundColor: Colors.red,
),
);
}
}
void _deletePreset(AIPromptPreset preset) {
showDialog(
context: context,
builder: (context) => AlertDialog(
backgroundColor: WebTheme.getSurfaceColor(context),
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: WebTheme.getSecondaryBorderColor(context),
width: 1,
),
),
title: Text(
'确认删除',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: WebTheme.getTextColor(context),
),
),
content: Text(
'确定要删除预设"${preset.presetName}"吗?此操作无法撤销。',
style: TextStyle(
fontSize: 13,
color: WebTheme.getSecondaryTextColor(context),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
foregroundColor: WebTheme.getSecondaryTextColor(context),
textStyle: TextStyle(fontSize: 13),
),
child: const Text('取消'),
),
ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
AppLogger.i(_tag, '删除预设: ${preset.presetId}');
context.read<PresetBloc>().add(DeletePreset(presetId: preset.presetId));
},
style: ElevatedButton.styleFrom(
backgroundColor: WebTheme.error,
foregroundColor: WebTheme.white,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
textStyle: TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
),
child: const Text('删除'),
),
],
),
);
}
/// 🚀 生成预设哈希值
String _generatePresetHash(String requestDataJson) {
try {
final bytes = utf8.encode(requestDataJson);
final digest = sha256.convert(bytes);
return digest.toString();
} catch (e) {
AppLogger.w(_tag, '生成预设哈希失败,使用时间戳: $e');
return DateTime.now().millisecondsSinceEpoch.toString();
}
}
}