// import 'package:ainoval/utils/logger.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../models/unified_ai_model.dart'; import '../../models/user_ai_model_config_model.dart'; // import '../../models/public_model_config.dart'; import '../../models/novel_structure.dart'; import '../../models/novel_setting_item.dart'; import '../../models/setting_group.dart'; import '../../models/novel_snippet.dart'; import '../../blocs/ai_config/ai_config_bloc.dart'; import '../../blocs/public_models/public_models_bloc.dart'; import '../../screens/chat/widgets/chat_settings_dialog.dart'; import '../../config/provider_icons.dart'; import '../../models/ai_request_models.dart'; import '../../screens/editor/managers/editor_layout_manager.dart'; import 'package:provider/provider.dart'; import 'package:ainoval/config/app_config.dart'; import 'package:ainoval/screens/settings/settings_panel.dart'; import 'package:ainoval/screens/editor/managers/editor_state_manager.dart'; import 'package:ainoval/widgets/common/top_toast.dart'; // ==================== 统一 AI 模型下拉菜单 - 尺寸常量定义 ==================== /// 菜单整体尺寸配置 class _MenuDimensions { /// 菜单固定宽度 static const double menuWidth = 320.0; /// 菜单默认最大高度 static const double defaultMaxHeight = 900.0; /// 屏幕边缘的安全边距,防止菜单被状态栏或导航栏遮挡 static const double screenSafeMargin = 80.0; /// 菜单最小高度(有设置按钮时) static const double minHeightWithSettings = 180.0; /// 菜单最小高度(无设置按钮时) static const double minHeightWithoutSettings = 120.0; /// 菜单与锚点的垂直间距 static const double anchorVerticalOffset = 6.0; /// 菜单水平边距 static const double horizontalMargin = 16.0; } /// 菜单内容区域尺寸配置 class _ContentDimensions { /// 供应商分组标题高度 static const double groupHeaderHeight = 36.0; /// 单个模型项的高度(包含标签显示空间) static const double modelItemHeight = 40.0; /// 底部操作按钮区域高度 static const double bottomButtonHeight = 56.0; /// 菜单内容的上下内边距 static const double verticalPadding = 6.0; /// 菜单内容的左右内边距 static const double horizontalPadding = 4.0; } /// 模型项内部尺寸配置 class _ModelItemDimensions { /// 模型图标容器大小 static const double iconContainerSize = 20.0; /// 模型图标实际大小 static const double iconSize = 12.0; /// 模型图标与文字的间距 static const double iconTextSpacing = 10.0; /// 选中指示器图标大小 static const double selectedIconSize = 16.0; /// 模型项的水平内边距 static const double itemHorizontalPadding = 12.0; /// 模型项的垂直内边距 static const double itemVerticalPadding = 10.0; /// 模型项的外边距 static const double itemMargin = 6.0; /// 模型项的圆角半径 static const double itemBorderRadius = 8.0; } /// 标签样式尺寸配置 class _TagDimensions { /// 标签水平内边距 static const double tagHorizontalPadding = 6.0; /// 标签垂直内边距 static const double tagVerticalPadding = 2.0; /// 标签圆角半径 static const double tagBorderRadius = 8.0; /// 标签边框宽度 static const double tagBorderWidth = 0.5; /// 标签之间的间距 static const double tagSpacing = 4.0; /// 标签行之间的间距 static const double tagRunSpacing = 2.0; /// 标签与模型名称的间距 static const double tagTopSpacing = 2.0; } /// 菜单外观样式配置 class _MenuStyling { /// 菜单圆角半径 static const double menuBorderRadius = 16.0; /// 菜单边框宽度 static const double menuBorderWidth = 0.8; /// 分割线高度 static const double dividerHeight = 8.0; /// 分割线厚度 static const double dividerThickness = 0.6; /// 分割线缩进 static const double dividerIndent = 16.0; /// 分割线结束缩进 static const double dividerEndIndent = 16.0; /// 菜单阴影高度(暗色主题) static const double elevationDark = 12.0; /// 菜单阴影高度(亮色主题) static const double elevationLight = 8.0; } /// 底部操作区域尺寸配置 class _BottomActionDimensions { /// 底部操作区域内边距 static const double bottomPadding = 12.0; /// 按钮垂直内边距 static const double buttonVerticalPadding = 12.0; /// 按钮圆角半径 static const double buttonBorderRadius = 10.0; /// 按钮边框宽度 static const double buttonBorderWidth = 0.8; /// 按钮图标大小 static const double buttonIconSize = 18.0; /// “添加我的私人模型”按钮的高度估算(用于高度计算) static const double secondaryButtonHeight = 44.0; } /// 空状态显示尺寸配置 class _EmptyStateDimensions { /// 空状态容器内边距 static const double emptyPadding = 24.0; /// 空状态图标大小 static const double emptyIconSize = 48.0; /// 空状态图标与文字的间距 static const double emptyIconTextSpacing = 12.0; /// 空状态标题与副标题的间距 static const double emptyTitleSubtitleSpacing = 8.0; } // ==================== 统一 AI 模型下拉菜单组件实现 ==================== /// 统一的AI模型下拉菜单组件,支持显示私有模型和公共模型 /// 通过 [show] 静态方法弹出 Overlay 菜单 class UnifiedAIModelDropdown { static OverlayEntry show({ required BuildContext context, LayerLink? layerLink, Rect? anchorRect, UnifiedAIModel? selectedModel, required Function(UnifiedAIModel?) onModelSelected, bool showSettingsButton = true, bool showAdjustAndGenerate = true, double maxHeight = _MenuDimensions.defaultMaxHeight, Novel? novel, List settings = const [], List settingGroups = const [], List snippets = const [], UniversalAIRequest? chatConfig, ValueChanged? onConfigChanged, VoidCallback? onClose, }) { assert(layerLink != null || anchorRect != null, '必须提供 layerLink 或 anchorRect'); late OverlayEntry entry; bool _closed = false; void safeClose() { if (_closed) return; _closed = true; if (entry.mounted) { entry.remove(); } onClose?.call(); } entry = OverlayEntry( builder: (ctx) { return Stack( children: [ // 点击空白处关闭 Positioned.fill( child: GestureDetector( onTap: safeClose, child: Container(color: Colors.transparent), ), ), if (layerLink != null) ...[ Positioned( width: _MenuDimensions.menuWidth, child: CompositedTransformFollower( link: layerLink, showWhenUnlinked: false, targetAnchor: Alignment.bottomCenter, followerAnchor: Alignment.topCenter, offset: const Offset(0, _MenuDimensions.anchorVerticalOffset), // 向下偏移 child: BlocBuilder( builder: (context, aiState) { return BlocBuilder( builder: (context, publicState) { final allModels = _combineModels(aiState, publicState); // 结合当前屏幕高度动态限制菜单高度,避免超出屏幕导致无法滚动 final screenH = MediaQuery.of(context).size.height; final double maxAllowableHeight = screenH - _MenuDimensions.screenSafeMargin; final menuHeight = _calculateMenuHeight(allModels, showSettingsButton, showAdjustAndGenerate, maxHeight) .clamp(0.0, maxAllowableHeight) .toDouble(); return _buildMenuContainer( context, menuHeight, allModels, selectedModel, onModelSelected, showSettingsButton, showAdjustAndGenerate, novel, settings, settingGroups, snippets, chatConfig, onConfigChanged, safeClose ); }, ); }, ), ), ), ] else if (anchorRect != null) ...[ BlocBuilder( builder: (context, aiState) { return BlocBuilder( builder: (context, publicState) { final allModels = _combineModels(aiState, publicState); // 结合当前屏幕高度动态限制菜单高度,避免超出屏幕导致无法滚动 final screenH = MediaQuery.of(context).size.height; final double maxAllowableHeight = screenH - _MenuDimensions.screenSafeMargin; final menuHeight = _calculateMenuHeight(allModels, showSettingsButton, showAdjustAndGenerate, maxHeight) .clamp(0.0, maxAllowableHeight) .toDouble(); return _buildPositionedMenu( context, anchorRect, menuHeight, allModels, selectedModel, onModelSelected, showSettingsButton, showAdjustAndGenerate, novel, settings, settingGroups, snippets, chatConfig, onConfigChanged, safeClose ); }, ); }, ), ], ], ); }, ); Overlay.of(context).insert(entry); return entry; } /// 合并私有模型和公共模型 static List _combineModels(AiConfigState aiState, PublicModelsState publicState) { final List allModels = []; // 添加已验证的私有模型 final validatedConfigs = aiState.validatedConfigs; for (final config in validatedConfigs) { allModels.add(PrivateAIModel(config)); } // 添加公共模型 if (publicState is PublicModelsLoaded) { for (final publicModel in publicState.models) { allModels.add(PublicAIModel(publicModel)); } } return allModels; } /// 按供应商分组模型,系统模型优先 static Map> _groupModelsByProvider(List models) { final Map> grouped = {}; for (var model in models) { final provider = model.provider; grouped.putIfAbsent(provider, () => []); grouped[provider]!.add(model); } // 对每个供应商内的模型进行排序 for (var list in grouped.values) { list.sort((a, b) { // 系统模型(公共模型)优先 if (a.isPublic && !b.isPublic) return -1; if (!a.isPublic && b.isPublic) return 1; // 如果都是公共模型,按优先级排序 if (a.isPublic && b.isPublic) { final aPriority = (a as PublicAIModel).publicConfig.priority ?? 0; final bPriority = (b as PublicAIModel).publicConfig.priority ?? 0; if (aPriority != bPriority) { return bPriority.compareTo(aPriority); // 优先级高的在前 } } // 如果都是私有模型,默认配置在前 if (!a.isPublic && !b.isPublic) { final aIsDefault = (a as PrivateAIModel).userConfig.isDefault; final bIsDefault = (b as PrivateAIModel).userConfig.isDefault; if (aIsDefault && !bIsDefault) return -1; if (!aIsDefault && bIsDefault) return 1; } return a.displayName.compareTo(b.displayName); }); } return grouped; } /// 计算菜单高度 static double _calculateMenuHeight( List models, bool showSettingsButton, bool showAdjustAndGenerate, double maxHeight, ) { final grouped = _groupModelsByProvider(models); int totalItems = models.length; final bool hasPrivateModels = models.any((m) => !m.isPublic); final double addButtonHeight = showSettingsButton && !hasPrivateModels ? (_BottomActionDimensions.secondaryButtonHeight + 8.0) : 0.0; final double adjustButtonHeight = showSettingsButton && showAdjustAndGenerate ? _ContentDimensions.bottomButtonHeight : 0.0; final double contentHeight = (grouped.length * _ContentDimensions.groupHeaderHeight) + (totalItems * _ContentDimensions.modelItemHeight) + addButtonHeight + adjustButtonHeight + (_ContentDimensions.verticalPadding * 2); final double minHeight = showSettingsButton ? _MenuDimensions.minHeightWithSettings : _MenuDimensions.minHeightWithoutSettings; return contentHeight.clamp(minHeight, maxHeight); } static Widget _buildMenuContainer( BuildContext context, double menuHeight, List models, UnifiedAIModel? selectedModel, Function(UnifiedAIModel?) onModelSelected, bool showSettingsButton, bool showAdjustAndGenerate, Novel? novel, List settings, List settingGroups, List snippets, UniversalAIRequest? chatConfig, ValueChanged? onConfigChanged, VoidCallback onClose, ) { final isDark = Theme.of(context).brightness == Brightness.dark; return Material( elevation: isDark ? _MenuStyling.elevationDark : _MenuStyling.elevationLight, borderRadius: BorderRadius.circular(_MenuStyling.menuBorderRadius), color: isDark ? Theme.of(context).colorScheme.surfaceContainer.withOpacity(0.95) : Theme.of(context).colorScheme.surfaceContainer, shadowColor: Colors.black.withOpacity(isDark ? 0.3 : 0.15), child: Container( height: menuHeight, decoration: BoxDecoration( borderRadius: BorderRadius.circular(_MenuStyling.menuBorderRadius), border: Border.all( color: Theme.of(context).colorScheme.outlineVariant.withOpacity(isDark ? 0.2 : 0.3), width: _MenuStyling.menuBorderWidth, ), ), child: _UnifiedMenuContent( models: models, selectedModel: selectedModel, onModelSelected: onModelSelected, onClose: onClose, showSettingsButton: showSettingsButton, showAdjustAndGenerate: showAdjustAndGenerate, novel: novel, settings: settings, settingGroups: settingGroups, snippets: snippets, chatConfig: chatConfig, onConfigChanged: onConfigChanged, ), ), ); } static Widget _buildPositionedMenu( BuildContext context, Rect anchorRect, double menuHeight, List models, UnifiedAIModel? selectedModel, Function(UnifiedAIModel?) onModelSelected, bool showSettingsButton, bool showAdjustAndGenerate, Novel? novel, List settings, List settingGroups, List snippets, UniversalAIRequest? chatConfig, ValueChanged? onConfigChanged, VoidCallback onClose, ) { final screenSize = MediaQuery.of(context).size; double left = anchorRect.left; if (left + _MenuDimensions.menuWidth > screenSize.width - _MenuDimensions.horizontalMargin) { left = screenSize.width - _MenuDimensions.menuWidth - _MenuDimensions.horizontalMargin; } // 计算垂直放置位置,确保菜单完整显示在屏幕内 double top = anchorRect.top - menuHeight - _MenuDimensions.anchorVerticalOffset; // 先尝试放在目标组件上方 final double safeTop = MediaQuery.of(context).padding.top + 10; final double safeBottom = screenSize.height - 10; // 如果上方空间不足则放到下方 if (top < safeTop) { top = anchorRect.bottom + _MenuDimensions.anchorVerticalOffset; } // 如果下方还是溢出,则将菜单整体上移 if (top + menuHeight > safeBottom) { top = safeBottom - menuHeight; // 仍保证不碰到状态栏 if (top < safeTop) { top = safeTop; } } return Positioned( left: left, top: top, width: _MenuDimensions.menuWidth, child: _buildMenuContainer( context, menuHeight, models, selectedModel, onModelSelected, showSettingsButton, showAdjustAndGenerate, novel, settings, settingGroups, snippets, chatConfig, onConfigChanged, onClose, ), ); } } // ------------------ 内部菜单内容 ------------------ class _UnifiedMenuContent extends StatelessWidget { const _UnifiedMenuContent({ Key? key, required this.models, required this.selectedModel, required this.onModelSelected, required this.onClose, required this.showSettingsButton, required this.showAdjustAndGenerate, this.novel, this.settings = const [], this.settingGroups = const [], this.snippets = const [], this.chatConfig, this.onConfigChanged, }) : super(key: key); final List models; final UnifiedAIModel? selectedModel; final Function(UnifiedAIModel?) onModelSelected; final VoidCallback onClose; final bool showSettingsButton; final bool showAdjustAndGenerate; final Novel? novel; final List settings; final List settingGroups; final List snippets; final UniversalAIRequest? chatConfig; final ValueChanged? onConfigChanged; @override Widget build(BuildContext context) { if (models.isEmpty) { return _buildEmpty(context); } final grouped = UnifiedAIModelDropdown._groupModelsByProvider(models); final providers = grouped.keys.toList(); // 供应商排序:有系统模型的供应商优先 providers.sort((a, b) { final aHasPublic = grouped[a]!.any((m) => m.isPublic); final bHasPublic = grouped[b]!.any((m) => m.isPublic); if (aHasPublic && !bHasPublic) return -1; if (!aHasPublic && bHasPublic) return 1; return a.compareTo(b); }); return Column( children: [ Expanded( child: ListView.separated( padding: const EdgeInsets.symmetric( horizontal: _ContentDimensions.horizontalPadding, vertical: _ContentDimensions.verticalPadding ), itemCount: providers.length, separatorBuilder: (c, i) => Divider( height: _MenuStyling.dividerHeight, thickness: _MenuStyling.dividerThickness, color: Theme.of(context) .colorScheme .outlineVariant .withOpacity(0.12), indent: _MenuStyling.dividerIndent, endIndent: _MenuStyling.dividerEndIndent, ), itemBuilder: (c, index) { final provider = providers[index]; final providerModels = grouped[provider]!; return _ProviderGroup( provider: provider, models: providerModels, selectedModel: selectedModel, onModelSelected: (m) { onModelSelected(m); onClose(); }, ); }, ), ), if (showSettingsButton) _buildBottomActions(context), ], ); } Widget _buildEmpty(BuildContext context) { final cs = Theme.of(context).colorScheme; return Center( child: Padding( padding: const EdgeInsets.all(_EmptyStateDimensions.emptyPadding), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.model_training_outlined, size: _EmptyStateDimensions.emptyIconSize, color: cs.onSurfaceVariant.withOpacity(0.5)), const SizedBox(height: _EmptyStateDimensions.emptyIconTextSpacing), Text('无可用模型', style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(color: cs.onSurfaceVariant)), const SizedBox(height: _EmptyStateDimensions.emptyTitleSubtitleSpacing), Text('请先配置AI模型或等待公共模型加载', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: cs.onSurfaceVariant.withOpacity(0.7))), ], ), ), ); } Widget _buildBottomActions(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( padding: const EdgeInsets.all(_BottomActionDimensions.bottomPadding), decoration: BoxDecoration( color: isDark ? cs.surface.withOpacity(0.8) : cs.surface, border: Border( top: BorderSide( color: cs.outlineVariant.withOpacity(isDark ? 0.15 : 0.2), width: 1.0, ), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [ if (!models.any((m) => !m.isPublic)) ...[ OutlinedButton.icon( onPressed: () { onClose(); // 优先尝试编辑器内打开 try { final layoutManager = Provider.of(context, listen: false); layoutManager.toggleSettingsPanel(); return; } catch (_) {} // 回退:列表页等环境直接弹出设置对话框 final userId = AppConfig.userId; if (userId == null || userId.isEmpty) { TopToast.info(context, '请先登录后再添加私人模型'); return; } showDialog( context: context, barrierDismissible: true, builder: (dialogContext) { return MultiBlocProvider( providers: [ BlocProvider.value(value: dialogContext.read()), ], child: Dialog( insetPadding: const EdgeInsets.all(16), backgroundColor: Colors.transparent, child: SettingsPanel( stateManager: EditorStateManager(), userId: userId, onClose: () => Navigator.of(dialogContext).pop(), ), ), ); }, ); }, icon: const Icon(Icons.add), label: const Text('添加我的私人模型'), style: OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 10), foregroundColor: isDark ? cs.primary.withOpacity(0.9) : cs.primary, side: BorderSide(color: cs.primary.withOpacity(isDark ? 0.2 : 0.3), width: _BottomActionDimensions.buttonBorderWidth), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(_BottomActionDimensions.buttonBorderRadius)), ), ), const SizedBox(height: 8), ], if (showAdjustAndGenerate) SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () { onClose(); // 先关闭 Overlay // 只有选中私有模型时才能进入设置对话框 UserAIModelConfigModel? userModel; if (selectedModel != null && !selectedModel!.isPublic) { userModel = (selectedModel as PrivateAIModel).userConfig; } showChatSettingsDialog( context, selectedModel: userModel, onModelChanged: (m) { if (m != null) { onModelSelected(PrivateAIModel(m)); } }, novel: novel, settings: settings, settingGroups: settingGroups, snippets: snippets, initialChatConfig: chatConfig, onConfigChanged: onConfigChanged, initialContextSelections: null, // 🚀 让ChatSettingsDialog自己构建上下文数据 ); }, icon: const Icon(Icons.tune_rounded, size: _BottomActionDimensions.buttonIconSize), label: const Text('调整并生成'), style: ElevatedButton.styleFrom( foregroundColor: isDark ? cs.primary.withOpacity(0.9) : cs.primary, backgroundColor: isDark ? cs.primaryContainer.withOpacity(0.08) : cs.primaryContainer.withOpacity(0.1), padding: const EdgeInsets.symmetric(vertical: _BottomActionDimensions.buttonVerticalPadding), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(_BottomActionDimensions.buttonBorderRadius)), elevation: 0, side: BorderSide(color: cs.primary.withOpacity(isDark ? 0.2 : 0.3), width: _BottomActionDimensions.buttonBorderWidth), ), ), ), ], ), ); } } // 供应商分组组件 class _ProviderGroup extends StatelessWidget { const _ProviderGroup({ Key? key, required this.provider, required this.models, required this.selectedModel, required this.onModelSelected, }) : super(key: key); final String provider; final List models; final UnifiedAIModel? selectedModel; final Function(UnifiedAIModel?) onModelSelected; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; // 检查是否有系统模型 final hasPublicModels = models.any((m) => m.isPublic); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.fromLTRB(12, 8, 12, 6), child: Row( children: [ Icon( hasPublicModels ? Icons.public : Icons.person_outline, size: 16, color: isDark ? cs.primary.withOpacity(0.8) : cs.primary, ), const SizedBox(width: 6), Text( provider.toUpperCase(), style: Theme.of(context).textTheme.titleSmall?.copyWith( color: isDark ? cs.primary.withOpacity(0.9) : cs.primary, fontWeight: FontWeight.w700, letterSpacing: 1, fontSize: 14, ), ), const Spacer(), Text( '${models.length}个', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: cs.onSurfaceVariant.withOpacity(0.7), fontSize: 12, ), ), ], ), ), ...models.map((m) => _UnifiedModelItem( model: m, isSelected: selectedModel?.id == m.id, onTap: () => onModelSelected(m), )), const SizedBox(height: 4), ], ); } } class _UnifiedModelItem extends StatelessWidget { const _UnifiedModelItem({ Key? key, required this.model, required this.isSelected, required this.onTap, }) : super(key: key); final UnifiedAIModel model; final bool isSelected; final VoidCallback onTap; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(_ModelItemDimensions.itemBorderRadius), splashColor: cs.primary.withOpacity(0.08), highlightColor: cs.primary.withOpacity(0.04), child: Container( margin: const EdgeInsets.symmetric(horizontal: _ModelItemDimensions.itemMargin, vertical: 1.0), padding: const EdgeInsets.symmetric( horizontal: _ModelItemDimensions.itemHorizontalPadding, vertical: _ModelItemDimensions.itemVerticalPadding ), decoration: BoxDecoration( color: isSelected ? (isDark ? cs.primaryContainer.withOpacity(0.2) : cs.primaryContainer.withOpacity(0.15)) : null, borderRadius: BorderRadius.circular(_ModelItemDimensions.itemBorderRadius), border: isSelected ? Border.all(color: cs.primary.withOpacity(0.2), width: 1.0) : null, ), child: Row( children: [ // Icon Container( padding: const EdgeInsets.all(2), child: _getModelIcon(model.provider, context), ), const SizedBox(width: _ModelItemDimensions.iconTextSpacing), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( model.displayName, style: Theme.of(context).textTheme.bodySmall?.copyWith( fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? cs.primary : (isDark ? cs.onSurface.withOpacity(0.9) : cs.onSurface), fontSize: 13, height: 1.2, ), overflow: TextOverflow.ellipsis, ), // 显示所有标签 if (model.modelTags.isNotEmpty) ...[ const SizedBox(height: _TagDimensions.tagTopSpacing), Wrap( spacing: _TagDimensions.tagSpacing, runSpacing: _TagDimensions.tagRunSpacing, children: model.modelTags.map((tag) => _buildTag(tag, context)).toList(), ), ], ], ), ), if (isSelected) Icon(Icons.check_circle_rounded, size: _ModelItemDimensions.selectedIconSize, color: cs.primary), ], ), ), ); } Widget _buildTag(String tag, BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; Color tagColor; Color backgroundColor; Color borderColor; if (tag == '私有') { tagColor = Colors.blue; backgroundColor = isDark ? Colors.blue.withOpacity(0.15) : Colors.blue.withOpacity(0.1); borderColor = Colors.blue.withOpacity(isDark ? 0.3 : 0.2); } else if (tag == '系统') { tagColor = Colors.green; backgroundColor = isDark ? Colors.green.withOpacity(0.15) : Colors.green.withOpacity(0.1); borderColor = Colors.green.withOpacity(isDark ? 0.3 : 0.2); } else if (tag == '推荐') { tagColor = Colors.orange; backgroundColor = isDark ? Colors.orange.withOpacity(0.15) : Colors.orange.withOpacity(0.1); borderColor = Colors.orange.withOpacity(isDark ? 0.3 : 0.2); } else if (tag == '免费') { tagColor = Colors.purple; backgroundColor = isDark ? Colors.purple.withOpacity(0.15) : Colors.purple.withOpacity(0.1); borderColor = Colors.purple.withOpacity(isDark ? 0.3 : 0.2); } else if (tag.contains('积分')) { tagColor = Colors.red; backgroundColor = isDark ? Colors.red.withOpacity(0.15) : Colors.red.withOpacity(0.1); borderColor = Colors.red.withOpacity(isDark ? 0.3 : 0.2); } else { tagColor = cs.outline; backgroundColor = isDark ? cs.surfaceVariant.withOpacity(0.3) : cs.surfaceVariant.withOpacity(0.5); borderColor = cs.outline.withOpacity(isDark ? 0.3 : 0.2); } return Container( padding: const EdgeInsets.symmetric( horizontal: _TagDimensions.tagHorizontalPadding, vertical: _TagDimensions.tagVerticalPadding ), decoration: BoxDecoration( color: backgroundColor, borderRadius: BorderRadius.circular(_TagDimensions.tagBorderRadius), border: Border.all( color: borderColor, width: _TagDimensions.tagBorderWidth, ), ), child: Text( tag, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: tagColor.withOpacity(isDark ? 0.9 : 0.8), fontSize: 10, fontWeight: FontWeight.w500, ), ), ); } Widget _getModelIcon(String provider, BuildContext context) { final color = ProviderIcons.getProviderColor(provider); final isDark = Theme.of(context).brightness == Brightness.dark; return Container( width: _ModelItemDimensions.iconContainerSize, height: _ModelItemDimensions.iconContainerSize, decoration: BoxDecoration( color: isDark ? Colors.white.withOpacity(0.9) : color.withOpacity(0.12), borderRadius: BorderRadius.circular(4), border: Border.all( color: isDark ? color.withOpacity(0.3) : color.withOpacity(0.25), width: 0.5, ), ), child: Padding( padding: const EdgeInsets.all(2), child: ProviderIcons.getProviderIcon(provider, size: _ModelItemDimensions.iconSize, useHighQuality: true), ), ); } }