Compare commits

...

1 Commits

Author SHA1 Message Date
Adam c0ed0e62df feat(web): redesign model-centric navigation 2026-06-04 19:16:30 -05:00
72 changed files with 2169 additions and 1667 deletions
-43
View File
@@ -1,43 +0,0 @@
name = "GLM-4.5-Air"
family = "glm-air"
release_date = "2025-07-28"
last_updated = "2025-07-28"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 131_072
output = 98_304
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.5-Air"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 23.8
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.5-air/benchmarks"
date = "2026-05-30"
[[benchmarks]]
name = "SciCode"
score = 30.6
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.5-air/benchmarks"
date = "2026-05-30"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 20.5
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.5-air/benchmarks"
date = "2026-05-30"
-43
View File
@@ -1,43 +0,0 @@
name = "GLM-4.5"
family = "glm"
release_date = "2025-07-28"
last_updated = "2025-07-28"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 131_072
output = 98_304
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.5"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 26.3
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.5/benchmarks"
date = "2026-03-11"
[[benchmarks]]
name = "SciCode"
score = 34.8
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.5/benchmarks"
date = "2026-03-11"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 22
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.5/benchmarks"
date = "2026-03-11"
-43
View File
@@ -1,43 +0,0 @@
name = "GLM-4.5V"
family = "glm"
release_date = "2025-08-11"
last_updated = "2025-08-11"
attachment = true
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 64_000
output = 16_384
[modalities]
input = ["text", "image", "video"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.5V"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 10.9
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.5v/benchmarks"
date = "2026-04-29"
[[benchmarks]]
name = "SciCode"
score = 22.1
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.5v/benchmarks"
date = "2026-04-29"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 5.3
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.5v/benchmarks"
date = "2026-04-29"
-50
View File
@@ -1,50 +0,0 @@
name = "GLM-4.6"
family = "glm"
release_date = "2025-09-30"
last_updated = "2025-09-30"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 204_800
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.6"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 29.5
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.6/benchmarks"
date = "2026-05-22"
[[benchmarks]]
name = "SciCode"
score = 38.4
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.6/benchmarks"
date = "2026-05-22"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 25
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.6/benchmarks"
date = "2026-05-22"
[[benchmarks]]
name = "SWE-Bench Pro"
score = 9.67
metric = "resolve rate"
dataset = "public"
source = "https://labs.scale.com/leaderboard/swe_bench_pro_public"
-22
View File
@@ -1,22 +0,0 @@
name = "GLM-4.6V"
family = "glm"
release_date = "2025-12-08"
last_updated = "2025-12-08"
attachment = true
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 128_000
output = 32_768
[modalities]
input = ["text", "image", "video"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.6V"
-28
View File
@@ -1,28 +0,0 @@
name = "GLM-4.7-Flash"
family = "glm-flash"
release_date = "2026-01-19"
last_updated = "2026-01-19"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 200_000
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.7-Flash"
[[benchmarks]]
name = "SWE-Bench Verified"
score = 59.2
metric = "resolved"
source = "https://huggingface.co/zai-org/GLM-4.7-Flash"
-22
View File
@@ -1,22 +0,0 @@
name = "GLM-4.7-FlashX"
family = "glm-flash"
release_date = "2026-01-19"
last_updated = "2026-01-19"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 200_000
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.7-Flash"
-34
View File
@@ -1,34 +0,0 @@
name = "GLM-4.7"
family = "glm"
release_date = "2025-12-22"
last_updated = "2025-12-22"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-04"
open_weights = true
[limit]
context = 204_800
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.7"
[[benchmarks]]
name = "SWE-Bench Verified"
score = 73.8
metric = "resolved"
source = "https://huggingface.co/zai-org/GLM-4.7"
[[benchmarks]]
name = "Terminal Bench 2.0"
score = 33.4
metric = "score"
source = "https://huggingface.co/zai-org/GLM-4.7"
-52
View File
@@ -1,52 +0,0 @@
name = "GLM-5.1"
family = "glm"
release_date = "2026-03-27"
last_updated = "2026-03-27"
attachment = false
reasoning = true
temperature = true
tool_call = true
structured_output = true
open_weights = true
[limit]
context = 200_000
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-5.1"
[[benchmarks]]
name = "Artificial Analysis Coding Agent Index"
score = 52.7
metric = "average pass@1"
harness = "Claude Code"
source = "https://artificialanalysis.ai/agents/coding-agents"
[[benchmarks]]
name = "SWE-Atlas Codebase QnA"
score = 73.2
metric = "pass@1"
harness = "Claude Code"
source = "https://artificialanalysis.ai/agents/coding-agents"
[[benchmarks]]
name = "SWE-Bench Pro"
score = 19.8
metric = "pass@1"
harness = "Claude Code"
dataset = "hard-aa"
source = "https://artificialanalysis.ai/agents/coding-agents"
[[benchmarks]]
name = "Terminal-Bench"
score = 65.1
metric = "pass@1"
harness = "Claude Code"
version = "2.1"
source = "https://artificialanalysis.ai/agents/coding-agents"
-48
View File
@@ -1,48 +0,0 @@
name = "GLM-5"
family = "glm"
release_date = "2026-02-11"
last_updated = "2026-02-11"
attachment = false
reasoning = true
temperature = true
tool_call = true
open_weights = true
[limit]
context = 204_800
output = 131_072
[modalities]
input = ["text"]
output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-5"
[[benchmarks]]
name = "SWE-Bench Verified"
score = 72.8
metric = "resolved"
source = "https://www.swebench.com/"
[[benchmarks]]
name = "SWE-Atlas Codebase QnA"
score = 20.5
metric = "score"
harness = "Mini-SWE-Agent"
source = "https://labs.scale.com/leaderboard/sweatlas-qna"
[[benchmarks]]
name = "SWE-Atlas Refactoring"
score = 24.24
metric = "score"
harness = "Mini-SWE-Agent"
source = "https://labs.scale.com/leaderboard/sweatlas-refactoring"
[[benchmarks]]
name = "SWE-Atlas Test Writing"
score = 28.74
metric = "score"
harness = "Mini-SWE-Agent"
source = "https://labs.scale.com/leaderboard/sweatlas-tw"
+21
View File
@@ -20,3 +20,24 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.5-Air"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 23.8
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.5-air/benchmarks"
date = "2026-05-30"
[[benchmarks]]
name = "SciCode"
score = 30.6
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.5-air/benchmarks"
date = "2026-05-30"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 20.5
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.5-air/benchmarks"
date = "2026-05-30"
+21
View File
@@ -20,3 +20,24 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.5"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 26.3
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.5/benchmarks"
date = "2026-03-11"
[[benchmarks]]
name = "SciCode"
score = 34.8
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.5/benchmarks"
date = "2026-03-11"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 22
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.5/benchmarks"
date = "2026-03-11"
+21
View File
@@ -20,3 +20,24 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.5V"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 10.9
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.5v/benchmarks"
date = "2026-04-29"
[[benchmarks]]
name = "SciCode"
score = 22.1
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.5v/benchmarks"
date = "2026-04-29"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 5.3
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.5v/benchmarks"
date = "2026-04-29"
+28
View File
@@ -20,3 +20,31 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.6"
[[benchmarks]]
name = "Artificial Analysis Coding Index"
score = 29.5
metric = "index"
source = "https://openrouter.ai/z-ai/glm-4.6/benchmarks"
date = "2026-05-22"
[[benchmarks]]
name = "SciCode"
score = 38.4
metric = "percent correct"
source = "https://openrouter.ai/z-ai/glm-4.6/benchmarks"
date = "2026-05-22"
[[benchmarks]]
name = "Terminal-Bench Hard"
score = 25
metric = "success rate"
source = "https://openrouter.ai/z-ai/glm-4.6/benchmarks"
date = "2026-05-22"
[[benchmarks]]
name = "SWE-Bench Pro"
score = 9.67
metric = "resolve rate"
dataset = "public"
source = "https://labs.scale.com/leaderboard/swe_bench_pro_public"
+6
View File
@@ -20,3 +20,9 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-4.7-Flash"
[[benchmarks]]
name = "SWE-Bench Verified"
score = 59.2
metric = "resolved"
source = "https://huggingface.co/zai-org/GLM-4.7-Flash"
+30
View File
@@ -20,3 +20,33 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-5.1"
[[benchmarks]]
name = "Artificial Analysis Coding Agent Index"
score = 52.7
metric = "average pass@1"
harness = "Claude Code"
source = "https://artificialanalysis.ai/agents/coding-agents"
[[benchmarks]]
name = "SWE-Atlas Codebase QnA"
score = 73.2
metric = "pass@1"
harness = "Claude Code"
source = "https://artificialanalysis.ai/agents/coding-agents"
[[benchmarks]]
name = "SWE-Bench Pro"
score = 19.8
metric = "pass@1"
harness = "Claude Code"
dataset = "hard-aa"
source = "https://artificialanalysis.ai/agents/coding-agents"
[[benchmarks]]
name = "Terminal-Bench"
score = 65.1
metric = "pass@1"
harness = "Claude Code"
version = "2.1"
source = "https://artificialanalysis.ai/agents/coding-agents"
+27
View File
@@ -19,3 +19,30 @@ output = ["text"]
[[weights]]
label = "Hugging Face"
url = "https://huggingface.co/zai-org/GLM-5"
[[benchmarks]]
name = "SWE-Bench Verified"
score = 72.8
metric = "resolved"
source = "https://www.swebench.com/"
[[benchmarks]]
name = "SWE-Atlas Codebase QnA"
score = 20.5
metric = "score"
harness = "Mini-SWE-Agent"
source = "https://labs.scale.com/leaderboard/sweatlas-qna"
[[benchmarks]]
name = "SWE-Atlas Refactoring"
score = 24.24
metric = "score"
harness = "Mini-SWE-Agent"
source = "https://labs.scale.com/leaderboard/sweatlas-refactoring"
[[benchmarks]]
name = "SWE-Atlas Test Writing"
score = 28.74
metric = "score"
harness = "Mini-SWE-Agent"
source = "https://labs.scale.com/leaderboard/sweatlas-tw"
+1 -1
View File
@@ -26,7 +26,7 @@ const ALLOWLIST = new Set<string>([
// Maps Ambient model IDs to canonical model metadata IDs in this repo.
const BASE_MODEL_MAP: Record<string, string> = {
"zai-org/GLM-5.1-FP8": "zai/glm-5.1",
"zai-org/GLM-5.1-FP8": "zhipuai/glm-5.1",
"moonshotai/kimi-k2.6": "moonshotai/kimi-k2.6",
};
@@ -25,8 +25,8 @@ const CANONICAL_PROVIDER_PREFIXES = {
"x-ai": { provider: "xai", metadata: "xai" },
xai: { provider: "xai", metadata: "xai" },
xiaomi: { provider: "xiaomi", metadata: "xiaomi" },
zai: { provider: "zai", metadata: "zai" },
"z-ai": { provider: "zai", metadata: "zai" },
zai: { provider: "zai", metadata: "zhipuai" },
"z-ai": { provider: "zai", metadata: "zhipuai" },
} as const;
export const OpenRouterModel = z.object({
+28 -7
View File
@@ -113,6 +113,8 @@ export default {
url.pathname === "/index"
) {
url.pathname = "/_index";
} else if (isHtmlRoute(url.pathname)) {
url.pathname = htmlRouteAssetPath(url.pathname);
} else if (url.pathname.startsWith("/logos/")) {
// Check if the specific provider logo exists in static assets
const logoResponse = await env.ASSETS.fetch(
@@ -129,18 +131,37 @@ export default {
}
return logoResponse;
} else {
// redirect to "/"
return new Response(null, {
status: 302,
headers: { Location: "/" },
});
}
return await env.ASSETS.fetch(new Request(url.toString(), request));
const response = await env.ASSETS.fetch(new Request(url.toString(), request));
if (response.status !== 404) return response;
return new Response(null, {
status: 302,
headers: { Location: "/" },
});
},
};
function isHtmlRoute(pathname: string) {
return (
pathname === "/models" ||
pathname === "/providers" ||
pathname === "/labs" ||
pathname.startsWith("/models/") ||
pathname.startsWith("/providers/") ||
pathname.startsWith("/labs/")
);
}
function htmlRouteAssetPath(pathname: string) {
const normalized =
pathname !== "/" && pathname.endsWith("/")
? pathname.slice(0, -1)
: pathname;
return `${normalized}/index.html`;
}
// Returns a stable lookup key for an IP address.
// IPv4: full address as /32 (e.g. "203.0.113.45/32").
// IPv6: the /64 network prefix (e.g. "2001:db8:abcd:1234::/64"). ISPs commonly
+1
View File
@@ -7,6 +7,7 @@
content="Models.dev is a comprehensive open-source database of AI model specifications, pricing, and features."
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<base href="/" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="preconnect"
+17 -9
View File
@@ -1,9 +1,8 @@
#!/usr/bin/env bun
import { Rendered, Providers, Models } from "../src/render";
import { RenderedPages, Providers, Models } from "../src/render";
import fs from "fs/promises";
import path from "path";
import { $ } from "bun";
await fs.rm("./dist", { recursive: true, force: true });
await Bun.build({
@@ -41,9 +40,17 @@ for (const entry of entries) {
}
}
let html = await Bun.file("./dist/index.html").text();
html = html.replace("<!--static-->", Rendered);
await Bun.write("./dist/index.html", html);
const template = await Bun.file("./dist/index.html").text();
for (const [route, rendered] of RenderedPages) {
const filePath = route === "/"
? "./dist/_index.html"
: path.join("./dist", route, "index.html");
await fs.mkdir(path.dirname(filePath), { recursive: true });
await Bun.write(filePath, template.replace("<!--static-->", rendered));
}
await Bun.write("./dist/api.json", JSON.stringify(Providers));
await Bun.write(
"./dist/catalog.json",
@@ -51,7 +58,8 @@ await Bun.write(
);
await Bun.write("./dist/models.json", JSON.stringify(Models));
await $`mv ./dist/index.html ./dist/_index.html`;
await $`mv ./dist/api.json ./dist/_api.json`;
await $`mv ./dist/catalog.json ./dist/_catalog.json`;
await $`mv ./dist/models.json ./dist/_models.json`;
await fs.rename("./dist/api.json", "./dist/_api.json");
await fs.rename("./dist/catalog.json", "./dist/_catalog.json");
await fs.rename("./dist/models.json", "./dist/_models.json");
await fs.rm("./dist/index.html", { force: true });
+634 -401
View File
File diff suppressed because it is too large Load Diff
+266 -358
View File
@@ -1,53 +1,14 @@
import {
Virtualizer,
elementScroll,
observeElementOffset,
observeElementRect,
} from "@tanstack/virtual-core";
import {
type TableRow,
renderRow,
escapeHtml,
booleanText,
knowledgeText,
weightsText,
} from "./shared.js";
declare global {
interface Window {
__TABLE_DATA__: TableRow[];
}
}
interface VirtualizedRow extends TableRow {
key: string;
searchText: string;
sortValues: Array<string | number | undefined>;
}
type SortDirection = "asc" | "desc";
const ESTIMATED_ROW_HEIGHT = 48;
const VIRTUAL_OVERSCAN = 5;
const modal = document.getElementById("modal") as HTMLDialogElement | null;
const modalClose = document.getElementById("close");
const help = document.getElementById("help");
const search = document.getElementById("search") as HTMLInputElement | null;
const tables = Array.from(
document.querySelectorAll<HTMLTableElement>("table[data-enhanced-table]"),
);
const modal = document.getElementById("modal") as HTMLDialogElement;
const modalClose = document.getElementById("close")!;
const help = document.getElementById("help")!;
const search = document.getElementById("search")! as HTMLInputElement;
const viewport = document.getElementById("table-viewport") as HTMLElement;
const tbody = document.getElementById(
"models-table-body"
) as HTMLTableSectionElement;
const headers = Array.from(document.querySelectorAll("th.sortable"));
const columnCount = document.querySelectorAll("thead th").length;
let isLoaded = false;
let allRows: VirtualizedRow[] = [];
let visibleRows: VirtualizedRow[] = [];
let currentSort: { column: number; direction: SortDirection } = {
column: -1,
direction: "asc",
};
let scrollYBeforeModal = 0;
/////////////////////////
// URL State Management
@@ -71,372 +32,319 @@ function updateQueryParams(updates: Record<string, string | null>) {
window.history.pushState({}, "", newPath);
}
function getColumnNameForURL(headerEl: Element): string {
const text = headerEl.textContent?.trim().toLowerCase() || "";
return text.replace(/↑|↓/g, "").trim().split(/\s+/).slice(0, 2).join("-");
}
function getColumnIndexByUrlName(name: string): number {
return headers.findIndex((header) => getColumnNameForURL(header) === name);
}
/////////////////////////
// Handle "How to use"
// Help Dialog
/////////////////////////
let y = 0;
help.addEventListener("click", () => {
y = window.scrollY;
help?.addEventListener("click", () => {
if (!modal) return;
scrollYBeforeModal = window.scrollY;
document.body.style.position = "fixed";
document.body.style.top = `-${y}px`;
document.body.style.top = `-${scrollYBeforeModal}px`;
modal.showModal();
});
function closeDialog() {
if (!modal) return;
modal.close();
document.body.style.position = "";
document.body.style.top = "";
window.scrollTo(0, y);
window.scrollTo(0, scrollYBeforeModal);
}
modalClose.addEventListener("click", closeDialog);
modal.addEventListener("cancel", closeDialog);
modal.addEventListener("click", (e) => {
if (e.target === modal) closeDialog();
modalClose?.addEventListener("click", closeDialog);
modal?.addEventListener("cancel", closeDialog);
modal?.addEventListener("click", (event) => {
if (event.target === modal) closeDialog();
});
////////////////////
// Row Data
// Search
////////////////////
function lockColumnWidths() {
const ths = document.querySelectorAll("#models-table thead th");
const widths = Array.from(ths).map((th) => th.getBoundingClientRect().width);
function filterRows() {
if (!search) return;
const measurementRow = tbody.querySelector('tr[aria-hidden="true"]');
if (measurementRow) measurementRow.remove();
const table = document.getElementById("models-table")!;
table.style.tableLayout = "fixed";
const colgroup = document.createElement("colgroup");
for (const width of widths) {
const col = document.createElement("col");
col.style.width = `${width}px`;
colgroup.appendChild(col);
}
table.insertBefore(colgroup, table.firstChild);
}
function prepareRow(row: TableRow): VirtualizedRow {
const sortValues: VirtualizedRow["sortValues"] = [
row.providerName,
row.modelName,
row.family,
row.providerId,
row.modelId,
booleanText(row.toolCall),
booleanText(row.reasoning),
row.input.length,
row.output.length,
row.inputCost,
row.outputCost,
row.reasoningCost,
row.cacheReadCost,
row.cacheWriteCost,
row.audioInputCost,
row.audioOutputCost,
row.contextLimit,
row.inputLimit,
row.outputLimit,
row.structuredOutput === undefined
? undefined
: booleanText(row.structuredOutput),
booleanText(row.temperature),
weightsText(row.openWeights),
row.knowledge ? knowledgeText(row.knowledge) : undefined,
row.releaseDate,
row.lastUpdated,
];
const searchableValues = [
row.providerName,
row.modelName,
row.family ?? "",
row.providerId,
row.modelId,
...row.weightLinks.map((link) => link.label),
row.releaseDate,
row.lastUpdated,
];
return {
...row,
key: `${row.providerId}/${row.modelId}`,
searchText: searchableValues.join(" ").toLowerCase(),
sortValues,
};
}
////////////////////
// Virtual Table
////////////////////
function getVirtualizerOptions(count: number) {
return {
count,
getScrollElement: () => viewport,
estimateSize: () => ESTIMATED_ROW_HEIGHT,
getItemKey: (index: number) => visibleRows[index]?.key ?? index,
initialRect: {
width: viewport.clientWidth || window.innerWidth,
height: viewport.clientHeight || window.innerHeight,
},
overscan: VIRTUAL_OVERSCAN,
observeElementRect,
observeElementOffset,
scrollToFn: elementScroll,
onChange: () => renderVirtualRows(),
};
}
const virtualizer = new Virtualizer<HTMLElement, HTMLTableRowElement>(
getVirtualizerOptions(0)
);
const cleanupVirtualizer = virtualizer._didMount();
virtualizer._willUpdate();
window.addEventListener("pagehide", () => cleanupVirtualizer());
function renderStatusRow(message: string) {
tbody.innerHTML = `<tr class="empty-row"><td colspan="${columnCount}"><div>${escapeHtml(
message
)}</div></td></tr>`;
}
function setVirtualizerCount(count: number, resetScroll: boolean) {
virtualizer.setOptions(getVirtualizerOptions(count));
virtualizer._willUpdate();
if (resetScroll) virtualizer.scrollToOffset(0);
renderVirtualRows();
}
function renderVirtualRows() {
if (!isLoaded) return;
if (visibleRows.length === 0) {
renderStatusRow("No models found");
return;
}
const virtualRows = virtualizer.getVirtualItems();
if (virtualRows.length === 0) return;
const firstRow = virtualRows[0]!;
const lastRow = virtualRows[virtualRows.length - 1]!;
const paddingTop = firstRow.start;
const paddingBottom = Math.max(virtualizer.getTotalSize() - lastRow.end, 0);
const html: string[] = [];
if (paddingTop > 0) html.push(renderSpacerRow(paddingTop));
for (const virtualRow of virtualRows) {
const row = visibleRows[virtualRow.index];
if (row) html.push(renderRow(row, virtualRow.index));
}
if (paddingBottom > 0) html.push(renderSpacerRow(paddingBottom));
tbody.innerHTML = html.join("");
tbody.querySelectorAll<HTMLTableRowElement>("tr[data-index]").forEach((row) =>
virtualizer.measureElement(row)
);
}
function renderSpacerRow(height: number) {
return `<tr class="virtual-spacer" style="height: ${height}px"><td colspan="${columnCount}"></td></tr>`;
}
function applyRows(resetScroll = true) {
if (!isLoaded) return;
visibleRows = getRowsForDisplay();
setVirtualizerCount(visibleRows.length, resetScroll);
}
////////////////////
// Sorting
////////////////////
function getRowsForDisplay() {
const terms = search.value
.toLowerCase()
.split(",")
.map((term) => term.trim())
.filter(Boolean);
const filteredRows =
terms.length === 0
? allRows
: allRows.filter((row) =>
terms.some((term) => row.searchText.includes(term))
);
if (currentSort.column === -1) return filteredRows;
for (const table of tables) {
const rows = Array.from(table.tBodies[0]?.rows ?? []);
let visibleCount = 0;
const columnType = headers[currentSort.column]?.getAttribute("data-type");
if (!columnType) return filteredRows;
for (const row of rows) {
if (row.classList.contains("empty-row")) continue;
return [...filteredRows].sort((a, b) => {
const aValue = a.sortValues[currentSort.column];
const bValue = b.sortValues[currentSort.column];
const searchText =
row.getAttribute("data-search")?.toLowerCase() ??
row.textContent?.toLowerCase() ??
"";
const visible =
terms.length === 0 ||
terms.some((term) => searchText.includes(term));
if (aValue === undefined && bValue === undefined) return 0;
if (aValue === undefined) return 1;
if (bValue === undefined) return -1;
let comparison = 0;
if (columnType === "number" || columnType === "modalities") {
comparison = (aValue as number) - (bValue as number);
} else {
comparison = String(aValue).localeCompare(String(bValue));
row.hidden = !visible;
if (visible) visibleCount++;
}
return currentSort.direction === "asc" ? comparison : -comparison;
});
}
function sortTable(
column: number,
direction: SortDirection,
updateURL = true
) {
const header = headers[column];
if (!header?.getAttribute("data-type")) return;
currentSort = { column, direction };
if (updateURL) {
updateQueryParams({
sort: getColumnNameForURL(header),
order: direction,
});
table
.closest(".table-section")
?.toggleAttribute("data-empty", visibleCount === 0);
}
updateSortIndicators();
applyRows();
}
function updateSortIndicators() {
headers.forEach((header, i) => {
const indicator = header.querySelector(".sort-indicator")!;
indicator.textContent =
i === currentSort.column
? currentSort.direction === "asc"
? "↑"
: "↓"
: "";
});
}
headers.forEach((header, column) => {
header.addEventListener("click", () => {
const direction =
currentSort.column === column && currentSort.direction === "asc"
? "desc"
: "asc";
sortTable(column, direction);
});
});
///////////////////
// Search
///////////////////
search.addEventListener("input", () => {
search?.addEventListener("input", () => {
updateQueryParams({ search: search.value || null });
applyRows();
filterRows();
});
document.addEventListener("keydown", (e) => {
const key = e.key.toLowerCase();
if ((e.metaKey || e.ctrlKey) && (key === "k" || key === "f")) {
e.preventDefault();
search.focus();
search.select();
document.addEventListener("keydown", (event) => {
const key = event.key.toLowerCase();
if ((event.metaKey || event.ctrlKey) && (key === "k" || key === "f")) {
event.preventDefault();
search?.focus();
search?.select();
}
});
search.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
search?.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
search.value = "";
search.dispatchEvent(new Event("input"));
}
});
///////////////////////////////////
// Handle Copy model ID function
///////////////////////////////////
async function copyModelId(button: HTMLButtonElement, modelId: string) {
////////////////////
// Sorting
////////////////////
function getCellSortValue(row: HTMLTableRowElement, index: number) {
const cell = row.cells[index];
return cell?.getAttribute("data-sort") ?? cell?.textContent?.trim() ?? "";
}
function compareValues(a: string, b: string, type: string | null) {
if (a === "" && b === "") return 0;
if (a === "") return 1;
if (b === "") return -1;
if (type === "number") {
return Number(a) - Number(b);
}
return a.localeCompare(b, undefined, {
numeric: true,
sensitivity: "base",
});
}
function sortTable(
table: HTMLTableElement,
column: number,
direction: SortDirection,
) {
const tbody = table.tBodies[0];
const header = table.tHead?.rows[0]?.cells[column];
if (!tbody || !header) return;
const type = header.getAttribute("data-type");
const rows = Array.from(tbody.rows).filter(
(row) => !row.classList.contains("empty-row"),
);
rows.sort((rowA, rowB) => {
const comparison = compareValues(
getCellSortValue(rowA, column),
getCellSortValue(rowB, column),
type,
);
return direction === "asc" ? comparison : -comparison;
});
for (const row of rows) {
tbody.appendChild(row);
}
for (const sortable of table.querySelectorAll("th.sortable")) {
sortable.removeAttribute("aria-sort");
const indicator = sortable.querySelector(".sort-indicator");
if (indicator) indicator.textContent = "";
}
header.setAttribute(
"aria-sort",
direction === "asc" ? "ascending" : "descending",
);
const indicator = header.querySelector(".sort-indicator");
if (indicator) indicator.textContent = direction === "asc" ? "↑" : "↓";
}
for (const table of tables) {
const headers = Array.from(table.querySelectorAll<HTMLTableCellElement>("th"));
headers.forEach((header, column) => {
if (!header.classList.contains("sortable")) return;
header.addEventListener("click", () => {
const current = header.getAttribute("aria-sort");
const direction: SortDirection =
current === "ascending" ? "desc" : "asc";
sortTable(table, column, direction);
});
});
}
////////////////////
// Copy Buttons
////////////////////
const copyTimers = new WeakMap<
HTMLButtonElement,
ReturnType<typeof setTimeout>
>();
const pointerCopyTimes = new WeakMap<HTMLButtonElement, number>();
function writeClipboardWithSelection(value: string) {
let copied = false;
const onCopy = (event: ClipboardEvent) => {
event.clipboardData?.setData("text/plain", value);
event.preventDefault();
copied = true;
};
const textarea = document.createElement("textarea");
textarea.value = value;
textarea.setAttribute("readonly", "");
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.width = "1px";
textarea.style.height = "1px";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
window.focus();
textarea.focus();
textarea.select();
textarea.setSelectionRange(0, value.length);
document.addEventListener("copy", onCopy);
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(modelId);
return document.execCommand("copy") || copied;
} finally {
document.removeEventListener("copy", onCopy);
textarea.remove();
}
}
// Switch to check icon
const copyIcon = button.querySelector(".copy-icon") as HTMLElement;
const checkIcon = button.querySelector(".check-icon") as HTMLElement;
async function writeClipboard(value: string) {
if (writeClipboardWithSelection(value)) return true;
copyIcon.style.display = "none";
checkIcon.style.display = "block";
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(value);
return true;
} catch {
return false;
}
}
// Switch back after 1 second
setTimeout(() => {
return false;
}
function selectCopySource(button: HTMLButtonElement) {
const source = button
.closest(".code-line, td")
?.querySelector<HTMLElement>("code, .copy-source, span");
const selection = window.getSelection();
if (!source || !selection) return false;
const range = document.createRange();
range.selectNodeContents(source);
selection.removeAllRanges();
selection.addRange(range);
return true;
}
async function copyValue(button: HTMLButtonElement, value: string) {
const originalLabel =
button.dataset.copyLabel ??
button.getAttribute("aria-label") ??
button.title ??
"Copy";
button.dataset.copyLabel = originalLabel;
const copyIcon = button.querySelector<HTMLElement>(".copy-icon");
const checkIcon = button.querySelector<HTMLElement>(".check-icon");
const copied = await writeClipboard(value);
const selected = copied ? false : selectCopySource(button);
window.clearTimeout(copyTimers.get(button));
button.classList.toggle("copied", copied);
button.classList.toggle("selected", selected);
button.classList.toggle("copy-failed", !copied && !selected);
const feedback = copied ? "Copied" : selected ? "Selected" : "Copy failed";
button.setAttribute("aria-label", feedback);
button.title = feedback;
if (copyIcon && checkIcon) {
copyIcon.style.display = copied ? "none" : "block";
checkIcon.style.display = copied ? "block" : "none";
}
copyTimers.set(
button,
setTimeout(() => {
button.classList.remove("copied", "selected", "copy-failed");
button.setAttribute("aria-label", originalLabel);
button.title = originalLabel;
if (copyIcon && checkIcon) {
copyIcon.style.display = "block";
checkIcon.style.display = "none";
}, 1000);
}
} catch (err) {
console.error("Failed to copy text: ", err);
}
}
}, 1200),
);
}
document.addEventListener("click", (event) => {
if (!(event.target instanceof Element)) return;
const button = event.target.closest<HTMLButtonElement>(
".copy-button[data-model-id]"
function copyFromEventTarget(target: EventTarget | null) {
if (!(target instanceof Element)) return undefined;
const button = target.closest<HTMLButtonElement>(
".copy-button[data-copy-value]",
);
if (!button) return;
const value = button?.dataset.copyValue;
if (!button || !value) return undefined;
return { button, value };
}
const modelId = button.dataset.modelId;
if (modelId) void copyModelId(button, modelId);
document.addEventListener("pointerdown", (event) => {
const copy = copyFromEventTarget(event.target);
if (!copy) return;
pointerCopyTimes.set(copy.button, Date.now());
void copyValue(copy.button, copy.value);
});
document.addEventListener("click", (event) => {
const copy = copyFromEventTarget(event.target);
if (!copy) return;
const pointerCopyTime = pointerCopyTimes.get(copy.button);
if (pointerCopyTime && Date.now() - pointerCopyTime < 500) return;
void copyValue(copy.button, copy.value);
});
document.addEventListener("keydown", (event) => {
if (event.key !== "Enter" && event.key !== " ") return;
if (!(event.target instanceof Element)) return;
const copy = copyFromEventTarget(event.target);
if (!copy) return;
event.preventDefault();
void copyValue(copy.button, copy.value);
});
///////////////////////////////////
// Initialize State from URL
///////////////////////////////////
function initializeFromURL() {
const params = getQueryParams();
search.value = params.get("search") ?? "";
currentSort = { column: -1, direction: "asc" };
const columnName = params.get("sort");
if (columnName) {
const columnIndex = getColumnIndexByUrlName(columnName);
if (columnIndex !== -1) {
currentSort = {
column: columnIndex,
direction: params.get("order") === "desc" ? "desc" : "asc",
};
}
}
updateSortIndicators();
applyRows(false);
if (!search) return;
search.value = getQueryParams().get("search") ?? "";
filterRows();
}
function loadRows() {
try {
allRows = window.__TABLE_DATA__.map(prepareRow);
lockColumnWidths();
isLoaded = true;
initializeFromURL();
} catch (error) {
console.error(error);
isLoaded = true;
visibleRows = [];
renderStatusRow("Failed to load model data");
}
}
loadRows();
initializeFromURL();
window.addEventListener("popstate", initializeFromURL);
File diff suppressed because it is too large Load Diff
+40 -12
View File
@@ -1,11 +1,32 @@
import Index from "../index.html";
import { Models, Providers, Rendered } from "./render";
import { getRenderedPage, Models, Providers } from "./render";
import path from "path";
const assetPort = Number(Bun.env.ASSET_PORT ?? 16000);
Bun.serve({
port: 16_000,
port: assetPort,
routes: {
"/": Index,
"/src/*": (req) => {
const url = new URL(req.url);
const file = Bun.file(
path.join(import.meta.dir, "..", url.pathname.slice(1)),
);
return new Response(file);
},
"/favicon.svg": () =>
new Response(Bun.file(path.join(import.meta.dir, "..", "public/favicon.svg")), {
headers: {
"Content-Type": "image/svg+xml",
},
}),
"/social-share.png": () =>
new Response(Bun.file(path.join(import.meta.dir, "..", "public/social-share.png")), {
headers: {
"Content-Type": "image/png",
},
}),
"/assets/*": (req) => {
const file = Bun.file(
path.join(import.meta.dir, new URL(req.url).pathname)
@@ -72,6 +93,7 @@ Bun.serve({
const server = Bun.serve({
development: true,
hostname: "0.0.0.0",
port: Number(Bun.env.PORT ?? 3000),
async fetch(req) {
// Reject WebSocket upgrade requests
if (req.headers.get("upgrade") === "websocket") {
@@ -84,18 +106,24 @@ const server = Bun.serve({
}
const url = new URL(req.url);
url.host = "localhost:16000";
const result = fetch(url.toString(), req);
const rendered = getRenderedPage(url.pathname);
if (rendered !== undefined) {
const shellUrl = new URL(url);
shellUrl.host = `localhost:${assetPort}`;
shellUrl.pathname = "/";
shellUrl.search = "";
if (url.pathname !== "/") return result;
let html = await fetch(shellUrl.toString(), req).then((r) => r.text());
html = html.replace("<!--static-->", rendered);
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
}
let html = await result.then((r) => r.text());
html = html.replace("<!--static-->", Rendered);
return new Response(html, {
headers: {
"Content-Type": "text/html",
},
});
url.host = `localhost:${assetPort}`;
return fetch(url.toString(), req);
},
});
+21 -135
View File
@@ -1,33 +1,3 @@
export interface TableRow {
providerId: string;
providerName: string;
providerLogoSvg: string;
modelId: string;
modelName: string;
family?: string;
toolCall: boolean;
reasoning: boolean;
input: string[];
output: string[];
inputCost?: number;
outputCost?: number;
reasoningCost?: number;
cacheReadCost?: number;
cacheWriteCost?: number;
audioInputCost?: number;
audioOutputCost?: number;
contextLimit: number;
inputLimit?: number;
outputLimit: number;
structuredOutput?: boolean;
temperature: boolean;
openWeights: boolean;
weightLinks: TableLink[];
knowledge?: string;
releaseDate: string;
lastUpdated: string;
}
export interface TableLink {
label: string;
url: string;
@@ -61,14 +31,11 @@ export function escapeHtml(value: string | number) {
});
}
export function booleanText(value: boolean) {
export function booleanText(value?: boolean) {
if (value === undefined) return "-";
return value ? "Yes" : "No";
}
export function optionalBooleanText(value?: boolean) {
return value === undefined ? "-" : booleanText(value);
}
export function formatCost(cost?: number) {
return cost === undefined ? "-" : `$${cost.toFixed(2)}`;
}
@@ -81,26 +48,11 @@ export function knowledgeText(value?: string) {
return value ? value.substring(0, 7) : "-";
}
export function weightsText(value: boolean) {
export function weightsText(value?: boolean) {
if (value === undefined) return "-";
return value ? "Open" : "Closed";
}
export function renderWeights(
row: Pick<TableRow, "openWeights" | "weightLinks">
) {
if (!row.openWeights) return weightsText(false);
const link = row.weightLinks[0];
if (!link) return weightsText(true);
const title = link.title ?? link.label;
return `<a href="${escapeHtml(
link.url
)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(
title
)}">${weightsText(true)}</a>`;
}
export function renderModalityIcon(modality: string) {
const label =
modality === "pdf"
@@ -111,96 +63,30 @@ export function renderModalityIcon(modality: string) {
return `<span class="modality-icon" data-tooltip="${label}">${icon}</span>`;
}
export function renderModalities(modalities: string[]) {
export function renderModalities(modalities?: string[]) {
if (!modalities || modalities.length === 0) return "-";
return `<div class="modalities">${modalities
.map(renderModalityIcon)
.join("")}</div>`;
}
export function renderCopyButton(modelId: string) {
const escapedModelId = escapeHtml(modelId);
return `<button type="button" class="copy-button" data-model-id="${escapedModelId}" aria-label="Copy model ID"><svg class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="m4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg><svg class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display: none;"><polyline points="20,6 9,17 4,12"></polyline></svg></button>`;
export function costSummary(input?: number, output?: number) {
if (input === undefined && output === undefined) return "-";
return `${formatCost(input)} / ${formatCost(output)}`;
}
export function renderRow(row: TableRow, index: number) {
return `<tr data-index="${index}">
<td><div class="provider-cell">${row.providerLogoSvg}<span>${escapeHtml(
row.providerName
)}</span></div></td>
<td>${escapeHtml(row.modelName)}</td>
<td>${escapeHtml(row.family ?? "-")}</td>
<td>${escapeHtml(row.providerId)}</td>
<td><div class="model-id-cell"><span class="model-id-text">${escapeHtml(
row.modelId
)}</span>${renderCopyButton(row.modelId)}</div></td>
<td>${booleanText(row.toolCall)}</td>
<td>${booleanText(row.reasoning)}</td>
<td>${renderModalities(row.input)}</td>
<td>${renderModalities(row.output)}</td>
<td>${formatCost(row.inputCost)}</td>
<td>${formatCost(row.outputCost)}</td>
<td>${formatCost(row.reasoningCost)}</td>
<td>${formatCost(row.cacheReadCost)}</td>
<td>${formatCost(row.cacheWriteCost)}</td>
<td>${formatCost(row.audioInputCost)}</td>
<td>${formatCost(row.audioOutputCost)}</td>
<td>${formatNumber(row.contextLimit)}</td>
<td>${formatNumber(row.inputLimit)}</td>
<td>${formatNumber(row.outputLimit)}</td>
<td>${optionalBooleanText(row.structuredOutput)}</td>
<td>${booleanText(row.temperature)}</td>
<td>${renderWeights(row)}</td>
<td>${knowledgeText(row.knowledge)}</td>
<td>${escapeHtml(row.releaseDate)}</td>
<td>${escapeHtml(row.lastUpdated)}</td>
</tr>`;
export function capabilitySummary(capabilities: Array<[string, boolean | undefined]>) {
const active = capabilities
.filter(([, value]) => value === true)
.map(([label]) => label);
return active.length > 0 ? active.join(", ") : "-";
}
export function getLargestRow(rows: TableRow[]): TableRow {
const worst: TableRow = {
providerId: "", providerName: "", providerLogoSvg: "", modelId: "", modelName: "",
toolCall: true, reasoning: true,
input: [], output: [],
contextLimit: 0, outputLimit: 0,
structuredOutput: true, temperature: true, openWeights: false,
weightLinks: [],
releaseDate: "", lastUpdated: "",
};
for (const row of rows) {
if (row.providerName.length > worst.providerName.length) worst.providerName = row.providerName;
if (row.modelName.length > worst.modelName.length) worst.modelName = row.modelName;
if ((row.family ?? "").length > (worst.family ?? "").length) worst.family = row.family;
if (row.providerId.length > worst.providerId.length) worst.providerId = row.providerId;
if (row.modelId.length > worst.modelId.length) worst.modelId = row.modelId;
if ((row.knowledge ?? "").length > (worst.knowledge ?? "").length) worst.knowledge = row.knowledge;
if (row.releaseDate.length > worst.releaseDate.length) worst.releaseDate = row.releaseDate;
if (row.lastUpdated.length > worst.lastUpdated.length) worst.lastUpdated = row.lastUpdated;
if (row.input.length > worst.input.length) worst.input = row.input;
if (row.output.length > worst.output.length) worst.output = row.output;
if (
renderWeights(row).length > renderWeights(worst).length
) {
worst.openWeights = row.openWeights;
worst.weightLinks = row.weightLinks;
}
const costWider = (a: number | undefined, b: number | undefined) =>
b !== undefined && (a === undefined || formatCost(b).length > formatCost(a).length);
if (costWider(worst.inputCost, row.inputCost)) worst.inputCost = row.inputCost;
if (costWider(worst.outputCost, row.outputCost)) worst.outputCost = row.outputCost;
if (costWider(worst.reasoningCost, row.reasoningCost)) worst.reasoningCost = row.reasoningCost;
if (costWider(worst.cacheReadCost, row.cacheReadCost)) worst.cacheReadCost = row.cacheReadCost;
if (costWider(worst.cacheWriteCost, row.cacheWriteCost)) worst.cacheWriteCost = row.cacheWriteCost;
if (costWider(worst.audioInputCost, row.audioInputCost)) worst.audioInputCost = row.audioInputCost;
if (costWider(worst.audioOutputCost, row.audioOutputCost)) worst.audioOutputCost = row.audioOutputCost;
const numWider = (a: number | undefined, b: number | undefined) =>
b !== undefined && (a === undefined || formatNumber(b).length > formatNumber(a).length);
if (numWider(worst.contextLimit as number | undefined, row.contextLimit)) worst.contextLimit = row.contextLimit;
if (numWider(worst.inputLimit, row.inputLimit)) worst.inputLimit = row.inputLimit;
if (numWider(worst.outputLimit as number | undefined, row.outputLimit)) worst.outputLimit = row.outputLimit;
}
return worst;
export function sortDate(value?: string) {
return value ?? "";
}
export function sortNumber(value?: number) {
return value === undefined ? "" : String(value);
}
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
@@ -1,6 +1,6 @@
# Auto-generated by generate-chutes.ts — do not edit pricing, limits, or capabilities.
# Manual overrides preserved on re-run: name, family, knowledge, interleaved, status
base_model = "zai/glm-4.7"
base_model = "zhipuai/glm-4.7"
name = "GLM 4.7 TEE"
structured_output = true
@@ -1,6 +1,6 @@
# Auto-generated by generate-chutes.ts — do not edit pricing, limits, or capabilities.
# Manual overrides preserved on re-run: name, family, knowledge, interleaved, status
base_model = "zai/glm-5"
base_model = "zhipuai/glm-5"
name = "GLM 5 TEE"
structured_output = true
@@ -1,6 +1,6 @@
# Auto-generated by generate-chutes.ts — do not edit pricing, limits, or capabilities.
# Manual overrides preserved on re-run: name, family, knowledge, interleaved, status
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
name = "GLM 5.1 TEE"
[interleaved]
+1 -1
View File
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.7"
base_model = "zhipuai/glm-4.7"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5"
base_model = "zhipuai/glm-5"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
name = "GLM 5.1"
[interleaved]
+1 -1
View File
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
name = "GLM 5.1"
knowledge = "2025-04"
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5-air"
base_model = "zhipuai/glm-4.5-air"
[cost]
input = 0.2
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5"
base_model = "zhipuai/glm-4.5"
[cost]
input = 0.6
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.6"
base_model = "zhipuai/glm-4.6"
[cost]
input = 0.6
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.7-flashx"
base_model = "zhipuai/glm-4.7-flashx"
[cost]
input = 0.07
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.7"
base_model = "zhipuai/glm-4.7"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5-turbo"
base_model = "zhipuai/glm-5-turbo"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5"
base_model = "zhipuai/glm-5"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5-air"
base_model = "zhipuai/glm-4.5-air"
structured_output = false
[cost]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5-air"
base_model = "zhipuai/glm-4.5-air"
name = "GLM 4.5 Air (free)"
structured_output = false
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5"
base_model = "zhipuai/glm-4.5"
structured_output = false
[cost]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5v"
base_model = "zhipuai/glm-4.5v"
structured_output = false
[cost]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.6"
base_model = "zhipuai/glm-4.6"
structured_output = true
[cost]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.6v"
base_model = "zhipuai/glm-4.6v"
structured_output = false
[cost]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.7-flash"
base_model = "zhipuai/glm-4.7-flash"
structured_output = true
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.7"
base_model = "zhipuai/glm-4.7"
structured_output = true
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-5-turbo"
base_model = "zhipuai/glm-5-turbo"
structured_output = false
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
+1 -1
View File
@@ -1,4 +1,4 @@
base_model = "zai/glm-5"
base_model = "zhipuai/glm-5"
structured_output = true
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-5v-turbo"
base_model = "zhipuai/glm-5v-turbo"
structured_output = false
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5-air"
base_model = "zhipuai/glm-4.5-air"
[cost]
input = 0.2
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.5"
base_model = "zhipuai/glm-4.5"
[cost]
input = 0.6
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.6"
base_model = "zhipuai/glm-4.6"
[cost]
input = 0.6
@@ -1,4 +1,4 @@
base_model = "zai/glm-4.7"
base_model = "zhipuai/glm-4.7"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
+1 -1
View File
@@ -1,4 +1,4 @@
base_model = "zai/glm-5"
base_model = "zhipuai/glm-5"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
name = "GLM 5.1 6bit"
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
name = "GLM 5.1"
[interleaved]
+1 -1
View File
@@ -1,4 +1,4 @@
name = "StepFun"
name = "StepFun AI"
env = ["STEPFUN_API_KEY"]
npm = "@ai-sdk/openai-compatible"
doc = "https://platform.stepfun.ai/docs/en/step-plan/integrations/open-code"
+1 -1
View File
@@ -1,4 +1,4 @@
name = "StepFun (China)"
name = "StepFun"
env = ["STEPFUN_API_KEY"]
npm = "@ai-sdk/openai-compatible"
doc = "https://platform.stepfun.com/docs/zh/overview/concept"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
name = "GLM 5.1"
[interleaved]
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
+1 -1
View File
@@ -1,4 +1,4 @@
base_model = "zai/glm-5.1"
base_model = "zhipuai/glm-5.1"
[interleaved]
field = "reasoning_content"
@@ -1,4 +1,4 @@
base_model = "zai/glm-5v-turbo"
base_model = "zhipuai/glm-5v-turbo"
[interleaved]
field = "reasoning_content"