fix(web): add credential warning for catalog imports from remote providers

When importing models from a catalog entry whose provider requires an API
key, a yellow warning banner now informs users that credentials will need
to be configured after import.
This commit is contained in:
SiYue-ZO 2026-05-06 18:11:38 +08:00
parent b4746b9b44
commit 9cb14eef52
3 changed files with 43 additions and 32 deletions

View file

@ -28,6 +28,7 @@ import { Input } from "@/components/ui/input"
import { refreshGatewayState } from "@/store/gateway" import { refreshGatewayState } from "@/store/gateway"
import { getProviderLabel } from "./provider-label" import { getProviderLabel } from "./provider-label"
import { PROVIDER_MAP } from "./provider-registry"
interface CatalogDialogProps { interface CatalogDialogProps {
open: boolean open: boolean
@ -89,7 +90,9 @@ export function CatalogDialog({
const next = new Map(prev) const next = new Map(prev)
const current = next.get(catalogId) || new Set() const current = next.get(catalogId) || new Set()
const filtered = filter const filtered = filter
? models.filter((m) => m.id.toLowerCase().includes(filter.toLowerCase())) ? models.filter((m) =>
m.id.toLowerCase().includes(filter.toLowerCase()),
)
: models : models
if (filtered.every((m) => current.has(m.id))) { if (filtered.every((m) => current.has(m.id))) {
next.set(catalogId, new Set()) next.set(catalogId, new Set())
@ -159,14 +162,14 @@ export function CatalogDialog({
<div className="space-y-3"> <div className="space-y-3">
{loading && ( {loading && (
<div className="flex items-center justify-center gap-2 py-8 text-muted-foreground"> <div className="text-muted-foreground flex items-center justify-center gap-2 py-8">
<IconLoader2 className="size-5 animate-spin" /> <IconLoader2 className="size-5 animate-spin" />
<span>{t("models.catalog.loading")}</span> <span>{t("models.catalog.loading")}</span>
</div> </div>
)} )}
{!loading && entries.length === 0 && ( {!loading && entries.length === 0 && (
<div className="py-8 text-center text-sm text-muted-foreground"> <div className="text-muted-foreground py-8 text-center text-sm">
{t("models.catalog.empty")} {t("models.catalog.empty")}
</div> </div>
)} )}
@ -189,27 +192,27 @@ export function CatalogDialog({
return ( return (
<div <div
key={entry.id} key={entry.id}
className="rounded-lg border bg-card text-card-foreground" className="bg-card text-card-foreground rounded-lg border"
> >
<div <div
className="flex cursor-pointer items-center gap-3 px-3 py-2.5 hover:bg-accent/50" className="hover:bg-accent/50 flex cursor-pointer items-center gap-3 px-3 py-2.5"
onClick={() => toggleExpand(entry.id)} onClick={() => toggleExpand(entry.id)}
> >
{isExpanded ? ( {isExpanded ? (
<IconChevronDown className="size-4 shrink-0 text-muted-foreground" /> <IconChevronDown className="text-muted-foreground size-4 shrink-0" />
) : ( ) : (
<IconChevronRight className="size-4 shrink-0 text-muted-foreground" /> <IconChevronRight className="text-muted-foreground size-4 shrink-0" />
)} )}
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium"> <span className="text-sm font-medium">
{getProviderLabel(entry.provider)} {getProviderLabel(entry.provider)}
</span> </span>
<span className="font-mono text-xs text-muted-foreground"> <span className="text-muted-foreground font-mono text-xs">
{entry.api_key_mask} {entry.api_key_mask}
</span> </span>
</div> </div>
<div className="flex items-center gap-2 text-xs text-muted-foreground"> <div className="text-muted-foreground flex items-center gap-2 text-xs">
<span> <span>
{entry.models.length} {t("models.catalog.models")} {entry.models.length} {t("models.catalog.models")}
</span> </span>
@ -234,7 +237,7 @@ export function CatalogDialog({
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="size-7 text-destructive" className="text-destructive size-7"
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
handleDelete(entry.id) handleDelete(entry.id)
@ -248,7 +251,7 @@ export function CatalogDialog({
{isExpanded && ( {isExpanded && (
<div className="border-t px-3 py-2"> <div className="border-t px-3 py-2">
<div className="mb-1.5 flex items-center justify-between text-xs text-muted-foreground"> <div className="text-muted-foreground mb-1.5 flex items-center justify-between text-xs">
<span> <span>
{t("models.catalog.found", { {t("models.catalog.found", {
count: filteredModels.length, count: filteredModels.length,
@ -259,9 +262,7 @@ export function CatalogDialog({
onClick={() => toggleAll(entry.id, entry.models)} onClick={() => toggleAll(entry.id, entry.models)}
className="text-primary hover:underline" className="text-primary hover:underline"
> >
{filteredModels.every((m) => {filteredModels.every((m) => entrySelected.has(m.id))
entrySelected.has(m.id),
)
? t("models.catalog.deselectAll") ? t("models.catalog.deselectAll")
: t("models.catalog.selectAll")} : t("models.catalog.selectAll")}
</button> </button>
@ -270,7 +271,7 @@ export function CatalogDialog({
{filteredModels.map((m) => ( {filteredModels.map((m) => (
<label <label
key={m.id} key={m.id}
className="flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1 text-sm hover:bg-accent" className="hover:bg-accent flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1 text-sm"
> >
<input <input
type="checkbox" type="checkbox"
@ -280,7 +281,7 @@ export function CatalogDialog({
/> />
<span className="font-mono text-xs">{m.id}</span> <span className="font-mono text-xs">{m.id}</span>
{m.owned_by && ( {m.owned_by && (
<span className="ml-auto text-xs text-muted-foreground"> <span className="text-muted-foreground ml-auto text-xs">
{m.owned_by} {m.owned_by}
</span> </span>
)} )}
@ -288,7 +289,14 @@ export function CatalogDialog({
))} ))}
</div> </div>
{entrySelected.size > 0 && ( {entrySelected.size > 0 && (
<div className="mt-2 flex justify-end"> <div className="mt-2 space-y-2">
{PROVIDER_MAP.get(entry.provider)?.requiresApiKey !==
false && (
<div className="rounded-lg border border-yellow-500/30 bg-yellow-500/10 p-2 text-xs text-yellow-700 dark:text-yellow-400">
{t("models.catalog.needApiKey")}
</div>
)}
<div className="flex justify-end">
<Button <Button
size="sm" size="sm"
onClick={() => handleAddSelected(entry)} onClick={() => handleAddSelected(entry)}
@ -302,6 +310,7 @@ export function CatalogDialog({
})} })}
</Button> </Button>
</div> </div>
</div>
)} )}
</div> </div>
)} )}

View file

@ -345,7 +345,8 @@
"selectAll": "Select All", "selectAll": "Select All",
"deselectAll": "Deselect All", "deselectAll": "Deselect All",
"addSelected": "Add {{count}} Selected", "addSelected": "Add {{count}} Selected",
"addSuccess": "Added {{count}} model(s) to configuration." "addSuccess": "Added {{count}} model(s) to configuration.",
"needApiKey": "These models require an API key. You'll need to configure credentials after import."
}, },
"test": { "test": {
"title": "Test Model Connectivity", "title": "Test Model Connectivity",

View file

@ -347,7 +347,8 @@
"selectAll": "全选", "selectAll": "全选",
"deselectAll": "取消全选", "deselectAll": "取消全选",
"addSelected": "添加 {{count}} 个选中模型", "addSelected": "添加 {{count}} 个选中模型",
"addSuccess": "已添加 {{count}} 个模型到配置中。" "addSuccess": "已添加 {{count}} 个模型到配置中。",
"needApiKey": "这些模型需要 API Key。导入后需要配置凭证才能使用。"
}, },
"test": { "test": {
"title": "测试模型连通性", "title": "测试模型连通性",