Files
picoclaw/web/frontend/src/api/models.ts
T
LC 548dc15acd refactor(models): unify provider metadata around backend catalog (#2896)
* feat(models): unify provider metadata around backend catalog

- Move shared provider metadata and alias normalization into backend-owned provider catalog
- Expose display, fetch, auth, and default model metadata through /api/models provider_options
- Replace frontend static provider registry with catalog-driven selection, validation, grouping, and fallback rendering
- Treat provider default api_base as placeholder and effective fetch/test base while keep submitted api_base separate from derived defaults
- Add model page retry handling, touched locale updates, and provider metadata assertions in backend tests

* fix(models): canonicalize backend provider aliases and common models

* fix(models): restore deepseek common model recommendations
2026-05-20 11:50:34 +08:00

223 lines
5.0 KiB
TypeScript

import { launcherFetch } from "@/api/http"
import { refreshGatewayState } from "@/store/gateway"
// API client for model list management.
export interface ModelInfo {
index: number
model_name: string
provider?: string
model: string
api_base?: string
api_key: string
proxy?: string
auth_method?: string
// Advanced fields
connect_mode?: string
workspace?: string
rpm?: number
max_tokens_field?: string
request_timeout?: number
thinking_level?: string
tool_schema_transform?: string
streaming?: {
enabled?: boolean
}
extra_body?: Record<string, unknown>
custom_headers?: Record<string, string>
// Meta
enabled: boolean
available: boolean
status: "available" | "unconfigured" | "unreachable"
is_default: boolean
is_virtual: boolean
default_model_allowed?: boolean
}
export interface ModelProviderOption {
id: string
display_name?: string
icon_slug?: string
domain?: string
default_api_base: string
empty_api_key_allowed: boolean
create_allowed: boolean
default_model_allowed: boolean
supports_fetch?: boolean
default_auth_method?: string
auth_method_locked?: boolean
local?: boolean
priority?: number
common_models?: string[]
aliases?: string[]
}
interface ModelsListResponse {
models: ModelInfo[]
total: number
default_model: string
provider_options: ModelProviderOption[]
}
interface ModelActionResponse {
status: string
index?: number
default_model?: string
}
const BASE_URL = ""
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await launcherFetch(`${BASE_URL}${path}`, options)
if (!res.ok) {
let detail = ""
try {
detail = await res.text()
} catch {
// ignore
}
throw new Error(detail || `API error: ${res.status} ${res.statusText}`)
}
return res.json() as Promise<T>
}
export async function getModels(): Promise<ModelsListResponse> {
return request<ModelsListResponse>("/api/models")
}
export async function addModel(
model: Partial<ModelInfo>,
): Promise<ModelActionResponse> {
return request<ModelActionResponse>("/api/models", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(model),
})
}
export async function updateModel(
index: number,
model: Partial<ModelInfo>,
): Promise<ModelActionResponse> {
return request<ModelActionResponse>(`/api/models/${index}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(model),
})
}
export async function deleteModel(index: number): Promise<ModelActionResponse> {
return request<ModelActionResponse>(`/api/models/${index}`, {
method: "DELETE",
})
}
export async function setDefaultModel(
modelName: string,
): Promise<ModelActionResponse> {
const response = await request<ModelActionResponse>("/api/models/default", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model_name: modelName }),
})
await refreshGatewayState()
return response
}
export interface TestModelResponse {
success: boolean
latency_ms: number
status: string
error?: string
}
export async function testModel(index: number): Promise<TestModelResponse> {
return request<TestModelResponse>(`/api/models/${index}/test`, {
method: "POST",
})
}
export interface TestModelInlineRequest {
provider: string
model: string
api_base?: string
api_key?: string
auth_method?: string
model_index?: number
}
export async function testModelInline(
params: TestModelInlineRequest,
): Promise<TestModelResponse> {
return request<TestModelResponse>("/api/models/test-inline", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
})
}
export interface UpstreamModel {
id: string
owned_by?: string
extra?: Record<string, unknown>
}
export interface FetchModelsRequest {
provider: string
api_key?: string
api_base?: string
}
export interface FetchModelsResponse {
models: UpstreamModel[]
total: number
}
export async function fetchUpstreamModels(
req: FetchModelsRequest,
): Promise<FetchModelsResponse> {
return request<FetchModelsResponse>("/api/models/fetch", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req),
})
}
// --- Model Catalog API ---
export interface CatalogModel {
id: string
owned_by?: string
extra?: Record<string, unknown>
}
export interface CatalogEntry {
id: string
provider: string
api_base: string
api_key_mask: string
models: CatalogModel[]
fetched_at: string
}
interface CatalogListResponse {
entries: CatalogEntry[]
total: number
}
export async function getCatalogs(): Promise<CatalogListResponse> {
return request<CatalogListResponse>("/api/models/catalog")
}
export async function deleteCatalog(id: string): Promise<void> {
await request<Record<string, never>>(
`/api/models/catalog/${encodeURIComponent(id)}`,
{
method: "DELETE",
},
)
}
export type { ModelsListResponse, ModelActionResponse }