import 'package:flutter/material.dart'; import '../../../config/provider_icons.dart'; /// 模型服务卡片的数据模型 class ModelServiceData { final String id; final String name; final String provider; final String path; final bool verified; final bool isDefault; final String? status; final DateTime timestamp; final String? description; final List? tags; final String? apiEndpoint; final ModelPerformance? performance; ModelServiceData({ required this.id, required this.name, required this.provider, required this.path, required this.verified, required this.isDefault, this.status, required this.timestamp, this.description, this.tags, this.apiEndpoint, this.performance, }); } /// 模型性能数据 class ModelPerformance { final int latency; // 毫秒 final double throughput; // 请求/秒 ModelPerformance({ required this.latency, required this.throughput, }); } /// 模型服务卡片组件 class ModelServiceCard extends StatefulWidget { const ModelServiceCard({ super.key, required this.model, required this.onSetDefault, required this.onValidate, required this.onEdit, required this.onDelete, }); final ModelServiceData model; final Function(String) onSetDefault; final Function(String) onValidate; final Function(String) onEdit; final Function(String) onDelete; @override State createState() => _ModelServiceCardState(); } class _ModelServiceCardState extends State { bool _expanded = false; // 未使用的变量已移除 // 获取提供商图标 Widget _getProviderLogo(String provider) { return ProviderIcons.getProviderIconForContext( provider, iconSize: IconSize.medium, ); } // 获取状态颜色(未使用,保留以备后续扩展) Color _getStatusColor(String status) { final statusLower = status.toLowerCase(); if (statusLower.contains('error') || statusLower.contains('失败')) { return Theme.of(context).colorScheme.error; } else if (statusLower.contains('warning') || statusLower.contains('警告')) { return Theme.of(context).colorScheme.tertiary; } else { return Theme.of(context).colorScheme.primary; } } // 获取状态文本(未使用,保留以备后续扩展) String _getStatusText(String status) { return status; } // 格式化日期 String _formatDate(DateTime date) { return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}'; } // 获取性能颜色 Color _getPerformanceColor(int latency) { if (latency < 100) { return Theme.of(context).colorScheme.secondary; } else if (latency < 300) { return Theme.of(context).colorScheme.tertiary; } else { return Theme.of(context).colorScheme.error; } } @override Widget build(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; return Container( margin: EdgeInsets.zero, decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: BorderRadius.circular(8), border: Border.all( color: widget.model.verified ? theme.colorScheme.outline.withAlpha(51) : theme.colorScheme.outline.withAlpha(77), width: widget.model.verified ? 0.5 : 1, ), boxShadow: [ BoxShadow( color: Theme.of(context).colorScheme.shadow.withOpacity(0.08), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 卡片主体内容 Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 头部:图标、名称和操作菜单 Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 提供商图标 Container( width: 36, height: 36, decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest.withAlpha(128), borderRadius: BorderRadius.circular(8), ), child: Center( child: _getProviderLogo(widget.model.provider), ), ), const SizedBox(width: 12), // 名称和路径 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.model.name, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ Text( widget.model.provider, style: theme.textTheme.bodySmall?.copyWith( fontWeight: FontWeight.w500, fontSize: 11, ), ), const SizedBox(width: 8), Text( '•', style: TextStyle( color: theme.colorScheme.onSurface.withAlpha(77), ), ), const SizedBox(width: 8), Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest.withAlpha(128), borderRadius: BorderRadius.circular(4), ), child: Text( widget.model.path, style: TextStyle( fontFamily: 'monospace', fontSize: 11, color: theme.colorScheme.onSurfaceVariant, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ), ], ), ], ), ), // 操作菜单 PopupMenuButton( icon: Icon( Icons.more_vert, size: 18, color: theme.colorScheme.onSurface.withAlpha(153), ), itemBuilder: (context) => >[ const PopupMenuItem( value: 'edit', child: Row( children: [ Icon(Icons.edit, size: 16), SizedBox(width: 8), Text('编辑', style: TextStyle(fontSize: 13)), ], ), ), const PopupMenuItem( value: 'copy_path', child: Row( children: [ Icon(Icons.copy, size: 16), SizedBox(width: 8), Text('复制模型路径', style: TextStyle(fontSize: 13)), ], ), ), if (widget.model.apiEndpoint != null) const PopupMenuItem( value: 'visit_api', child: Row( children: [ Icon(Icons.open_in_new, size: 16), SizedBox(width: 8), Text('访问API', style: TextStyle(fontSize: 13)), ], ), ), const PopupMenuDivider(), PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete_outline, size: 16, color: theme.colorScheme.error), const SizedBox(width: 8), Text('删除', style: TextStyle(fontSize: 13, color: theme.colorScheme.error)), ], ), ), ], onSelected: (String value) { switch (value) { case 'edit': widget.onEdit(widget.model.id); break; case 'copy_path': // 复制路径逻辑 break; case 'visit_api': // 访问API逻辑 break; case 'delete': widget.onDelete(widget.model.id); break; } }, ), ], ), const SizedBox(height: 8), // 状态标签和时间戳 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 状态标签 Row( children: [ // 验证状态 Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( color: widget.model.verified ? theme.colorScheme.secondaryContainer : theme.colorScheme.tertiaryContainer, borderRadius: BorderRadius.circular(16), border: Border.all( color: widget.model.verified ? theme.colorScheme.secondary.withOpacity(0.5) : theme.colorScheme.tertiary.withOpacity(0.5), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( widget.model.verified ? Icons.check_circle_outline : Icons.access_time, size: 12, color: widget.model.verified ? theme.colorScheme.secondary : theme.colorScheme.tertiary, ), const SizedBox(width: 4), Text( widget.model.verified ? '已验证' : '未验证', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w500, color: widget.model.verified ? theme.colorScheme.onSecondaryContainer : theme.colorScheme.onTertiaryContainer, ), ), ], ), ), // 默认状态标签 if (widget.model.isDefault) Padding( padding: const EdgeInsets.only(left: 8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( color: theme.colorScheme.primary.withAlpha(26), borderRadius: BorderRadius.circular(16), border: Border.all( color: theme.colorScheme.primary.withAlpha(77), width: 1, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.star, size: 12, color: theme.colorScheme.primary, ), const SizedBox(width: 4), Text( '默认', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w500, color: theme.colorScheme.primary, ), ), ], ), ), ), ], ), // 时间戳 Row( children: [ Icon( Icons.access_time, size: 12, color: theme.colorScheme.onSurface.withAlpha(128), ), const SizedBox(width: 4), Text( _formatDate(widget.model.timestamp), style: TextStyle( fontSize: 11, color: theme.colorScheme.onSurface.withAlpha(128), ), ), ], ), ], ), // 性能指标 if (widget.model.performance != null) Padding( padding: const EdgeInsets.only(top: 8), child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest.withAlpha(77), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ // 延迟 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '延迟', style: TextStyle( fontSize: 11, color: theme.colorScheme.onSurface.withAlpha(153), ), ), const SizedBox(height: 2), Row( children: [ Icon( Icons.bolt, size: 14, color: _getPerformanceColor(widget.model.performance!.latency), ), const SizedBox(width: 4), Text( '${widget.model.performance!.latency}ms', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: _getPerformanceColor(widget.model.performance!.latency), ), ), ], ), ], ), ), // 吞吐量 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '吞吐量', style: TextStyle( fontSize: 11, color: theme.colorScheme.onSurface.withAlpha(153), ), ), const SizedBox(height: 2), Text( '${widget.model.performance!.throughput} 次/秒', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: theme.colorScheme.onSurface, ), ), ], ), ), ], ), ), ), // 展开的详情内容 if (_expanded && widget.model.description != null) Padding( padding: const EdgeInsets.only(top: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Divider( color: theme.colorScheme.outline.withAlpha(26), ), const SizedBox(height: 8), Text( widget.model.description!, style: TextStyle( fontSize: 13, color: theme.colorScheme.onSurface.withAlpha(204), height: 1.5, ), ), // 标签 if (widget.model.tags != null && widget.model.tags!.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 12), child: Wrap( spacing: 6, runSpacing: 6, children: widget.model.tags!.map((tag) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: theme.colorScheme.primary.withAlpha(26), borderRadius: BorderRadius.circular(16), border: Border.all( color: theme.colorScheme.primary.withAlpha(77), width: 1, ), ), child: Text( tag, style: TextStyle( fontSize: 11, color: theme.colorScheme.primary, ), ), ); }).toList(), ), ), ], ), ), ], ), ), // 底部操作区 Container( decoration: BoxDecoration( border: Border( top: BorderSide( color: theme.colorScheme.outline.withAlpha(26), width: 1, ), ), ), child: Row( children: [ // 查看详情按钮 Expanded( child: InkWell( onTap: () { setState(() { _expanded = !_expanded; }); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest.withAlpha(77), ), alignment: Alignment.center, child: Text( _expanded ? '收起详情' : '查看详情', style: TextStyle( fontSize: 13, color: theme.colorScheme.onSurface.withAlpha(179), ), ), ), ), ), // 设为默认按钮(仅未验证时显示) Expanded( child: InkWell( onTap: () { // 如果未验证,则执行验证逻辑 if (!widget.model.verified) { widget.onValidate(widget.model.id); } else { // 如果已验证,则执行设为默认逻辑 if (!widget.model.isDefault) { widget.onSetDefault(widget.model.id); } } }, child: Container( padding: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( border: Border( left: BorderSide( color: theme.colorScheme.outline.withAlpha(26), width: 1, ), ), ), alignment: Alignment.center, child: Text( widget.model.verified ? (widget.model.isDefault ? '默认模型' : '设为默认') : '验证连接', // 未验证时显示验证 style: TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: widget.model.verified && widget.model.isDefault ? theme.colorScheme.onSurface.withAlpha(100) // 如果是默认,灰色显示 : theme.colorScheme.primary, // 否则高亮 ), ), ), ), ), ], ), ), ], ), ); } }