mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-13 04:59:51 +08:00
Step 2 of the provider refactor plan. lib/providers.ts holds one record per provider (SDK kind, credential kind, env prefix, default URL, picker group, notes and base URL hints in four languages, and the short model list of the six providers that cannot list models). ProviderName is now the record's key set and lib/types/model-config.ts re-exports the record as PROVIDER_INFO, so the twelve importers are unchanged. Derived from the record, under their old names and in their old files: FIXED_CRED_PROVIDERS, PROVIDER_LOGO_MAP and chatRequestUrl's own-path list (model-config.ts), SINGLE_SYSTEM_PROVIDERS, PROVIDER_ENV_VARS and SDK_KNOWS_ENDPOINT (ai-providers.ts), PROVIDER_GROUPS and OPTIONS_OPEN (provider-setup.ts), NO_KEY_NEEDED (provider-models route). tests/unit/providers.test.ts freezes the old tables' values and checks the derived ones against them. The record is written in the picker's order, so the provider lists in two 'No AI provider configured' messages change order; those two snapshot rows are the only snapshot change.
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
// What the model settings show about providers: the groups of the provider
|
|
// picker, and how far a provider's setup has come
|
|
|
|
import {
|
|
PROVIDER_NAMES,
|
|
PROVIDERS,
|
|
type ProviderGroup as ProviderGroupId,
|
|
} from "@/lib/providers"
|
|
import {
|
|
PROVIDER_INFO,
|
|
type ProviderConfig,
|
|
type ProviderName,
|
|
} from "@/lib/types/model-config"
|
|
|
|
export type { ProviderGroupId }
|
|
|
|
/** The provider picker's groups, each provider in exactly one, in record order */
|
|
export const PROVIDER_GROUPS: {
|
|
id: ProviderGroupId
|
|
providers: ProviderName[]
|
|
}[] = (["vendors", "aggregators", "cloud", "selfHosted"] as const).map(
|
|
(id) => ({
|
|
id,
|
|
providers: PROVIDER_NAMES.filter((p) => PROVIDERS[p].group === id),
|
|
}),
|
|
)
|
|
|
|
/** Shown when the user has no provider yet */
|
|
export const POPULAR_PROVIDERS: ProviderName[] = [
|
|
"openai",
|
|
"anthropic",
|
|
"google",
|
|
"deepseek",
|
|
"openrouter",
|
|
"qwen",
|
|
]
|
|
|
|
/** Providers that need something other than a plain API key */
|
|
export const PROVIDER_NOTE_KEYS: Partial<Record<ProviderName, string>> = {
|
|
bedrock: "noteBedrock",
|
|
vertexai: "noteVertex",
|
|
azure: "noteAzure",
|
|
edgeone: "noteEdgeone",
|
|
ollama: "noteOllama",
|
|
sglang: "noteSglang",
|
|
}
|
|
|
|
/**
|
|
* Providers whose base URL usually needs changing (Azure's default is only
|
|
* an example; Ollama's is its cloud, SGLang's a local server) or comes with
|
|
* a hint (MiniMax, MiMo): their folded options start open
|
|
*/
|
|
export const OPTIONS_OPEN: ProviderName[] = PROVIDER_NAMES.filter(
|
|
(p) => PROVIDERS[p].optionsOpen,
|
|
)
|
|
|
|
/** The groups with only the providers whose name matches the search */
|
|
export function filterProviderGroups(query: string) {
|
|
const q = query.trim().toLowerCase()
|
|
if (!q) return PROVIDER_GROUPS
|
|
return PROVIDER_GROUPS.map((group) => ({
|
|
...group,
|
|
providers: group.providers.filter(
|
|
(p) =>
|
|
PROVIDER_INFO[p].label.toLowerCase().includes(q) ||
|
|
p.includes(q),
|
|
),
|
|
})).filter((group) => group.providers.length > 0)
|
|
}
|
|
|
|
/** The credentials a test or a chat request needs are filled in */
|
|
export function hasCredentials(p: ProviderConfig): boolean {
|
|
switch (p.provider) {
|
|
case "bedrock":
|
|
return (
|
|
(!!p.apiKey ||
|
|
(!!p.awsAccessKeyId && !!p.awsSecretAccessKey)) &&
|
|
!!p.awsRegion
|
|
)
|
|
case "vertexai":
|
|
return !!p.vertexApiKey
|
|
// Its default base URL names no real resource
|
|
case "azure":
|
|
return (
|
|
!!p.apiKey &&
|
|
!!p.baseUrl &&
|
|
p.baseUrl !== PROVIDER_INFO.azure.defaultBaseUrl
|
|
)
|
|
case "edgeone":
|
|
case "ollama":
|
|
return true
|
|
default:
|
|
return !!p.apiKey
|
|
}
|
|
}
|
|
|
|
export type ProviderStatus =
|
|
| { kind: "error"; count: number }
|
|
| { kind: "incomplete" }
|
|
| { kind: "untested"; count: number }
|
|
| { kind: "ok"; count: number }
|
|
|
|
/** The one thing about a provider that most needs the user's attention */
|
|
export function providerStatus(p: ProviderConfig): ProviderStatus {
|
|
const failed = p.models.filter((m) => m.validated === false).length
|
|
if (failed > 0) return { kind: "error", count: failed }
|
|
if (!hasCredentials(p) || p.models.length === 0) {
|
|
return { kind: "incomplete" }
|
|
}
|
|
const untested = p.models.filter((m) => m.validated !== true).length
|
|
if (untested > 0) return { kind: "untested", count: untested }
|
|
return { kind: "ok", count: p.models.length }
|
|
}
|