mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-10 19:49:52 +08:00
feat(settings): fetch the model list from any provider and flag models that cannot draw
The "Fetch models" button asks the provider for its models (OpenAI-style /models, Anthropic, Google, Ollama, OpenRouter, Vercel Gateway, AIHubMix) and shows them in a searchable picker. This replaces the route that only worked for AIHubMix. A snapshot of models.dev (MIT) says which models support tool calls. Models without them get a "no tool calls" badge in the picker and a hint in the model list, since drawing needs tool calls. Refresh the snapshot with scripts/update-model-catalog.mjs.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// Refresh lib/model-catalog.json from models.dev (MIT licensed model data):
|
||||
// whether each model can call tools and read images, and its limits. The
|
||||
// app only uses it for hints in model settings. Run before a release:
|
||||
// node scripts/update-model-catalog.mjs
|
||||
import { writeFileSync } from "node:fs"
|
||||
|
||||
// Our provider id -> models.dev provider id
|
||||
const PROVIDERS = {
|
||||
openai: "openai",
|
||||
anthropic: "anthropic",
|
||||
google: "google",
|
||||
vertexai: "google-vertex",
|
||||
azure: "azure",
|
||||
bedrock: "amazon-bedrock",
|
||||
ollama: "ollama-cloud",
|
||||
openrouter: "openrouter",
|
||||
aihubmix: "aihubmix",
|
||||
deepseek: "deepseek",
|
||||
siliconflow: "siliconflow-cn",
|
||||
gateway: "vercel",
|
||||
doubao: "volcengine",
|
||||
modelscope: "modelscope",
|
||||
glm: "zhipuai",
|
||||
qwen: "alibaba-cn",
|
||||
qiniu: "qiniu-ai",
|
||||
kimi: "moonshotai-cn",
|
||||
minimax: "minimax-cn",
|
||||
novita: "novita-ai",
|
||||
mimo: "xiaomi",
|
||||
}
|
||||
|
||||
const response = await fetch("https://models.dev/api.json")
|
||||
if (!response.ok) throw new Error(`models.dev answered ${response.status}`)
|
||||
const data = await response.json()
|
||||
|
||||
const catalog = {}
|
||||
for (const [provider, source] of Object.entries(PROVIDERS)) {
|
||||
const models = data[source]?.models
|
||||
if (!models) {
|
||||
console.warn(`models.dev has no provider ${source}`)
|
||||
continue
|
||||
}
|
||||
catalog[provider] = {}
|
||||
for (const [id, m] of Object.entries(models).sort(([a], [b]) =>
|
||||
a.localeCompare(b),
|
||||
)) {
|
||||
// Chat models only, and none that are on their way out
|
||||
if (!m.modalities?.output?.includes("text")) continue
|
||||
if (m.status === "deprecated") continue
|
||||
catalog[provider][id] = {
|
||||
tools: m.tool_call === true,
|
||||
images: m.modalities?.input?.includes("image") === true,
|
||||
reasoning: m.reasoning === true,
|
||||
...(m.limit?.context && { context: m.limit.context }),
|
||||
...(m.limit?.output && { output: m.limit.output }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// One model per line, so a refresh shows up as a readable diff
|
||||
const lines = ["{"]
|
||||
const providers = Object.entries(catalog)
|
||||
providers.forEach(([provider, models], p) => {
|
||||
lines.push(` ${JSON.stringify(provider)}: {`)
|
||||
const entries = Object.entries(models)
|
||||
entries.forEach(([id, info], i) => {
|
||||
const comma = i < entries.length - 1 ? "," : ""
|
||||
lines.push(` ${JSON.stringify(id)}: ${JSON.stringify(info)}${comma}`)
|
||||
})
|
||||
lines.push(` }${p < providers.length - 1 ? "," : ""}`)
|
||||
})
|
||||
lines.push("}")
|
||||
writeFileSync(
|
||||
new URL("../lib/model-catalog.json", import.meta.url),
|
||||
`${lines.join("\n")}\n`,
|
||||
)
|
||||
const count = providers.reduce((n, [, m]) => n + Object.keys(m).length, 0)
|
||||
console.log(`Wrote ${count} models for ${providers.length} providers`)
|
||||
Reference in New Issue
Block a user