1 line
40 KiB
JSON
1 line
40 KiB
JSON
{"repo": "aarnwang/testcase-manager", "files": {"frontend/src/views/AiConfig.vue": {"a3df62198cf151c8a921b3a70994f9a18c164141": {"t": 1785768139.9036076, "n": 35443, "h": "fb44c0aa8599ca06", "c": "<template>\n <div class=\"ai-config-container\">\n <el-tabs tab-position=\"left\" style=\"height: calc(100vh - 80px)\" class=\"demo-tabs\">\n <el-tab-pane label=\"模型配置\">\n <div class=\"tab-content\">\n <div class=\"config-container\">\n <div class=\"config-layout\">\n <div class=\"config-left\">\n <el-form \n :model=\"configForm\" \n :rules=\"configRules\" \n ref=\"configFormRef\" \n label-width=\"120px\"\n class=\"config-form\"\n >\n <div class=\"form-section\">\n <h4>基础信息</h4>\n <el-form-item label=\"配置名称\" prop=\"configName\">\n <el-input \n v-model=\"configForm.configName\" \n placeholder=\"请输入配置名称\"\n class=\"config-input\"\n />\n </el-form-item>\n <el-form-item label=\"模型类型\" prop=\"modelType\">\n <el-select \n v-model=\"configForm.modelType\" \n placeholder=\"请选择模型类型\"\n class=\"config-input\"\n >\n <el-option label=\"通义千问\" value=\"qwen\" />\n <el-option label=\"字节跳动(火山引擎)\" value=\"volcengine\" />\n <el-option label=\"DeepSeek\" value=\"deepseek\" />\n <el-option label=\"智谱AI\" value=\"zhipu\" />\n <el-option label=\"OpenAI\" value=\"openai\" />\n <el-option label=\"百度文心一言\" value=\"wenxin\" />\n <el-option label=\"讯飞星火\" value=\"xunfei\" />\n <el-option label=\"MiniMax\" value=\"minimax\" />\n <el-option label=\"月之暗面\" value=\"moonshot\" />\n <el-option label=\"360智脑\" value=\"360\" />\n <el-option label=\"Claude (Anthropic)\" value=\"claude\" />\n <el-option label=\"Gemini (Google)\" value=\"gemini\" />\n </el-select>\n </el-form-item>\n \n <el-form-item label=\"API密钥\" prop=\"apiKey\">\n <el-input \n v-model=\"configForm.apiKey\" \n placeholder=\"请输入API密钥\"\n type=\"password\"\n show-password\n class=\"config-input\"\n />\n </el-form-item>\n \n <el-form-item label=\"模型URL\" prop=\"baseUrl\">\n <el-input \n v-model=\"configForm.baseUrl\" \n placeholder=\"请输入模型URL\"\n class=\"config-input\"\n />\n </el-form-item>\n \n <el-form-item label=\"模型版本\" prop=\"modelVersion\">\n <el-input \n v-model=\"configForm.modelVersion\" \n placeholder=\"请输入模型版本\"\n class=\"config-input\"\n />\n </el-form-item>\n </div>\n \n <div class=\"form-section\">\n <h4>价格配置</h4>\n <el-form-item label=\"输入价格\" prop=\"promptPrice\">\n <el-input \n v-model=\"configForm.promptPrice\" \n placeholder=\"每1000个输入token的价格\"\n class=\"config-input\"\n >\n <template #append>CNY</template>\n </el-input>\n </el-form-item>\n \n <el-form-item label=\"输出价格\" prop=\"completionPrice\">\n <el-input \n v-model=\"configForm.completionPrice\" \n placeholder=\"每1000个输出token的价格\"\n class=\"config-input\"\n >\n <template #append>CNY</template>\n </el-input>\n </el-form-item>\n </div>\n \n <el-form-item class=\"form-actions\">\n <el-button type=\"primary\" @click=\"saveConfig\" :disabled=\"saving\" class=\"save-btn\">\n <template v-if=\"saving\">\n <div class=\"saving-inline\">\n <div class=\"circular-loading\"></div>\n 正在保存\n </div>\n </template>\n <template v-else>\n <span>保存配置</span>\n </template>\n </el-button>\n <el-button @click=\"resetConfig\" :disabled=\"saving\" class=\"reset-btn\">重置</el-button>\n </el-form-item>\n </el-form>\n </div>\n <div class=\"config-right\">\n <div class=\"form-section\">\n <h4>配置列表</h4>\n <div class=\"config-list\">\n <div v-if=\"configList.length === 0\" class=\"empty-list\">\n <p>暂无配置</p>\n </div>\n <div v-else class=\"config-items\">\n <div class=\"config-header\">\n <div class=\"config-name\">配置名称</div>\n <div class=\"config-info\">\n <div class=\"model-type-container\">\n <span>模型类型</span>\n </div>\n <div class=\"model-version\">模型版本</div>\n <div class=\"config-time\">更新时间</div>\n <div class=\"header-enabled\">启用状态</div>\n <div class=\"config-actions\">操作</div>\n </div>\n </div>\n <div \n v-for=\"config in configList\" \n :key=\"config.id\" \n class=\"config-item\"\n >\n <div class=\"config-name\">{{ config.config_name }}</div>\n <div class=\"config-info\">\n <div class=\"model-type-container\">\n <span class=\"model-type\" :style=\"{ \n backgroundColor: getModelTypeInfo(config.model_type).bgColor, \n color: getModelTypeInfo(config.model_type).color \n }\">\n {{ getModelTypeInfo(config.model_type).name }}\n </span>\n </div>\n <div class=\"model-version\">{{ config.model_version }}</div>\n <div class=\"config-time\">{{ formatTime(config.updated_at) }}</div>\n <div class=\"config-enabled\">\n <el-switch \n :model-value=\"Boolean(config.is_enabled)\" \n @update:model-value=\"(value) => toggleConfigEnabled(config, value)\"\n size=\"small\"\n class=\"animated-switch\"\n />\n </div>\n <div class=\"config-actions\">\n <el-button size=\"small\" type=\"success\" @click=\"viewConfig(config)\" class=\"action-btn view-btn\">查看</el-button>\n <el-button size=\"small\" type=\"primary\" @click=\"editConfig(config)\" class=\"action-btn edit-btn\">编辑</el-button>\n <el-popconfirm\n title=\"确定要删除这个配置吗?\"\n confirm-button-text=\"确定\"\n cancel-button-text=\"取消\"\n placement=\"left-end\"\n @confirm=\"deleteConfig(config)\"\n >\n <template #reference>\n <el-button size=\"small\" type=\"danger\" class=\"action-btn delete-btn\">删除</el-button>\n </template>\n </el-popconfirm>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </el-tab-pane>\n \n <el-tab-pane label=\"帮助文档\">\n <div class=\"tab-content\">\n <div class=\"help-container\">\n <el-collapse>\n <el-collapse-item title=\"模型推荐\" name=\"1\" class=\"custom-collapse-item\">\n <p><strong>通义千问系列:</strong></p>\n <ul>\n <li><strong>qwen-turbo</strong>:强烈推荐!快速响应,生成的用例完整覆盖度高</li>\n <li><strong>qwen-plus</strong>:平衡性能,适合复杂对话和中等难度任务</li>\n <li><strong>qwen-max</strong>:最强性能,适合高难度任务和专业应用</li>\n </ul>\n </el-collapse-item>\n <el-collapse-item title=\"如何获取API密钥?\" name=\"2\" class=\"custom-collapse-item\">\n <p>1. 登录您的AI服务提供商账户</p>\n <p>2. 进入API管理页面</p>\n <p>3. 创建新的API密钥</p>\n <p>4. 复制密钥并粘贴到配置中</p>\n </el-collapse-item>\n <el-collapse-item title=\"模型URL接口地址说明\" name=\"3\" class=\"custom-collapse-item\">\n <p>通义千问:https://dashscope.aliyuncs.com/compatible-mode/v1</p>\n <p>火山引擎:https://ark.cn-beijing.volces.com/api/v3</p>\n <p>DeepSeek:https://api.deepseek.com</p>\n <p>智谱AI:https://open.bigmodel.cn/api/paas/v4</p>\n <p>OpenAI:https://api.openai.com/v1</p>\n <p>百度文心一言:https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat</p>\n <p>讯飞星火:https://spark-api.xf-yun.com/v3.1/chat</p>\n <p>MiniMax:https://api.minimax.chat/v1</p>\n <p>月之暗面:https://api.moonshot.cn/v1</p>\n <p>360智脑:https://api.360.cn/v1</p>\n <p>Claude (Anthropic):https://api.anthropic.com/v1</p>\n <p>Gemini (Google):https://generativelanguage.googleapis.com/v1beta</p>\n </el-collapse-item>\n <el-collapse-item title=\"更新日志\" name=\"4\" class=\"custom-collapse-item update-log-item\">\n <p><strong>V1.0.9 (2025-09-03)</strong></p>\n <ul>\n <li>更新AI模型多配置</li>\n </ul>\n <p><strong>V1.0.8 (2025-09-02)</strong></p>\n <ul>\n <li>优化AI生成用例系统,支持Windows、macOS、Linux跨平台运行</li>\n <li>改进subprocess命令执行机制,提升跨平台兼容性</li>\n <li>增强错误处理和调试信息,便于问题诊断</li>\n </ul>\n <p><strong>V1.0.7 (2025-09-02)</strong></p>\n <ul>\n <li>新增支持Claude (Anthropic)、Gemini (Google)等全球顶级AI模型</li>\n <li>优化AI模型配置界面,支持12种主流AI模型</li>\n <li>完善模型URL接口地址说明文档</li>\n </ul>\n <p><strong>V1.0.6 (2025-09-02)</strong></p>\n <ul>\n <li>新增支持OpenAI、百度文心一言、讯飞星火、MiniMax、月之暗面、360智脑等AI模型</li>\n <li>优化AI模型配置界面,支持10种主流AI模型</li>\n <li>完善模型URL接口地址说明文档</li>\n </ul>\n <p><strong>V1.0.5 (2025-09-02)</strong></p>\n <ul>\n <li>完成AI配置服务层</li>\n <li>增加多模型适配</li>\n <li>修复一些报错</li>\n <li>脱离env文件,改为配置化</li>\n </ul>\n <p><strong>V1.0.4 (2025-08-29)</strong></p>\n <ul>\n <li>增加AI生成用例策略组json识别</li>\n <li>增加用例需求分析功能点规则匹配度</li>\n <li>补充用例上传的部分识别逻辑</li>\n <li>增加系统设置,后续所有的模型配置将不在代码中写死,改为配置</li>\n <li>解决AutoAgent框架识别部分模型时,无法计算价格导致的warning</li>\n </ul>\n <p><strong>V1.0.3 (2025-08-22)</strong></p>\n <ul>\n <li>手动增加测试用例功能</li>\n <li>运行日志</li>\n </ul>\n <p><strong>V1.0.2 (2025-08-12)</strong></p>\n <ul>\n <li>优化AI生成用例的用户体验</li>\n <li>修复文件选择后清除生成总结的问题</li>\n <li>修复用例类型显示问题</li>\n <li>优化下载中心弹窗标题样式</li>\n <li>改进错误处理和用户提示</li>\n </ul>\n <p><strong>V1.0.1 (2025-08-10)</strong></p>\n <ul>\n <li>AI生成能力(已开发完成)接入用例平台</li>\n <li>生成用例总结数据实时显示</li>\n <li>支持多种测试类型(功能测试、接口测试、UI自动化测试)</li>\n <li>智能文档解析和用例生成</li>\n <li>生成历史记录和统计</li>\n </ul>\n <p><strong>v1.0.0 (2025-08-01)</strong></p>\n <ul>\n <li>基础项目管理功能</li>\n <li>Excel文件上传和解析</li>\n <li>测试用例批量导入</li>\n <li>重复数据检测和处理</li>\n <li>响应式Web界面</li>\n </ul>\n </el-collapse-item>\n </el-collapse>\n </div>\n </div>\n </el-tab-pane>\n </el-tabs>\n \n <!-- 查看配置详情对话框 -->\n <el-dialog\n v-model=\"viewDialogVisible\"\n width=\"600px\"\n :close-on-click-modal=\"false\"\n :close-on-press-escape=\"true\"\n >\n <div class=\"config-detail\">\n <div class=\"detail-section\">\n <h4>基础信息</h4>\n <div class=\"detail-item\">\n <label>配置名称:</label>\n <span>{{ viewConfigData.configName }}</span>\n </div>\n <div class=\"detail-item\">\n <label>模型类型:</label>\n <span class=\"model-type-tag\" :style=\"{ \n backgroundColor: getModelTypeInfo(viewConfigData.modelType).bgColor, \n color: getModelTypeInfo(viewConfigData.modelType).color \n }\">\n {{ getModelTypeInfo(viewConfigData.modelType).name }}\n </span>\n </div>\n <div class=\"detail-item\">\n <label>模型版本:</label>\n <span>{{ viewConfigData.modelVersion }}</span>\n </div>\n <div class=\"detail-item\">\n <label>API密钥:</label>\n <span class=\"api-key\">{{ viewConfigData.apiKey }}</span>\n </div>\n <div class=\"detail-item\">\n <label>模型URL:</label>\n <span class=\"url-text\">{{ viewConfigData.baseUrl }}</span>\n </div>\n </div>\n \n <div class=\"detail-section\">\n <h4>价格配置</h4>\n <div class=\"detail-item\">\n <label>输入价格:</label>\n <span>{{ viewConfigData.promptPrice }} CNY / 1000 tokens</span>\n </div>\n <div class=\"detail-item\">\n <label>输出价格:</label>\n <span>{{ viewConfigData.completionPrice }} CNY / 1000 tokens</span>\n </div>\n </div>\n \n <div class=\"detail-section\">\n <h4>状态信息</h4>\n <div class=\"detail-item\">\n <label>启用状态:</label>\n <el-tag :type=\"viewConfigData.isEnabled ? 'success' : 'info'\">\n {{ viewConfigData.isEnabled ? '启用' : '禁用' }}\n </el-tag>\n </div>\n <div class=\"detail-item\">\n <label>创建时间:</label>\n <span>{{ formatTime(viewConfigData.createdAt) }}</span>\n </div>\n <div class=\"detail-item\">\n <label>更新时间:</label>\n <span>{{ formatTime(viewConfigData.updatedAt) }}</span>\n </div>\n </div>\n </div>\n \n <template #footer>\n <div class=\"dialog-footer\">\n <el-button @click=\"viewDialogVisible = false\">关闭</el-button>\n </div>\n </template>\n </el-dialog>\n </div>\n</template>\n\n<script setup>\nimport { ref, reactive, onMounted } from 'vue';\nimport { ElMessage, ElMessageBox } from 'element-plus';\nimport { Setting } from '@element-plus/icons-vue';\n\n// 响应式数据\nconst configFormRef = ref();\nconst saving = ref(false);\nconst configList = ref([]);\nconst viewDialogVisible = ref(false);\nconst viewConfigData = ref({});\n\nconst configForm = reactive({\n configId: null,\n configName: '',\n modelType: 'qwen',\n apiKey: '',\n baseUrl: '',\n modelVersion: '',\n promptPrice: '',\n completionPrice: ''\n});\n\n// 表单验证规则\nconst configRules = {\n configName: [\n { required: true, message: '请输入配置名称', trigger: 'blur' }\n ],\n modelType: [\n { required: true, message: '请选择模型类型', trigger: 'change' }\n ],\n apiKey: [\n { required: true, message: '请输入API密钥', trigger: 'blur' }\n ],\n baseUrl: [\n { required: true, message: '请输入基础URL', trigger: 'blur' }\n ],\n modelVersion: [\n { required: true, message: '请输入模型版本', trigger: 'blur' }\n ],\n promptPrice: [\n { required: true, message: '请输入每1000个输入token的价格', trigger: 'blur' }\n ],\n completionPrice: [\n { required: true, message: '请输入每1000个输出token的价格', trigger: 'blur' }\n ]\n};\n\n// 页面加载时初始化\nonMounted(async () => {\n // 初始化表单为空,只有模型类型默认为通义千问\n resetFormToDefault();\n // 加载配置列表\n await loadConfigList();\n});\n\n// 加载配置列表\nconst loadConfigList = async () => {\n try {\n const response = await fetch('/api/ai_configs?include_sensitive=true');\n const result = await response.json();\n \n if (result.success) {\n configList.value = result.data || [];\n } else {\n console.error('加载配置列表失败:', result.message);\n }\n } catch (error) {\n console.error('加载配置列表失败:', error);\n }\n};\n\n// 获取模型类型中文名称和颜色\nconst getModelTypeInfo = (modelType) => {\n const modelInfo = {\n 'qwen': { name: '通义千问', color: '#ff6b35', bgColor: '#fff2ed' },\n 'volcengine': { name: '字节跳动(火山引擎)', color: '#000000', bgColor: '#f5f5f5' },\n 'deepseek': { name: 'DeepSeek', color: '#6366f1', bgColor: '#eef2ff' },\n 'zhipu': { name: '智谱AI', color: '#059669', bgColor: '#ecfdf5' },\n 'openai': { name: 'OpenAI', color: '#10b981', bgColor: '#ecfdf5' },\n 'wenxin': { name: '百度文心一言', color: '#3b82f6', bgColor: '#eff6ff' },\n 'xunfei': { name: '讯飞星火', color: '#f59e0b', bgColor: '#fffbeb' },\n 'minimax': { name: 'MiniMax', color: '#8b5cf6', bgColor: '#f3f4f6' },\n 'moonshot': { name: '月之暗面', color: '#6366f1', bgColor: '#eef2ff' },\n '360': { name: '360智脑', color: '#059669', bgColor: '#ecfdf5' },\n 'claude': { name: 'Claude (Anthropic)', color: '#dc2626', bgColor: '#fef2f2' },\n 'gemini': { name: 'Gemini (Google)', color: '#ea4335', bgColor: '#fef2f2' }\n };\n return modelInfo[modelType] || { name: modelType, color: '#6b7280', bgColor: '#f9fafb' };\n};\n\n// 格式化时间\nconst formatTime = (timeStr) => {\n if (!timeStr) return '';\n const date = new Date(timeStr);\n return date.toLocaleString('zh-CN', {\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit'\n });\n};\n\n// 查看配置\nconst viewConfig = async (config) => {\n try {\n console.log('查看配置:', config);\n \n const response = await fetch(`/api/ai_config/${config.id}`);\n const result = await response.json();\n \n if (result.success) {\n viewConfigData.value = result.data;\n viewDialogVisible.value = true;\n } else {\n ElMessage.error(result.message || '获取配置详情失败');\n }\n } catch (error) {\n console.error('获取配置详情失败:', error);\n ElMessage.error('获取配置详情失败');\n }\n};\n\n// 编辑配置\nconst editConfig = (config) => {\n console.log('编辑配置:', config);\n // 将选中的配置填充到表单中\n configForm.configId = config.id; // 添加配置ID\n configForm.configName = config.config_name;\n configForm.modelType = config.model_type;\n configForm.apiKey = config.api_key;\n configForm.baseUrl = config.model_url;\n configForm.modelVersion = config.model_version;\n configForm.promptPrice = config.prompt_price_per_1k;\n configForm.completionPrice = config.completion_price_per_1k;\n \n ElMessage.success('配置已加载到表单中,请修改后保存');\n};\n\n// 删除配置\nconst deleteConfig = async (config) => {\n try {\n console.log('删除配置:', config);\n \n const response = await fetch(`/api/ai_config/${config.id}/delete`, {\n method: 'DELETE',\n headers: {\n 'Content-Type': 'application/json',\n }\n });\n \n const result = await response.json();\n \n if (result.success) {\n ElMessage.success(result.message);\n // 刷新配置列表\n await loadConfigList();\n } else {\n ElMessage.error(result.message);\n }\n } catch (error) {\n console.error('删除配置失败:', error);\n ElMessage.error('删除配置失败');\n }\n};\n\n// 切换配置启用状态\nconst toggleConfigEnabled = async (config, newValue) => {\n try {\n console.log('切换配置启用状态:', config, '新值:', newValue);\n \n // 如果要启用,先禁用所有其他配置\n if (newValue) {\n // 找到所有已启用的配置(除了当前配置)\n const otherEnabledConfigs = configList.value.filter(c => c.is_enabled && c.id !== config.id);\n \n if (otherEnabledConfigs.length > 0) {\n // 显示提示信息\n ElMessage.info('正在切换配置,请稍候...');\n \n // 先禁用其他已启用的配置\n for (const otherConfig of otherEnabledConfigs) {\n try {\n const disableResponse = await fetch(`/api/ai_config/${otherConfig.id}/toggle_enabled`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n enabled: false\n })\n });\n \n const disableResult = await disableResponse.json();\n if (!disableResult.success) {\n console.error('禁用其他配置失败:', disableResult.message);\n }\n } catch (error) {\n console.error('禁用其他配置失败:', error);\n }\n }\n }\n }\n \n // 然后切换当前配置的状态\n const response = await fetch(`/api/ai_config/${config.id}/toggle_enabled`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n enabled: newValue\n })\n });\n \n const result = await response.json();\n \n if (result.success) {\n ElMessage.success(result.message);\n // 刷新配置列表以获取最新状态\n await loadConfigList();\n } else {\n ElMessage.error(result.message);\n }\n } catch (error) {\n console.error('切换启用状态失败:', error);\n ElMessage.error('切换启用状态失败');\n }\n};\n\n// 重置表单为默认状态\nconst resetFormToDefault = () => {\n configForm.configId = null;\n configForm.configName = '';\n configForm.modelType = 'qwen';\n configForm.apiKey = '';\n configForm.baseUrl = '';\n configForm.modelVersion = '';\n configForm.promptPrice = '';\n configForm.completionPrice = '';\n};\n\n// 加载配置(暂时注释掉,后续从配置列表中选择时调用)\n// const loadConfig = async () => {\n// try {\n// const response = await fetch('/api/ai_config');\n// const result = await response.json();\n// \n// if (result.success && result.data) {\n// // 填充表单数据\n// configForm.modelType = result.data.modelType || 'qwen';\n// configForm.apiKey = result.data.apiKey || '';\n// configForm.baseUrl = result.data.baseUrl || '';\n// configForm.modelVersion = result.data.modelVersion || '';\n// configForm.promptPrice = result.data.promptPrice || '0.001';\n// configForm.completionPrice = result.data.completionPrice || '0.002';\n// }\n// } catch (error) {\n// console.error('加载配置失败:', error);\n// ElMessage.error('加载配置失败');\n// }\n// };\n\n// 保存配置\nconst saveConfig = async () => {\n try {\n await configFormRef.value.validate();\n saving.value = true;\n \n const isEdit = configForm.configId !== null;\n const url = isEdit ? `/api/ai_config/${configForm.configId}` : '/api/ai_config';\n const method = isEdit ? 'PUT' : 'POST';\n \n const response = await fetch(url, {\n method: method,\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n configId: configForm.configId,\n configName: configForm.configName,\n modelType: configForm.modelType,\n apiKey: configForm.apiKey,\n baseUrl: configForm.baseUrl,\n modelVersion: configForm.modelVersion,\n promptPrice: configForm.promptPrice,\n completionPrice: configForm.completionPrice\n })\n });\n \n const result = await response.json();\n \n if (result.success) {\n ElMessage.success(isEdit ? '配置更新成功!' : '配置保存成功!');\n // 保存成功后清空表单\n resetFormToDefault();\n // 刷新配置列表\n await loadConfigList();\n } else {\n // 显示具体的验证错误信息\n if (result.errors) {\n // 显示所有验证错误\n const errorMessages = Object.values(result.errors);\n if (errorMessages.length === 1) {\n ElMessage.error(errorMessages[0]);\n } else {\n // 如果有多个错误,显示所有错误\n const errorText = errorMessages.join(';');\n ElMessage.error(`验证失败:${errorText}`);\n console.error('所有验证错误:', result.errors);\n }\n } else {\n ElMessage.error(result.message || '配置保存失败');\n }\n }\n } catch (error) {\n console.error('配置保存失败:', error);\n ElMessage.error('配置保存失败,请检查输入');\n } finally {\n saving.value = false;\n }\n};\n\n// 重置配置\nconst resetConfig = () => {\n ElMessageBox.confirm(\n '确定要重置所有配置吗?',\n '提示',\n {\n confirmButtonText: '确定',\n cancelButtonText: '取消',\n type: 'warning',\n }\n ).then(() => {\n resetFormToDefault();\n ElMessage.success('配置已重置');\n }).catch(() => {\n // 用户取消\n });\n};\n</script>\n\n<style scoped>\n.ai-config-container {\n padding: 24px;\n width: 100%;\n height: 100%;\n background: #f8fafc;\n border-radius: 20px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n overflow: hidden;\n}\n\n.demo-tabs {\n height: 100%;\n border-radius: 16px;\n overflow: hidden;\n}\n\n.demo-tabs > .el-tabs__content {\n padding: 32px;\n color: #6b778c;\n font-size: 16px;\n font-weight: 400;\n}\n\n.el-tabs--right .el-tabs__content,\n.el-tabs--left .el-tabs__content {\n height: 100%;\n}\n\n.el-tabs--left .el-tabs__header {\n border-right: 1px solid #e4e7ed;\n border-bottom: none;\n border-top: none;\n border-left: none;\n}\n\n.el-tabs--left .el-tabs__nav-wrap {\n border-bottom: none;\n}\n\n.el-tabs--left .el-tabs__nav {\n border: none;\n}\n\n.el-tabs--left .el-tabs__item {\n border: none;\n border-right: 2px solid transparent;\n transition: all 0.3s;\n}\n\n.el-tabs--left .el-tabs__item.is-active {\n border-right: 2px solid #409eff;\n background-color: #f0f9ff;\n}\n\n.tab-content {\n height: 100%;\n overflow-y: auto;\n}\n\n.config-container {\n padding: 20px;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n\n.config-layout {\n display: flex;\n gap: 30px;\n height: 100%;\n}\n\n.config-left {\n flex: 1;\n max-width: 620px;\n display: flex;\n flex-direction: column;\n}\n\n.config-right {\n flex: 1;\n max-width: 800px;\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.config-right {\n flex: 1;\n max-width: 800px;\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n.config-list {\n margin-top: 15px;\n flex: 1;\n display: flex;\n flex-direction: column;\n}\n\n.config-list {\n margin-top: 15px;\n}\n\n.empty-list {\n text-align: center;\n padding: 40px 20px;\n color: #999;\n font-size: 14px;\n}\n\n.config-items {\n height: calc(100vh - 300px);\n overflow-y: auto;\n /* 隐藏滚动条 */\n scrollbar-width: none; /* Firefox */\n -ms-overflow-style: none; /* IE and Edge */\n}\n\n/* 隐藏 Webkit 浏览器的滚动条 */\n.config-items::-webkit-scrollbar {\n display: none;\n}\n\n.config-header {\n display: flex;\n align-items: center;\n padding: 10px 15px;\n background-color: #f1f5f9;\n border-bottom: 2px solid #e2e8f0;\n font-weight: 600;\n color: #475569;\n font-size: 13px;\n}\n\n.header-name {\n flex: 0 0 150px;\n}\n\n.header-info {\n display: flex;\n align-items: center;\n font-size: 12px;\n flex: 1;\n}\n\n.config-header .config-name {\n font-weight: 600;\n color: #475569;\n font-size: 13px;\n flex: 0 0 120px;\n padding-right: 8px;\n}\n\n.header-model-type {\n width: 140px;\n text-align: left;\n display: flex;\n align-items: center;\n padding-right: 8px;\n}\n\n.header-model-version {\n width: 90px;\n text-align: left;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n padding-right: 8px;\n}\n\n.header-update-time {\n width: 120px;\n text-align: left;\n}\n\n.header-enabled {\n width: 80px;\n text-align: center;\n}\n\n.header-actions {\n width: 140px;\n text-align: left;\n}\n\n.config-item {\n display: flex;\n align-items: center;\n padding: 12px 15px;\n border-bottom: 1px solid #e2e8f0;\n transition: background-color 0.3s ease;\n}\n\n.config-item:hover {\n background-color: #f8fafc;\n}\n\n.config-item:last-child {\n border-bottom: none;\n}\n\n.config-name {\n font-weight: 600;\n color: #1e293b;\n font-size: 14px;\n flex: 0 0 120px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n padding-right: 8px;\n}\n\n.config-info {\n display: flex;\n align-items: center;\n font-size: 12px;\n flex: 1;\n}\n\n.model-type-container {\n width: 140px;\n display: flex;\n align-items: center;\n padding-right: 8px;\n}\n\n.model-type {\n padding: 2px 6px;\n border-radius: 3px;\n white-space: nowrap;\n text-align: left;\n display: inline-block;\n font-size: 12px;\n font-weight: 500;\n}\n\n.model-version {\n color: #64748b;\n white-space: nowrap;\n width: 90px;\n overflow: hidden;\n text-overflow: ellipsis;\n padding-right: 8px;\n}\n\n.config-time {\n color: #94a3b8;\n white-space: nowrap;\n width: 120px;\n}\n\n.config-enabled {\n width: 80px;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.config-actions {\n width: 140px;\n display: flex;\n gap: 6px;\n}\n\n/* 自定义按钮样式 */\n.action-btn {\n font-weight: 500;\n}\n\n.view-btn {\n background-color: #10b981 !important;\n border-color: #10b981 !important;\n color: white !important;\n}\n\n.view-btn:hover {\n background-color: #059669 !important;\n border-color: #059669 !important;\n}\n\n.edit-btn {\n background-color: #409eff !important;\n border-color: #409eff !important;\n color: white !important;\n}\n\n.edit-btn:hover {\n background-color: #337ecc !important;\n border-color: #337ecc !important;\n}\n\n.delete-btn {\n background-color: #dc2626 !important;\n border-color: #dc2626 !important;\n color: white !important;\n}\n\n.delete-btn:hover {\n background-color: #b91c1c !important;\n border-color: #b91c1c !important;\n}\n\n/* 气泡确认框样式优化 */\n:deep(.el-popconfirm) {\n .el-popconfirm__main {\n padding: 12px 16px;\n }\n \n .el-popconfirm__title {\n font-size: 14px;\n color: #374151;\n margin-bottom: 8px;\n }\n \n .el-popconfirm__action {\n margin-top: 12px;\n }\n \n .el-button {\n padding: 6px 12px;\n font-size: 12px;\n }\n}\n\n/* 配置详情对话框样式 */\n.config-detail {\n padding: 0;\n}\n\n.detail-section {\n margin-bottom: 24px;\n}\n\n.detail-section:last-child {\n margin-bottom: 0;\n}\n\n.detail-section h4 {\n margin-bottom: 16px;\n color: #409eff;\n font-size: 16px;\n font-weight: 600;\n display: flex;\n align-items: center;\n border-bottom: 1px solid #e2e8f0;\n padding-bottom: 8px;\n}\n\n.detail-section h4::before {\n content: '';\n width: 3px;\n height: 16px;\n background: #409eff;\n margin-right: 8px;\n border-radius: 2px;\n}\n\n.detail-item {\n display: flex;\n align-items: flex-start;\n margin-bottom: 12px;\n line-height: 1.6;\n}\n\n.detail-item:last-child {\n margin-bottom: 0;\n}\n\n.detail-item label {\n width: 100px;\n font-weight: 600;\n color: #374151;\n flex-shrink: 0;\n}\n\n.detail-item span {\n color: #64748b;\n word-break: break-all;\n}\n\n.detail-item .api-key {\n font-family: 'Courier New', monospace;\n background: #f1f5f9;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 12px;\n}\n\n.detail-item .url-text {\n font-family: 'Courier New', monospace;\n background: #f1f5f9;\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n word-break: break-all;\n}\n\n.detail-item .model-type-tag {\n padding: 4px 8px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 500;\n display: inline-block;\n}\n\n.dialog-footer {\n text-align: center;\n}\n\n/* 开关动画效果 */\n.animated-switch {\n transition: all 0.3s ease;\n}\n\n:deep(.animated-switch .el-switch__core) {\n transition: all 0.3s ease;\n}\n\n:deep(.animated-switch .el-switch__action) {\n transition: all 0.3s ease;\n}\n\n:deep(.animated-switch.is-checked .el-switch__core) {\n background-color: #10b981;\n border-color: #10b981;\n}\n\n:deep(.animated-switch:not(.is-checked) .el-switch__core) {\n background-color: #d1d5db;\n border-color: #d1d5db;\n}\n\n.form-section {\n margin-bottom: 25px;\n}\n\n.form-section h4 {\n margin-bottom: 15px;\n color: #409eff;\n font-size: 18px;\n font-weight: 600;\n display: flex;\n align-items: center;\n}\n\n.form-section h4::before {\n content: '';\n width: 4px;\n height: 18px;\n background: #409eff;\n margin-right: 8px;\n border-radius: 2px;\n}\n\n.config-form {\n max-width: 600px;\n}\n\n.config-input {\n width: 100%;\n}\n\n.el-form-item {\n margin-bottom: 20px;\n}\n\n.el-form-item:last-child {\n margin-bottom: 0;\n text-align: center;\n}\n\n.form-actions {\n text-align: center;\n margin-top: 30px;\n padding-top: 20px;\n border-top: 1px solid #eee;\n}\n\n.save-btn, .reset-btn {\n margin: 0 10px;\n padding: 12px 24px;\n font-weight: 600;\n border-radius: 8px;\n transition: all 0.3s ease;\n}\n\n.save-btn {\n background: #38bdf8;\n border-color: #38bdf8;\n box-shadow: 0 2px 8px rgba(56,189,248,0.25);\n}\n\n.save-btn:hover {\n background: #0ea5e9;\n border-color: #0ea5e9;\n box-shadow: 0 4px 12px rgba(56,189,248,0.35);\n}\n\n.reset-btn {\n background: #f8fafc;\n border: 1px solid #e2e8f0;\n color: #64748b;\n}\n\n.reset-btn:hover {\n background: #f1f5f9;\n border-color: #cbd5e1;\n color: #475569;\n}\n\n.saving-inline {\n display: inline-flex;\n align-items: center;\n}\n\n.circular-loading {\n width: 16px;\n height: 16px;\n border: 2px solid #e4e7ed;\n border-top: 2px solid #409eff;\n border-radius: 50%;\n margin-right: 8px;\n animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n.stat-card {\n margin-top: 20px;\n max-width: 400px;\n}\n\n.stat-container {\n padding-left: 40px;\n padding-top: 20px;\n}\n\n.help-container {\n /* 隐藏右侧滚动条 */\n -ms-overflow-style: none; /* IE and Edge */\n scrollbar-width: none; /* Firefox */\n padding-left: 40px;\n padding-top: 20px;\n max-height: calc(100vh - 200px);\n overflow-y: auto;\n padding-bottom: 40px;\n}\n\n.custom-collapse-item {\n margin-bottom: 16px;\n border-radius: 8px;\n overflow: hidden;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n transition: all 0.3s ease;\n}\n\n.custom-collapse-item:hover {\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n}\n\n.custom-collapse-item :deep(.el-collapse-item__header) {\n background: #94a3b8;\n color: white;\n font-weight: 600;\n font-size: 16px;\n padding: 16px 20px;\n border: none;\n border-radius: 8px 8px 0 0;\n transition: all 0.3s ease;\n}\n\n.custom-collapse-item :deep(.el-collapse-item__header:hover) {\n background: #64748b;\n}\n\n.custom-collapse-item :deep(.el-collapse-item__content) {\n background: #f8fafc;\n padding: 20px;\n border-radius: 0 0 8px 8px;\n border: 1px solid #e2e8f0;\n border-top: none;\n overflow-y: visible;\n}\n\n/* 更新日志特殊样式 */\n.update-log-item :deep(.el-collapse-item__content) {\n max-height: 220px;\n overflow-y: auto;\n}\n\n/* 隐藏更新日志内容区域的滚动条 */\n.update-log-item :deep(.el-collapse-item__content)::-webkit-scrollbar {\n display: none;\n}\n\n.update-log-item :deep(.el-collapse-item__content) {\n -ms-overflow-style: none; /* IE and Edge */\n scrollbar-width: none; /* Firefox */\n}\n\n.custom-collapse-item :deep(.el-collapse-item__wrap) {\n border: none;\n}\n\n.custom-collapse-item :deep(.el-collapse-item__arrow) {\n color: white;\n font-weight: bold;\n}\n\n/* 自定义滚动条样式 */\n.custom-collapse-item :deep(.el-collapse-item__content)::-webkit-scrollbar {\n width: 6px;\n}\n\n.custom-collapse-item :deep(.el-collapse-item__content)::-webkit-scrollbar-track {\n background: #f1f5f9;\n border-radius: 3px;\n}\n\n.custom-collapse-item :deep(.el-collapse-item__content)::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 3px;\n}\n\n.custom-collapse-item :deep(.el-collapse-item__content)::-webkit-scrollbar-thumb:hover {\n background: #94a3b8;\n}\n\n.stat-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 0;\n border-bottom: 1px solid #f0f0f0;\n}\n\n.stat-item:last-child {\n border-bottom: none;\n}\n\n.stat-label {\n font-weight: 500;\n color: #666;\n}\n\n.stat-value {\n font-weight: 600;\n color: #409eff;\n font-size: 18px;\n}\n\nh3 {\n margin-bottom: 16px;\n color: #333;\n font-size: 20px;\n font-weight: 600;\n}\n\np {\n margin-bottom: 16px;\n line-height: 1.6;\n color: #666;\n}\n\nul {\n padding-left: 20px;\n margin-bottom: 16px;\n}\n\nli {\n margin-bottom: 8px;\n color: #666;\n}\n</style> "}}}} |