Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0ed0e62df |
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+266
-358
@@ -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);
|
||||
|
||||
+962
-314
File diff suppressed because it is too large
Load Diff
+40
-12
@@ -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
@@ -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,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,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,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,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,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,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,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"
|
||||
|
||||
Reference in New Issue
Block a user