mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
548dc15acd
* 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
119 lines
3.1 KiB
TypeScript
119 lines
3.1 KiB
TypeScript
/**
|
|
* Real-time model field validation utilities.
|
|
* All checks are pure frontend, no network required.
|
|
*
|
|
* Messages use i18n keys with interpolation params — callers must
|
|
* translate them via t(key, params).
|
|
*/
|
|
import type { ModelProviderOption } from "@/api/models"
|
|
|
|
import {
|
|
findClosestProvider,
|
|
getCanonicalProviderKey,
|
|
getKnownProviderKeys,
|
|
} from "./provider-registry"
|
|
|
|
export type ValidationLevel = "error" | "warning" | "success"
|
|
|
|
export interface FieldValidation {
|
|
level: ValidationLevel
|
|
messageKey: string
|
|
messageParams?: Record<string, string>
|
|
fix?: string
|
|
}
|
|
|
|
/**
|
|
* Validate a model identifier string with optional provider context.
|
|
* Returns validation result with optional one-click fix suggestion.
|
|
*/
|
|
export function validateModelField(
|
|
input: string,
|
|
selectedProvider?: string,
|
|
backendOptions?: ModelProviderOption[],
|
|
): FieldValidation {
|
|
const trimmed = input.trim()
|
|
if (!trimmed) return { level: "success", messageKey: "" }
|
|
const knownProviderKeys = getKnownProviderKeys(backendOptions)
|
|
|
|
// Hard errors
|
|
if (/\s/.test(trimmed)) {
|
|
return {
|
|
level: "error",
|
|
messageKey: "models.validation.whitespace",
|
|
fix: trimmed.replace(/\s+/g, "/"),
|
|
}
|
|
}
|
|
if (trimmed.startsWith("/")) {
|
|
return {
|
|
level: "error",
|
|
messageKey: "models.validation.leadingSlash",
|
|
fix: trimmed.replace(/^\/+/, ""),
|
|
}
|
|
}
|
|
if (trimmed.includes("//")) {
|
|
return {
|
|
level: "error",
|
|
messageKey: "models.validation.consecutiveSlash",
|
|
fix: trimmed.replace(/\/+/g, "/"),
|
|
}
|
|
}
|
|
|
|
const slashIdx = trimmed.indexOf("/")
|
|
if (slashIdx === -1) {
|
|
// No provider prefix — when a provider is already selected,
|
|
// the model ID is provider-local and needs no prefix.
|
|
if (selectedProvider) {
|
|
return {
|
|
level: "success",
|
|
messageKey: "models.validation.parsed",
|
|
messageParams: { provider: selectedProvider, model: trimmed },
|
|
}
|
|
}
|
|
return {
|
|
level: "warning",
|
|
messageKey: "models.validation.defaultToOpenAI",
|
|
fix: `openai/${trimmed}`,
|
|
}
|
|
}
|
|
|
|
const provider = trimmed.slice(0, slashIdx)
|
|
const model = trimmed.slice(slashIdx + 1)
|
|
if (!model) {
|
|
return { level: "error", messageKey: "models.validation.emptyModel" }
|
|
}
|
|
|
|
if (!knownProviderKeys.has(provider)) {
|
|
// Check aliases
|
|
const alias = getCanonicalProviderKey(provider, backendOptions)
|
|
if (alias && alias !== provider) {
|
|
return {
|
|
level: "warning",
|
|
messageKey: "models.validation.shouldUse",
|
|
messageParams: { provider, alias },
|
|
fix: `${alias}/${model}`,
|
|
}
|
|
}
|
|
// Typo check
|
|
const closest = findClosestProvider(provider, backendOptions)
|
|
if (closest) {
|
|
return {
|
|
level: "warning",
|
|
messageKey: "models.validation.didYouMean",
|
|
messageParams: { closest },
|
|
fix: `${closest}/${model}`,
|
|
}
|
|
}
|
|
return {
|
|
level: "warning",
|
|
messageKey: "models.validation.unknownProvider",
|
|
messageParams: { provider },
|
|
}
|
|
}
|
|
|
|
return {
|
|
level: "success",
|
|
messageKey: "models.validation.parsed",
|
|
messageParams: { provider, model },
|
|
}
|
|
}
|