import 'package:ainoval/models/user_ai_model_config_model.dart'; import 'package:ainoval/models/context_selection_models.dart'; import 'package:ainoval/models/novel_setting_item.dart'; import 'package:ainoval/models/setting_group.dart'; import 'package:ainoval/models/novel_snippet.dart'; import 'package:ainoval/models/ai_request_models.dart'; import 'package:ainoval/models/preset_models.dart'; import 'package:ainoval/models/unified_ai_model.dart'; import 'package:ainoval/services/ai_preset_service.dart'; import 'package:ainoval/config/app_config.dart'; import 'package:ainoval/utils/logger.dart'; import 'package:ainoval/widgets/common/top_toast.dart'; import 'package:ainoval/widgets/common/model_display_selector.dart'; import 'package:ainoval/widgets/common/context_selection_dropdown_menu_anchor.dart'; import 'package:ainoval/widgets/common/credit_display.dart'; import 'package:flutter/material.dart'; import 'package:ainoval/utils/web_theme.dart'; // import 'package:flutter_bloc/flutter_bloc.dart'; class ChatInput extends StatefulWidget { const ChatInput({ Key? key, required this.controller, required this.onSend, this.isGenerating = false, this.onCancel, this.onModelSelected, this.initialModel, this.novel, this.contextData, this.onContextChanged, this.settings = const [], this.settingGroups = const [], this.snippets = const [], this.chatConfig, this.onConfigChanged, this.onCreditError, // 🚀 新增:积分不足错误回调 this.initialChapterId, this.initialSceneId, }) : super(key: key); final TextEditingController controller; final VoidCallback onSend; final Function(String)? onCreditError; // 🚀 新增:积分不足错误回调 final bool isGenerating; final VoidCallback? onCancel; final Function(UserAIModelConfigModel?)? onModelSelected; final UserAIModelConfigModel? initialModel; final dynamic novel; final ContextSelectionData? contextData; final ValueChanged? onContextChanged; final List settings; final List settingGroups; final List snippets; final UniversalAIRequest? chatConfig; final ValueChanged? onConfigChanged; final String? initialChapterId; final String? initialSceneId; @override State createState() => _ChatInputState(); } class _ChatInputState extends State { OverlayEntry? _presetOverlay; final LayerLink _layerLink = LayerLink(); bool _isComposing = false; // 预设相关状态 // final GlobalKey _presetButtonKey = GlobalKey(); List _availablePresets = []; bool _isLoadingPresets = false; AIPromptPreset? _currentPreset; @override void initState() { super.initState(); widget.controller.addListener(_handleTextChange); _handleTextChange(); _loadPresets(); } @override void dispose() { widget.controller.removeListener(_handleTextChange); _removePresetOverlay(); super.dispose(); } /// 加载预设数据 Future _loadPresets() async { if (_isLoadingPresets) return; setState(() { _isLoadingPresets = true; }); try { final presetService = AIPresetService(); // 直接获取AI_CHAT类型的预设 final chatPresets = await presetService.getUserPresets(featureType: 'AI_CHAT'); setState(() { _availablePresets = chatPresets; _isLoadingPresets = false; }); AppLogger.i('ChatInput', '加载了 ${_availablePresets.length} 个聊天预设'); } catch (e) { setState(() { _isLoadingPresets = false; }); AppLogger.e('ChatInput', '加载预设失败', e); } } void _handleTextChange() { final bool composingNow = widget.controller.text.trim().isNotEmpty; if (composingNow != _isComposing) { // 只有从空 → 非空 或 非空 → 空 时才重建,避免输入过程中频繁 setState setState(() { _isComposing = composingNow; }); } } /// 显示预设下拉菜单 void _showPresetOverlay() { if (_presetOverlay != null) { _removePresetOverlay(); return; } _presetOverlay = OverlayEntry( builder: (context) => Stack( children: [ Positioned.fill( child: GestureDetector( onTap: _removePresetOverlay, child: Container(color: Colors.transparent), ), ), CompositedTransformFollower( link: _layerLink, showWhenUnlinked: false, targetAnchor: Alignment.topRight, followerAnchor: Alignment.bottomRight, offset: const Offset(0, -8), child: Material( elevation: 8, borderRadius: BorderRadius.circular(12), color: Theme.of(context).colorScheme.surfaceContainer, shadowColor: WebTheme.getShadowColor(context, opacity: 0.15), child: Container( width: 240, constraints: const BoxConstraints(maxHeight: 320), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( color: Theme.of(context).colorScheme.outlineVariant.withOpacity(0.3), ), ), child: _buildPresetMenuContent(), ), ), ), ], ), ); Overlay.of(context).insert(_presetOverlay!); } /// 移除预设下拉菜单 void _removePresetOverlay() { _presetOverlay?.remove(); _presetOverlay = null; } /// 构建预设菜单内容 Widget _buildPresetMenuContent() { if (_isLoadingPresets) { return Container( height: 120, padding: const EdgeInsets.all(16), child: const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ), SizedBox(height: 8), Text( '加载预设中...', style: TextStyle(fontSize: 12), ), ], ), ), ); } if (_availablePresets.isEmpty) { return Container( height: 120, padding: const EdgeInsets.all(16), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.auto_awesome_outlined, size: 32, color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.5), ), const SizedBox(height: 8), Text( '暂无可用预设', style: TextStyle( fontSize: 14, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 4), Text( '可在设置中创建预设', style: TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.7), ), ), ], ), ), ); } // 对预设进行分组 final Map> groupedPresets = { '最近使用': _availablePresets.where((p) => p.lastUsedAt != null).take(3).toList(), '收藏预设': _availablePresets.where((p) => p.isFavorite).toList(), '所有预设': _availablePresets, }; return ListView( padding: const EdgeInsets.all(8), shrinkWrap: true, children: [ // 标题 Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), child: Row( children: [ Icon( Icons.auto_awesome, size: 16, color: Theme.of(context).colorScheme.primary, ), const SizedBox(width: 8), Text( '快速预设', style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurface, ), ), ], ), ), const Divider(height: 1), // 预设分组列表 ...groupedPresets.entries.where((entry) => entry.value.isNotEmpty).map((entry) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (entry.key != '所有预设' || (entry.key == '所有预设' && groupedPresets['最近使用']!.isEmpty && groupedPresets['收藏预设']!.isEmpty)) Padding( padding: const EdgeInsets.fromLTRB(12, 12, 12, 4), child: Text( entry.key, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Theme.of(context).colorScheme.onSurfaceVariant, letterSpacing: 0.5, ), ), ), ...entry.value.map((preset) => _buildPresetMenuItem(preset)).toList(), ], ); }).toList(), ], ); } /// 构建预设菜单项 Widget _buildPresetMenuItem(AIPromptPreset preset) { final colorScheme = Theme.of(context).colorScheme; final isSelected = _currentPreset?.presetId == preset.presetId; return InkWell( onTap: () => _handlePresetSelected(preset), borderRadius: BorderRadius.circular(8), child: Container( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( color: isSelected ? colorScheme.primaryContainer.withOpacity(0.3) : null, borderRadius: BorderRadius.circular(6), ), child: Row( children: [ // 预设图标 Container( width: 20, height: 20, decoration: BoxDecoration( color: colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(4), ), child: Icon( Icons.auto_awesome, size: 12, color: colorScheme.primary, ), ), const SizedBox(width: 8), // 预设信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Flexible( child: Text( preset.displayName, style: TextStyle( fontSize: 12, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? colorScheme.primary : colorScheme.onSurface, ), overflow: TextOverflow.ellipsis, ), ), if (preset.isFavorite) ...[ const SizedBox(width: 4), Icon( Icons.star, size: 10, color: Colors.amber.shade600, ), ], ], ), if (preset.presetDescription != null && preset.presetDescription!.isNotEmpty) Text( preset.presetDescription!, style: TextStyle( fontSize: 10, color: colorScheme.onSurfaceVariant.withOpacity(0.7), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), // 选中标识 if (isSelected) Icon( Icons.check_circle, size: 14, color: colorScheme.primary, ), ], ), ), ); } /// 处理预设选择 void _handlePresetSelected(AIPromptPreset preset) { _removePresetOverlay(); try { setState(() { _currentPreset = preset; }); // 解析预设并应用到聊天配置 final parsedRequest = preset.parsedRequest; if (parsedRequest != null && widget.onConfigChanged != null) { // 创建新的配置,保留现有的基础信息 final baseConfig = widget.chatConfig ?? UniversalAIRequest( requestType: AIRequestType.chat, userId: AppConfig.userId ?? 'unknown', novelId: widget.novel?.id, ); // 应用预设配置 final updatedConfig = baseConfig.copyWith( modelConfig: parsedRequest.modelConfig ?? baseConfig.modelConfig, instructions: parsedRequest.instructions?.isNotEmpty == true ? parsedRequest.instructions : preset.effectiveUserPrompt.isNotEmpty ? preset.effectiveUserPrompt : null, contextSelections: parsedRequest.contextSelections ?? baseConfig.contextSelections, enableSmartContext: parsedRequest.enableSmartContext, parameters: { ...baseConfig.parameters, ...parsedRequest.parameters, }, metadata: { ...baseConfig.metadata, 'appliedPreset': preset.presetId, 'presetName': preset.presetName, 'lastPresetApplied': DateTime.now().toIso8601String(), }, ); widget.onConfigChanged!(updatedConfig); // 如果预设包含模型配置,也要通知模型选择器 if (parsedRequest.modelConfig != null) { widget.onModelSelected?.call(parsedRequest.modelConfig); } AppLogger.i('ChatInput', '预设已应用: ${preset.displayName}'); // 记录预设使用 AIPresetService().applyPreset(preset.presetId); // 显示成功提示 TopToast.success(context, '已应用预设: ${preset.displayName}'); } else { AppLogger.w('ChatInput', '预设解析失败或缺少配置变更回调'); TopToast.error(context, '应用预设失败'); } } catch (e) { AppLogger.e('ChatInput', '应用预设失败', e); TopToast.error(context, '应用预设失败: $e'); } } void _updateContextData(ContextSelectionData newData, {bool isAddOperation = true}) { if (widget.onConfigChanged != null) { if (widget.chatConfig != null) { // 🚀 修复:使用完整的菜单结构而不是可能不完整的currentSelections final currentSelections = widget.chatConfig!.contextSelections; // 🚀 获取完整的菜单结构数据 ContextSelectionData? fullContextData; if (widget.contextData != null) { fullContextData = widget.contextData; } else if (widget.novel != null) { fullContextData = ContextSelectionDataBuilder.fromNovelWithContext( widget.novel!, settings: widget.settings, settingGroups: widget.settingGroups, snippets: widget.snippets, ); } if (fullContextData != null) { ContextSelectionData updatedSelections; if (isAddOperation && currentSelections != null) { // 🚀 添加操作:将现有选择应用到完整结构,然后添加新选择 // 先应用现有选择到完整结构 updatedSelections = fullContextData.applyPresetSelections(currentSelections); // 再添加新选择的项目 for (final newItem in newData.selectedItems.values) { if (!updatedSelections.selectedItems.containsKey(newItem.id)) { updatedSelections = updatedSelections.selectItem(newItem.id); } } } else if (!isAddOperation && currentSelections != null) { // 🚀 删除操作:将现有选择应用到完整结构,然后移除指定项目 updatedSelections = fullContextData.applyPresetSelections(currentSelections); // 找出被删除的项目并移除 for (final existingId in currentSelections.selectedItems.keys) { if (!newData.selectedItems.containsKey(existingId)) { updatedSelections = updatedSelections.deselectItem(existingId); } } } else { // 🚀 如果当前没有选择,直接使用新数据(但保持完整结构) updatedSelections = fullContextData; for (final newItem in newData.selectedItems.values) { updatedSelections = updatedSelections.selectItem(newItem.id); } } final updatedConfig = widget.chatConfig!.copyWith( contextSelections: updatedSelections, ); widget.onConfigChanged!(updatedConfig); } else { // 如果无法获取完整菜单结构,回退到原来的逻辑 final updatedConfig = widget.chatConfig!.copyWith( contextSelections: newData, ); widget.onConfigChanged!(updatedConfig); } } else { // 如果没有chatConfig,创建一个基础配置 final newConfig = UniversalAIRequest( requestType: AIRequestType.chat, userId: 'unknown', // 这应该从某个地方获取 novelId: widget.novel?.id, contextSelections: newData, ); widget.onConfigChanged!(newConfig); } } else { // 🚀 如果没有onConfigChanged回调,则使用传统的onContextChanged widget.onContextChanged?.call(newData); } } @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final bool canSend = _isComposing && !widget.isGenerating; ContextSelectionData? currentContextData; if (widget.contextData != null) { // 🚀 使用EditorScreenController维护的级联菜单数据(静态结构) currentContextData = widget.contextData; } else if (widget.novel != null) { // 备用方案:如果EditorScreenController还没有准备好数据,则临时构建 currentContextData = ContextSelectionDataBuilder.fromNovelWithContext( widget.novel!, settings: widget.settings, settingGroups: widget.settingGroups, snippets: widget.snippets, ); } // final contextSelectionCount = widget.chatConfig?.contextSelections?.selectedCount ?? 0; return Container( decoration: BoxDecoration( color: colorScheme.surface, border: Border( top: BorderSide( color: colorScheme.outlineVariant.withOpacity(0.5), width: 1.0, ), ), ), padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 上下文选择区域 - 始终显示,以便用户可以点击添加 Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: colorScheme.outline.withOpacity(0.1), width: 1.0, ), ), ), child: Wrap( spacing: 8, runSpacing: 8, crossAxisAlignment: WrapCrossAlignment.center, // 垂直居中对齐 children: [ // 使用完整的上下文选择组件 - 包含完整的级联菜单 if (currentContextData != null) ContextSelectionDropdownBuilder.buildMenuAnchor( data: currentContextData, onSelectionChanged: _updateContextData, placeholder: '+ Context', maxHeight: 400, initialChapterId: widget.initialChapterId, initialSceneId: widget.initialSceneId, ) else // 当没有数据时显示占位符 Container( height: 36, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest.withOpacity(0.5), borderRadius: BorderRadius.circular(8), border: Border.all( color: colorScheme.outline.withOpacity(0.3), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.pending_outlined, size: 16, color: colorScheme.onSurface.withOpacity(0.5), ), const SizedBox(width: 8), Text( '等待级联菜单数据...', style: TextStyle( fontSize: 12, color: colorScheme.onSurface.withOpacity(0.5), ), ), ], ), ), // 🚀 修复:使用完整菜单结构中的已选择项目显示标签 if (currentContextData != null && widget.chatConfig?.contextSelections != null) ..._buildSelectedContextTags(currentContextData, widget.chatConfig!.contextSelections!).map((item) { return Container( height: 36, constraints: const BoxConstraints(maxWidth: 200), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: colorScheme.surfaceContainerHighest.withOpacity(0.75), borderRadius: BorderRadius.circular(8), border: Border.all( color: colorScheme.outline.withOpacity(0.2), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( item.type.icon, size: 16, color: colorScheme.onSurface.withOpacity(0.7), ), const SizedBox(width: 8), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( item.title, style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: colorScheme.onSurface, height: 1.2, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), if (item.displaySubtitle.isNotEmpty) Text( item.displaySubtitle, style: TextStyle( fontSize: 9, color: colorScheme.onSurface.withOpacity(0.6), height: 1.2, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), const SizedBox(width: 4), InkWell( onTap: () { // 🚀 修复:使用完整菜单结构进行删除操作 if (currentContextData != null && widget.chatConfig!.contextSelections != null) { // 将当前选择应用到完整结构,然后删除指定项目 final fullDataWithSelections = currentContextData.applyPresetSelections(widget.chatConfig!.contextSelections!); final newData = fullDataWithSelections.deselectItem(item.id); _updateContextData(newData, isAddOperation: false); } }, borderRadius: BorderRadius.circular(10), child: Container( padding: const EdgeInsets.all(2), child: Icon( Icons.close, size: 14, color: colorScheme.onSurface.withOpacity(0.5), ), ), ), ], ), ); }).toList(), ], ), ), const SizedBox(height: 8.0), // 输入框行 - 独占一行,去掉圆角,紧贴边缘 Container( width: double.infinity, child: TextField( controller: widget.controller, decoration: InputDecoration( hintText: widget.isGenerating ? 'AI 正在回复...' : '输入消息...', border: OutlineInputBorder( borderRadius: BorderRadius.circular(0), // 去掉圆角 borderSide: BorderSide( color: colorScheme.outline.withOpacity(0.5)), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(0), // 去掉圆角 borderSide: BorderSide( color: colorScheme.outline.withOpacity(0.3)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(0), // 去掉圆角 borderSide: BorderSide(color: colorScheme.primary, width: 1.5), ), filled: true, fillColor: colorScheme.surfaceContainerHighest, contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12), // 增加垂直内边距 isDense: false, // 改为false以获得更多空间 disabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(0), // 去掉圆角 borderSide: BorderSide(color: Theme.of(context).colorScheme.outline.withOpacity(0.3)), ), ), readOnly: widget.isGenerating, minLines: 1, maxLines: 5, textInputAction: TextInputAction.newline, style: TextStyle(fontSize: 14, color: colorScheme.onSurface), onSubmitted: (_) { if (canSend) { widget.onSend(); } }, ), ), const SizedBox(height: 8.0), // 预设按钮、积分显示、模型选择器和发送按钮行 Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 预设快捷按钮 - 使用PopupMenuButton实现精准定位 CompositedTransformTarget( link: _layerLink, child: GestureDetector( onTap: _showPresetOverlay, child: Container( width: 40, height: 36, // 与模型选择器保持一致的高度 decoration: BoxDecoration( color: Theme.of(context).brightness == Brightness.dark ? Theme.of(context).colorScheme.surfaceContainerHighest // 深色容器 : Theme.of(context).colorScheme.surface, // 浅色容器 border: Border.all( color: Theme.of(context).colorScheme.outline.withOpacity(0.4), width: 1.0, ), borderRadius: BorderRadius.circular(20), // rounded-full boxShadow: [ BoxShadow( color: WebTheme.getShadowColor(context, opacity: 0.1), blurRadius: 1, offset: const Offset(0, 1), ), ], ), child: Material( type: MaterialType.transparency, child: InkWell( onTap: _showPresetOverlay, borderRadius: BorderRadius.circular(20), hoverColor: Theme.of(context).colorScheme.surfaceContainerHighest.withOpacity(0.8), child: Container( width: 40, height: 36, child: Center( child: Icon( Icons.auto_awesome, size: 16, color: _currentPreset != null ? colorScheme.primary : colorScheme.onSurfaceVariant, ), ), ), ), ), ), ), ), const SizedBox(width: 8), // 🚀 积分显示组件 const CreditDisplay( size: CreditDisplaySize.small, showRefreshButton: false, ), const SizedBox(width: 8), // 模型选择按钮 - 使用统一的显示/选择组件 Expanded( child: ModelDisplaySelector( selectedModel: widget.initialModel != null ? PrivateAIModel(widget.initialModel!) : null, onModelSelected: (unifiedModel) { // 将UnifiedAIModel转换为UserAIModelConfigModel以保持兼容性 UserAIModelConfigModel? compatModel; if (unifiedModel != null) { if (unifiedModel.isPublic) { final publicModel = (unifiedModel as PublicAIModel).publicConfig; compatModel = UserAIModelConfigModel.fromJson({ 'id': 'public_${publicModel.id}', 'userId': AppConfig.userId ?? 'unknown', 'alias': publicModel.displayName, 'modelName': publicModel.modelId, 'provider': publicModel.provider, 'apiEndpoint': '', 'isDefault': false, 'isValidated': true, 'createdAt': DateTime.now().toIso8601String(), 'updatedAt': DateTime.now().toIso8601String(), }); } else { compatModel = (unifiedModel as PrivateAIModel).userConfig; } } widget.onModelSelected?.call(compatModel); }, chatConfig: widget.chatConfig, onConfigChanged: widget.onConfigChanged, novel: widget.novel, settings: widget.settings, settingGroups: widget.settingGroups, snippets: widget.snippets, size: ModelDisplaySize.medium, showIcon: true, showTags: true, showSettingsButton: true, placeholder: '选择模型', ), ), const SizedBox(width: 8), // 发送/停止按钮 - 改为纯黑/灰黑主题 SizedBox( height: 36, // 与模型选择器保持一致的高度 width: 36, child: widget.isGenerating ? Material( color: colorScheme.primary, // 使用主色 borderRadius: BorderRadius.circular(18), child: InkWell( borderRadius: BorderRadius.circular(18), onTap: widget.onCancel, child: Container( width: 36, height: 36, child: const Icon( Icons.stop_rounded, size: 20, color: Colors.white, ), ), ), ) : Material( color: canSend ? colorScheme.primary : colorScheme.onSurfaceVariant, borderRadius: BorderRadius.circular(18), child: InkWell( borderRadius: BorderRadius.circular(18), onTap: canSend ? _handleSendWithCreditCheck : null, child: Container( width: 36, height: 36, child: Icon( Icons.arrow_upward_rounded, size: 20, color: canSend ? colorScheme.onPrimary : colorScheme.onPrimary.withOpacity(0.5), ), ), ), ), ), ], ), ], ), ); } /// 🚀 新增:带积分检查的发送处理 void _handleSendWithCreditCheck() { try { // 调用原发送方法,积分校验将在后端处理 widget.onSend(); } catch (e) { // 如果发送失败,检查是否为积分不足错误 final errorMessage = e.toString(); if (errorMessage.contains('积分不足') || errorMessage.contains('InsufficientCredits')) { // 积分不足,调用错误回调 widget.onCreditError?.call('积分不足,无法发送消息。请充值后重试。'); // 同时显示Toast提示 TopToast.error(context, '积分不足,无法发送消息'); } else { // 其他错误,显示通用错误提示 TopToast.error(context, '发送失败: $errorMessage'); } } } /// 🚀 构建已选择的上下文标签,使用完整菜单结构中的数据 List _buildSelectedContextTags( ContextSelectionData fullContextData, ContextSelectionData currentSelections, ) { // 将当前选择应用到完整菜单结构中 final updatedContextData = fullContextData.applyPresetSelections(currentSelections); // 返回应用后的选中项目列表 return updatedContextData.selectedItems.values.toList(); } }