diff --git a/web/src/components/GlobalTaskProgressBar.vue b/web/src/components/GlobalTaskProgressBar.vue index d248a59..342f3cb 100644 --- a/web/src/components/GlobalTaskProgressBar.vue +++ b/web/src/components/GlobalTaskProgressBar.vue @@ -62,6 +62,16 @@ async function pollOnce() { localStorage.setItem("last_closed_task", task.finished_at || ""); }, }); + + // 触发分组数据刷新 + if (task.group_name && task.finished_at) { + appState.lastCompletedTask = { + groupName: task.group_name, + taskType: task.task_type, + finishedAt: task.finished_at, + }; + appState.groupDataRefreshTrigger++; + } } } return; diff --git a/web/src/components/keys/GroupFormModal.vue b/web/src/components/keys/GroupFormModal.vue index 2d6358e..287701d 100644 --- a/web/src/components/keys/GroupFormModal.vue +++ b/web/src/components/keys/GroupFormModal.vue @@ -2,18 +2,18 @@ import { keysApi } from "@/api/keys"; import { settingsApi } from "@/api/settings"; import type { Group, GroupConfigOption, UpstreamInfo } from "@/types/models"; -import { Add, Close, Remove } from "@vicons/ionicons5"; +import { Add, Close, HelpCircleOutline, Remove } from "@vicons/ionicons5"; import { NButton, NCard, - NCollapse, - NCollapseItem, NForm, NFormItem, + NIcon, NInput, NInputNumber, NModal, NSelect, + NTooltip, useMessage, type FormRules, } from "naive-ui"; @@ -27,6 +27,7 @@ interface Props { interface Emits { (e: "update:show", value: boolean): void; (e: "success", value: Group): void; + (e: "switchToGroup", groupId: number): void; } // 配置项类型 @@ -51,7 +52,7 @@ interface GroupFormData { display_name: string; description: string; upstreams: UpstreamInfo[]; - channel_type: "openai" | "gemini" | "anthropic"; + channel_type: "anthropic" | "gemini" | "openai"; sort: number; test_model: string; validation_endpoint: string; @@ -85,15 +86,21 @@ const configOptions = ref([]); const channelTypesFetched = ref(false); const configOptionsFetched = ref(false); +// 跟踪用户是否已手动修改过字段(仅在新增模式下使用) +const userModifiedFields = ref({ + test_model: false, + upstream: false, +}); + // 根据渠道类型动态生成占位符提示 const testModelPlaceholder = computed(() => { switch (formData.channel_type) { case "openai": - return "如:gpt-4.1-nano"; + return "gpt-4.1-nano"; case "gemini": - return "如:gemini-2.0-flash-lite"; + return "gemini-2.0-flash-lite"; case "anthropic": - return "如:claude-3-haiku-20240307"; + return "claude-3-haiku-20240307"; default: return "请输入模型名称"; } @@ -112,6 +119,19 @@ const upstreamPlaceholder = computed(() => { } }); +const validationEndpointPlaceholder = computed(() => { + switch (formData.channel_type) { + case "openai": + return "/v1/chat/completions"; + case "anthropic": + return "/v1/messages"; + case "gemini": + return ""; // Gemini 不显示此字段 + default: + return "请输入验证端点路径"; + } +}); + // 表单验证规则 const rules: FormRules = { name: [ @@ -169,21 +189,95 @@ watch( } ); +// 监听渠道类型变化,在新增模式下智能更新默认值 +watch( + () => formData.channel_type, + (_newChannelType, oldChannelType) => { + if (!props.group && oldChannelType) { + // 仅在新增模式且不是初始设置时处理 + // 检查测试模型是否应该更新(为空或是旧渠道类型的默认值) + if ( + !userModifiedFields.value.test_model || + formData.test_model === getOldDefaultTestModel(oldChannelType) + ) { + formData.test_model = testModelPlaceholder.value; + userModifiedFields.value.test_model = false; + } + + // 检查第一个上游地址是否应该更新 + if ( + formData.upstreams.length > 0 && + (!userModifiedFields.value.upstream || + formData.upstreams[0].url === getOldDefaultUpstream(oldChannelType)) + ) { + formData.upstreams[0].url = upstreamPlaceholder.value; + userModifiedFields.value.upstream = false; + } + } + } +); + +// 获取旧渠道类型的默认值(用于比较) +function getOldDefaultTestModel(channelType: string): string { + switch (channelType) { + case "openai": + return "gpt-4.1-nano"; + case "gemini": + return "gemini-2.0-flash-lite"; + case "anthropic": + return "claude-3-haiku-20240307"; + default: + return ""; + } +} + +function getOldDefaultUpstream(channelType: string): string { + switch (channelType) { + case "openai": + return "https://api.openai.com"; + case "gemini": + return "https://generativelanguage.googleapis.com"; + case "anthropic": + return "https://api.anthropic.com"; + default: + return ""; + } +} + // 重置表单 function resetForm() { + const isCreateMode = !props.group; + const defaultChannelType = "openai"; + + // 先设置渠道类型,这样 computed 属性能正确计算默认值 + formData.channel_type = defaultChannelType; + Object.assign(formData, { name: "", display_name: "", description: "", - upstreams: [{ url: "", weight: 1 }], - channel_type: "openai", + upstreams: [ + { + url: isCreateMode ? upstreamPlaceholder.value : "", + weight: 1, + }, + ], + channel_type: defaultChannelType, sort: 1, - test_model: "", + test_model: isCreateMode ? testModelPlaceholder.value : "", validation_endpoint: "", param_overrides: "", config: {}, configItems: [], }); + + // 重置用户修改状态追踪 + if (isCreateMode) { + userModifiedFields.value = { + test_model: false, + upstream: false, + }; + } } // 加载分组数据(编辑模式) @@ -326,6 +420,10 @@ async function handleSubmit() { } emit("success", res); + // 如果是新建模式,发出切换到新分组的事件 + if (!props.group?.id && res.id) { + emit("switchToGroup", res.id); + } handleClose(); } finally { loading.value = false; @@ -363,52 +461,156 @@ async function handleSubmit() {

基础信息

- - - + +
+ + + + - - - + + + + +
- - - + +
+ + + + - - - + + + + +
- - - + +
+ + + + - - - + + + + + +
+
+ + + @@ -417,7 +619,6 @@ async function handleSubmit() {

上游地址

- -
- - 权重 - -
+ +
+
+ +
+
+ 权重 + + + 负载均衡权重,数值越大被选中的概率越高。例如:权重为2的上游被选中的概率是权重为1的两倍 + +
+