When editing an existing model, the edit form initializes apiKey as empty for security. This caused "Fetch Available Models" to reject with "please enter API Key first" even though the key is saved server-side. Add model_index support: the frontend passes the model's index to the backend, which looks up the stored key from config. The key never leaves the backend. Provider and API base are validated to prevent a stored key from being sent to an unrelated endpoint. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
223 lines
5 KiB
TypeScript
223 lines
5 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
|
|
model_index?: number
|
|
}
|
|
|
|
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 }
|