fix(web): clarify unreachable model status and wording

- Show unreachable status in model cards instead of API key preview when service is down

- Keep API key placeholder preview in model settings whenever an API key is already saved

- Rename model status wording from configured to available across backend, frontend, and i18n

- Update backend model status tests to match renamed status semantics
This commit is contained in:
lc6464 2026-03-31 09:46:17 +08:00
parent 134779b266
commit 26612790fe
No known key found for this signature in database
GPG key ID: 53C61B42FEC71D6D
7 changed files with 21 additions and 20 deletions

View file

@ -16,7 +16,7 @@ import (
const modelProbeTimeout = 800 * time.Millisecond
const (
modelStatusConfigured = "configured"
modelStatusAvailable = "available"
modelStatusUnconfigured = "unconfigured"
modelStatusUnreachable = "unreachable"
)
@ -60,11 +60,11 @@ func modelConfigurationStatus(m *config.ModelConfig) modelConfigurationSummary {
}
if requiresRuntimeProbe(m) {
if probeLocalModelAvailability(m) {
return modelConfigurationSummary{Available: true, Status: modelStatusConfigured}
return modelConfigurationSummary{Available: true, Status: modelStatusAvailable}
}
return modelConfigurationSummary{Available: false, Status: modelStatusUnreachable}
}
return modelConfigurationSummary{Available: true, Status: modelStatusConfigured}
return modelConfigurationSummary{Available: true, Status: modelStatusAvailable}
}
func requiresRuntimeProbe(m *config.ModelConfig) bool {

View file

@ -138,17 +138,17 @@ func TestHandleListModels_AvailabilityUsesRuntimeProbesForLocalModels(t *testing
if gotStatus["openai-oauth"] != modelStatusUnconfigured {
t.Fatalf("openai oauth model status = %q, want %q", gotStatus["openai-oauth"], modelStatusUnconfigured)
}
if gotStatus["vllm-local"] != modelStatusConfigured {
t.Fatalf("vllm local model status = %q, want %q", gotStatus["vllm-local"], modelStatusConfigured)
if gotStatus["vllm-local"] != modelStatusAvailable {
t.Fatalf("vllm local model status = %q, want %q", gotStatus["vllm-local"], modelStatusAvailable)
}
if gotStatus["ollama-default"] != modelStatusConfigured {
t.Fatalf("ollama default model status = %q, want %q", gotStatus["ollama-default"], modelStatusConfigured)
if gotStatus["ollama-default"] != modelStatusAvailable {
t.Fatalf("ollama default model status = %q, want %q", gotStatus["ollama-default"], modelStatusAvailable)
}
if gotStatus["vllm-remote"] != modelStatusConfigured {
t.Fatalf("remote vllm model status = %q, want %q", gotStatus["vllm-remote"], modelStatusConfigured)
if gotStatus["vllm-remote"] != modelStatusAvailable {
t.Fatalf("remote vllm model status = %q, want %q", gotStatus["vllm-remote"], modelStatusAvailable)
}
if gotStatus["copilot-gpt-5.4"] != modelStatusConfigured {
t.Fatalf("copilot model status = %q, want %q", gotStatus["copilot-gpt-5.4"], modelStatusConfigured)
if gotStatus["copilot-gpt-5.4"] != modelStatusAvailable {
t.Fatalf("copilot model status = %q, want %q", gotStatus["copilot-gpt-5.4"], modelStatusAvailable)
}
if len(openAIProbes) != 1 || openAIProbes[0] != "http://127.0.0.1:8000/v1|custom-model|" {
t.Fatalf("openAI probes = %#v, want only local vllm probe", openAIProbes)

View file

@ -21,7 +21,7 @@ export interface ModelInfo {
extra_body?: Record<string, unknown>
// Meta
available: boolean
status?: "configured" | "unconfigured" | "unreachable"
status?: "available" | "unconfigured" | "unreachable"
is_default: boolean
is_virtual: boolean
}

View file

@ -133,9 +133,10 @@ export function EditModelSheet({
}
const isOAuth = model?.auth_method === "oauth"
const apiKeyPlaceholder = model?.available
const hasSavedAPIKey = Boolean(model?.api_key)
const apiKeyPlaceholder = hasSavedAPIKey
? maskedSecretPlaceholder(
model.api_key,
model?.api_key ?? "",
t("models.field.apiKeyPlaceholderSet"),
)
: t("models.field.apiKeyPlaceholder")
@ -161,7 +162,7 @@ export function EditModelSheet({
<Field
label={t("models.field.apiKey")}
hint={
model?.available ? t("models.edit.apiKeyHint") : undefined
hasSavedAPIKey ? t("models.edit.apiKeyHint") : undefined
}
>
<KeyInput

View file

@ -28,7 +28,7 @@ export function ModelCard({
}: ModelCardProps) {
const { t } = useTranslation()
const isOAuth = model.auth_method === "oauth"
const status = model.status ?? (model.available ? "configured" : "unconfigured")
const status = model.status ?? (model.available ? "available" : "unconfigured")
const statusLabel = t(`models.status.${status}`)
const canSetDefault =
model.available && !model.is_default && !model.is_virtual
@ -49,7 +49,7 @@ export function ModelCard({
"mt-0.5 h-2 w-2 shrink-0 rounded-full",
model.is_default
? "bg-green-400 shadow-[0_0_0_2px_rgba(74,222,128,0.35)]"
: status === "configured"
: status === "available"
? "bg-green-500"
: status === "unreachable"
? "bg-amber-500"
@ -127,7 +127,7 @@ export function ModelCard({
<span className="text-muted-foreground bg-muted rounded px-1.5 py-0.5 text-[10px] font-medium">
OAuth
</span>
) : model.api_key ? (
) : status === "available" && model.api_key ? (
<span className="text-muted-foreground/70 flex items-center gap-1 font-mono text-[11px]">
<IconKey className="size-3" />
{model.api_key}

View file

@ -170,7 +170,7 @@
"noDefaultHintPrefix": "No default model set yet. Click",
"noDefaultHintSuffix": "to set one.",
"status": {
"configured": "Configured",
"available": "Available",
"unconfigured": "Not configured",
"unreachable": "Service unreachable"
},

View file

@ -170,7 +170,7 @@
"noDefaultHintPrefix": "尚未设置默认模型,点击",
"noDefaultHintSuffix": "设为默认。",
"status": {
"configured": "已配置",
"available": "可用",
"unconfigured": "未配置",
"unreachable": "服务不可达"
},