docs/index.html
Rama master · réplica de lectura del repositorio original.
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DevinAPI · 把 Devin 雲端的 AI 模型變成 OpenAI + Anthropic + Gemini 端點</title>
<meta name="description" content="WindsurfAPI 已進化為 DevinAPI:無頭直連 Devin 雲端的百餘個 AI 模型,同时兼容 /v1/chat/completions、/v1/responses、/v1/messages 与 Gemini 四套协议。Claude Code、Cursor、Cline 直接連。零 npm 依賴,無需二進位。">
<meta name="theme-color" content="#FAF9F5">
<meta property="og:title" content="DevinAPI">
<meta property="og:description" content="從 Windsurf 代理進化成 Devin 雲端代理 · 四协议 · 零依赖 · 无需二进位">
<meta property="og:type" content="website">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ctext y='26' font-size='26'%3E%E3%80%B0%EF%B8%8F%3C/text%3E%3C/svg%3E">
<style>
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
/* ── Theme tokens as @property so a light/dark switch recolors the whole
surface as one coherent, interpolable state change (the terminal + reveals
never freeze). Same technique the spec template uses. Colors are declared
interpolable here; the actual values live in :root (light) and
[data-theme="dark"] below. ── */
@property --bg{syntax:'<color>';inherits:true;initial-value:#FAF9F5}
@property --bg-alt{syntax:'<color>';inherits:true;initial-value:#F5F2EB}
@property --paper{syntax:'<color>';inherits:true;initial-value:#FFFFFF}
@property --ink{syntax:'<color>';inherits:true;initial-value:#1A1612}
@property --ink-soft{syntax:'<color>';inherits:true;initial-value:#3D362C}
@property --muted{syntax:'<color>';inherits:true;initial-value:#6B6556}
@property --muted-soft{syntax:'<color>';inherits:true;initial-value:#A09888}
@property --rule{syntax:'<color>';inherits:true;initial-value:#E7E2D4}
@property --rule-soft{syntax:'<color>';inherits:true;initial-value:#EFEBDF}
@property --coral-soft{syntax:'<color>';inherits:true;initial-value:#F2DCD0}
@property --coral-wash{syntax:'<color>';inherits:true;initial-value:#FBF1EB}
:root{
--bg:#FAF9F5;
--bg-alt:#F5F2EB;
--paper:#FFFFFF;
--ink:#1A1612;
--ink-soft:#3D362C;
--muted:#6B6556;
--muted-soft:#A09888;
--rule:#E7E2D4;
--rule-soft:#EFEBDF;
--coral:#CC785C;
--coral-deep:#A05234;
--coral-soft:#F2DCD0;
--coral-wash:#FBF1EB;
--plum:#8B6FB0;
--sand:#D4A574;
--nav-bg:rgba(250,249,245,.92);
--shadow-rgb:26,22,18;
/* code blocks — soft warm surface in light, near-black terminal in dark */
--code-bg:#F4EFE6;
--code-bg-head:#EDE6D8;
--code-border:#E2D9C8;
--code-fg:#4A4034;
--code-str:#6E8A4E;
--code-flag:#A9762A;
--code-cmd:#3D362C;
--code-com:#9B9078;
--code-key:#9A6A24;
--serif:'Charter','Iowan Old Style','Palatino Linotype','Palatino','URW Palladio L','P052','Noto Serif TC',Georgia,serif;
--sans:'Helvetica Neue','Segoe UI',-apple-system,BlinkMacSystemFont,'PingFang TC','Microsoft JhengHei',system-ui,sans-serif;
--mono:'SF Mono','JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
}
/* Dark surface — warm charcoal, keeps the coral accent legible on dark. */
:root[data-theme="dark"]{
--bg:#17140F;
--bg-alt:#1E1A14;
--paper:#211D16;
--ink:#F0EADD;
--ink-soft:#CFC7B6;
--muted:#9B9384;
--muted-soft:#6E675A;
--rule:#332D23;
--rule-soft:#2A251D;
--coral:#E0956F;
--coral-deep:#EBA983;
--coral-soft:#3A2A20;
--coral-wash:#241a13;
--nav-bg:rgba(23,20,15,.92);
--shadow-rgb:0,0,0;
/* dark theme: keep the deep terminal look for code */
--code-bg:#1E1B17;
--code-bg-head:#242019;
--code-border:#2A2520;
--code-fg:#E8E2D4;
--code-str:#B8C99A;
--code-flag:#D4A574;
--code-cmd:#E8E2D4;
--code-com:#706A5C;
--code-key:#D4A574;
}
/* Recolor as one register-flip once the first paint is done (so we don't
animate from @property initial-value on load). Reduced-motion → instant. */
html[data-theme-ready]{transition:--bg .22s linear,--bg-alt .22s linear,--paper .22s linear,--ink .22s linear,--ink-soft .22s linear,--muted .22s linear,--muted-soft .22s linear,--rule .22s linear,--rule-soft .22s linear,--coral-soft .22s linear,--coral-wash .22s linear}
@media(prefers-reduced-motion:reduce){html[data-theme-ready]{transition:none}}
/* Theme toggle button in the nav */
.theme-toggle{width:34px;height:34px;border-radius:6px;border:1px solid var(--rule);background:transparent;color:var(--ink-soft);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;transition:background .15s,border-color .15s;flex:none}
.theme-toggle:hover{background:var(--bg-alt);border-color:var(--muted-soft)}
.theme-toggle svg{width:16px;height:16px}
.theme-toggle .icon-moon{display:none}
:root[data-theme="dark"] .theme-toggle .icon-sun{display:none}
:root[data-theme="dark"] .theme-toggle .icon-moon{display:inline}
body{
background:var(--bg);color:var(--ink);
font-family:var(--sans);font-size:16.5px;line-height:1.72;
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
letter-spacing:.01em;
}
body::before{
content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
background:
radial-gradient(ellipse at 25% 0%,rgba(204,120,92,.03),transparent 55%),
radial-gradient(ellipse at 80% 100%,rgba(139,111,176,.02),transparent 50%);
}
body::after{
content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.02'/%3E%3C/svg%3E");
background-size:200px 200px;
}
a{color:var(--coral);text-decoration:none;transition:color .2s ease}
a:hover{color:var(--coral-deep)}
code{font-family:var(--mono);font-size:.88em;background:var(--rule-soft);padding:.1em .35em;border-radius:3px;color:var(--ink-soft)}
.container{max-width:1080px;margin:0 auto;padding:0 2rem;position:relative}
.narrow{max-width:780px;margin:0 auto;padding:0 2rem}
section{padding:5.5rem 0}
h1,h2,h3,h4{font-family:var(--serif);font-weight:500;letter-spacing:-.015em;color:var(--ink)}
.kicker{font-family:var(--sans);font-size:.74rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--coral);margin-bottom:.9rem;opacity:.85}
.section-title{font-size:2.4rem;line-height:1.14;margin-bottom:.9rem;font-weight:500}
.section-title em{font-style:italic;color:var(--coral);font-weight:400}
.section-sub{color:var(--muted);font-size:1.02rem;max-width:540px;margin-bottom:2.5rem;line-height:1.65}
/* ── Nav ── */
nav{position:sticky;top:0;z-index:50;background:var(--nav-bg);border-bottom:1px solid var(--rule);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
nav .nav-inner{max-width:1080px;margin:0 auto;padding:.8rem 2rem;display:flex;align-items:center;justify-content:space-between;gap:1rem}
nav .logo{font-family:var(--serif);font-size:1.1rem;font-weight:500;color:var(--ink);display:flex;align-items:center;gap:.5rem}
nav .nav-links{display:flex;gap:1.4rem;font-size:.88rem}
nav .nav-links a{color:var(--muted);font-weight:500;position:relative}
nav .nav-links a::after{content:"";position:absolute;left:0;bottom:-2px;width:0;height:1.5px;background:var(--coral);transition:width .25s ease}
nav .nav-links a:hover{color:var(--ink)}
nav .nav-links a:hover::after{width:100%}
nav .nav-cta{font-size:.84rem;padding:.45rem .9rem;border-radius:6px;background:var(--paper);color:var(--ink);border:1px solid var(--rule);font-weight:500;display:inline-flex;align-items:center;gap:.35rem;transition:border-color .15s,color .15s}
nav .nav-cta:hover{border-color:var(--coral);color:var(--coral-deep)}
@media(max-width:720px){nav .nav-links{display:none}}
/* ── Hero ── */
.hero{padding:6rem 0 5rem;border-bottom:1px solid var(--rule)}
.hero-inner{max-width:680px}
.hero-badge{display:inline-flex;align-items:center;gap:.4rem;padding:.3rem .7rem;border-radius:4px;background:var(--coral-wash);color:var(--coral-deep);font-size:.78rem;font-weight:500;margin-bottom:1.8rem;border:1px solid var(--coral-soft)}
.hero h1{font-size:clamp(2.4rem,5vw,3.6rem);line-height:1.06;margin-bottom:1.5rem;letter-spacing:-.025em;font-weight:500}
.hero h1 em{font-style:italic;color:var(--coral);font-weight:400}
.hero h1 .hl{background:linear-gradient(180deg,transparent 62%,var(--coral-soft) 62%,var(--coral-soft) 92%,transparent 92%);padding:0 .06em}
.hero .lede{font-size:1.12rem;color:var(--ink-soft);line-height:1.6;max-width:560px;margin-bottom:2rem}
.hero-cta{display:flex;gap:.7rem;flex-wrap:wrap;margin-bottom:1.8rem}
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.7rem 1.2rem;border-radius:6px;font-size:.92rem;font-weight:500;cursor:pointer;transition:all .15s;border:1px solid transparent;text-decoration:none}
.btn-primary{background:var(--ink);color:var(--bg)}
.btn-primary:hover{background:var(--coral-deep);color:#fff;transform:translateY(-1px);box-shadow:0 6px 20px -6px rgba(160,82,52,.35)}
.btn-ghost{background:transparent;color:var(--ink);border-color:var(--rule)}
.btn-ghost:hover{border-color:var(--muted-soft);transform:translateY(-1px)}
.btn svg{width:14px;height:14px}
.hero-meta{display:flex;gap:1.2rem;flex-wrap:wrap;font-size:.84rem;color:var(--muted)}
.hero-meta span{display:inline-flex;align-items:center;gap:.3rem}
/* ── Hero terminal (live typing) ── */
.hero-term{margin-top:2rem;max-width:600px;border:1px solid var(--code-border);border-radius:10px;overflow:hidden;background:var(--code-bg);box-shadow:0 10px 30px -12px rgba(var(--shadow-rgb),.25)}
.hero-term-head{display:flex;align-items:center;gap:.5rem;padding:.6rem .9rem;background:var(--code-bg-head);border-bottom:1px solid var(--code-border)}
.hero-term-dot{width:11px;height:11px;border-radius:50%}
.hero-term-dot.r{background:#E06C5A}.hero-term-dot.y{background:#D9A44C}.hero-term-dot.g{background:#8FB86B}
.hero-term-title{margin-left:.4rem;font-family:var(--mono);font-size:.72rem;color:var(--code-com);letter-spacing:.04em}
.hero-term-body{padding:1rem 1.1rem;font-family:var(--mono);font-size:.82rem;line-height:1.7;color:var(--code-fg);white-space:pre-wrap;word-break:break-word;min-height:118px}
.hero-term-body .c-prompt{color:var(--coral);font-weight:600}
.hero-term-body .c-cmd{color:var(--code-cmd);font-weight:500}
.hero-term-body .c-flag{color:var(--code-flag)}
.hero-term-body .c-str{color:var(--code-str)}
.hero-term-body .c-com{color:var(--code-com);font-style:italic}
.hero-term-body .c-ok{color:#5E8A4E}
.hero-term-caret{display:inline-block;width:7px;height:14px;background:var(--code-fg);margin-left:2px;vertical-align:-2px;animation:term-blink 1.1s steps(1,end) infinite}
@keyframes term-blink{0%,54%{opacity:1}55%,100%{opacity:0}}
@media(prefers-reduced-motion:reduce){.hero-term-caret{animation:none}}
/* ── Stats ── */
.stats{background:var(--bg-alt);border-bottom:1px solid var(--rule);padding:3rem 0}
.stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--rule);border:1px solid var(--rule);border-radius:10px;overflow:hidden}
@media(max-width:760px){.stats-grid{grid-template-columns:repeat(2,1fr)}}
.stat{background:var(--paper);padding:1.8rem 1.2rem;text-align:center}
.stat .num{font-family:var(--serif);font-size:3.2rem;line-height:1;font-weight:500;color:var(--ink);letter-spacing:-.03em}
.stat .label{margin-top:.4rem;color:var(--muted);font-size:.84rem;font-weight:500}
/* ── Dual Protocol ── */
.proto-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.2rem;margin-top:1.5rem}
@media(max-width:860px){.proto-grid{grid-template-columns:1fr}}
.proto-card{background:var(--paper);border:1px solid var(--rule);border-radius:10px;overflow:hidden;transition:transform .2s ease,box-shadow .2s ease}
.proto-card:hover{transform:translateY(-2px);box-shadow:0 8px 24px -8px rgba(26,22,18,.08)}
.proto-head{padding:.9rem 1.1rem;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--rule)}
.proto-head .proto-brand{display:flex;align-items:center;gap:.5rem}
.proto-head .logo-chip{width:24px;height:24px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;font-family:var(--serif);font-size:.72rem;font-weight:600;color:#fff}
.proto-head .logo-chip.openai{background:#10A37F;color:#fff}
.proto-head .logo-chip.anthropic{background:var(--coral);color:#fff}
.proto-head .logo-chip svg{width:15px;height:15px;display:block}
.proto-head h4{font-family:var(--sans);font-size:.92rem;font-weight:600}
.proto-head .proto-meta{font-size:.74rem;color:var(--muted);font-family:var(--mono)}
.proto-body{background:var(--code-bg);padding:1rem 1.2rem;font-family:var(--mono);font-size:.76rem;line-height:1.7;color:var(--code-fg);overflow-x:auto;white-space:pre-wrap;word-break:break-all;border-top:1px solid var(--code-border)}
.proto-body .c-str{color:var(--code-str)}
.proto-body .c-key{color:var(--code-key)}
.proto-body .c-cmd{color:var(--code-cmd);font-weight:500}
.proto-body .c-flag{color:var(--code-flag)}
.proto-body .c-prompt{color:var(--coral);font-weight:600}
.proto-body .c-com{color:var(--code-com);font-style:italic}
.note{background:var(--coral-wash);border-left:3px solid var(--coral);padding:.9rem 1.1rem;border-radius:0 6px 6px 0;margin-top:1.5rem;font-size:.88rem;color:var(--ink-soft);line-height:1.6}
.note strong{color:var(--coral-deep)}
/* ── Models ── */
.models-section{background:var(--bg-alt)}
.model-card[data-provider="anthropic"]{--provider-color:#CC785C}
.model-card[data-provider="openai"]{--provider-color:#10A37F}
.model-card[data-provider="google"]{--provider-color:#4285F4}
.model-card[data-provider="deepseek"]{--provider-color:#5E6AD2}
.model-card[data-provider="xai"]{--provider-color:#1F1F1F}
.model-card[data-provider="qwen"]{--provider-color:#A855F7}
.model-card[data-provider="moonshot"]{--provider-color:#000}
.model-card[data-provider="zhipu"]{--provider-color:#3B82F6}
.model-card[data-provider="minimax"]{--provider-color:#FF6B35}
.model-card[data-provider="astraflow"]{--provider-color:#0EA5A5}
.model-card[data-provider="windsurf"]{--provider-color:#6B6556}
/* ===== Models — grouped catalog, dark-mode safe (tokens only) ===== */
.ms-model{--ms-model-gap:14px}
.ms-model-head{max-width:720px}
.ms-model-title{margin:0;font-family:var(--font-serif,Charter,Georgia,serif);font-size:clamp(28px,3.4vw,42px);line-height:1.08;letter-spacing:-.02em;color:var(--ink)}
.ms-model-sub{margin:16px 0 0;font-size:clamp(15px,1.4vw,17px);line-height:1.62;color:var(--muted);text-wrap:pretty}
.ms-model-sub b{color:var(--ink-soft);font-weight:600}
/* filter bar */
.ms-model-filters{display:flex;flex-wrap:wrap;gap:8px;margin:28px 0 36px}
.ms-model-filter{display:inline-flex;align-items:center;gap:7px;font:500 13px/1 var(--font-mono,ui-monospace,monospace);color:var(--muted);background:var(--paper);border:1px solid var(--rule);border-radius:999px;padding:8px 13px;cursor:pointer;transition:color .16s ease,background-color .16s ease,border-color .16s ease}
.ms-model-filter:hover{color:var(--ink);border-color:var(--muted-soft)}
.ms-model-filter:focus-visible{outline:2px solid var(--coral);outline-offset:2px}
.ms-model-filter[aria-pressed="true"],.ms-model-filter.is-active{color:var(--coral-deep);background:var(--coral-wash);border-color:var(--coral)}
.ms-model-filter .ms-count{font-variant-numeric:tabular-nums;color:var(--muted-soft);background:var(--bg-alt);border-radius:999px;padding:1px 7px;font-size:11.5px}
.ms-model-filter[aria-pressed="true"] .ms-count,.ms-model-filter.is-active .ms-count{color:var(--coral-deep);background:var(--coral-soft)}
/* ===== Models — compact catalog table (no cards, no glow) ===== */
.ms-model-groups{border:1px solid var(--rule);border-radius:12px;overflow:hidden;background:var(--paper)}
.ms-model-group{--brand:var(--provider-color,var(--muted-soft));padding:0;border-bottom:1px solid var(--rule)}
.ms-model-group:last-child{border-bottom:0}
.ms-model-group.is-empty{display:none}
/* provider row header — clickable band that collapses the whole group */
.ms-model-group-head{display:flex;align-items:center;gap:11px;width:100%;padding:11px 18px;background:var(--bg-alt);
border-left:3px solid var(--brand);border-top:0;border-right:0;border-bottom:0;cursor:pointer;
text-align:left;font-family:inherit;color:inherit;transition:background-color .14s ease}
.ms-model-group-head:hover{background:color-mix(in srgb,var(--brand) 7%,var(--bg-alt))}
.ms-model-group-head:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
.ms-group-chev{width:15px;height:15px;flex:none;color:var(--muted-soft);transform:rotate(90deg);transition:transform .2s ease}
.ms-model-group.is-collapsed .ms-group-chev{transform:rotate(0deg)}
.ms-model-group.is-collapsed .ms-fam-list{display:none}
.ms-model-logo{width:24px;height:24px;border-radius:6px;background:#fff;border:1px solid var(--rule);
display:inline-flex;align-items:center;justify-content:center;flex:none}
.ms-model-logo svg{width:15px;height:15px;display:block}
.ms-model-logo.is-mono{font:800 10px/1 var(--font-mono,ui-monospace,monospace);color:var(--brand);letter-spacing:-.02em}
.ms-model-group-titles{display:flex;align-items:baseline;gap:9px;min-width:0}
.ms-model-group-name{font:700 13.5px/1 var(--font-sans,system-ui,sans-serif);letter-spacing:-.01em;color:var(--ink)}
.ms-model-group-count{font:500 11px/1 var(--font-mono,ui-monospace,monospace);color:var(--muted);
letter-spacing:.02em;font-variant-numeric:tabular-nums}
.ms-model-group-count b{color:var(--ink-soft);font-weight:700}
/* family rows — flat table rows, hover tints faintly */
.ms-fam-list{display:flex;flex-direction:column}
.ms-fam{border-top:1px solid var(--rule-soft)}
.ms-fam:first-child{border-top:0}
.ms-fam[hidden]{display:none}
.ms-fam-row{display:flex;align-items:center;gap:12px;width:100%;padding:9px 18px 9px 34px;background:none;border:0;
cursor:pointer;text-align:left;font-family:inherit;color:inherit;transition:background-color .12s ease}
.ms-fam-row:hover{background:var(--bg-alt)}
.ms-fam-row:focus-visible{outline:2px solid var(--brand);outline-offset:-2px}
.ms-fam-single{cursor:default}
.ms-fam-single:hover{background:none}
.ms-fam-dot{width:5px;height:5px;border-radius:50%;background:var(--brand);flex:none}
.ms-fam-name{font:500 13px/1.3 var(--font-mono,ui-monospace,monospace);color:var(--ink)}
.ms-fam-badges{display:inline-flex;gap:5px}
.ms-fam-badge{font:600 9px/1 var(--font-mono,ui-monospace,monospace);letter-spacing:.02em;padding:2px 5px;border-radius:4px;white-space:nowrap}
.ms-fam-badge.is-think{color:var(--coral-deep);background:var(--coral-wash)}
.ms-fam-badge.is-free{color:var(--coral-deep);background:var(--coral-wash);border:1px solid var(--coral-soft)}
.ms-fam-badge.is-ctx{color:var(--muted);background:var(--rule-soft)}
.ms-fam-spacer{flex:1}
.ms-fam-cr{font:500 11px/1 var(--font-mono,ui-monospace,monospace);color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.ms-fam-cr b{color:var(--ink-soft);font-weight:700}
.ms-fam-chev{width:14px;height:14px;flex:none;color:var(--muted-soft);transition:transform .18s ease}
.ms-fam.is-open .ms-fam-chev{transform:rotate(90deg);color:var(--brand)}
.ms-fam-variants{display:none;padding:0 18px 8px 51px}
.ms-fam.is-open .ms-fam-variants{display:block}
.ms-fam-var{display:flex;align-items:center;gap:10px;padding:5px 0;border-bottom:1px dashed var(--rule-soft)}
.ms-fam-var:last-child{border-bottom:0}
.ms-fam-var-name{font:500 11.5px/1.3 var(--font-mono,ui-monospace,monospace);color:var(--ink-soft);overflow-wrap:anywhere}
.ms-fam-var .ms-fam-spacer{min-width:8px}
.ms-fam-var-cr{font:500 11px/1 var(--font-mono,ui-monospace,monospace);color:var(--muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.ms-fam-var-cr b{color:var(--ink-soft);font-weight:700}
@media (prefers-reduced-motion:reduce){.ms-fam-row,.ms-fam-chev,.ms-model-filter{transition:none}}
/* ── Architecture (adapts to theme; coral accents on both) ── */
.arch-section{background:var(--bg-alt);color:var(--ink);padding:5rem 0 4rem;position:relative;overflow:hidden}
.arch-section::before{content:"";position:absolute;inset:0;background:radial-gradient(ellipse at 50% 40%,rgba(204,120,92,.06),transparent 65%),radial-gradient(ellipse at 20% 80%,rgba(139,111,176,.03),transparent 50%);pointer-events:none}
.arch-section .kicker{color:var(--coral);opacity:.9}
.arch-section .section-title{color:var(--ink)}
.arch-section .section-title em{color:var(--coral)}
.arch-section .section-sub{color:var(--muted)}
.arch-canvas{position:relative;margin-top:2rem;min-height:520px}
@media(max-width:860px){.arch-canvas{min-height:auto}}
.arch-row{display:flex;align-items:center;justify-content:center;gap:0;position:relative;padding:0 1rem}
@media(max-width:860px){.arch-row{flex-direction:column;gap:2rem}}
.arch-nd{position:relative;text-align:center;z-index:2;flex-shrink:0}
.arch-nd-box{border:1px solid var(--rule);border-radius:16px;padding:1.4rem 1.6rem;background:var(--paper);transition:border-color .3s,box-shadow .3s}
.arch-nd:hover .arch-nd-box{border-color:color-mix(in srgb,var(--coral) 40%,var(--rule));box-shadow:0 0 30px -10px rgba(204,120,92,.2)}
.arch-nd-icon{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;margin:0 auto .7rem;font-size:1.2rem;border:1px solid var(--rule)}
.arch-nd-label{font-family:var(--mono);font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-bottom:.3rem}
.arch-nd-title{font-family:var(--serif);font-size:1.05rem;font-weight:500;color:var(--ink);margin-bottom:.2rem}
.arch-nd-sub{font-size:.75rem;color:var(--muted-soft);line-height:1.5}
.arch-nd.hero-node .arch-nd-box{border-color:color-mix(in srgb,var(--coral) 45%,var(--rule));background:var(--coral-wash);padding:1.8rem 2rem}
.arch-nd.hero-node .arch-nd-icon{width:56px;height:56px;border-radius:14px;background:color-mix(in srgb,var(--coral) 15%,transparent);border-color:color-mix(in srgb,var(--coral) 35%,var(--rule));font-size:1.5rem}
.arch-nd.hero-node .arch-nd-title{font-size:1.3rem;color:var(--coral-deep)}
.arch-hero-glow{position:absolute;width:280px;height:280px;top:50%;left:50%;transform:translate(-50%,-50%);border-radius:50%;background:radial-gradient(circle,rgba(204,120,92,.12) 0%,transparent 70%);animation:a-pulse 4s ease-in-out infinite;pointer-events:none;z-index:0}
@keyframes a-pulse{0%,100%{transform:translate(-50%,-50%) scale(.9);opacity:.6}50%{transform:translate(-50%,-50%) scale(1.15);opacity:1}}
.arch-conn{flex:1;min-width:40px;height:40px;position:relative;display:flex;align-items:center;z-index:1}
@media(max-width:860px){.arch-conn{min-width:0;width:2px;min-height:40px;height:40px;flex:0;flex-direction:column;justify-content:center}}
.arch-conn-line{position:absolute;top:50%;left:0;right:0;height:2px;background:linear-gradient(90deg,rgba(204,120,92,.08),rgba(204,120,92,.25),rgba(204,120,92,.08))}
@media(max-width:860px){.arch-conn-line{top:0;bottom:0;left:50%;width:2px;height:100%;background:linear-gradient(180deg,rgba(204,120,92,.08),rgba(204,120,92,.25),rgba(204,120,92,.08))}}
.arch-conn-dot{position:absolute;width:6px;height:6px;border-radius:50%;background:#CC785C;box-shadow:0 0 8px 2px rgba(204,120,92,.4);animation:a-flow 2.5s ease-in-out infinite}
@media(max-width:860px){.arch-conn-dot{animation-name:a-flow-v}}
.arch-conn:nth-of-type(2) .arch-conn-dot:not(.ret){animation-delay:.6s}
.arch-conn:nth-of-type(3) .arch-conn-dot:not(.ret){animation-delay:1.2s}
.arch-conn:nth-of-type(1) .arch-conn-dot.ret{animation-delay:1.5s}
.arch-conn:nth-of-type(2) .arch-conn-dot.ret{animation-delay:.9s}
.arch-conn:nth-of-type(3) .arch-conn-dot.ret{animation-delay:.3s}
@keyframes a-flow{0%{left:0;opacity:0}10%{opacity:1}90%{opacity:1}100%{left:calc(100% - 6px);opacity:0}}
@keyframes a-flow-v{0%{top:0;opacity:0}10%{opacity:1}90%{opacity:1}100%{top:calc(100% - 6px);opacity:0}}
.arch-conn-proto{position:absolute;bottom:-20px;left:50%;transform:translateX(-50%);font-family:var(--mono);font-size:.65rem;color:var(--muted);letter-spacing:.06em;white-space:nowrap;background:var(--bg-alt);border:1px solid var(--rule);padding:.1rem .5rem;border-radius:3px}
@media(max-width:860px){.arch-conn-proto{bottom:auto;top:50%;left:calc(50% + 14px);transform:translateY(-50%) translateX(0)}}
.arch-conn-dot.ret{background:#8B6FB0;box-shadow:0 0 8px 2px rgba(139,111,176,.4);animation:a-flow-ret 3s ease-in-out infinite}
@keyframes a-flow-ret{0%{left:calc(100% - 6px);opacity:0}10%{opacity:1}90%{opacity:1}100%{left:0;opacity:0}}
.arch-features{display:grid;grid-template-columns:repeat(3,1fr);gap:.5rem;margin-top:2.5rem;max-width:680px;margin-left:auto;margin-right:auto}
@media(max-width:600px){.arch-features{grid-template-columns:repeat(2,1fr)}}
.arch-ft{display:flex;align-items:center;gap:.5rem;padding:.6rem .8rem;border-radius:10px;border:1px solid var(--rule);background:var(--paper);transition:all .25s;cursor:default}
.arch-ft:hover{border-color:color-mix(in srgb,var(--coral) 30%,var(--rule));background:var(--coral-wash);transform:translateY(-1px)}
.arch-ft-dot{width:6px;height:6px;border-radius:50%;background:#CC785C;flex-shrink:0;box-shadow:0 0 6px rgba(204,120,92,.4)}
.arch-ft-text{font-size:.78rem;color:var(--ink-soft);font-family:var(--sans)}
.arch-ft:hover .arch-ft-text{color:var(--ink)}
.arch-ft-desc{font-size:.65rem;color:var(--muted);margin-top:.1rem}
.arch-flow-label{text-align:center;margin-top:2rem;font-size:.72rem;color:var(--muted);font-family:var(--mono);letter-spacing:.08em;display:flex;align-items:center;justify-content:center;gap:.8rem}
.arch-flow-label .arr{display:inline-flex;align-items:center;gap:.3rem}
.arch-flow-label .arr::before{content:"";width:24px;height:1px}
.arch-flow-label .arr.req::before{background:linear-gradient(90deg,transparent,#CC785C)}
.arch-flow-label .arr.res::before{background:linear-gradient(90deg,transparent,#8B6FB0)}
/* ── Dashboard ── */
.dashboard-section{background:var(--bg-alt)}
.panel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:.7rem;margin-top:1.5rem}
.panel-card{background:var(--paper);border:1px solid var(--rule);border-radius:8px;padding:1rem 1.1rem;transition:all .2s ease}
.panel-card:hover{border-color:var(--coral-soft);transform:translateY(-2px);box-shadow:0 6px 16px -6px rgba(26,22,18,.07)}
.panel-head{display:flex;align-items:center;gap:.5rem;margin-bottom:.4rem}
.panel-icon{width:24px;height:24px;border-radius:5px;display:inline-flex;align-items:center;justify-content:center;background:var(--coral-wash);color:var(--coral-deep);font-family:var(--serif);font-size:.78rem;font-weight:600;flex-shrink:0}
.panel-head h4{font-family:var(--sans);font-size:.9rem;font-weight:600}
.panel-card p{font-size:.8rem;color:var(--muted);line-height:1.5}
.contrib-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:1rem;margin-top:1.5rem}
.contrib-card{background:var(--paper);border:1px solid var(--rule);border-radius:10px;padding:1.1rem 1.2rem;transition:all .2s ease;text-decoration:none;color:inherit;display:block}
.contrib-card:hover{border-color:var(--coral-soft);transform:translateY(-2px);box-shadow:0 8px 20px -8px rgba(26,22,18,.08)}
.contrib-head{display:flex;align-items:flex-start;gap:.8rem;margin-bottom:.7rem}
.contrib-avatar{width:48px;height:48px;border-radius:50%;border:1px solid var(--rule);background:var(--coral-wash);object-fit:cover;flex-shrink:0}
.contrib-meta{min-width:0;flex:1}
.contrib-name{font-family:var(--sans);font-weight:600;font-size:.95rem;color:var(--ink);display:flex;align-items:center;gap:.45rem;flex-wrap:wrap;line-height:1.3}
.contrib-prs{font-family:var(--mono,ui-monospace,monospace);font-size:.74rem;color:var(--muted);margin-top:.25rem;letter-spacing:.02em}
.contrib-weight{display:inline-flex;align-items:center;padding:1px 7px;border-radius:4px;font-family:var(--mono,ui-monospace,monospace);font-size:.68rem;font-weight:700;letter-spacing:.6px;line-height:1.55}
.contrib-weight-S-plus{background:linear-gradient(135deg,#f3c677,#d97706);color:#3a2806;box-shadow:0 0 0 1px rgba(217,119,6,.25)}
.contrib-weight-S{background:#c2410c;color:#fff7ed}
.contrib-weight-A-plus{background:#d97706;color:#fffbeb}
.contrib-weight-A{background:var(--coral-wash);color:var(--coral-deep);border:1px solid var(--coral-soft)}
.contrib-weight-B-plus{background:transparent;color:var(--muted);border:1px dashed var(--rule)}
.contrib-weight-GOD{background:#1A1612;color:#FAF9F5}
.contrib-weight-MR{background:linear-gradient(135deg,#4c1d95,#db2777);color:#fff}
.contrib-weight-LR{background:linear-gradient(135deg,#7c2d12,#f59e0b);color:#fff7ed}
.contrib-weight-UR{background:linear-gradient(135deg,#7c3aed,#e11d48,#f59e0b);color:#fff}
.contrib-weight-SSR{background:linear-gradient(135deg,#f59e0b,#b45309);color:#fff7ed}
.contrib-weight-SR{background:#dbeafe;color:#1d4ed8;border:1px solid #bfdbfe}
.contrib-weight-B{background:transparent;color:var(--muted);border:1px solid var(--rule)}
.contrib-loading{grid-column:1/-1;padding:1rem 1.2rem;border:1px dashed var(--rule);border-radius:10px;color:var(--muted);background:var(--paper)}
.contrib-summary{font-size:.82rem;color:var(--muted);line-height:1.65}
/* ── Deploy ── */
.deploy-tabs{display:flex;gap:.3rem;margin-bottom:1.5rem;border-bottom:1px solid var(--rule);padding-bottom:-1px}
.deploy-tab{padding:.5rem 1rem;font-size:.88rem;font-weight:500;color:var(--muted);cursor:pointer;border:none;background:none;border-bottom:2px solid transparent;transition:all .15s;font-family:var(--sans)}
.deploy-tab:hover{color:var(--ink)}
.deploy-tab.active{color:var(--coral);border-bottom-color:var(--coral)}
.deploy-content{display:none}
.deploy-content.active{display:block}
.deploy-steps{display:flex;flex-direction:column;gap:1.2rem;counter-reset:step}
.step{display:flex;gap:1rem;align-items:flex-start;counter-increment:step}
.step-num{width:32px;height:32px;border-radius:50%;background:var(--coral-wash);border:1.5px solid var(--coral-soft);color:var(--coral-deep);display:inline-flex;align-items:center;justify-content:center;font-family:var(--serif);font-size:.9rem;font-weight:500;flex-shrink:0}
.step-num::before{content:counter(step)}
.step-body{flex:1;min-width:0}
.step-body h3{font-size:1.1rem;margin-bottom:.3rem;font-weight:500}
.step-body>p{color:var(--muted);margin-bottom:.6rem;font-size:.9rem}
.code-box{background:var(--code-bg);border-radius:8px;font-family:var(--mono);font-size:.78rem;color:var(--code-fg);overflow:hidden;border:1px solid var(--code-border)}
.code-box-head{display:flex;align-items:center;justify-content:space-between;padding:.45rem .9rem;background:var(--code-bg-head);border-bottom:1px solid var(--code-border);font-size:.7rem;color:var(--code-com)}
.copy-btn{background:transparent;border:1px solid var(--code-border);color:var(--code-com);cursor:pointer;padding:.15rem .5rem;border-radius:3px;font-family:var(--sans);font-size:.7rem;transition:all .15s}
.copy-btn:hover{color:var(--code-fg);border-color:var(--muted-soft)}
.copy-btn.ok{color:var(--coral);border-color:var(--coral)}
.code-box pre{padding:.9rem 1rem;overflow-x:auto;line-height:1.65}
.code-box .c-str{color:var(--code-str)}
.code-box .c-flag{color:var(--code-flag)}
.code-box .c-cmd{color:var(--code-cmd);font-weight:500}
.code-box .c-com{color:var(--code-com);font-style:italic}
.code-box .c-prompt{color:var(--coral);user-select:none}
.code-box .c-key{color:var(--code-key)}
/* ── Usage ── */
.usage-section{background:var(--bg-alt)}
.usage-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1rem;margin-top:1.5rem}
.usage-card{background:var(--paper);border:1px solid var(--rule);border-radius:8px;padding:1.2rem 1.1rem;transition:all .2s ease}
.usage-card:hover{border-color:var(--coral-soft);transform:translateY(-2px);box-shadow:0 6px 16px -6px rgba(26,22,18,.07)}
.usage-card h4{font-family:var(--sans);font-size:.95rem;font-weight:600;margin-bottom:.2rem;display:flex;align-items:center;gap:.4rem}
.usage-card .logo-chip{width:22px;height:22px;border-radius:5px;display:inline-flex;align-items:center;justify-content:center;font-family:var(--serif);font-size:.65rem;font-weight:600;color:#fff;flex-shrink:0}
.usage-card .endpoint-tag{font-family:var(--mono);font-size:.72rem;color:var(--muted);margin-bottom:.7rem}
.usage-card .endpoint-tag strong{color:var(--coral)}
.usage-card pre{background:var(--code-bg);padding:.7rem .9rem;border-radius:6px;font-family:var(--mono);font-size:.74rem;line-height:1.6;color:var(--code-fg);overflow-x:auto;border:1px solid var(--code-border)}
.usage-card pre .c-str{color:var(--code-str)}
.usage-card pre .c-key{color:var(--code-key)}
.usage-card pre .c-com{color:var(--code-com);font-style:italic}
/* ── FAQ ── */
.faq-list{display:flex;flex-direction:column;gap:.5rem;margin-top:1.5rem}
.faq-item{background:var(--paper);border:1px solid var(--rule);border-radius:8px;padding:.9rem 1.1rem;transition:border-color .15s}
.faq-item[open]{border-color:var(--coral-soft)}
.faq-item summary{cursor:pointer;list-style:none;font-weight:500;font-size:.95rem;display:flex;align-items:center;justify-content:space-between;gap:1rem}
.faq-item summary::-webkit-details-marker{display:none}
.faq-item summary::after{content:"+";font-family:var(--serif);font-size:1.3rem;color:var(--muted);line-height:1}
.faq-item[open] summary::after{content:"\2212";color:var(--coral)}
.faq-item .a{margin-top:.7rem;padding-top:.7rem;border-top:1px solid var(--rule);color:var(--muted);font-size:.9rem;line-height:1.65}
.faq-item .a code{font-size:.84em}
/* ── Footer ── */
footer{border-top:1px solid var(--rule);padding:3rem 0 2.5rem;margin-top:2rem}
.footer-inner{display:grid;grid-template-columns:2fr 1fr 1fr;gap:2rem}
@media(max-width:720px){.footer-inner{grid-template-columns:1fr}}
.footer-brand .logo{font-family:var(--serif);font-size:1.15rem;font-weight:500;margin-bottom:.6rem;color:var(--ink)}
.footer-brand p{color:var(--muted);font-size:.85rem;max-width:320px}
.footer-col h5{font-size:.78rem;font-weight:600;color:var(--ink);margin-bottom:.6rem;text-transform:uppercase;letter-spacing:.08em}
.footer-col ul{list-style:none;display:flex;flex-direction:column;gap:.35rem}
.footer-col a{color:var(--muted);font-size:.85rem}
.footer-col a:hover{color:var(--ink)}
.footer-bottom{margin-top:2rem;padding-top:1.2rem;border-top:1px solid var(--rule);display:flex;justify-content:space-between;flex-wrap:wrap;gap:.8rem;font-size:.78rem;color:var(--muted-soft)}
/* ── Reveal ── */
.reveal{opacity:0;transform:translateY(20px);transition:opacity .7s cubic-bezier(.23,1,.32,1),transform .7s cubic-bezier(.23,1,.32,1)}
.reveal.in{opacity:1;transform:none}
.reveal:nth-child(2){transition-delay:.08s}
.reveal:nth-child(3){transition-delay:.16s}
.reveal:nth-child(4){transition-delay:.24s}
@media(prefers-reduced-motion:reduce){.reveal{opacity:1;transform:none}}
</style>
<script>
/* Apply saved / system theme BEFORE first paint to avoid a flash. */
(function(){
try{
var saved = localStorage.getItem('devinapi-theme');
var dark = saved ? saved === 'dark'
: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (dark) document.documentElement.setAttribute('data-theme','dark');
}catch(e){}
})();
</script>
</head>
<body>
<!-- ── Nav ── -->
<nav>
<div class="nav-inner">
<a href="#" class="logo">DevinAPI</a>
<div class="nav-links">
<a href="#protocols">协议</a>
<a href="#models">模型</a>
<a href="#architecture">架構</a>
<a href="review.html">審閱</a>
<a href="#deploy">部署</a>
<a href="#dashboard">管理</a>
<a href="#faq">FAQ</a>
</div>
<a href="https://github.com/dwgx/WindsurfAPI" class="nav-cta" target="_blank" rel="noopener">
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
GitHub
</a>
<button type="button" class="theme-toggle" id="theme-toggle" aria-label="切換深色 / 淺色主題" title="切換主題">
<svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
<svg class="icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
</button>
</div>
</nav>
<!-- ── Hero ── -->
<section class="hero">
<div class="container">
<div class="hero-inner reveal">
<div class="hero-badge">v3.141.592661 — Devin 云端直连 · 零二进位 · 四协议</div>
<h1>
把 <em>Devin</em> 雲端的 AI 模型<br>
變成你熟悉的 <span class="hl">API</span>。
</h1>
<p class="lede">
WindsurfAPI 長成了 DevinAPI:同一个服务同时说 <code>/v1/chat/completions</code>、<code>/v1/responses</code>、<code>/v1/messages</code> 与 Gemini 四套协议,直連 Devin 雲端的百餘個模型。Claude Code、Cursor、Cline 直接連。零 npm 依賴,預設路徑無需任何二進位。
</p>
<div class="hero-cta">
<a href="#deploy" class="btn btn-primary">
開始部署
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 8h8M8 4l4 4-4 4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</a>
<a href="HISTORY-LEDGER-VIZ.html" class="btn btn-ghost" rel="noopener">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 14h12M4 14V8m4 6V4m4 10V6" stroke-linecap="round"/></svg>
工程的年輪
</a>
<a href="https://github.com/dwgx/WindsurfAPI" class="btn btn-ghost" target="_blank" rel="noopener">
<svg viewBox="0 0 16 16" fill="currentColor" width="14" height="14"><path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82a7.64 7.64 0 014 0c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
查看原始碼
</a>
</div>
<div class="hero-meta">
<span>零 npm 依賴</span>
<span>Node.js ≥ 22.13</span>
<span>MIT License</span>
<span>Docker 就緒</span>
</div>
<div class="hero-term" aria-hidden="true">
<div class="hero-term-head">
<span class="hero-term-dot r"></span>
<span class="hero-term-dot y"></span>
<span class="hero-term-dot g"></span>
<span class="hero-term-title">bash — devin-api</span>
</div>
<pre class="hero-term-body" id="hero-term-body"></pre>
</div>
</div>
</div>
</section>
<!-- ── Stats ── -->
<section class="stats" style="padding:2.5rem 0">
<div class="container">
<div class="stats-grid">
<div class="stat reveal"><div class="num" id="stat-models">0</div><div class="label">AI 模型</div></div>
<div class="stat reveal"><div class="num">9</div><div class="label">供應商</div></div>
<div class="stat reveal"><div class="num">4</div><div class="label">相容協定</div></div>
<div class="stat reveal"><div class="num">0</div><div class="label">npm 依賴</div></div>
</div>
</div>
</section>
<!-- ── Dual Protocol ── -->
<section class="dual-protocol" id="protocols">
<div class="container">
<div class="reveal">
<div class="kicker">协议 · Protocols</div>
<h2 class="section-title">一个服务,<em>四套 API 面</em>。</h2>
<p class="section-sub">OpenAI Chat Completions、OpenAI Responses、Anthropic Messages、Google Gemini —— 四套主流协议同时暴露,指向同一个 DevinAPI 实例即可。全部汇进同一个 chat 处理器、同一个账号池,再送往 Devin 云端。</p>
</div>
<div class="proto-grid reveal">
<div class="proto-card">
<div class="proto-head">
<div class="proto-brand"><span class="logo-chip openai"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="15" height="15" fill="currentColor" role="img" aria-label="OpenAI"><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.1419.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg></span><h4>OpenAI 相容</h4></div>
<span class="proto-meta">POST /v1/chat/completions</span>
</div>
<pre class="proto-body"><span class="c-prompt">$</span> <span class="c-cmd">curl</span> http://localhost:3003<span class="c-str">/v1/chat/completions</span> \
<span class="c-flag">-H</span> <span class="c-str">"Authorization: Bearer sk-dv-demo"</span> \
<span class="c-flag">-d</span> <span class="c-str">'{
<span class="c-key">"model"</span>: "gpt-5.2",
<span class="c-key">"messages"</span>: [
{"role":"user","content":"hello"}
],
<span class="c-key">"stream"</span>: true
}'</span>
<span class="c-com"># → Server-Sent Events (OpenAI chunk 格式)</span></pre>
</div>
<div class="proto-card">
<div class="proto-head">
<div class="proto-brand"><span class="logo-chip anthropic"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="15" height="15" fill="currentColor" role="img" aria-label="Anthropic"><path d="M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.541Zm-.3712 10.2232 2.2932-5.9456 2.2932 5.9456Z"/></svg></span><h4>Anthropic 相容</h4></div>
<span class="proto-meta">POST /v1/messages</span>
</div>
<pre class="proto-body"><span class="c-prompt">$</span> <span class="c-cmd">curl</span> http://localhost:3003<span class="c-str">/v1/messages</span> \
<span class="c-flag">-H</span> <span class="c-str">"x-api-key: sk-dv-demo"</span> \
<span class="c-flag">-d</span> <span class="c-str">'{
<span class="c-key">"model"</span>: "claude-opus-4.6",
<span class="c-key">"messages"</span>: [
{"role":"user","content":"hello"}
],
<span class="c-key">"stream"</span>: true
}'</span>
<span class="c-com"># → SSE (Anthropic message_delta 格式)</span></pre>
</div>
<div class="proto-card">
<div class="proto-head">
<div class="proto-brand"><span class="logo-chip openai"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="15" height="15" fill="currentColor" role="img" aria-label="OpenAI"><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.1419.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg></span><h4>OpenAI Responses</h4></div>
<span class="proto-meta">POST /v1/responses</span>
</div>
<pre class="proto-body"><span class="c-prompt">$</span> <span class="c-cmd">curl</span> http://localhost:3003<span class="c-str">/v1/responses</span> \
<span class="c-flag">-H</span> <span class="c-str">"Authorization: Bearer sk-dv-demo"</span> \
<span class="c-flag">-d</span> <span class="c-str">'{
<span class="c-key">"model"</span>: "gpt-5.5",
<span class="c-key">"input"</span>: "hello",
<span class="c-key">"stream"</span>: true
}'</span>
<span class="c-com"># → SSE (response.output_text.delta 事件)</span></pre>
</div>
<div class="proto-card">
<div class="proto-head">
<div class="proto-brand"><span class="logo-chip" style="background:#4285F4"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="15" height="15" fill="#fff" aria-label="Gemini"><path d="M12 0c.6 6.3 5.7 11.4 12 12-6.3.6-11.4 5.7-12 12-.6-6.3-5.7-11.4-12-12C6.3 11.4 11.4 6.3 12 0z"/></svg></span><h4>Google Gemini</h4></div>
<span class="proto-meta">POST …:generateContent</span>
</div>
<pre class="proto-body"><span class="c-prompt">$</span> <span class="c-cmd">curl</span> http://localhost:3003<span class="c-str">/v1beta/models/gemini-3.0-pro:streamGenerateContent</span> \
<span class="c-flag">-H</span> <span class="c-str">"x-goog-api-key: sk-dv-demo"</span> \
<span class="c-flag">-d</span> <span class="c-str">'{
<span class="c-key">"contents"</span>: [{"parts":[{"text":"hello"}]}]
}'</span>
<span class="c-com"># → SSE (Gemini streamGenerateContent 格式)</span></pre>
</div>
</div>
<div class="note reveal">
<strong>同一个请求体、同一个账号池、同一个速率限制器</strong>——切换协议只是换路由前缀。Claude Code / Cline 走 <code>/v1/messages</code>,Cursor / OpenAI SDK 走 <code>/v1/chat/completions</code>,Codex / Agents SDK 走 <code>/v1/responses</code>,Gemini 客户端走 <code>generateContent</code> / <code>streamGenerateContent</code>,互不干扰。另有 <code>/v1/models</code> 目录与 <code>/v1/messages/count_tokens</code>。
</div>
</div>
</section>
<!-- ── Models ── -->
<section class="models-section" id="models">
<div class="container">
<div class="reveal">
<div class="kicker">模型 · Models</div>
<h2 class="section-title">一個端點,<em>283 個模型</em>。</h2>
<p class="section-sub">橫跨 11 家供應商的 <b>283</b> 個現役模型,一致計費、即時切換,不必為每家重接一次 API。最新入列 <b>Claude Opus 5.5 / Fable 5.1</b>、<b>GPT-6 全系</b>(sol / luna / astra)、<b>Gemini 4 Argon</b>與 <b>DeepSeek V4.1</b>;另有思考型變體。清單由 <code>src/models.js</code> 權威生成、後端雲端目錄自動發現。頁面載入後數字跟陣列走。</p>
</div>
<div class="ms-model-filters reveal" role="group" aria-label="依供應商篩選模型" id="model-filters">
<button type="button" class="ms-model-filter is-active" data-filter="all" aria-pressed="true">全部 <span class="ms-count" id="count-all">283</span></button>
<button type="button" class="ms-model-filter" data-filter="anthropic" aria-pressed="false">Claude <span class="ms-count" id="count-anthropic">77</span></button>
<button type="button" class="ms-model-filter" data-filter="openai" aria-pressed="false">GPT <span class="ms-count" id="count-openai">107</span></button>
<button type="button" class="ms-model-filter" data-filter="google" aria-pressed="false">Gemini <span class="ms-count" id="count-google">26</span></button>
<button type="button" class="ms-model-filter" data-filter="deepseek" aria-pressed="false">DeepSeek <span class="ms-count" id="count-deepseek">9</span></button>
<button type="button" class="ms-model-filter" data-filter="moonshot" aria-pressed="false">Kimi <span class="ms-count" id="count-moonshot">8</span></button>
<button type="button" class="ms-model-filter" data-filter="zhipu" aria-pressed="false">GLM <span class="ms-count" id="count-zhipu">16</span></button>
<button type="button" class="ms-model-filter" data-filter="xai" aria-pressed="false">Grok <span class="ms-count" id="count-xai">14</span></button>
<button type="button" class="ms-model-filter" data-filter="minimax" aria-pressed="false">MiniMax <span class="ms-count" id="count-minimax">1</span></button>
<button type="button" class="ms-model-filter" data-filter="thinking_machines" aria-pressed="false">Inkling <span class="ms-count" id="count-thinking_machines">6</span></button>
<button type="button" class="ms-model-filter" data-filter="nvidia" aria-pressed="false">Nemotron <span class="ms-count" id="count-nvidia">3</span></button>
<button type="button" class="ms-model-filter" data-filter="windsurf" aria-pressed="false">SWE <span class="ms-count" id="count-windsurf">16</span></button>
<button type="button" class="ms-model-filter" data-filter="thinking" aria-pressed="false">思考型 <span class="ms-count" id="count-thinking">15</span></button>
<button type="button" class="ms-model-filter" data-filter="free" aria-pressed="false">免費 <span class="ms-count" id="count-free">4</span></button>
</div>
<div class="ms-model-groups reveal" id="model-groups"></div>
<div class="note reveal">
<strong>免費帳號:</strong>可用模型依帳號 tier 而定,免費層通常僅開放輕量模型;Claude、GPT-5 全系等需要對應的 Devin 付費 tier,後台會自動偵測並標記。<br>
<strong>看不到最新模型?</strong>Devin 雲端路徑(<code>DEVIN_CONNECT=1</code>)透過 GetChatMessage 自動發現雲端目錄,<code>/v1/models</code> 即時反映,無需拷貝任何二進位。<br>
<strong>模型清單怎麼更新?</strong>本頁清單由 <code>scripts/gen-docs-models.js</code> 從 <code>src/models.js</code> 自動生成。
</div>
</div>
</section>
<!-- ── Architecture ── -->
<section id="architecture" class="arch-section">
<div class="container">
<div class="reveal">
<div class="kicker">架構 · Architecture</div>
<h2 class="section-title">一层翻译,<em>直连云端</em>。</h2>
<p class="section-sub">請求從客戶端出發,經過協定翻譯、帳號池選號、工具仿真、路徑淨化,再以手寫 Protobuf 透過 Connect-RPC 直送 Devin 雲端的 GetChatMessage——預設路徑不需要任何二進位。回應原路返回,流式解析後交付。傳統的 Windsurf 語言伺服器(本地 gRPC)仍保留為第二條傳輸鏈。</p>
</div>
<div class="arch-canvas reveal">
<div class="arch-row">
<!-- Node 1: Client -->
<div class="arch-nd">
<div class="arch-nd-box">
<div class="arch-nd-icon" style="background:rgba(232,226,212,.06);color:#E8E2D4">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="14" rx="2"/><path d="M7 21h10M12 17v4"/></svg>
</div>
<div class="arch-nd-label">Client</div>
<div class="arch-nd-title">IDE / CLI</div>
<div class="arch-nd-sub">Claude Code · Cursor<br>Cline · OpenAI SDK</div>
</div>
</div>
<!-- Connection 1 -->
<div class="arch-conn">
<div class="arch-conn-line"></div>
<div class="arch-conn-dot"></div>
<div class="arch-conn-dot ret"></div>
<div class="arch-conn-proto">HTTP · SSE</div>
</div>
<!-- Node 2: DevinAPI (HERO) -->
<div class="arch-nd hero-node">
<div class="arch-hero-glow"></div>
<div class="arch-nd-box">
<div class="arch-nd-icon" style="background:rgba(204,120,92,.15);color:#CC785C">
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
</div>
<div class="arch-nd-label">Node.js Proxy</div>
<div class="arch-nd-title">DevinAPI</div>
<div class="arch-nd-sub">協定翻譯 · 帳號池輪詢<br>工具仿真 · 上下文複用<br>速率限制 · 故障轉移</div>
</div>
</div>
<!-- Connection 2 -->
<div class="arch-conn">
<div class="arch-conn-line"></div>
<div class="arch-conn-dot"></div>
<div class="arch-conn-dot ret"></div>
<div class="arch-conn-proto">Connect-RPC · HTTP</div>
</div>
<!-- Node 3: Devin Connect -->
<div class="arch-nd">
<div class="arch-nd-box">
<div class="arch-nd-icon" style="background:rgba(232,226,212,.06);color:#A09888">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M6 14h12"/></svg>
</div>
<div class="arch-nd-label">Connect-RPC · 帳號池</div>
<div class="arch-nd-title">Devin Connect</div>
<div class="arch-nd-sub">手寫 Protobuf 編解碼<br>語言伺服器為選用第二鏈</div>
</div>
</div>
<!-- Connection 3 -->
<div class="arch-conn">
<div class="arch-conn-line"></div>
<div class="arch-conn-dot"></div>
<div class="arch-conn-dot ret"></div>
<div class="arch-conn-proto">TLS · HTTPS</div>
</div>
<!-- Node 4: Cloud -->
<div class="arch-nd">
<div class="arch-nd-box">
<div class="arch-nd-icon" style="background:rgba(139,111,176,.08);color:#8B6FB0">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 10a6 6 0 00-11.3-2A4.5 4.5 0 005 17h13a4 4 0 100-7z"/></svg>
</div>
<div class="arch-nd-label">Upstream</div>
<div class="arch-nd-title">Devin Cloud</div>
<div class="arch-nd-sub">GetChatMessage 推理引擎<br>100+ 模型</div>
</div>
</div>
</div>
<!-- Feature modules fanning out from center -->
<div class="arch-features reveal">
<div class="arch-ft">
<span class="arch-ft-dot"></span>
<div><div class="arch-ft-text">四协议翻译</div><div class="arch-ft-desc">Chat · Responses · Messages · Gemini</div></div>
</div>
<div class="arch-ft">
<span class="arch-ft-dot"></span>
<div><div class="arch-ft-text">多帳號池</div><div class="arch-ft-desc">輪詢 · 故障轉移 · 自動封禁偵測</div></div>
</div>
<div class="arch-ft">
<span class="arch-ft-dot"></span>
<div><div class="arch-ft-text">工具仿真</div><div class="arch-ft-desc">Prompt 注入 · 三格式解析</div></div>
</div>
<div class="arch-ft">
<span class="arch-ft-dot"></span>
<div><div class="arch-ft-text">上下文複用</div><div class="arch-ft-desc">Fingerprint 池 · Devin 會話快取</div></div>
</div>
<div class="arch-ft">
<span class="arch-ft-dot"></span>
<div><div class="arch-ft-text">安全淨化</div><div class="arch-ft-desc">路徑剝離 · SSRF 防護 · Proto 截斷檢測</div></div>
</div>
<div class="arch-ft">
<span class="arch-ft-dot"></span>
<div><div class="arch-ft-text">SOCKS5 · Docker</div><div class="arch-ft-desc">每帳號獨立代理 · 容器化部署</div></div>
</div>
</div>
<div class="arch-flow-label reveal">
<span class="arr req">請求流</span>
<span style="color:#4A433B">·</span>
<span class="arr res">回應流</span>
</div>
</div>
</div>
</section>
<!-- ── Under the Hood ── -->
<section class="dashboard-section" id="under-the-hood">
<div class="container">
<div class="reveal">
<div class="kicker">工程內幕 · Under the Hood</div>
<h2 class="section-title">從 Windsurf 代理到 Devin 雲端,<em>底層做了什麼</em>。</h2>
<p class="section-sub">遷移不只是換個上游位址。為了純 HTTP 直連 Devin 雲端,我們手寫了整條協定棧——沒有 protobuf 執行庫,沒有 npm 依賴。</p>
</div>
<div class="panel-grid reveal">
<div class="panel-card"><div class="panel-head"><div class="panel-icon">1</div><h4>手寫 Protobuf 編解碼</h4></div><p>零依賴、免 schema 的 wire-format codec,varint 全手寫,透過 Connect-RPC 對話 Devin 雲端。</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">2</div><h4>四协议翻译</h4></div><p>Chat Completions、Responses、Anthropic Messages、Gemini 四套前端汇进同一个 chat 处理器再扇出。</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">3</div><h4>雙傳輸鏈共用底座</h4></div><p>Devin 雲端(純 HTTP,新預設)與傳統 Windsurf 語言伺服器(本地 gRPC)共用同一套 proto + connect 底層。</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">4</div><h4>精準計費解碼</h4></div><p>解析 Devin 免費的 GetUserStatus 帳本,還原真實餘額、計費週期與各模型 credit 費率,零成本查詢。</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">5</div><h4>視覺圖像支援</h4></div><p>忠實重建圖像 tool_result 的 wire 結構,讓圖片輸入與文字走同一個 protobuf 封包。</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">6</div><h4>零依賴影像編解碼</h4></div><p>內建 jpeg-js(BSD-3)與自寫的純 Node PNG 解碼器(node:zlib)負責縮圖,不引入原生或 npm 影像庫。</p></div>
</div>
</div>
</section>
<!-- ── Dashboard ── -->
<section class="dashboard-section" id="dashboard">
<div class="container">
<div class="reveal">
<div class="kicker">管理後台 · Dashboard</div>
<h2 class="section-title">10 個面板,<em>帳號運維</em>全搞定。</h2>
<p class="section-sub">訪問 <code>/dashboard</code>,暗色 Web 介面。日誌即時串流、帳號一鍵登入、模型黑白名單、封禁偵測。</p>
</div>
<div class="panel-grid reveal">
<div class="panel-card"><div class="panel-head"><div class="panel-icon">1</div><h4>總覽</h4></div><p>運行時間、帳號池狀態、分模型成功率</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">2</div><h4>登入取號</h4></div><p>Email/密碼直接註冊,自動取得 Token</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">3</div><h4>帳號管理</h4></div><p>新增、刪除、停用、真實餘額 / credit 查詢</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">4</div><h4>模型控制</h4></div><p>全域與帳號層的模型白/黑名單</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">5</div><h4>代理配置</h4></div><p>全域及個別帳號 HTTP/SOCKS5 代理</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">6</div><h4>日誌檢視</h4></div><p>即時 SSE 串流,級別篩選,關鍵字高亮</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">7</div><h4>請求統計</h4></div><p>按模型/帳號維度的指標與圖表</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">8</div><h4>封禁偵測</h4></div><p>錯誤模式偵測,帳號健康自動監控</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">9</div><h4>自動更新</h4></div><p>一鍵 git pull + PM2 重啟服務</p></div>
<div class="panel-card"><div class="panel-head"><div class="panel-icon">10</div><h4>致謝</h4></div><p>Contributors 列表</p></div>
</div>
</div>
</section>
<!-- ── Deploy ── -->
<section class="deploy-section" id="deploy">
<div class="container">
<div class="reveal">
<div class="kicker">部署 · Deploy</div>
<h2 class="section-title">從零到跑起來,<em>五分鐘</em>。</h2>
<p class="section-sub">兩種方式任選。</p>
</div>
<div class="reveal">
<div class="deploy-tabs">
<button class="deploy-tab active" data-tab="manual">手動部署</button>
<button class="deploy-tab" data-tab="docker">Docker</button>
<button class="deploy-tab" data-tab="windows">Windows 一鍵 EXE</button>
</div>
<div class="deploy-content active" id="tab-manual">
<div class="deploy-steps">
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>安裝 Node.js 24(最低 22.13)</h3>
<div class="code-box"><div class="code-box-head"><span class="lang">bash</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-prompt">$</span> <span class="c-cmd">curl</span> -fsSL https://deb.nodesource.com/setup_24.x | bash -
<span class="c-prompt">$</span> <span class="c-cmd">apt install</span> -y nodejs</pre>
</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>Clone + 直接啟動</h3>
<div class="code-box"><div class="code-box-head"><span class="lang">bash</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-prompt">$</span> <span class="c-cmd">git clone</span> https://github.com/dwgx/WindsurfAPI.git
<span class="c-prompt">$</span> <span class="c-cmd">cd</span> WindsurfAPI
<span class="c-prompt">$</span> <span class="c-cmd">cp</span> .env.example .env
<span class="c-prompt">$</span> <span class="c-cmd">node</span> src/index.js <span class="c-com"># 零依賴,免安裝直接跑</span></pre>
</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>設定 <code>.env</code></h3>
<div class="code-box"><div class="code-box-head"><span class="lang">.env</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-key">DEVIN_CONNECT</span>=<span class="c-str">1</span> <span class="c-com"># 走 Devin 雲端 GetChatMessage,無需二進位</span>
<span class="c-key">PORT</span>=<span class="c-str">3003</span>
<span class="c-key">API_KEY</span>= <span class="c-com"># 留空 = fail-closed 401;本機開放要 WINDSURFAPI_ALLOW_UNAUTHENTICATED=1</span>
<span class="c-key">DEFAULT_MODEL</span>=<span class="c-str">claude-sonnet-4.6</span>
<span class="c-key">DASHBOARD_PASSWORD</span>= <span class="c-com"># 留空 = 後台 401(Windows exe 首次會自動生成)</span></pre>
</div>
<div class="note"><strong>帳號池:</strong>把 Devin session token 放進 <code>accounts.json</code> 即可,其餘免配置。傳統語言伺服器路徑仍可用,設 <code>LS_BINARY_PATH</code> 即回退到本地 gRPC。</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>啟動</h3>
<div class="code-box"><div class="code-box-head"><span class="lang">bash</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-prompt">$</span> <span class="c-cmd">npm install</span> -g pm2
<span class="c-prompt">$</span> <span class="c-cmd">pm2 start</span> src/index.js --name devin-api
<span class="c-prompt">$</span> <span class="c-cmd">pm2 save</span> && <span class="c-cmd">pm2 startup</span></pre>
</div>
<div class="note"><strong>一鍵更新:</strong><code>bash update.sh</code></div>
</div></div>
</div>
</div>
<div class="deploy-content" id="tab-docker">
<div class="deploy-steps">
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>準備配置</h3>
<div class="code-box"><div class="code-box-head"><span class="lang">bash</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-prompt">$</span> <span class="c-cmd">git clone</span> https://github.com/dwgx/WindsurfAPI.git
<span class="c-prompt">$</span> <span class="c-cmd">cd</span> WindsurfAPI
<span class="c-prompt">$</span> <span class="c-cmd">cp</span> .env.example .env</pre>
</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>啟動容器</h3>
<div class="code-box"><div class="code-box-head"><span class="lang">bash</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-prompt">$</span> <span class="c-cmd">docker compose</span> up -d --build
<span class="c-prompt">$</span> <span class="c-cmd">docker compose</span> logs -f</pre>
</div>
<div class="note">
<strong>預設掛載:</strong><code>.docker-data/data</code> 持久化帳號 / 配置。Devin 雲端路徑無需下載任何二進位,設好 <code>accounts.json</code> 即可啟動。
</div>
</div></div>
</div>
</div>
<div class="deploy-content" id="tab-windows">
<div class="deploy-steps">
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>到 Releases 下載</h3>
<div class="note">到 <a href="https://github.com/dwgx/WindsurfAPI/releases/latest" target="_blank" rel="noopener">GitHub Releases</a> 下載,兩種任選(都<strong>零依賴、免裝 Node、免 clone</strong>,exe 內建 Node 執行環境):<br>
· <code>windsurfapi-windows.zip</code> —— 系統匣托盤版(推薦)<br>
· <code>windsurfapi.exe</code> —— 純控制台單檔(約 60 MB)</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>雙擊啟動</h3>
<div class="note">解壓 zip 後<strong>雙擊 <code>tray.vbs</code></strong>:無黑窗,右下角<strong>系統匣</strong>出現圖示;或直接雙擊 <code>windsurfapi.exe</code>:彈出控制台視窗顯示日誌。<strong>首次執行自動產生</strong> <code>API_KEY</code> 與 <code>DASHBOARD_PASSWORD</code>(寫入同目錄 <code>.env</code>)並自動開啟後台面板。</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>右鍵系統匣圖示</h3>
<div class="note">托盤圖示右鍵選單一鍵完成日常操作:<br>
<strong>打開面板</strong> · <strong>複製面板密碼</strong> · <strong>複製 API Key</strong> · <strong>狀態</strong> · <strong>重啟</strong> · <strong>退出</strong>。<br>
密碼與金鑰直接複製到剪貼簿,不必翻找 <code>.env</code>。服務崩潰會自動重拉,連續異常才停並提示。</div>
</div></div>
<div class="step"><div class="step-num"></div><div class="step-body">
<h3>預設即最佳配置</h3>
<div class="code-box"><div class="code-box-head"><span class="lang">.env(自動生成)</span><button class="copy-btn" data-copy>複製</button></div>
<pre><span class="c-key">DEVIN_CONNECT</span>=<span class="c-str">1</span> <span class="c-com"># Devin 雲端純 HTTP 主路,無需二進位</span>
<span class="c-key">HOST</span>=<span class="c-str">127.0.0.1</span> <span class="c-com"># 預設僅本機綁定</span>
<span class="c-key">PORT</span>=<span class="c-str">3003</span>
<span class="c-key">API_KEY</span>=<span class="c-str">sk-windsurf-…</span> <span class="c-com"># 首次隨機生成</span>
<span class="c-key">DASHBOARD_PASSWORD</span>=<span class="c-str">…</span> <span class="c-com"># 首次隨機生成</span></pre>
</div>
<div class="note"><strong>上號:</strong>開後台面板貼上 Devin session token 即可,其餘免配置。要對外提供服務時,把 <code>HOST</code> 改成 <code>0.0.0.0</code> 並保留強 <code>API_KEY</code>。</div>
</div></div>
</div>
</div>
</div>
</div>
</section>
<!-- ── Usage ── -->
<section class="usage-section">
<div class="container">
<div class="reveal">
<div class="kicker">客戶端接入 · Integrations</div>
<h2 class="section-title">你常用的那個 IDE,<em>已經兼容了</em>。</h2>
<p class="section-sub">改 BASE_URL,塞 API KEY,完事。</p>
</div>
<div class="usage-grid reveal">
<div class="usage-card">
<h4><span class="logo-chip" style="background:#CC785C">C</span>Claude Code</h4>
<p class="endpoint-tag">→ <strong>/v1/messages</strong> · Anthropic 協定</p>
<pre><span class="c-key">export</span> ANTHROPIC_BASE_URL=<span class="c-str">"http://YOUR_IP:3003"</span>
<span class="c-key">export</span> ANTHROPIC_API_KEY=<span class="c-str">"sk-dv-your-key"</span>
claude</pre>
</div>
<div class="usage-card">
<h4><span class="logo-chip" style="background:#3B82F6">C</span>Cursor</h4>
<p class="endpoint-tag">→ <strong>/v1/chat/completions</strong> · OpenAI 協定</p>
<pre><span class="c-com"># Settings → Models → Custom OpenAI</span>
Base URL: <span class="c-str">http://YOUR_IP:3003/v1</span>
API Key: <span class="c-str">sk-dv-your-key</span>
Model: <span class="c-str">claude-opus-4.6</span></pre>
</div>
<div class="usage-card">
<h4><span class="logo-chip" style="background:#8B6FB0">C</span>Cline / Roo Code</h4>
<p class="endpoint-tag">→ <strong>Anthropic</strong> 或 <strong>OpenAI</strong> provider 皆可</p>
<pre><span class="c-com"># Provider: OpenAI Compatible</span>
Base URL: <span class="c-str">http://YOUR_IP:3003/v1</span>
API Key: <span class="c-str">sk-dv-your-key</span></pre>
</div>
<div class="usage-card">
<h4><span class="logo-chip" style="background:#10A37F">O</span>OpenAI SDK</h4>
<p class="endpoint-tag">→ <strong>/v1/chat/completions</strong></p>
<pre><span class="c-key">from</span> openai <span class="c-key">import</span> OpenAI
client = OpenAI(
base_url=<span class="c-str">"http://YOUR_IP:3003/v1"</span>,
api_key=<span class="c-str">"sk-dv-your-key"</span>,
)</pre>
</div>
</div>
</div>
</section>
<!-- ── FAQ ── -->
<section id="faq">
<div class="narrow">
<div class="reveal">
<div class="kicker">常見問題 · FAQ</div>
<h2 class="section-title">你可能想問的。</h2>
</div>
<div class="faq-list reveal">
<details class="faq-item">
<summary>需要 Devin 付費帳號嗎?</summary>
<div class="a">免費帳號可以跑,但可用模型依 tier 而定,免費層通常只開放輕量模型。Claude、GPT-5 全系、Gemini 3、GLM、Kimi 等需要對應的 Devin 付費 tier,後台會自動偵測並標記。</div>
</details>
<details class="faq-item">
<summary>可以在 Windows 上跑嗎?</summary>
<div class="a">可以,而且最省事——到 <a href="https://github.com/dwgx/WindsurfAPI/releases/latest" target="_blank" rel="noopener">Releases</a> 下載 <code>windsurfapi-windows.zip</code>(內建 Node,免安裝、免 clone),解壓後雙擊 <code>tray.vbs</code> 即縮進系統匣,首次自動生成金鑰與後台密碼,右鍵可一鍵複製密碼 / API Key、開面板、重啟;只想要單檔也可下裸 <code>windsurfapi.exe</code>(控制台視窗版)。當然也能照手動 / Docker 方式跑:預設的 Devin 雲端路徑(<code>DEVIN_CONNECT=1</code>)是純 HTTP,不依賴平台專屬二進位,Windows / macOS / Linux 通吃,只有選用的傳統語言伺服器路徑仍限 Linux。</div>
</details>
<details class="faq-item">
<summary>看不到最新模型怎麼辦?</summary>
<div class="a">走 Devin 雲端路徑時,<code>/v1/models</code> 透過 GetChatMessage 自動發現雲端目錄,新模型上線即可見,無需拷貝或更新任何二進位;清單看似落後就重啟服務重新拉取。</div>
</details>
<details class="faq-item">
<summary>帳號會被封嗎?</summary>
<div class="a">高頻 burst 確實容易被識別。後台的封禁偵測面板會監控錯誤率,按 5 分鐘窗口做速率冷卻。推薦:每帳號 RPM < 10、配不同出口代理(支援 SOCKS5)、混用思考型和普通模型。</div>
</details>
<details class="faq-item">
<summary>和其他類似專案的區別?</summary>
<div class="a">三點核心差異:<br>
(1) <strong>四协议</strong>——<code>/v1/chat/completions</code>、<code>/v1/responses</code>、<code>/v1/messages</code> 与 Gemini 端点。<br>
(2) <strong>planner_mode=NO_TOOL</strong>——關掉 Devin 內建工具循環,消除路徑洩露。<br>
(3) <strong>手寫零依賴協定棧 + 帳號池</strong>——純 HTTP 直連 Devin 雲端,多號輪詢 + 故障轉移 + Docker 一鍵部署。</div>
</details>
<details class="faq-item">
<summary>MIT License,可以商用嗎?</summary>
<div class="a">代碼本體 MIT License,法律上允許商用。README 顶上有一段作者態度:沒給 Star 和 Follow 的請別商業轉售,點了的隨便用。</div>
</details>
</div>
</div>
</section>
<!-- ── Contributors ── -->
<section class="contributors-section" id="contributors" style="padding:5rem 0">
<div class="container">
<div class="reveal">
<div class="kicker">致謝 · Credits</div>
<h2 class="section-title">這些朋友把這個專案<em>撐起來</em>了。</h2>
<p class="section-sub">每一條 PR 都附上 root-cause 分析;每一個 root-cause 都對應半夜在 Issues 區罵 Claude 的瞬間。權重按貢獻次數與精準度排,不是按代碼行數。</p>
</div>
<div class="contrib-grid reveal" id="contributors-grid" data-source="dashboard/data/contributors.json">
<div class="contrib-loading">正在載入貢獻者名單...</div>
</div>
<p class="reveal" style="text-align:center;margin-top:2rem;font-size:.85rem;color:var(--muted)">
想加入這份名單?到 <a href="https://github.com/dwgx/WindsurfAPI/issues" target="_blank" rel="noopener" style="color:var(--coral-deep)">Issues</a> 提 bug 或到 <a href="https://github.com/dwgx/WindsurfAPI/pulls" target="_blank" rel="noopener" style="color:var(--coral-deep)">Pull requests</a> 直接動手都歡迎。
</p>
</div>
</section>
<!-- ── Footer ── -->
<footer>
<div class="container">
<div class="footer-inner">
<div class="footer-brand">
<div class="logo">DevinAPI</div>
<p>Windsurf / Devin 雲端反向代理 · 純 Node.js 零依賴 · MIT License · <a href="https://github.com/dwgx" target="_blank" rel="noopener">@dwgx</a></p>
</div>
<div class="footer-col">
<h5>專案</h5>
<ul>
<li><a href="https://github.com/dwgx/WindsurfAPI" target="_blank" rel="noopener">GitHub</a></li>
<li><a href="https://www.star-history.com/?type=date&repos=dwgx/WindsurfAPI" target="_blank" rel="noopener">Star History</a></li>
<li><a href="https://github.com/dwgx/WindsurfAPI/releases" target="_blank" rel="noopener">Releases</a></li>
<li><a href="https://github.com/dwgx/WindsurfAPI/issues" target="_blank" rel="noopener">Issues</a></li>
<li><a href="https://github.com/dwgx/WindsurfAPI/blob/master/SECURITY.md" target="_blank" rel="noopener">Security</a></li>
</ul>
</div>
<div class="footer-col">
<h5>文檔</h5>
<ul>
<li><a href="https://github.com/dwgx/WindsurfAPI/blob/master/README.md" target="_blank" rel="noopener">README (中文)</a></li>
<li><a href="https://github.com/dwgx/WindsurfAPI/blob/master/README.en.md" target="_blank" rel="noopener">README (EN)</a></li>
<li><a href="review.html">Architecture Review</a></li>
<li><a href="https://github.com/dwgx/WindsurfAPI/blob/master/CONTRIBUTING.md" target="_blank" rel="noopener">Contributing</a></li>
</ul>
</div>
</div>
<div class="footer-bottom">
<span id="contributors-footer">Contributors: <a href="#contributors" style="opacity:.7">完整名單 ↑</a></span>
<span>© 2026 dwgx</span>
</div>
</div>
</footer>
<script>
/* ── Models ── */
(function(){
const MODELS = [
{k:'claude-4-sonnet',p:'anthropic',c:2},
{k:'claude-4-sonnet-thinking',p:'anthropic',c:3,thinking:1},
{k:'claude-4-opus',p:'anthropic',c:4},
{k:'claude-4-opus-thinking',p:'anthropic',c:5,thinking:1},
{k:'claude-4.1-opus',p:'anthropic',c:4},
{k:'claude-4.1-opus-thinking',p:'anthropic',c:5,thinking:1},
{k:'claude-4.5-haiku',p:'anthropic',c:1},
{k:'claude-4.5-sonnet',p:'anthropic',c:2},
{k:'claude-4.5-sonnet-thinking',p:'anthropic',c:3,thinking:1},
{k:'claude-4.5-opus',p:'anthropic',c:4},
{k:'claude-4.5-opus-thinking',p:'anthropic',c:5,thinking:1},
{k:'claude-sonnet-4.6',p:'anthropic',c:4},
{k:'claude-sonnet-4.6-thinking',p:'anthropic',c:6,thinking:1},
{k:'claude-sonnet-4.6-1m',p:'anthropic',c:12},
{k:'claude-sonnet-4.6-thinking-1m',p:'anthropic',c:16,thinking:1},
{k:'claude-opus-4.6',p:'anthropic',c:6},
{k:'claude-opus-4.6-thinking',p:'anthropic',c:8,thinking:1},
{k:'claude-opus-4-6-1m',p:'anthropic',c:10},
{k:'claude-opus-4-6-thinking-1m',p:'anthropic',c:12,thinking:1},
{k:'claude-opus-4-7-medium',p:'anthropic',c:40},
{k:'claude-opus-4-7-low',p:'anthropic',c:20},
{k:'claude-opus-4-7-high',p:'anthropic',c:60},
{k:'claude-opus-4-7-xhigh',p:'anthropic',c:65},
{k:'claude-opus-4-7-medium-thinking',p:'anthropic',c:10,thinking:1},
{k:'claude-opus-4-7-high-thinking',p:'anthropic',c:12,thinking:1},
{k:'claude-opus-4-7-xhigh-thinking',p:'anthropic',c:16,thinking:1},
{k:'claude-opus-4-7-max',p:'anthropic',c:75},
{k:'claude-opus-4-8-low',p:'anthropic',c:20},
{k:'claude-opus-4-8-medium',p:'anthropic',c:25},
{k:'claude-opus-4-8-high',p:'anthropic',c:35},
{k:'claude-opus-4-8-xhigh',p:'anthropic',c:40},
{k:'claude-opus-4-8-max',p:'anthropic',c:50},
{k:'claude-opus-4-8-low-fast',p:'anthropic',c:40},
{k:'claude-opus-4-8-medium-fast',p:'anthropic',c:50},
{k:'claude-opus-4-8-high-fast',p:'anthropic',c:70},
{k:'claude-opus-4-8-xhigh-fast',p:'anthropic',c:80},
{k:'claude-opus-4-8-max-fast',p:'anthropic',c:100},
{k:'claude-fable-5-1-low',p:'anthropic',c:150},
{k:'claude-fable-5-1-medium',p:'anthropic',c:175},
{k:'claude-fable-5-1-high',p:'anthropic',c:200},
{k:'claude-fable-5-1-xhigh',p:'anthropic',c:225},
{k:'claude-fable-5-1-max',p:'anthropic',c:250},
{k:'claude-opus-5-5-low',p:'anthropic',c:35},
{k:'claude-opus-5-5-medium',p:'anthropic',c:45},
{k:'claude-opus-5-5-high',p:'anthropic',c:65},
{k:'claude-opus-5-5-xhigh',p:'anthropic',c:125},
{k:'claude-opus-5-5-max',p:'anthropic',c:150},
{k:'claude-sonnet-5-5-low',p:'anthropic',c:12},
{k:'claude-sonnet-5-5-medium',p:'anthropic',c:15},
{k:'claude-sonnet-5-5-high',p:'anthropic',c:20},
{k:'claude-sonnet-5-5-xhigh',p:'anthropic',c:30},
{k:'claude-sonnet-5-5-max',p:'anthropic',c:40},
{k:'claude-haiku-5-5-low',p:'anthropic',c:3},
{k:'claude-haiku-5-5-medium',p:'anthropic',c:4},
{k:'claude-haiku-5-5-high',p:'anthropic',c:5},
{k:'claude-haiku-5-5-xhigh',p:'anthropic',c:8},
{k:'claude-haiku-5-5-max',p:'anthropic',c:10},
{k:'claude-5-fable-low',p:'anthropic',c:40},
{k:'claude-5-fable-medium',p:'anthropic',c:50},
{k:'claude-5-fable-high',p:'anthropic',c:70},
{k:'claude-5-fable-xhigh',p:'anthropic',c:80},
{k:'claude-5-fable-max',p:'anthropic',c:100},
{k:'claude-sonnet-5-low',p:'anthropic',c:12},
{k:'claude-sonnet-5-medium',p:'anthropic',c:15},
{k:'claude-sonnet-5-high',p:'anthropic',c:20},
{k:'claude-sonnet-5-xhigh',p:'anthropic',c:30},
{k:'claude-sonnet-5-max',p:'anthropic',c:40},
{k:'claude-opus-5-low',p:'anthropic',c:75},
{k:'claude-opus-5-medium',p:'anthropic',c:80},
{k:'claude-opus-5-high',p:'anthropic',c:115},
{k:'claude-opus-5-xhigh',p:'anthropic',c:155},
{k:'claude-opus-5-max',p:'anthropic',c:160},
{k:'claude-opus-5-low-fast',p:'anthropic',c:150},
{k:'claude-opus-5-medium-fast',p:'anthropic',c:160},
{k:'claude-opus-5-high-fast',p:'anthropic',c:230},
{k:'claude-opus-5-xhigh-fast',p:'anthropic',c:310},
{k:'claude-opus-5-max-fast',p:'anthropic',c:320},
{k:'gpt-5',p:'openai',c:0.5},
{k:'gpt-5-medium',p:'openai',c:1},
{k:'gpt-5-high',p:'openai',c:2},
{k:'gpt-5-codex',p:'openai',c:0.5},
{k:'gpt-5.1',p:'openai',c:0.5},
{k:'gpt-5.1-low',p:'openai',c:0.5},
{k:'gpt-5.1-medium',p:'openai',c:1},
{k:'gpt-5.1-high',p:'openai',c:2},
{k:'gpt-5.1-fast',p:'openai',c:1},
{k:'gpt-5.1-low-fast',p:'openai',c:1},
{k:'gpt-5.1-medium-fast',p:'openai',c:2},
{k:'gpt-5.1-high-fast',p:'openai',c:4},
{k:'gpt-5.1-codex-low',p:'openai',c:0.5},
{k:'gpt-5.1-codex-medium',p:'openai',c:1},
{k:'gpt-5.1-codex-mini-low',p:'openai',c:0.25},
{k:'gpt-5.1-codex-mini',p:'openai',c:0.5},
{k:'gpt-5.1-codex-max-low',p:'openai',c:1},
{k:'gpt-5.1-codex-max-medium',p:'openai',c:1.25},
{k:'gpt-5.1-codex-max-high',p:'openai',c:1.5},
{k:'gpt-5.2',p:'openai',c:2},
{k:'gpt-5.2-none',p:'openai',c:1},
{k:'gpt-5.2-low',p:'openai',c:1},
{k:'gpt-5.2-high',p:'openai',c:3},
{k:'gpt-5.2-xhigh',p:'openai',c:8},
{k:'gpt-5.2-none-fast',p:'openai',c:2},
{k:'gpt-5.2-low-fast',p:'openai',c:2},
{k:'gpt-5.2-medium-fast',p:'openai',c:4},
{k:'gpt-5.2-high-fast',p:'openai',c:6},
{k:'gpt-5.2-xhigh-fast',p:'openai',c:16},
{k:'gpt-5.2-codex-low',p:'openai',c:1},
{k:'gpt-5.2-codex-medium',p:'openai',c:1},
{k:'gpt-5.2-codex-high',p:'openai',c:2},
{k:'gpt-5.2-codex-xhigh',p:'openai',c:3},
{k:'gpt-5.2-codex-low-fast',p:'openai',c:2},
{k:'gpt-5.2-codex-medium-fast',p:'openai',c:2},
{k:'gpt-5.2-codex-high-fast',p:'openai',c:4},
{k:'gpt-5.2-codex-xhigh-fast',p:'openai',c:6},
{k:'gpt-5.3-codex',p:'openai',c:2},
{k:'gpt-5.4-none',p:'openai',c:1.5},
{k:'gpt-5.4-low',p:'openai',c:1.5},
{k:'gpt-5.4-medium',p:'openai',c:3},
{k:'gpt-5.4-high',p:'openai',c:4},
{k:'gpt-5.4-xhigh',p:'openai',c:12},
{k:'gpt-5.4-mini-low',p:'openai',c:1.5},
{k:'gpt-5.4-mini-medium',p:'openai',c:1.5},
{k:'gpt-5.4-mini-high',p:'openai',c:4.5},
{k:'gpt-5.4-mini-xhigh',p:'openai',c:12},
{k:'gpt-5.5',p:'openai',c:15},
{k:'gpt-5.5-none',p:'openai',c:9},
{k:'gpt-5.5-low',p:'openai',c:12},
{k:'gpt-5.5-medium',p:'openai',c:15},
{k:'gpt-5.5-high',p:'openai',c:24},
{k:'gpt-5.5-xhigh',p:'openai',c:36},
{k:'gpt-5.5-none-fast',p:'openai',c:22.5},
{k:'gpt-5.5-low-fast',p:'openai',c:30},
{k:'gpt-5.5-medium-fast',p:'openai',c:37.5},
{k:'gpt-5.5-high-fast',p:'openai',c:60},
{k:'gpt-5.5-xhigh-fast',p:'openai',c:90},
{k:'gpt-5.6-luna',p:'openai',c:5},
{k:'gpt-5.6-luna-none',p:'openai',c:3},
{k:'gpt-5.6-luna-low',p:'openai',c:4},
{k:'gpt-5.6-luna-medium',p:'openai',c:5},
{k:'gpt-5.6-luna-high',p:'openai',c:6},
{k:'gpt-5.6-luna-xhigh',p:'openai',c:8},
{k:'gpt-5.3-codex-low',p:'openai',c:1.5},
{k:'gpt-5.3-codex-high',p:'openai',c:2.5},
{k:'gpt-5.3-codex-xhigh',p:'openai',c:3},
{k:'gpt-5.3-codex-low-fast',p:'openai',c:3},
{k:'gpt-5.3-codex-medium-fast',p:'openai',c:4},
{k:'gpt-5.3-codex-high-fast',p:'openai',c:5},
{k:'gpt-5.3-codex-xhigh-fast',p:'openai',c:6},
{k:'gpt-oss-120b',p:'openai',c:0.25},
{k:'gpt-5-6-luna-max',p:'openai',c:30},
{k:'gpt-5-6-sol-none',p:'openai',c:10},
{k:'gpt-5-6-sol-low',p:'openai',c:30},
{k:'gpt-5-6-sol-medium',p:'openai',c:50},
{k:'gpt-5-6-sol-high',p:'openai',c:90},
{k:'gpt-5-6-sol-xhigh',p:'openai',c:150},
{k:'gpt-5-6-sol-max',p:'openai',c:200},
{k:'gpt-5-6-terra-none',p:'openai',c:4},
{k:'gpt-5-6-terra-low',p:'openai',c:8},
{k:'gpt-5-6-terra-medium',p:'openai',c:10},
{k:'gpt-5-6-terra-high',p:'openai',c:20},
{k:'gpt-5-6-terra-xhigh',p:'openai',c:30},
{k:'gpt-5-6-terra-max',p:'openai',c:50},
{k:'gpt-6-1-sol-low',p:'openai',c:30},
{k:'gpt-6-1-sol-medium',p:'openai',c:40},
{k:'gpt-6-1-sol-high',p:'openai',c:50},
{k:'gpt-6-1-sol-xhigh',p:'openai',c:70},
{k:'gpt-6-1-sol-max',p:'openai',c:100},
{k:'gpt-6-astra-low',p:'openai',c:100},
{k:'gpt-6-astra-medium',p:'openai',c:200},
{k:'gpt-6-astra-high',p:'openai',c:400},
{k:'gpt-6-astra-xhigh',p:'openai',c:375},
{k:'gpt-6-astra-max',p:'openai',c:500},
{k:'gpt-6-luna-none',p:'openai',c:2},
{k:'gpt-6-luna-low',p:'openai',c:3},
{k:'gpt-6-luna-medium',p:'openai',c:4},
{k:'gpt-6-luna-high',p:'openai',c:5},
{k:'gpt-6-luna-xhigh',p:'openai',c:6},
{k:'gpt-6-luna-max',p:'openai',c:7},
{k:'gpt-6-sol-none',p:'openai',c:65},
{k:'gpt-6-sol-low',p:'openai',c:135},
{k:'gpt-6-sol-medium',p:'openai',c:145},
{k:'gpt-6-sol-high',p:'openai',c:150},
{k:'gpt-6-sol-xhigh',p:'openai',c:190},
{k:'gpt-6-sol-max',p:'openai',c:200},
{k:'gemini-2.5-pro',p:'google',c:1},
{k:'gemini-2.5-flash',p:'google',c:0.5,free:1},
{k:'gemini-3.0-pro',p:'google',c:1},
{k:'gemini-3.0-flash-minimal',p:'google',c:0.75,free:1},
{k:'gemini-3.0-flash-low',p:'google',c:1},
{k:'gemini-3.0-flash',p:'google',c:1},
{k:'gemini-3.0-flash-high',p:'google',c:1.75},
{k:'gemini-3.1-pro-low',p:'google',c:1},
{k:'gemini-3.1-pro-high',p:'google',c:2},
{k:'gemini-3-5-flash-minimal',p:'google',c:2},
{k:'gemini-3-5-flash-low',p:'google',c:4},
{k:'gemini-3-5-flash-medium',p:'google',c:8},
{k:'gemini-3-5-flash-high',p:'google',c:9},
{k:'gemini-3-6-flash-minimal',p:'google',c:2},
{k:'gemini-3-6-flash-low',p:'google',c:4},
{k:'gemini-3-6-flash-medium',p:'google',c:8},
{k:'gemini-3-6-flash-high',p:'google',c:9},
{k:'gemini-3-7-flash-low',p:'google',c:6},
{k:'gemini-3-7-flash-medium',p:'google',c:8},
{k:'gemini-3-7-flash-high',p:'google',c:10},
{k:'gemini-3-8-flash-low',p:'google',c:6},
{k:'gemini-3-8-flash-medium',p:'google',c:8},
{k:'gemini-3-8-flash-high',p:'google',c:10},
{k:'gemini-4-argon-low',p:'google',c:50},
{k:'gemini-4-argon-medium',p:'google',c:70},
{k:'gemini-4-argon-high',p:'google',c:90},
{k:'deepseek-v4',p:'deepseek',c:0.5},
{k:'deepseek-v4-flash-low',p:'deepseek',c:0.25},
{k:'deepseek-v4-flash-high',p:'deepseek',c:0.5},
{k:'deepseek-v4-flash-max',p:'deepseek',c:1},
{k:'deepseek-v4-pro-low',p:'deepseek',c:3},
{k:'deepseek-v4-pro-high',p:'deepseek',c:3},
{k:'deepseek-v4-pro-max',p:'deepseek',c:3},
{k:'deepseek-v4-1-flash-high',p:'deepseek',c:2},
{k:'deepseek-v4-1-flash-max',p:'deepseek',c:3},
{k:'grok-3',p:'xai',c:1},
{k:'grok-3-mini-thinking',p:'xai',c:0.125,thinking:1},
{k:'grok-code-fast-1',p:'xai',c:0.5},
{k:'grok-4-5-low',p:'xai',c:5},
{k:'grok-4-5-medium',p:'xai',c:8},
{k:'grok-4-5-high',p:'xai',c:10},
{k:'grok-4-6-low',p:'xai',c:10},
{k:'grok-4-6-medium',p:'xai',c:20},
{k:'grok-4-6-high',p:'xai',c:25},
{k:'grok-4-6-xhigh',p:'xai',c:30},
{k:'grok-4-7-low',p:'xai',c:15},
{k:'grok-4-7-medium',p:'xai',c:20},
{k:'grok-4-7-high',p:'xai',c:30},
{k:'grok-4-7-xhigh',p:'xai',c:55},
{k:'kimi-k2',p:'moonshot',c:0.5},
{k:'kimi-k2-thinking',p:'moonshot',c:1,thinking:1},
{k:'kimi-k2.5',p:'moonshot',c:1},
{k:'kimi-k2-6',p:'moonshot',c:1},
{k:'kimi-k2-7',p:'moonshot',c:1},
{k:'kimi-k3-low',p:'moonshot',c:4},
{k:'kimi-k3-high',p:'moonshot',c:6},
{k:'kimi-k3-max',p:'moonshot',c:9},
{k:'glm-4.7',p:'zhipu',c:0.25,free:1},
{k:'glm-4.7-fast',p:'zhipu',c:0.5},
{k:'glm-5',p:'zhipu',c:1.5},
{k:'glm-5.1',p:'zhipu',c:1.5},
{k:'glm-5.2',p:'zhipu',c:1.5},
{k:'glm-5-2-none',p:'zhipu',c:1},
{k:'glm-5-2-none-1m',p:'zhipu',c:2},
{k:'glm-5-2-1m',p:'zhipu',c:3},
{k:'glm-5-2-max',p:'zhipu',c:3},
{k:'glm-5-2-max-1m',p:'zhipu',c:6},
{k:'glm-5-3-flash-low',p:'zhipu',c:1},
{k:'glm-5-3-flash-high',p:'zhipu',c:1},
{k:'glm-5-3-flash-max',p:'zhipu',c:1},
{k:'glm-5-3-low',p:'zhipu',c:1},
{k:'glm-5-3-high',p:'zhipu',c:1},
{k:'glm-5-3-max',p:'zhipu',c:1},
{k:'minimax-m2.5',p:'minimax',c:1},
{k:'inkling-none',p:'thinking_machines',c:2},
{k:'inkling-low',p:'thinking_machines',c:4},
{k:'inkling-medium',p:'thinking_machines',c:6},
{k:'inkling-high',p:'thinking_machines',c:8},
{k:'inkling-xhigh',p:'thinking_machines',c:10},
{k:'inkling-max',p:'thinking_machines',c:14},
{k:'nemotron-3-ultra-none',p:'nvidia',c:1},
{k:'nemotron-3-ultra-medium',p:'nvidia',c:1},
{k:'nemotron-3-ultra-high',p:'nvidia',c:1},
{k:'swe-1.5',p:'windsurf',c:0.5},
{k:'swe-1.5-fast',p:'windsurf',c:0.5,free:1},
{k:'swe-1.5-thinking',p:'windsurf',c:0.75,thinking:1},
{k:'swe-1.6',p:'windsurf',c:3},
{k:'swe-1.6-fast',p:'windsurf',c:3},
{k:'swe-1-7',p:'windsurf',c:9},
{k:'swe-1-7-lightning',p:'windsurf',c:18},
{k:'swe-2',p:'windsurf',c:6},
{k:'swe-2-medium',p:'windsurf',c:6},
{k:'swe-2-high',p:'windsurf',c:9},
{k:'swe-2-max',p:'windsurf',c:12},
{k:'swe-2-medium-lightning',p:'windsurf',c:12},
{k:'swe-2-high-lightning',p:'windsurf',c:18},
{k:'swe-2-max-lightning',p:'windsurf',c:24},
{k:'swe-1-7-lightning-medium',p:'windsurf',c:9},
{k:'swe-1-7-medium',p:'windsurf',c:3},
];
const groups = document.getElementById('model-groups');
if(!groups) return;
const PROVIDER_LABEL = {anthropic:'Claude', openai:'GPT', google:'Gemini', xai:'Grok', moonshot:'Kimi', zhipu:'GLM', minimax:'MiniMax', deepseek:'DeepSeek', thinking_machines:'Inkling', nvidia:'Nemotron', astraflow:'AstraFlow', windsurf:'Windsurf'};
// Brand accent per provider (dark-mode-safe: xai/moonshot lightened from pure black).
const PROVIDER_COLOR = {anthropic:'#CC785C', openai:'#10A37F', google:'#4285F4', xai:'#8A8F98', moonshot:'#7C6FF0', zhipu:'#3B82F6', minimax:'#FF6B35', deepseek:'#5E6AD2', thinking_machines:'#E0B252', nvidia:'#76B900', astraflow:'#0EA5A5', windsurf:'#9A8FD8'};
const PROVIDER_LOGOS = {"anthropic":"<svg fill=\"#000\" fill-rule=\"evenodd\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z\"></path></svg>","openai":"<svg xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMidYMid\" viewBox=\"0 0 256 260\"><path d=\"M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z\"/></svg>","google":"<svg viewBox=\"0 0 296 298\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\"><mask id=\"a\" x=\"0\" y=\"0\" maskUnits=\"userSpaceOnUse\"><path fill=\"#3186FF\" d=\"M141.201 4.886c2.282-6.17 11.042-6.071 13.184.148l5.985 17.37a184.004 184.004 0 0 0 111.257 113.049l19.304 6.997c6.143 2.227 6.156 10.91.02 13.155l-19.35 7.082a184.001 184.001 0 0 0-109.495 109.385l-7.573 20.629c-2.241 6.105-10.869 6.121-13.133.025l-7.908-21.296a184 184 0 0 0-109.02-108.658l-19.698-7.239c-6.102-2.243-6.118-10.867-.025-13.132l20.083-7.467A183.998 183.998 0 0 0 133.291 26.28l7.91-21.394Z\"/></mask><g mask=\"url(#a)\"><g filter=\"url(#b)\"><ellipse cx=\"163\" cy=\"149\" fill=\"#3689FF\" rx=\"196\" ry=\"159\"/></g><g filter=\"url(#c)\"><ellipse cx=\"33.5\" cy=\"142.5\" fill=\"#F6C013\" rx=\"68.5\" ry=\"72.5\"/></g><g filter=\"url(#d)\"><ellipse cx=\"19.5\" cy=\"148.5\" fill=\"#F6C013\" rx=\"68.5\" ry=\"72.5\"/></g><g filter=\"url(#e)\"><path fill=\"#FA4340\" d=\"M194 10.5C172 82.5 65.5 134.333 22.5 135L144-66l50 76.5Z\"/></g><g filter=\"url(#f)\"><path fill=\"#FA4340\" d=\"M190.5-12.5C168.5 59.5 62 111.333 19 112L140.5-89l50 76.5Z\"/></g><g filter=\"url(#g)\"><path fill=\"#14BB69\" d=\"M194.5 279.5C172.5 207.5 66 155.667 23 155l121.5 201 50-76.5Z\"/></g><g filter=\"url(#h)\"><path fill=\"#14BB69\" d=\"M196.5 320.5C174.5 248.5 68 196.667 25 196l121.5 201 50-76.5Z\"/></g></g><defs><filter id=\"b\" x=\"-69\" y=\"-46\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"18\"/></filter><filter id=\"c\" x=\"-99\" y=\"6\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"32\"/></filter><filter id=\"d\" x=\"-113\" y=\"12\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"32\"/></filter><filter id=\"e\" x=\"-41.5\" y=\"-130\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"32\"/></filter><filter id=\"f\" x=\"-45\" y=\"-153\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"32\"/></filter><filter id=\"g\" x=\"-41\" y=\"91\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"32\"/></filter><filter id=\"h\" x=\"-39\" y=\"132\" color-interpolation-filters=\"sRGB\" filterUnits=\"userSpaceOnUse\"><feFlood flood-opacity=\"0\" result=\"BackgroundImageFix\"/><feBlend in=\"SourceGraphic\" in2=\"BackgroundImageFix\" result=\"shape\"/><feGaussianBlur result=\"effect1_foregroundBlur_69_17998\" stdDeviation=\"32\"/></filter></defs></svg>","xai":"<svg viewBox=\"0 0 1024 1024\" fill=\"#111\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M395.479 633.828L735.91 381.105C752.599 368.715 776.454 373.548 784.406 392.792C826.26 494.285 807.561 616.253 724.288 699.996C641.016 783.739 525.151 802.104 419.247 760.277L303.556 814.143C469.49 928.202 670.987 899.995 796.901 773.282C896.776 672.843 927.708 535.937 898.785 412.476L899.047 412.739C857.105 231.37 909.358 158.874 1016.4 10.6326C1018.93 7.11771 1021.47 3.60279 1024 0L883.144 141.651V141.212L395.392 633.916\" fill=\"#0A0A0A\"/><path d=\"M325.226 695.251C206.128 580.84 226.662 403.776 328.285 301.668C403.431 226.097 526.549 195.254 634.026 240.596L749.454 186.994C728.657 171.88 702.007 155.623 671.424 144.2C533.19 86.9942 367.693 115.465 255.323 228.382C147.234 337.081 113.244 504.215 171.613 646.833C215.216 753.423 143.739 828.818 71.7385 904.916C46.2237 931.893 20.6216 958.87 0 987.429L325.139 695.339\" fill=\"#0A0A0A\"/></svg>","moonshot":"<svg viewBox=\"0 0 512 512\" xmlns=\"http://www.w3.org/2000/svg\" fill-rule=\"evenodd\" clip-rule=\"evenodd\" stroke-linejoin=\"round\" stroke-miterlimit=\"2\"><path d=\"M503 114.333v280c0 60.711-49.29 110-110 110H113c-60.711 0-110-49.289-110-110v-280c0-60.71 49.289-110 110-110h280c60.71 0 110 49.29 110 110z\"/><path d=\"M342.065 189.759c1.886-2.42 3.541-4.63 5.289-6.77.81-1.007.74-1.771-.046-2.824-7.58-9.965-8.298-21.028-3.935-32.254 3.275-8.448 10.52-12.406 19.373-13.25 5.52-.521 10.936.046 15.959 2.73 6.596 3.53 10.438 8.912 11.688 16.341.995 5.926.81 11.712-.868 17.452-2.974 10.161-10.277 15.427-20.287 16.758-8.31 1.11-16.734 1.25-25.113 1.817-.648.046-1.308 0-2.06 0z\" fill=\"#027aff\"/><path d=\"M321.512 144.254h-50.064l-39.637 90.384h-56.036v-89.99H131v232.868h44.787v-98.103h78.973c13.598 0 26.015-7.927 31.744-20.252v118.355h44.787v-98.103c0-23.342-18.239-42.97-41.523-44.671v-.116h-24.593a45.577 45.577 0 0026.884-24.534l29.453-65.838z\" fill=\"#fff\"/></svg>","minimax":"<svg fill=\"#E73562\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M11.43 3.92a.86.86 0 1 0-1.718 0v14.236a1.999 1.999 0 0 1-3.997 0V9.022a.86.86 0 1 0-1.718 0v3.87a1.999 1.999 0 0 1-3.997 0V11.49a.57.57 0 0 1 1.139 0v1.404a.86.86 0 0 0 1.719 0V9.022a1.999 1.999 0 0 1 3.997 0v9.134a.86.86 0 0 0 1.719 0V3.92a1.998 1.998 0 1 1 3.996 0v11.788a.57.57 0 1 1-1.139 0zm10.572 3.105a2 2 0 0 0-1.999 1.997v7.63a.86.86 0 0 1-1.718 0V3.923a1.999 1.999 0 0 0-3.997 0v16.16a.86.86 0 0 1-1.719 0V18.08a.57.57 0 1 0-1.138 0v2a1.998 1.998 0 0 0 3.996 0V3.92a.86.86 0 0 1 1.719 0v12.73a1.999 1.999 0 0 0 3.996 0V9.023a.86.86 0 1 1 1.72 0v6.686a.57.57 0 0 0 1.138 0V9.022a2 2 0 0 0-1.998-1.997\"/></svg>","windsurf":"<svg viewBox=\"0 0 1024 1024\" fill=\"#111\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M897.246 286.869H889.819C850.735 286.808 819.017 318.46 819.017 357.539V515.589C819.017 547.15 792.93 572.716 761.882 572.716C743.436 572.716 725.02 563.433 714.093 547.85L552.673 317.304C539.28 298.16 517.486 286.747 493.895 286.747C457.094 286.747 423.976 318.034 423.976 356.657V515.619C423.976 547.181 398.103 572.746 366.842 572.746C348.335 572.746 329.949 563.463 319.021 547.881L138.395 289.882C134.316 284.038 125.154 286.93 125.154 294.052V431.892C125.154 438.862 127.285 445.619 131.272 451.34L309.037 705.2C319.539 720.204 335.033 731.344 352.9 735.392C397.616 745.557 438.77 711.135 438.77 667.278V508.406C438.77 476.845 464.339 451.279 495.904 451.279H495.995C515.02 451.279 532.857 460.562 543.785 476.145L705.235 706.661C718.659 725.835 739.327 737.218 763.983 737.218C801.606 737.218 833.841 705.9 833.841 667.308V508.376C833.841 476.815 859.41 451.249 890.975 451.249H897.276C901.233 451.249 904.43 448.053 904.43 444.097V294.021C904.43 290.065 901.233 286.869 897.276 286.869H897.246Z\" fill=\"#0b100f\"/></svg>"};
const MONOGRAM = {zhipu:"GLM", astraflow:"AF", deepseek:"DS", thinking_machines:"INK", nvidia:"NV"};
function logoChip(key){ if(PROVIDER_LOGOS[key]) return '<span class="ms-model-logo">'+PROVIDER_LOGOS[key]+'</span>'; if(MONOGRAM[key]) return '<span class="ms-model-logo is-mono">'+MONOGRAM[key]+'</span>'; return ''; }
// Order of the rendered provider bands. Every provider the generator emits must be listed here,
// or its models count toward "全部" but never render (astraflow dropped 2026-10-16: all 9 rows
// are deprecated, so the generator emits none).
const PROVIDER_ORDER = ['anthropic','openai','google','deepseek','moonshot','zhipu','xai','minimax','thinking_machines','nvidia','windsurf'];
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
// Reduce a model id to its family key by stripping effort/lane/thinking/ctx suffixes.
function familyKey(name){
let f = name;
for(let i=0;i<3;i++) f = f.replace(/-(low|medium|high|xhigh|max|none|fast|thinking|1m|priority)$/,'');
return f;
}
// Human family label.
function familyLabel(fk){
return fk
.replace(/^claude-opus-4-8/,'Opus 4.8').replace(/^claude-opus-4-7/,'Opus 4.7').replace(/^claude-opus-4\.6/,'Opus 4.6')
.replace(/^claude-sonnet-4\.6/,'Sonnet 4.6').replace(/^claude-4-sonnet/,'Sonnet 4').replace(/^claude-4-opus/,'Opus 4')
.replace(/^claude-4\.1-opus/,'Opus 4.1').replace(/^claude-4\.5-sonnet/,'Sonnet 4.5').replace(/^claude-4\.5-opus/,'Opus 4.5').replace(/^claude-4\.5-haiku/,'Haiku 4.5')
.replace(/^gpt-/,'GPT-').replace(/^gemini-/,'Gemini ').replace(/^grok-/,'Grok ').replace(/^kimi-/,'Kimi ')
.replace(/^glm-/,'GLM ').replace(/^swe-/,'SWE ').replace(/^minimax-/,'MiniMax ').replace(/^qwen-?/,'Qwen ').replace(/^astraflow\//,'')
.replace(/^deepseek-/,'DeepSeek ').replace(/^inkling/,'Inkling').replace(/^nemotron-/,'Nemotron ');
}
// Suffix shown for a variant inside an open family (the part after the family key).
function variantSuffix(name, fk){
const s = name.slice(fk.length).replace(/^-/,'');
return s || 'base';
}
function famBadges(fam){
let b='';
if(fam.some(m=>m.thinking)) b+='<span class="ms-fam-badge is-think">思考</span>';
if(fam.some(m=>m.ctx==='1m')) b+='<span class="ms-fam-badge is-ctx">1M</span>';
if(fam.some(m=>m.free)) b+='<span class="ms-fam-badge is-free">免費</span>';
return b;
}
const chevSvg = '<svg class="ms-fam-chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l4 4-4 4"/></svg>';
function crRange(fam){
const cs = fam.map(m=>m.c);
const lo = Math.min(...cs), hi = Math.max(...cs);
return lo===hi ? (lo+' cr') : (lo+'–'+hi+' cr');
}
// Build one family row (single = flat, multi = expandable).
function famNode(fk, fam){
const label = familyLabel(fk);
const single = fam.length===1;
if(single){
const m = fam[0];
return '<div class="ms-fam ms-fam-flat">'
+ '<div class="ms-fam-row ms-fam-single">'
+ '<span class="ms-fam-dot"></span>'
+ '<span class="ms-fam-name" title="'+esc(m.k)+'">'+esc(m.k)+'</span>'
+ '<span class="ms-fam-badges">'+famBadges(fam)+'</span>'
+ '<span class="ms-fam-spacer"></span>'
+ '<span class="ms-fam-cr"><b>'+m.c+'</b> cr</span></div></div>';
}
const vars = fam.map(m =>
'<div class="ms-fam-var"><span class="ms-fam-var-name">'+esc(m.k)+'</span>'
+ '<span class="ms-fam-badges">'+(m.thinking?'<span class="ms-fam-badge is-think">思考</span>':'')+(m.ctx==='1m'?'<span class="ms-fam-badge is-ctx">1M</span>':'')+(m.free?'<span class="ms-fam-badge is-free">免費</span>':'')+'</span>'
+ '<span class="ms-fam-spacer"></span><span class="ms-fam-var-cr"><b>'+m.c+'</b></span></div>'
).join('');
return '<div class="ms-fam">'
+ '<button type="button" class="ms-fam-row" aria-expanded="false">'
+ '<span class="ms-fam-dot"></span>'
+ '<span class="ms-fam-name">'+esc(label)+'</span>'
+ '<span class="ms-fam-badges">'+famBadges(fam)+'</span>'
+ '<span class="ms-fam-spacer"></span>'
+ '<span class="ms-fam-cr">'+fam.length+' 個 · '+crRange(fam)+'</span>'
+ chevSvg + '</button>'
+ '<div class="ms-fam-variants">'+vars+'</div></div>';
}
// Bucket by provider, then by family (preserving first-seen order within provider).
const buckets = {};
MODELS.forEach(m => { (buckets[m.p] = buckets[m.p] || []).push(m); });
let html = '';
// Per-provider expand state, remembered across refreshes. Default: all collapsed.
var GROUP_STATE_KEY = 'devinapi-model-groups';
function loadGroupState(){ try{ return JSON.parse(localStorage.getItem(GROUP_STATE_KEY)) || {}; }catch(e){ return {}; } }
function saveGroupState(st){ try{ localStorage.setItem(GROUP_STATE_KEY, JSON.stringify(st)); }catch(e){} }
var groupState = loadGroupState();
PROVIDER_ORDER.forEach(key => {
const list = buckets[key];
if(!list || !list.length) return;
const fams = {}; const order = [];
list.forEach(m => { const fk=familyKey(m.k); if(!fams[fk]){fams[fk]=[];order.push(fk);} fams[fk].push(m); });
const rows = order.map(fk => famNode(fk, fams[fk])).join('');
var brand = PROVIDER_COLOR[key] || '#6B6556';
var expanded = groupState[key] === true; // default collapsed unless saved open
html += '<section class="ms-model-group'+(expanded?'':' is-collapsed')+'" data-provider="'+key+'" style="--provider-color:'+brand+'">'
+ '<button type="button" class="ms-model-group-head" aria-expanded="'+(expanded?'true':'false')+'">'
+ '<span class="ms-model-logo'+(PROVIDER_LOGOS[key]?'':' is-mono')+'">'+(PROVIDER_LOGOS[key]||MONOGRAM[key]||key.slice(0,2).toUpperCase())+'</span>'
+ '<span class="ms-model-group-titles">'
+ '<span class="ms-model-group-name">'+(PROVIDER_LABEL[key]||key)+'</span>'
+ '<span class="ms-model-group-count"><b>'+list.length+'</b> 個模型 · '+order.length+' 個家族</span>'
+ '</span><span class="ms-fam-spacer"></span>'+chevSvg.replace('ms-fam-chev','ms-group-chev')+'</button>'
+ '<div class="ms-fam-list">' + rows + '</div></section>';
});
groups.innerHTML = html;
// Collapse / expand a whole provider group — persist per-provider state.
groups.querySelectorAll('.ms-model-group-head').forEach(btn => {
btn.addEventListener('click', () => {
const g = btn.closest('.ms-model-group');
const collapsed = g.classList.toggle('is-collapsed');
btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
groupState[g.dataset.provider] = !collapsed; // true = expanded
saveGroupState(groupState);
});
});
// Expand / collapse family rows.
groups.querySelectorAll('.ms-fam-row:not(.ms-fam-single)').forEach(btn => {
btn.addEventListener('click', () => {
const fam = btn.closest('.ms-fam');
const open = fam.classList.toggle('is-open');
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
});
});
// Counts — derive from data (HTML has fallbacks; guard each).
function setCount(id, n){ const el=document.getElementById(id); if(el) el.textContent = n; }
setCount('stat-models', MODELS.length);
setCount('count-all', MODELS.length);
PROVIDER_ORDER.forEach(key => setCount('count-'+key, (buckets[key]||[]).length));
setCount('count-thinking', MODELS.filter(m=>m.thinking).length);
setCount('count-free', MODELS.filter(m=>m.free).length);
// Filter: hide/show whole family rows + collapse empty provider bands.
function famMatches(fam, f){
return f==='all'
|| fam.some(m=>m.p===f)
|| (f==='thinking' && fam.some(m=>m.thinking))
|| (f==='free' && fam.some(m=>m.free));
}
// attach family data to each node for filtering
const famData = new WeakMap();
{ // rebuild a quick lookup: map each .ms-fam element to its models
let idx=0; const flat=[];
PROVIDER_ORDER.forEach(key => {
const list = buckets[key]; if(!list) return;
const fams={}; const order=[];
list.forEach(m=>{const fk=familyKey(m.k); if(!fams[fk]){fams[fk]=[];order.push(fk);} fams[fk].push(m);});
order.forEach(fk=>flat.push(fams[fk]));
});
groups.querySelectorAll('.ms-fam').forEach((el,i)=>famData.set(el, flat[i]||[]));
}
const filterBtns = document.querySelectorAll('.ms-model-filter');
filterBtns.forEach(b => b.addEventListener('click', () => {
filterBtns.forEach(x => { x.classList.remove('is-active'); x.setAttribute('aria-pressed','false'); });
b.classList.add('is-active'); b.setAttribute('aria-pressed','true');
const f = b.dataset.filter;
groups.querySelectorAll('.ms-fam').forEach(el => { el.hidden = !famMatches(famData.get(el)||[], f); });
groups.querySelectorAll('.ms-model-group').forEach(g => {
const anyVisible = g.querySelector('.ms-fam:not([hidden])');
g.classList.toggle('is-empty', !anyVisible);
});
}));
})();
/* ── Deploy tabs ── */
(function(){
document.querySelectorAll('.deploy-tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.deploy-tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.deploy-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById('tab-'+tab.dataset.tab).classList.add('active');
});
});
})();
/* ── Copy ── */
(function(){
document.querySelectorAll('[data-copy]').forEach(btn => {
btn.addEventListener('click', async () => {
const pre = btn.closest('.code-box').querySelector('pre');
try{
await navigator.clipboard.writeText(pre.innerText);
btn.textContent='已複製';btn.classList.add('ok');
setTimeout(()=>{btn.textContent='複製';btn.classList.remove('ok')},1400);
}catch{btn.textContent='失敗'}
});
});
})();
/* ── Contributors ── */
(function(){
const grid = document.getElementById('contributors-grid');
if(!grid) return;
const footer = document.getElementById('contributors-footer');
const source = grid.dataset.source || 'dashboard/data/contributors.json';
const weightRank = {GOD:100,MR:80,LR:70,UR:60,SSR:50,SR:40,'S+':30,S:25,'A+':20,A:15,'B+':10,B:5};
const escMap = {'&':'&','<':'<','>':'>','"':'"',"'":'''};
const esc = value => String(value ?? '').replace(/[&<>"']/g, ch => escMap[ch]);
const rankOf = weight => weightRank[weight] || 0;
const weightClass = weight => String(weight || 'B')
.replace(/\+/g,'-plus')
.replace(/[^\w-]/g,'');
function groupContributors(entries){
const byLogin = new Map();
for(const entry of entries){
if(!entry || !entry.login) continue;
const login = String(entry.login);
if(!byLogin.has(login)) byLogin.set(login,{login,prs:[],topWeight:'B',latest:null});
const group = byLogin.get(login);
group.prs.push(entry);
if(rankOf(entry.weight) > rankOf(group.topWeight)) group.topWeight = entry.weight;
if(!group.latest || String(entry.mergedAt || '') > String(group.latest.mergedAt || '')) group.latest = entry;
}
return [...byLogin.values()]
.map(group => {
group.prs.sort((a,b) => String(b.mergedAt || '').localeCompare(String(a.mergedAt || '')) || Number(b.pr || 0) - Number(a.pr || 0));
return group;
})
.sort((a,b) => rankOf(b.topWeight) - rankOf(a.topWeight) || String(b.latest?.mergedAt || '').localeCompare(String(a.latest?.mergedAt || '')));
}
function prLabel(entry){
const pr = Number(entry?.pr || 0);
return pr > 0 ? `PR #${pr}` : 'Project';
}
function renderContributors(entries){
const groups = groupContributors(entries);
if(!groups.length) throw new Error('contributors list is empty');
grid.innerHTML = groups.map(group => {
const latest = group.latest || {};
const summary = latest.summary || latest.title || '';
// Dedupe: a contributor can legitimately have two ledger rows carrying the SAME pr —
// one cumulative ("四次出手累计"), one for that specific PR — and aict666 (#54) plus
// baily-zhang (#61) both do. Without the Set those render as "PR #54 · PR #54".
// Deduping here rather than in contributors.json deliberately: the double row may be
// intentional, and the display bug is fixable without deciding that.
const prs = [...new Set(group.prs.map(prLabel))].slice(0,4).join(' · ');
const extra = group.prs.length > 4 ? ` · +${group.prs.length - 4}` : '';
const loginRaw = String(group.login);
const login = esc(loginRaw);
const loginPath = encodeURIComponent(loginRaw);
const badge = esc(group.topWeight || latest.weight || 'B');
const badgeTitle = esc(latest.weightLabel || latest.title || group.topWeight || '');
return `<a class="contrib-card" href="https://github.com/${loginPath}" target="_blank" rel="noopener">
<div class="contrib-head">
<img class="contrib-avatar" src="https://github.com/${loginPath}.png?size=128" alt="@${login}" loading="lazy" onerror="this.style.visibility='hidden'">
<div class="contrib-meta">
<div class="contrib-name">@${login} <span class="contrib-weight contrib-weight-${weightClass(group.topWeight)}" title="${badgeTitle}">${badge}</span></div>
<div class="contrib-prs">${esc(prs + extra)}</div>
</div>
</div>
<div class="contrib-summary">${esc(summary)}</div>
</a>`;
}).join('');
if(footer){
const names = groups
.filter(group => group.login !== 'dwgx')
.slice(0,9)
.map(group => {
const loginRaw = String(group.login);
return `<a href="https://github.com/${encodeURIComponent(loginRaw)}">@${esc(loginRaw)}</a>`;
})
.join(' · ');
footer.innerHTML = `Contributors: ${names}${names ? ' · ' : ''}<a href="#contributors" style="opacity:.7">完整名單 ↑</a>`;
}
}
fetch(source,{cache:'no-cache'})
.then(response => {
if(!response.ok) throw new Error(`contributors fetch failed: ${response.status}`);
return response.json();
})
.then(data => renderContributors(Array.isArray(data?.contributors) ? data.contributors : []))
.catch(() => {
grid.innerHTML = '<div class="contrib-loading">貢獻者名單載入失敗,請查看 <a href="https://github.com/dwgx/WindsurfAPI/pulls">Pull requests</a>。</div>';
});
})();
/* ── Hero terminal: live typing quickstart transcript ── */
(function(){
var body = document.getElementById('hero-term-body');
if (!body) return;
// The transcript. Each line is pre-tokenized into {t:class, s:text} spans.
var LINES = [
[{t:'prompt',s:'$ '},{t:'cmd',s:'git clone'},{t:'str',s:' https://github.com/dwgx/WindsurfAPI'}],
[{t:'prompt',s:'$ '},{t:'cmd',s:'cd'},{t:'plain',s:' WindsurfAPI && '},{t:'cmd',s:'node'},{t:'plain',s:' src/index.js'}],
[{t:'com',s:'# 零依賴,免安裝直接跑 · 端口 3003'}],
[{t:'ok',s:'✓ DevinAPI 已启动 — Chat · Responses · Messages · Gemini 四协议就绪'}],
[{t:'prompt',s:'$ '},{t:'cmd',s:'export'},{t:'flag',s:' ANTHROPIC_BASE_URL'},{t:'plain',s:'='},{t:'str',s:'http://localhost:3003'}],
[{t:'prompt',s:'$ '},{t:'cmd',s:'claude'},{t:'com',s:' # Claude Code 直接連'}]
];
// Flatten to a single character stream with span boundaries for typing.
var FLAT = [];
LINES.forEach(function(toks, li){
toks.forEach(function(tok){
for (var i=0;i<tok.s.length;i++) FLAT.push({t:tok.t, ch:tok.s[i]});
});
if (li < LINES.length-1) FLAT.push({t:'nl', ch:'\n'});
});
var TOTAL = FLAT.length;
// Render the first `n` characters as highlighted spans + a caret.
function render(n){
var html = '', curT = null, buf = '';
function flush(){
if (buf){ html += '<span class="c-'+curT+'">'+esc(buf)+'</span>'; buf=''; }
}
for (var i=0;i<n;i++){
var c = FLAT[i];
if (c.t === 'nl'){ flush(); html += '\n'; curT=null; continue; }
if (c.t !== curT){ flush(); curT = c.t; }
buf += c.ch;
}
flush();
html += '<span class="hero-term-caret"></span>';
body.innerHTML = html;
}
function esc(s){ return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function run(){
if (reduce){ render(TOTAL); return; }
var cps = 62, t0 = null, raf;
render(0);
function tick(now){
if (t0 === null) t0 = now;
var n = Math.min(TOTAL, Math.floor((now - t0)/1000 * cps));
render(n);
if (n < TOTAL) raf = requestAnimationFrame(tick);
}
setTimeout(function(){ raf = requestAnimationFrame(tick); }, 320);
}
// Start when the terminal scrolls into view (once). Fallback: if it's
// already on-screen at load, a short timer guarantees it runs even if the
// observer is flaky.
var started = false;
function kick(){ if (started) return; started = true; run(); }
var io = new IntersectionObserver(function(entries){
entries.forEach(function(e){ if (e.isIntersecting){ kick(); io.disconnect(); } });
}, {threshold:.35});
io.observe(body);
// If it's within the initial viewport, don't wait for a scroll event.
requestAnimationFrame(function(){
var r = body.getBoundingClientRect();
if (r.top < (window.innerHeight||800) * 0.9) setTimeout(kick, 400);
});
})();
/* ── Theme toggle ── */
(function(){
var root = document.documentElement;
var btn = document.getElementById('theme-toggle');
// Enable the token crossfade only after the first paint, so loading with a
// saved dark theme doesn't animate from the @property initial (light) value.
requestAnimationFrame(function(){ requestAnimationFrame(function(){
root.setAttribute('data-theme-ready','');
}); });
if (btn) btn.addEventListener('click', function(){
var dark = root.getAttribute('data-theme') === 'dark';
if (dark) root.removeAttribute('data-theme'); else root.setAttribute('data-theme','dark');
try{ localStorage.setItem('devinapi-theme', dark ? 'light' : 'dark'); }catch(e){}
btn.blur();
});
})();
/* ── Reveal ── */
(function(){
const io = new IntersectionObserver(entries => {
entries.forEach(e => { if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)} });
},{threshold:.12,rootMargin:'0px 0px -40px 0px'});
document.querySelectorAll('.reveal').forEach(el => io.observe(el));
})();
</script>
</body>
</html>