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:
parent
b4746b9b44
commit
9cb14eef52
3 changed files with 43 additions and 32 deletions
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,8 @@
|
||||||
"selectAll": "全选",
|
"selectAll": "全选",
|
||||||
"deselectAll": "取消全选",
|
"deselectAll": "取消全选",
|
||||||
"addSelected": "添加 {{count}} 个选中模型",
|
"addSelected": "添加 {{count}} 个选中模型",
|
||||||
"addSuccess": "已添加 {{count}} 个模型到配置中。"
|
"addSuccess": "已添加 {{count}} 个模型到配置中。",
|
||||||
|
"needApiKey": "这些模型需要 API Key。导入后需要配置凭证才能使用。"
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"title": "测试模型连通性",
|
"title": "测试模型连通性",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue