mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-08 18:57:47 +08:00
Tabs Models, General, Drawing and About, each with a fixed title row. The models tab becomes three pages: the user's providers with one status each and the server's models; a searchable picker of all 24 providers in four groups; and a provider page in three steps (connection, models, test). A new provider gets its first suggested model, and a passing test offers to use the model in the chat. Bedrock asks for one way of signing in, with an optional session token for temporary access keys. The composer's model picker lists every model, marking untested and failed ones, and can open the provider picker. A chat error that offers model settings opens the provider the request went to.
139 lines
3.9 KiB
TypeScript
139 lines
3.9 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_INFO,
|
|
type ProviderConfig,
|
|
type ProviderName,
|
|
} from "@/lib/types/model-config"
|
|
|
|
export type ProviderGroupId = "vendors" | "aggregators" | "cloud" | "selfHosted"
|
|
|
|
/** The provider picker's groups, each provider in exactly one */
|
|
export const PROVIDER_GROUPS: {
|
|
id: ProviderGroupId
|
|
providers: ProviderName[]
|
|
}[] = [
|
|
{
|
|
id: "vendors",
|
|
providers: [
|
|
"openai",
|
|
"anthropic",
|
|
"google",
|
|
"deepseek",
|
|
"qwen",
|
|
"doubao",
|
|
"glm",
|
|
"kimi",
|
|
"minimax",
|
|
"mimo",
|
|
],
|
|
},
|
|
{
|
|
id: "aggregators",
|
|
providers: [
|
|
"openrouter",
|
|
"aihubmix",
|
|
"siliconflow",
|
|
"modelscope",
|
|
"novita",
|
|
"atlascloud",
|
|
"qiniu",
|
|
"gateway",
|
|
],
|
|
},
|
|
{ id: "cloud", providers: ["bedrock", "vertexai", "azure", "edgeone"] },
|
|
{ id: "selfHosted", providers: ["ollama", "sglang"] },
|
|
]
|
|
|
|
/** 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[] = [
|
|
"azure",
|
|
"ollama",
|
|
"sglang",
|
|
"minimax",
|
|
"mimo",
|
|
]
|
|
|
|
/** 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 }
|
|
}
|