picoclaw/web/frontend/src/components/models/provider-section.tsx
wenjie f1a5f42c6d refactor(models): refactor models page into modular components and improve UX
- split /models route into dedicated components (page, provider section, card, add/edit sheets, delete dialog)
- add provider grouping/sorting, provider labels/icons, and a no-default hint in the models page
- add "Set as default model" toggle to add/edit flows with safer defaults
- introduce shared form helpers and new UI primitives (field, label, switch)
- update i18n strings (en/zh) for models and gateway header text usage
- apply minor UI polish (models nav icon, separator client directive)
2026-03-09 19:24:10 +08:00

72 lines
2.1 KiB
TypeScript

import { IconChevronDown } from "@tabler/icons-react"
import { useState } from "react"
import type { ModelInfo } from "@/api/models"
import { ModelCard } from "./model-card"
import { ProviderIcon } from "./provider-icon"
interface ProviderSectionProps {
provider: string
providerKey: string
models: ModelInfo[]
onEdit: (model: ModelInfo) => void
onSetDefault: (model: ModelInfo) => void
onDelete: (model: ModelInfo) => void
settingDefaultIndex: number | null
}
export function ProviderSection({
provider,
providerKey,
models,
onEdit,
onSetDefault,
onDelete,
settingDefaultIndex,
}: ProviderSectionProps) {
const [open, setOpen] = useState(true)
return (
<section className="my-8">
<button
type="button"
onClick={() => setOpen((v) => !v)}
className="mb-3 grid w-full grid-cols-[1fr_auto_1fr_auto] items-center gap-2 px-1 py-1.5 text-left"
aria-expanded={open}
>
<div className="border-border/40 border-t" />
<span className="text-foreground/80 text-center text-xs font-semibold tracking-wide uppercase">
<span className="bg-background inline-flex items-center gap-1.5 px-2">
<ProviderIcon providerKey={providerKey} providerLabel={provider} />
{provider}
</span>
</span>
<div className="border-border/40 border-t" />
<span className="flex justify-end">
<IconChevronDown
className={[
"text-muted-foreground size-4 transition-transform",
open ? "rotate-180" : "",
].join(" ")}
/>
</span>
</button>
{open && (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{models.map((model) => (
<ModelCard
key={model.index}
model={model}
onEdit={onEdit}
onSetDefault={onSetDefault}
onDelete={onDelete}
settingDefault={settingDefaultIndex === model.index}
/>
))}
</div>
)}
</section>
)
}