import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../blocs/setting_generation/setting_generation_bloc.dart'; import '../../../blocs/setting_generation/setting_generation_event.dart'; import '../../../blocs/setting_generation/setting_generation_state.dart'; import '../../../models/unified_ai_model.dart'; import '../../../models/strategy_template_info.dart'; import '../../../models/setting_generation_session.dart'; import '../../../widgets/common/model_display_selector.dart'; import '../../../blocs/ai_config/ai_config_bloc.dart'; import 'strategy_selector_dropdown.dart'; /// 生成控制面板 class GenerationControlPanel extends StatefulWidget { final String? initialPrompt; final UnifiedAIModel? selectedModel; final String? initialStrategy; final Function(String prompt, String strategy, String modelConfigId)? onGenerationStart; const GenerationControlPanel({ Key? key, this.initialPrompt, this.selectedModel, this.initialStrategy, this.onGenerationStart, }) : super(key: key); @override State createState() => _GenerationControlPanelState(); } class _GenerationControlPanelState extends State { late TextEditingController _promptController; late TextEditingController _adjustmentController; UnifiedAIModel? _selectedModel; StrategyTemplateInfo? _selectedStrategy; // 防抖计时器,降低输入频率带来的状态分发与重建 Timer? _adjustmentDebounce; // 🔧 新增:跟踪当前活动的会话ID,用于检测会话切换 String? _currentActiveSessionId; // 🔧 新增:跟踪用户是否手动修改了原始创意,避免覆盖用户输入 bool _userHasModifiedPrompt = false; @override void initState() { super.initState(); _promptController = TextEditingController(text: widget.initialPrompt ?? ''); _adjustmentController = TextEditingController(); // 注意:_selectedStrategy 将在策略加载完成后根据 widget.initialStrategy 设置 // 获取用户默认模型配置 final defaultConfig = context.read().state.defaultConfig ?? (context.read().state.validatedConfigs.isNotEmpty ? context.read().state.validatedConfigs.first : null); _selectedModel = widget.selectedModel ?? (defaultConfig != null ? PrivateAIModel(defaultConfig) : null); // 🔧 新增:在初始化时同步当前活动会话的原始创意 WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { final currentState = context.read().state; _handleActiveSessionChange(currentState); } }); } @override void dispose() { _promptController.dispose(); _adjustmentController.dispose(); _adjustmentDebounce?.cancel(); super.dispose(); } /// 🔧 新增:处理活动会话变化,自动填充原始创意 void _handleActiveSessionChange(SettingGenerationState state) { String? activeSessionId; SettingGenerationSession? activeSession; // 从不同状态中提取活动会话信息 if (state is SettingGenerationReady) { activeSessionId = state.activeSessionId; if (activeSessionId != null) { try { activeSession = state.sessions.firstWhere( (s) => s.sessionId == activeSessionId, ); } catch (e) { activeSession = state.sessions.isNotEmpty ? state.sessions.first : null; } } } else if (state is SettingGenerationInProgress) { activeSessionId = state.activeSessionId; activeSession = state.activeSession; } else if (state is SettingGenerationCompleted) { activeSessionId = state.activeSessionId; activeSession = state.activeSession; } else if (state is SettingGenerationError) { activeSessionId = state.activeSessionId; if (activeSessionId != null) { try { activeSession = state.sessions.firstWhere( (s) => s.sessionId == activeSessionId, ); } catch (e) { activeSession = state.sessions.isNotEmpty ? state.sessions.first : null; } } } // 检测会话是否发生变化 if (_currentActiveSessionId != activeSessionId && activeSession != null) { _currentActiveSessionId = activeSessionId; // 🎯 核心功能:将历史记录的原始提示词填充到原始创意输入框 final newPrompt = activeSession.initialPrompt; // 🔧 智能填充:只有在用户未手动修改原始创意时才自动填充 // 或者当前输入框为空时总是填充 final shouldUpdatePrompt = !_userHasModifiedPrompt || _promptController.text.trim().isEmpty; if (newPrompt.isNotEmpty && _promptController.text != newPrompt && shouldUpdatePrompt) { if (mounted) { setState(() { _promptController.text = newPrompt; // 重置用户修改标记,因为这是系统自动填充 _userHasModifiedPrompt = false; }); } // 📝 记录日志用于调试 print('🔄 历史记录切换 - 原始创意已更新: ${newPrompt.substring(0, newPrompt.length > 50 ? 50 : newPrompt.length)}${newPrompt.length > 50 ? "..." : ""}'); } else if (_userHasModifiedPrompt && newPrompt.isNotEmpty) { // 📝 用户已修改,不覆盖但记录日志 print('🛡️ 历史记录切换 - 检测到用户已修改原始创意,跳过自动填充以保护用户输入'); } } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return BlocListener( listener: (context, state) { // 🔧 新增:监听活动会话变化,自动填充原始创意 _handleActiveSessionChange(state); }, child: Card( elevation: 0, color: isDark ? const Color(0xFF1F2937).withOpacity(0.5) : const Color(0xFFF9FAFB).withOpacity(0.5), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), side: BorderSide( color: isDark ? const Color(0xFF1F2937) : const Color(0xFFE5E7EB), width: 1, ), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( '创作控制台', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 16), // 🔧 修复:自适应高度,紧凑布局 Flexible( child: SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ // 提示词输入区域 BlocBuilder( builder: (context, state) { return _buildPromptInput(state); }, ), const SizedBox(height: 16), // 策略选择器 _buildStrategySelector(), const SizedBox(height: 16), // 模型选择器 _buildModelSelector(), const SizedBox(height: 24), // 适度间距 // 操作按钮 BlocBuilder( builder: (context, state) { return _buildActionButtons(state); }, ), const SizedBox(height: 16), // 底部留白 ], ), ), ), ], ), ), ), ); } Widget _buildPromptInput(SettingGenerationState state) { final hasGeneratedSettings = state is SettingGenerationInProgress || state is SettingGenerationCompleted; if (!hasGeneratedSettings) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '你的核心想法', style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, color: Theme.of(context).textTheme.bodySmall?.color, ), ), const SizedBox(height: 8), TextField( controller: _promptController, decoration: InputDecoration( hintText: '例如:一个发生在赛博朋克都市的侦探故事', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Theme.of(context).colorScheme.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), // 🔧 修复:设置合理的行数范围,避免布局问题 maxLines: 5, minLines: 2, textInputAction: TextInputAction.newline, onChanged: (value) { // 🔧 新增:标记用户已手动修改原始创意 _userHasModifiedPrompt = true; }, ), ], ); } else { // 🔧 修复:生成完成后显示两个输入框 - 原始提示词和调整提示词 return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 原始提示词(只读显示,可以编辑用于新建生成) Text( '原始创意', style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, color: Theme.of(context).textTheme.bodySmall?.color, ), ), const SizedBox(height: 8), TextField( controller: _promptController, decoration: InputDecoration( hintText: '例如:一个发生在赛博朋克都市的侦探故事', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Theme.of(context).colorScheme.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), // 🎯 自适应行数:根据内容长度调整,最多3行 maxLines: 3, minLines: 1, textInputAction: TextInputAction.newline, onChanged: (value) { // 🔧 新增:标记用户已手动修改原始创意 _userHasModifiedPrompt = true; }, ), const SizedBox(height: 16), // 调整提示词 Text( '调整设定', style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, color: Theme.of(context).textTheme.bodySmall?.color, ), ), const SizedBox(height: 8), TextField( controller: _adjustmentController, decoration: InputDecoration( hintText: '例如:将背景改为蒸汽朋克风格', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Theme.of(context).colorScheme.surface, contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), // 🔧 修复:设置合理的行数范围,避免布局问题 maxLines: 4, minLines: 2, textInputAction: TextInputAction.newline, onChanged: (value) { // 250ms 防抖,避免每个字符都触发 BLoC 更新与重建 _adjustmentDebounce?.cancel(); _adjustmentDebounce = Timer(const Duration(milliseconds: 250), () { if (!mounted) return; context.read().add( UpdateAdjustmentPromptEvent(_adjustmentController.text), ); }); }, ), ], ); } } Widget _buildStrategySelector() { return BlocBuilder( builder: (context, state) { List strategies = []; // 策略列表 bool isLoading = false; if (state is SettingGenerationReady) { strategies = state.strategies; } else if (state is SettingGenerationInProgress) { strategies = state.strategies; } else if (state is SettingGenerationCompleted) { strategies = state.strategies; } else { isLoading = true; } // 🔧 修复:根据 initialStrategy 初始化选中的策略 if (_selectedStrategy == null && strategies.isNotEmpty) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { StrategyTemplateInfo? initialSelected; if (widget.initialStrategy != null) { // 根据名称查找策略 initialSelected = strategies.firstWhere( (s) => s.name == widget.initialStrategy, orElse: () => strategies.first, ); } else { initialSelected = strategies.first; } setState(() { _selectedStrategy = initialSelected; }); } }); } // 确保当前选中的策略在可用列表中 if (_selectedStrategy != null && !strategies.contains(_selectedStrategy)) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && strategies.isNotEmpty) { setState(() { _selectedStrategy = strategies.first; }); } }); } return StrategySelectorDropdown( strategies: strategies, selectedStrategy: _selectedStrategy, isLoading: isLoading || strategies.isEmpty, onChanged: (value) { if (value != null) { setState(() { _selectedStrategy = value; }); } }, ); }, ); } Widget _buildModelSelector() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'AI模型', style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, color: Theme.of(context).textTheme.bodySmall?.color, ), ), const SizedBox(height: 8), ModelDisplaySelector( selectedModel: _selectedModel, onModelSelected: (model) { setState(() { _selectedModel = model; }); }, size: ModelDisplaySize.medium, height: 60, // 扩大一倍高度 (36px * 2) showIcon: true, showTags: true, showSettingsButton: false, placeholder: '选择AI模型', ), ], ); } Widget _buildActionButtons(SettingGenerationState state) { final hasGeneratedSettings = state is SettingGenerationInProgress || state is SettingGenerationCompleted; final isGenerating = state is SettingGenerationInProgress && state.isGenerating; if (!hasGeneratedSettings) { return SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isGenerating || _selectedModel == null || _promptController.text.trim().isEmpty ? null : () { final prompt = _promptController.text.trim(); final strategy = _selectedStrategy; final modelConfigId = _selectedModel!.id; if (strategy != null) { // 通知主屏幕更新参数 - 传递策略名称用于显示 widget.onGenerationStart?.call(prompt, strategy.name, modelConfigId); final model = _selectedModel!; final bool usePublic = model.isPublic; final String? publicProvider = usePublic ? model.provider : null; final String? publicModelId = usePublic ? model.modelId : null; context.read().add( StartGenerationEvent( initialPrompt: prompt, promptTemplateId: strategy.promptTemplateId, // 🔧 修复:使用策略ID而非名称 modelConfigId: modelConfigId, usePublicTextModel: usePublic, textPhasePublicProvider: publicProvider, textPhasePublicModelId: publicModelId, ), ); } }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: isGenerating ? Row( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation( Theme.of(context).colorScheme.onPrimary, ), ), ), const SizedBox(width: 8), const Text('生成中...'), ], ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.auto_awesome, size: 16, color: Theme.of(context).colorScheme.onPrimary, ), const SizedBox(width: 8), const Text('生成设定'), ], ), ), ); } else { // 🔧 修复:生成完成后的按钮逻辑 return Column( children: [ // 新建生成按钮 - 基于当前配置重新生成 SizedBox( width: double.infinity, child: ElevatedButton( onPressed: isGenerating || _selectedModel == null ? null : () { // 使用原始提示词和当前配置重新生成 final prompt = _promptController.text.trim(); final strategy = _selectedStrategy; final modelConfigId = _selectedModel!.id; if (prompt.isNotEmpty && strategy != null) { // 通知主屏幕更新参数 - 传递策略名称用于显示 widget.onGenerationStart?.call(prompt, strategy.name, modelConfigId); final model = _selectedModel!; final bool usePublic = model.isPublic; final String? publicProvider = usePublic ? model.provider : null; final String? publicModelId = usePublic ? model.modelId : null; context.read().add( StartGenerationEvent( initialPrompt: prompt, promptTemplateId: strategy.promptTemplateId, modelConfigId: modelConfigId, usePublicTextModel: usePublic, textPhasePublicProvider: publicProvider, textPhasePublicModelId: publicModelId, ), ); } }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.auto_awesome, size: 16, color: Theme.of(context).colorScheme.onPrimary, ), const SizedBox(width: 8), const Text('新建生成'), ], ), ), ), const SizedBox(height: 8), // 调整生成按钮行 // Row( // children: [ // // --- 调整生成按钮(改为基于会话整体调整) --- // Expanded( // child: ElevatedButton( // onPressed: isGenerating || _selectedModel == null || _adjustmentController.text.trim().isEmpty // ? null // : () { // final prompt = _adjustmentController.text.trim(); // final modelConfigId = _selectedModel!.id; // // 读取当前活跃会话ID // final currentState = context.read().state; // String? sessionId; // if (currentState is SettingGenerationInProgress) { // sessionId = currentState.activeSessionId; // } else if (currentState is SettingGenerationCompleted) { // sessionId = currentState.activeSessionId; // } // if (sessionId != null && sessionId.isNotEmpty) { // // 推测当前策略模板ID(若可获取) // String? promptTemplateId; // final state = context.read().state; // if (state is SettingGenerationInProgress) { // promptTemplateId = state.activeSession.metadata['promptTemplateId'] as String?; // } else if (state is SettingGenerationCompleted) { // promptTemplateId = state.activeSession.metadata['promptTemplateId'] as String?; // } // // 优先使用当前选择的策略模板ID // if (_selectedStrategy != null) { // promptTemplateId = _selectedStrategy!.promptTemplateId; // } // context.read().add( // AdjustGenerationEvent( // sessionId: sessionId, // adjustmentPrompt: prompt, // modelConfigId: modelConfigId, // promptTemplateId: promptTemplateId, // ), // ); // } // }, // style: ElevatedButton.styleFrom( // padding: const EdgeInsets.symmetric(vertical: 10), // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // ), // child: Row( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Icon( // Icons.refresh, // size: 14, // color: Theme.of(context).colorScheme.onPrimary, // ), // const SizedBox(width: 4), // const Text('调整生成', style: TextStyle(fontSize: 12)), // ], // ), // ), // ), // const SizedBox(width: 8), // // --- 创建分支按钮 --- // Expanded( // child: Tooltip( // message: '基于当前设定和调整提示词创建新的历史记录', // child: ElevatedButton( // onPressed: isGenerating || _selectedModel == null || _adjustmentController.text.trim().isEmpty // ? null // : () { // final prompt = _adjustmentController.text.trim(); // final strategy = _selectedStrategy; // final modelConfigId = _selectedModel!.id; // if (strategy != null) { // // 通知主屏幕更新参数 - 传递策略名称用于显示 // widget.onGenerationStart?.call(prompt, strategy.name, modelConfigId); // // 创建分支 // context.read().add( // StartGenerationEvent( // initialPrompt: prompt, // promptTemplateId: strategy.promptTemplateId, // 🔧 修复:使用策略ID而非名称 // modelConfigId: modelConfigId, // ), // ); // } // }, // style: ElevatedButton.styleFrom( // padding: const EdgeInsets.symmetric(vertical: 10), // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // ), // child: Row( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // Icon( // Icons.call_split, // size: 14, // color: Theme.of(context).colorScheme.onPrimary, // ), // const SizedBox(width: 4), // const Text('创建分支', style: TextStyle(fontSize: 12)), // ], // ), // ), // ), // ), // ], // ), ], ); } } }