refactor: support explicit provider field in model list entries (#2609)

* refactor: support explicit model list providers

* fix(web): preserve explicit model providers

* fix(web): preserve legacy provider prefixes on model updates

fix(models): normalize explicit provider-prefixed ids

fix(api): preserve legacy model updates across providers

fix(agent): preserve config identity for explicit provider refs

* fix ci
This commit is contained in:
lxowalle
2026-04-22 11:28:47 +08:00
committed by GitHub
parent 3316ee6923
commit 77b0c43392
42 changed files with 1559 additions and 441 deletions
@@ -24,6 +24,7 @@ import { Textarea } from "@/components/ui/textarea"
interface AddForm {
modelName: string
provider: string
model: string
apiBase: string
apiKey: string
@@ -41,6 +42,7 @@ interface AddForm {
const EMPTY_ADD_FORM: AddForm = {
modelName: "",
provider: "",
model: "",
apiBase: "",
apiKey: "",
@@ -119,9 +121,11 @@ export function AddModelSheet({
setServerError("")
try {
const modelName = form.modelName.trim()
const provider = form.provider.trim()
const modelId = form.model.trim()
await addModel({
model_name: modelName,
provider: provider || undefined,
model: modelId,
api_base: form.apiBase.trim() || undefined,
api_key: form.apiKey.trim() || undefined,
@@ -186,6 +190,17 @@ export function AddModelSheet({
)}
</Field>
<Field
label={t("models.field.provider")}
hint={t("models.field.providerHint")}
>
<Input
value={form.provider}
onChange={setField("provider")}
placeholder={t("models.field.providerPlaceholder")}
/>
</Field>
<Field
label={t("models.add.modelId")}
hint={t("models.add.modelIdHint")}
@@ -23,6 +23,8 @@ import {
import { Textarea } from "@/components/ui/textarea"
interface EditForm {
provider: string
modelId: string
apiKey: string
apiBase: string
proxy: string
@@ -52,6 +54,8 @@ export function EditModelSheet({
}: EditModelSheetProps) {
const { t } = useTranslation()
const [form, setForm] = useState<EditForm>({
provider: "",
modelId: "",
apiKey: "",
apiBase: "",
proxy: "",
@@ -72,6 +76,8 @@ export function EditModelSheet({
useEffect(() => {
if (model) {
setForm({
provider: model.provider ?? "",
modelId: model.model,
apiKey: "",
apiBase: model.api_base ?? "",
proxy: model.proxy ?? "",
@@ -103,12 +109,17 @@ export function EditModelSheet({
const handleSave = async () => {
if (!model) return
if (!form.modelId.trim()) {
setError(t("models.add.errorRequired"))
return
}
setSaving(true)
setError("")
try {
await updateModel(model.index, {
model_name: model.model_name,
model: model.model,
provider: form.provider.trim(),
model: form.modelId.trim(),
api_base: form.apiBase || undefined,
api_key: form.apiKey || undefined,
proxy: form.proxy || undefined,
@@ -166,6 +177,29 @@ export function EditModelSheet({
<div className="min-h-0 flex-1 overflow-y-auto">
<div className="space-y-5 px-6 py-5">
<Field
label={t("models.field.provider")}
hint={t("models.field.providerHint")}
>
<Input
value={form.provider}
onChange={setField("provider")}
placeholder={t("models.field.providerPlaceholder")}
/>
</Field>
<Field
label={t("models.add.modelId")}
hint={t("models.add.modelIdHint")}
>
<Input
value={form.modelId}
onChange={setField("modelId")}
placeholder={t("models.add.modelIdPlaceholder")}
className="font-mono text-sm"
/>
</Field>
{!isOAuth && (
<Field
label={t("models.field.apiKey")}
@@ -20,19 +20,28 @@ const PROVIDER_PRIORITY: Record<string, number> = {
zhipu: 4,
deepseek: 5,
openrouter: 6,
qwen: 7,
moonshot: 8,
groq: 9,
"github-copilot": 10,
antigravity: 11,
nvidia: 12,
cerebras: 13,
shengsuanyun: 14,
ollama: 15,
vllm: 16,
mistral: 17,
avian: 18,
mimo: 19,
"qwen-portal": 7,
"qwen-intl": 8,
moonshot: 9,
groq: 10,
"github-copilot": 11,
antigravity: 12,
nvidia: 13,
cerebras: 14,
shengsuanyun: 15,
venice: 16,
vivgrid: 17,
minimax: 18,
longcat: 19,
modelscope: 20,
mistral: 21,
avian: 22,
azure: 23,
ollama: 24,
vllm: 25,
lmstudio: 26,
zai: 27,
mimo: 28,
}
interface ProviderGroup {
@@ -95,10 +104,10 @@ export function ModelsPage() {
const grouped: Record<string, { label: string; models: ModelInfo[] }> = {}
for (const model of models) {
const providerKey = getProviderKey(model.model)
const providerKey = getProviderKey(model.provider)
if (!grouped[providerKey]) {
grouped[providerKey] = {
label: getProviderLabel(model.model),
label: getProviderLabel(model.provider),
models: [],
}
}
@@ -3,9 +3,11 @@ import { useMemo, useState } from "react"
const PROVIDER_ICON_SLUGS: Record<string, string> = {
openai: "openai",
anthropic: "anthropic",
azure: "microsoftazure",
gemini: "googlegemini",
deepseek: "deepseek",
qwen: "alibabacloud",
"qwen-portal": "alibabacloud",
"qwen-intl": "alibabacloud",
groq: "groq",
openrouter: "openrouter",
nvidia: "nvidia",
@@ -20,9 +22,11 @@ const PROVIDER_ICON_SLUGS: Record<string, string> = {
const PROVIDER_DOMAINS: Record<string, string> = {
openai: "openai.com",
anthropic: "anthropic.com",
azure: "azure.com",
gemini: "gemini.google.com",
deepseek: "deepseek.com",
qwen: "qwenlm.ai",
"qwen-portal": "qwenlm.ai",
"qwen-intl": "alibabacloud.com",
moonshot: "moonshot.ai",
groq: "groq.com",
openrouter: "openrouter.ai",
@@ -33,11 +37,18 @@ const PROVIDER_DOMAINS: Record<string, string> = {
antigravity: "antigravity.google",
"github-copilot": "github.com",
ollama: "ollama.com",
lmstudio: "lmstudio.ai",
mistral: "mistral.ai",
avian: "avian.io",
vllm: "vllm.ai",
zhipu: "zhipuai.cn",
zai: "z.ai",
mimo: "xiaomi.com",
venice: "venice.ai",
vivgrid: "vivgrid.com",
minimax: "minimaxi.com",
longcat: "longcat.chat",
modelscope: "modelscope.cn",
}
interface ProviderIconProps {
@@ -1,9 +1,11 @@
const PROVIDER_LABELS: Record<string, string> = {
openai: "OpenAI",
anthropic: "Anthropic",
azure: "Azure OpenAI",
gemini: "Google Gemini",
deepseek: "DeepSeek",
qwen: "Qwen (阿里云)",
"qwen-portal": "Qwen (阿里云)",
"qwen-intl": "Qwen International",
moonshot: "Moonshot (月之暗面)",
groq: "Groq",
openrouter: "OpenRouter",
@@ -14,21 +16,37 @@ const PROVIDER_LABELS: Record<string, string> = {
antigravity: "Google Code Assist",
"github-copilot": "GitHub Copilot",
ollama: "Ollama (local)",
lmstudio: "LM Studio (local)",
mistral: "Mistral AI",
avian: "Avian",
vllm: "VLLM (local)",
zhipu: "Zhipu AI (智谱)",
zai: "Z.ai",
mimo: "Xiaomi MiMo",
venice: "Venice AI",
vivgrid: "Vivgrid",
minimax: "MiniMax",
longcat: "LongCat",
modelscope: "ModelScope (魔搭社区)",
}
export function getProviderKey(model: string): string {
return model.split("/")[0]
const PROVIDER_ALIASES: Record<string, string> = {
qwen: "qwen-portal",
"qwen-international": "qwen-intl",
"dashscope-intl": "qwen-intl",
"z.ai": "zai",
"z-ai": "zai",
google: "gemini",
"google-antigravity": "antigravity",
}
export function getProviderLabel(model: string): string {
const prefix = getProviderKey(model)
const labels: Record<string, string> = {
...PROVIDER_LABELS,
}
return labels[prefix] ?? prefix
export function getProviderKey(provider?: string): string {
const normalized = provider?.trim().toLowerCase()
if (!normalized) return "openai"
return PROVIDER_ALIASES[normalized] ?? normalized
}
export function getProviderLabel(provider?: string): string {
const prefix = getProviderKey(provider)
return PROVIDER_LABELS[prefix] ?? prefix
}