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:
parent
134779b266
commit
26612790fe
7 changed files with 21 additions and 20 deletions
|
|
@ -16,7 +16,7 @@ import (
|
||||||
const modelProbeTimeout = 800 * time.Millisecond
|
const modelProbeTimeout = 800 * time.Millisecond
|
||||||
|
|
||||||
const (
|
const (
|
||||||
modelStatusConfigured = "configured"
|
modelStatusAvailable = "available"
|
||||||
modelStatusUnconfigured = "unconfigured"
|
modelStatusUnconfigured = "unconfigured"
|
||||||
modelStatusUnreachable = "unreachable"
|
modelStatusUnreachable = "unreachable"
|
||||||
)
|
)
|
||||||
|
|
@ -60,11 +60,11 @@ func modelConfigurationStatus(m *config.ModelConfig) modelConfigurationSummary {
|
||||||
}
|
}
|
||||||
if requiresRuntimeProbe(m) {
|
if requiresRuntimeProbe(m) {
|
||||||
if probeLocalModelAvailability(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: false, Status: modelStatusUnreachable}
|
||||||
}
|
}
|
||||||
return modelConfigurationSummary{Available: true, Status: modelStatusConfigured}
|
return modelConfigurationSummary{Available: true, Status: modelStatusAvailable}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requiresRuntimeProbe(m *config.ModelConfig) bool {
|
func requiresRuntimeProbe(m *config.ModelConfig) bool {
|
||||||
|
|
|
||||||
|
|
@ -138,17 +138,17 @@ func TestHandleListModels_AvailabilityUsesRuntimeProbesForLocalModels(t *testing
|
||||||
if gotStatus["openai-oauth"] != modelStatusUnconfigured {
|
if gotStatus["openai-oauth"] != modelStatusUnconfigured {
|
||||||
t.Fatalf("openai oauth model status = %q, want %q", gotStatus["openai-oauth"], modelStatusUnconfigured)
|
t.Fatalf("openai oauth model status = %q, want %q", gotStatus["openai-oauth"], modelStatusUnconfigured)
|
||||||
}
|
}
|
||||||
if gotStatus["vllm-local"] != modelStatusConfigured {
|
if gotStatus["vllm-local"] != modelStatusAvailable {
|
||||||
t.Fatalf("vllm local model status = %q, want %q", gotStatus["vllm-local"], modelStatusConfigured)
|
t.Fatalf("vllm local model status = %q, want %q", gotStatus["vllm-local"], modelStatusAvailable)
|
||||||
}
|
}
|
||||||
if gotStatus["ollama-default"] != modelStatusConfigured {
|
if gotStatus["ollama-default"] != modelStatusAvailable {
|
||||||
t.Fatalf("ollama default model status = %q, want %q", gotStatus["ollama-default"], modelStatusConfigured)
|
t.Fatalf("ollama default model status = %q, want %q", gotStatus["ollama-default"], modelStatusAvailable)
|
||||||
}
|
}
|
||||||
if gotStatus["vllm-remote"] != modelStatusConfigured {
|
if gotStatus["vllm-remote"] != modelStatusAvailable {
|
||||||
t.Fatalf("remote vllm model status = %q, want %q", gotStatus["vllm-remote"], modelStatusConfigured)
|
t.Fatalf("remote vllm model status = %q, want %q", gotStatus["vllm-remote"], modelStatusAvailable)
|
||||||
}
|
}
|
||||||
if 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"], modelStatusConfigured)
|
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|" {
|
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)
|
t.Fatalf("openAI probes = %#v, want only local vllm probe", openAIProbes)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export interface ModelInfo {
|
||||||
extra_body?: Record<string, unknown>
|
extra_body?: Record<string, unknown>
|
||||||
// Meta
|
// Meta
|
||||||
available: boolean
|
available: boolean
|
||||||
status?: "configured" | "unconfigured" | "unreachable"
|
status?: "available" | "unconfigured" | "unreachable"
|
||||||
is_default: boolean
|
is_default: boolean
|
||||||
is_virtual: boolean
|
is_virtual: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,10 @@ export function EditModelSheet({
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOAuth = model?.auth_method === "oauth"
|
const isOAuth = model?.auth_method === "oauth"
|
||||||
const apiKeyPlaceholder = model?.available
|
const hasSavedAPIKey = Boolean(model?.api_key)
|
||||||
|
const apiKeyPlaceholder = hasSavedAPIKey
|
||||||
? maskedSecretPlaceholder(
|
? maskedSecretPlaceholder(
|
||||||
model.api_key,
|
model?.api_key ?? "",
|
||||||
t("models.field.apiKeyPlaceholderSet"),
|
t("models.field.apiKeyPlaceholderSet"),
|
||||||
)
|
)
|
||||||
: t("models.field.apiKeyPlaceholder")
|
: t("models.field.apiKeyPlaceholder")
|
||||||
|
|
@ -161,7 +162,7 @@ export function EditModelSheet({
|
||||||
<Field
|
<Field
|
||||||
label={t("models.field.apiKey")}
|
label={t("models.field.apiKey")}
|
||||||
hint={
|
hint={
|
||||||
model?.available ? t("models.edit.apiKeyHint") : undefined
|
hasSavedAPIKey ? t("models.edit.apiKeyHint") : undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<KeyInput
|
<KeyInput
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export function ModelCard({
|
||||||
}: ModelCardProps) {
|
}: ModelCardProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const isOAuth = model.auth_method === "oauth"
|
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 statusLabel = t(`models.status.${status}`)
|
||||||
const canSetDefault =
|
const canSetDefault =
|
||||||
model.available && !model.is_default && !model.is_virtual
|
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",
|
"mt-0.5 h-2 w-2 shrink-0 rounded-full",
|
||||||
model.is_default
|
model.is_default
|
||||||
? "bg-green-400 shadow-[0_0_0_2px_rgba(74,222,128,0.35)]"
|
? "bg-green-400 shadow-[0_0_0_2px_rgba(74,222,128,0.35)]"
|
||||||
: status === "configured"
|
: status === "available"
|
||||||
? "bg-green-500"
|
? "bg-green-500"
|
||||||
: status === "unreachable"
|
: status === "unreachable"
|
||||||
? "bg-amber-500"
|
? "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">
|
<span className="text-muted-foreground bg-muted rounded px-1.5 py-0.5 text-[10px] font-medium">
|
||||||
OAuth
|
OAuth
|
||||||
</span>
|
</span>
|
||||||
) : model.api_key ? (
|
) : status === "available" && model.api_key ? (
|
||||||
<span className="text-muted-foreground/70 flex items-center gap-1 font-mono text-[11px]">
|
<span className="text-muted-foreground/70 flex items-center gap-1 font-mono text-[11px]">
|
||||||
<IconKey className="size-3" />
|
<IconKey className="size-3" />
|
||||||
{model.api_key}
|
{model.api_key}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@
|
||||||
"noDefaultHintPrefix": "No default model set yet. Click",
|
"noDefaultHintPrefix": "No default model set yet. Click",
|
||||||
"noDefaultHintSuffix": "to set one.",
|
"noDefaultHintSuffix": "to set one.",
|
||||||
"status": {
|
"status": {
|
||||||
"configured": "Configured",
|
"available": "Available",
|
||||||
"unconfigured": "Not configured",
|
"unconfigured": "Not configured",
|
||||||
"unreachable": "Service unreachable"
|
"unreachable": "Service unreachable"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@
|
||||||
"noDefaultHintPrefix": "尚未设置默认模型,点击",
|
"noDefaultHintPrefix": "尚未设置默认模型,点击",
|
||||||
"noDefaultHintSuffix": "设为默认。",
|
"noDefaultHintSuffix": "设为默认。",
|
||||||
"status": {
|
"status": {
|
||||||
"configured": "已配置",
|
"available": "可用",
|
||||||
"unconfigured": "未配置",
|
"unconfigured": "未配置",
|
||||||
"unreachable": "服务不可达"
|
"unreachable": "服务不可达"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue