refine AI provider model layout

This commit is contained in:
tianma-if committed 2026-08-11 16:58:55 +08:00
1 parent 6cdd5b7a12
commit 1160db7b65
2 files changed
+66 -72

No files matched your search

@@ -168,21 +168,22 @@ export const AiModelCard = () => {
</Button>
</div>
<div className="grid gap-3">
{settings?.providers.map((item, index) => (
<AiProviderCard
key={item.id}
provider={item}
defaultDisplayName={getDefaultProviderName(index)}
defaultModelId={settings.defaultModelId}
readOnly={readOnly}
onChanged={refresh}
/>
))}
{!settings?.providers.length ? (
<p className="rounded-lg border border-dashed p-5 text-center text-sm text-slate-500">{t("aiModel.noProviders")}</p>
) : null}
</div>
{settings?.providers.length ? (
<div className="divide-y border-y">
{settings.providers.map((item, index) => (
<AiProviderCard
key={item.id}
provider={item}
defaultDisplayName={getDefaultProviderName(index)}
defaultModelId={settings.defaultModelId}
readOnly={readOnly}
onChanged={refresh}
/>
))}
</div>
) : (
<p className="rounded-lg border border-dashed p-5 text-center text-sm text-slate-500">{t("aiModel.noProviders")}</p>
)}
</section>
<Dialog open={showAdd} onOpenChange={handleAddDialogChange}>
@@ -157,15 +157,40 @@ export const AiProviderCard = ({ provider: saved, defaultDisplayName, defaultMod
};
return (
<section className="overflow-hidden rounded-lg border bg-white">
<div className="flex items-start justify-between gap-3 p-4">
<div className="min-w-0">
<h3 className="truncate text-sm font-semibold text-slate-900">{effectiveDisplayName}</h3>
<div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-slate-500">
<span className="rounded bg-slate-100 px-1.5 py-0.5 font-medium text-slate-600">{providerLabel}</span>
<span className="max-w-full truncate">{formatBaseUrl(saved.baseUrl)}</span>
<span aria-hidden="true">·</span>
<span>{t("aiModel.modelCount", { count: saved.models.length })}</span>
<section>
<div className="flex items-start justify-between gap-3 px-4 py-2.5">
<div className="min-w-0 flex-1">
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
<h3 className="truncate text-sm font-semibold text-slate-900">{effectiveDisplayName}</h3>
<span className="rounded bg-slate-100 px-1.5 py-0.5 text-xs font-medium text-slate-600">{providerLabel}</span>
<span className="max-w-full truncate text-xs text-slate-500">{formatBaseUrl(saved.baseUrl)}</span>
</div>
<div className="mt-1.5 flex min-w-0 flex-wrap items-center gap-1.5">
{saved.models.length ? saved.models.map((model) => (
<span key={model.id} className="inline-flex h-7 max-w-full min-w-0 items-center gap-1 rounded-md bg-slate-100/80 pl-2 pr-1 text-xs text-slate-700">
<span className="min-w-0 truncate font-medium">{model.displayName}</span>
{model.modelId !== model.displayName ? (
<span className="min-w-0 truncate text-slate-500">{model.modelId}</span>
) : null}
{model.id === defaultModelId ? (
<span className="shrink-0 text-[11px] font-medium text-emerald-700">{t("aiModel.defaultBadge")}</span>
) : null}
<Button
type="button"
variant="ghost"
size="icon"
className="h-5 w-5 shrink-0 text-slate-400 hover:bg-white hover:text-rose-600"
disabled={readOnly || deleteModelMutation.isPending}
onClick={() => deleteModel(model)}
aria-label={`${t("aiModel.removeModel")}: ${model.displayName}`}
title={t("aiModel.removeModel")}
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
</span>
)) : (
<span className="text-xs text-slate-400">{t("aiModel.noModels")}</span>
)}
</div>
</div>
<div className="flex shrink-0 items-center gap-2">
@@ -178,6 +203,17 @@ export const AiProviderCard = ({ provider: saved, defaultDisplayName, defaultMod
aria-label={t("aiModel.serviceEnabled")}
onCheckedChange={(checked) => toggleMutation.mutate(checked)}
/>
<Button
type="button"
variant="ghost"
size="icon"
disabled={readOnly}
onClick={() => setShowAddModel(true)}
aria-label={t("aiModel.addModel")}
title={t("aiModel.addModel")}
>
<Plus className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="button" variant="ghost" size="icon" disabled={cardBusy}>
@@ -198,54 +234,11 @@ export const AiProviderCard = ({ provider: saved, defaultDisplayName, defaultMod
</div>
</div>
<div className="grid gap-3 border-t bg-slate-50/40 p-4">
<div className="flex items-center justify-between gap-3">
<h4 className="text-sm font-semibold text-slate-900">{t("aiModel.modelsTitle")}</h4>
<Button type="button" variant="outline" size="sm" disabled={readOnly} onClick={() => setShowAddModel(true)}>
<Plus className="h-4 w-4" />{t("aiModel.addModel")}
</Button>
</div>
{saved.models.length ? (
<div className="divide-y overflow-hidden rounded-md border bg-white">
{saved.models.map((model) => (
<div key={model.id} className="group flex min-w-0 items-center justify-between gap-3 px-3 py-2.5">
<div className="min-w-0">
<div className="flex min-w-0 items-center gap-2">
<p className="truncate text-sm text-slate-800">{model.displayName}</p>
{model.id === defaultModelId ? (
<span className="shrink-0 rounded-full bg-emerald-50 px-2 py-0.5 text-[11px] font-medium text-emerald-700">
{t("aiModel.defaultBadge")}
</span>
) : null}
</div>
{model.modelId !== model.displayName ? <p className="mt-0.5 truncate text-xs text-slate-500">{model.modelId}</p> : null}
</div>
<Button
type="button"
variant="ghost"
size="icon"
className="h-8 w-8 shrink-0 text-slate-400 hover:text-rose-600"
disabled={readOnly || deleteModelMutation.isPending}
onClick={() => deleteModel(model)}
aria-label={t("aiModel.removeModel")}
title={t("aiModel.removeModel")}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
))}
</div>
) : (
<p className="rounded-md border border-dashed bg-white p-4 text-center text-xs text-slate-500">{t("aiModel.noModels")}</p>
)}
{cardError ? (
<p className="text-xs font-medium text-rose-600" role="alert">
{aiErrorMessage(cardError, t("aiModel.failed"), t("aiModel.encryptionKeyMissing"))}
</p>
) : null}
</div>
{cardError ? (
<p className="border-t px-4 py-3 text-xs font-medium text-rose-600" role="alert">
{aiErrorMessage(cardError, t("aiModel.failed"), t("aiModel.encryptionKeyMissing"))}
</p>
) : null}
<Dialog open={showConnection} onOpenChange={handleConnectionChange}>
<DialogContent className="max-h-[calc(100vh-2rem)] overflow-y-auto sm:max-w-2xl">