* feat: improve model configuration workflows
Add model catalog browsing, provider registry with form validation,
model fetch/test dialogs, and enhanced model management UI.
- Add model catalog API and catalog-dialog component for browsing saved models
- Add provider-registry with auto-populated form fields per provider
- Add provider-combobox, fetch-models-dialog, test-model-dialog components
- Add model-validation for provider-aware model ID validation
- Add command and popover UI components
- Enhance edit-model-sheet with tool schema transform support
- Add anthropic to protocolMetaByName for correct default API base
- Apply NormalizeBaseURL to anthropic provider for consistent URL handling
- Add i18n keys for new model management features (en/zh)
* fix(web): prevent auto-fetch when API key is missing in fetch models dialog
When a provider requires an API key but none is set, the dialog now shows
the warning without triggering a doomed fetch attempt. Fetch is deferred
until the user provides a key.
* 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.
* feat(web,api): test connection with real connectivity verification and unsaved form values
Add POST /api/models/test-inline endpoint that performs actual network
probes (GET /models) instead of just checking config. Frontend Test
Connection now uses current form values (not saved state) and is
available in both Add and Edit model flows.
* style(web): apply linter formatting across model config components
Normalize quote style, import ordering, and class name ordering as
reported by the project linter.
* fix(web,api): fix edit test connection false negative and gate fetch for unsupported providers
- handleTestInlineModel now accepts optional model_index to fall back to stored credentials when api_key is empty, fixing false negatives when testing edited models
- Add supportsFetch to provider registry and FETCHABLE_PROVIDER_KEYS derived set
- Gate Fetch Models button to only show for OpenAI-compatible and Ollama providers
- Add backend guard in handleFetchModels to reject unsupported providers with clear error
* fix: address review feedback on model config workflow
- Send explicit {} for empty extra_body/custom_headers fields so the
backend clears stored values instead of preserving them
- Merge backend provider_options with frontend PROVIDERS registry so
the provider picker reflects backend-supported providers and policy
fields (create_allowed, default_auth_method, auth_method_locked)
- Render provider combobox popover inside the sheet scroll container
to fix wheel events scrolling the sheet instead of the provider list
* feat(web,api): add provider selection, model form foundation, and validation
Split from PR #2752 (part 1 of 3).
Backend:
- CRUD model endpoints (list/add/update/delete/set-default)
- Provider metadata with default API bases and model provider options
- Model ID validation and normalization
- Anthropic default API base normalization
Frontend:
- Provider registry with metadata, labels, icons, and aliases
- Provider combobox with backend option merging
- Model field validation with provider-aware checks
- Redesigned add/edit model sheets with provider selection
- Dynamic imports for fetch/catalog/test dialogs (coming in PR2/PR3)
- i18n support for model configuration UI
72 lines
2.1 KiB
TypeScript
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.model_name}
|
|
model={model}
|
|
onEdit={onEdit}
|
|
onSetDefault={onSetDefault}
|
|
onDelete={onDelete}
|
|
settingDefault={settingDefaultIndex === model.index}
|
|
/>
|
|
))}
|
|
</div>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|