fix(docs): DocsView fidelity port (plan A)
Some checks failed
continuous-integration/drone/push Build is passing
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
Security Scan / backend-security (push) Has been cancelled
Security Scan / frontend-security (push) Has been cancelled

Visual alignment with zip Docs.html (excluding 3-col layout):
- SVG hexagon logo (replaces ⬢ emoji)
- h2 cyan accent bar (::before 3px left strip)
- Models section: replace <ul> with structured table
  (provider badges with brand-color dots, OK/BETA status chips)
- Wrap all code blocks in .code-panel with:
  - traffic-light header + filename tab
  - 复制 button with clipboard API + 已复制 feedback

Kept intentionally different per Stage 1 decisions:
- Section 1 uses 'contact admin@puro.im' (not OAuth self-serve)
- Nav omits pricing / design-system links
- Codex CLI section preserved (Vue-only)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mini
2026-04-19 23:18:51 +08:00
parent e843a7aef8
commit 49ee2cba8a

View File

@@ -4,7 +4,12 @@
<nav class="nav"> <nav class="nav">
<div class="container nav-inner"> <div class="container nav-inner">
<router-link to="/" class="brand"><span class="hex"></span><span>PURO AI</span></router-link> <router-link to="/" class="brand">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z" fill="rgba(34, 211, 238, 0.08)"/>
</svg>
<span>PURO AI</span>
</router-link>
<div class="nav-links"> <div class="nav-links">
<router-link to="/">首页</router-link> <router-link to="/">首页</router-link>
<a href="#codex">Codex</a> <a href="#codex">Codex</a>
@@ -35,6 +40,22 @@
<section id="codex" class="docs-section"> <section id="codex" class="docs-section">
<h2>2. Codex CLI 接入</h2> <h2>2. Codex CLI 接入</h2>
<p>修改 <code class="mono">~/.codex/config.toml</code></p> <p>修改 <code class="mono">~/.codex/config.toml</code></p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
<span></span><span></span><span></span>
</div>
<div class="code-tabs">
<span class="tab active">~/.codex/config.toml</span>
</div>
<button class="code-copy" @click="copyCode($event)" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
</button>
</div>
<pre class="mono"><code>model_provider = <span class="str">"OpenAI"</span> <pre class="mono"><code>model_provider = <span class="str">"OpenAI"</span>
model = <span class="str">"gpt-5.4"</span> model = <span class="str">"gpt-5.4"</span>
wire_api = <span class="str">"responses"</span> wire_api = <span class="str">"responses"</span>
@@ -44,49 +65,177 @@ name = <span class="str">"OpenAI"</span>
base_url = <span class="str">"https://ai.puro.im"</span> base_url = <span class="str">"https://ai.puro.im"</span>
wire_api = <span class="str">"responses"</span> wire_api = <span class="str">"responses"</span>
requires_openai_auth = <span class="kw">true</span></code></pre> requires_openai_auth = <span class="kw">true</span></code></pre>
</div>
<p>然后 <code class="mono">~/.codex/auth.json</code></p> <p>然后 <code class="mono">~/.codex/auth.json</code></p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
<span></span><span></span><span></span>
</div>
<div class="code-tabs">
<span class="tab active">~/.codex/auth.json</span>
</div>
<button class="code-copy" @click="copyCode($event)" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
</button>
</div>
<pre class="mono"><code>{ <pre class="mono"><code>{
<span class="str">"OPENAI_API_KEY"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span> <span class="str">"OPENAI_API_KEY"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre> }</code></pre>
</div>
<p>验证</p> <p>验证</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
<span></span><span></span><span></span>
</div>
<div class="code-tabs">
<span class="tab active">shell</span>
</div>
<button class="code-copy" @click="copyCode($event)" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> codex exec --sandbox read-only <span class="str">"say hi"</span></code></pre> <pre class="mono"><code><span class="cm">$</span> codex exec --sandbox read-only <span class="str">"say hi"</span></code></pre>
</div>
</section> </section>
<section id="claude-code" class="docs-section"> <section id="claude-code" class="docs-section">
<h2>3. Claude Code 接入</h2> <h2>3. Claude Code 接入</h2>
<p>修改 <code class="mono">~/.claude/settings.json</code></p> <p>修改 <code class="mono">~/.claude/settings.json</code></p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
<span></span><span></span><span></span>
</div>
<div class="code-tabs">
<span class="tab active">~/.claude/settings.json</span>
</div>
<button class="code-copy" @click="copyCode($event)" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
</button>
</div>
<pre class="mono"><code>{ <pre class="mono"><code>{
<span class="str">"base_url"</span>: <span class="str">"https://ai.puro.im"</span>, <span class="str">"base_url"</span>: <span class="str">"https://ai.puro.im"</span>,
<span class="str">"api_key"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span> <span class="str">"api_key"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre> }</code></pre>
</div>
<p class="note">Claude Code 通过 <code class="mono">/v1/messages</code> endpoint 调用 Anthropic 兼容 API</p> <p class="note">Claude Code 通过 <code class="mono">/v1/messages</code> endpoint 调用 Anthropic 兼容 API</p>
</section> </section>
<section id="curl" class="docs-section"> <section id="curl" class="docs-section">
<h2>4. curl 直连测试</h2> <h2>4. curl 直连测试</h2>
<p>OpenAI Responses API</p> <p>OpenAI Responses API</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
<span></span><span></span><span></span>
</div>
<div class="code-tabs">
<span class="tab active">curl</span>
</div>
<button class="code-copy" @click="copyCode($event)" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/responses \ <pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/responses \
-H <span class="str">"Authorization: Bearer sk-xxx"</span> \ -H <span class="str">"Authorization: Bearer sk-xxx"</span> \
-H <span class="str">"Content-Type: application/json"</span> \ -H <span class="str">"Content-Type: application/json"</span> \
-d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre> -d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre>
</div>
<p>Anthropic Messages API</p> <p>Anthropic Messages API</p>
<div class="code-panel">
<div class="code-head">
<div class="traffic">
<span></span><span></span><span></span>
</div>
<div class="code-tabs">
<span class="tab active">curl</span>
</div>
<button class="code-copy" @click="copyCode($event)" type="button">
<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor">
<path d="M4 1.5h5a.5.5 0 0 1 .5.5v1h1V2a1.5 1.5 0 0 0-1.5-1.5H4A1.5 1.5 0 0 0 2.5 2v8A1.5 1.5 0 0 0 4 11.5h1v-1H4a.5.5 0 0 1-.5-.5V2a.5.5 0 0 1 .5-.5z"/>
<path d="M7 4.5A1.5 1.5 0 0 1 8.5 3h5A1.5 1.5 0 0 1 15 4.5v9a1.5 1.5 0 0 1-1.5 1.5h-5A1.5 1.5 0 0 1 7 13.5v-9z"/>
</svg>
复制
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/v1/messages \ <pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/v1/messages \
-H <span class="str">"Authorization: Bearer sk-xxx"</span> \ -H <span class="str">"Authorization: Bearer sk-xxx"</span> \
-H <span class="str">"Content-Type: application/json"</span> \ -H <span class="str">"Content-Type: application/json"</span> \
-H <span class="str">"anthropic-version: 2023-06-01"</span> \ -H <span class="str">"anthropic-version: 2023-06-01"</span> \
-d <span class="str">'{"model":"claude-opus-4-7","max_tokens":100,"messages":[{"role":"user","content":"hi"}]}'</span></code></pre> -d <span class="str">'{"model":"claude-opus-4-7","max_tokens":100,"messages":[{"role":"user","content":"hi"}]}'</span></code></pre>
</div>
</section> </section>
<section id="models" class="docs-section"> <section id="models" class="docs-section">
<h2>5. 支持的模型</h2> <h2>5. 支持的模型</h2>
<ul class="model-list"> <div class="table-wrap">
<li><code class="mono">gpt-5.4</code> · OpenAIvia ChatGPT Plus / Codex OAuth</li> <table class="models-table mono">
<li><code class="mono">gpt-5.4-codex</code> · OpenAI Codex 专用</li> <thead>
<li><code class="mono">claude-opus-4-7</code> · Anthropicvia Claude Pro / Max OAuth</li> <tr>
<li><code class="mono">claude-sonnet-4-6</code> · Anthropic</li> <th>模型</th>
<li><code class="mono">gemini-2.5-pro</code> · Googlevia Code Assist OAuth</li> <th>平台 / 来源</th>
<li><code class="mono">gemini-2.5-flash</code> · Google</li> <th>上下文</th>
</ul> <th>状态</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>gpt-5.4</code></td>
<td><span class="provider gpt"><span class="dot"></span>OpenAIChatGPT Plus / Codex OAuth</span></td>
<td>272K</td>
<td><span class="badge-ok">OK</span></td>
</tr>
<tr>
<td><code>gpt-5.4-codex</code></td>
<td><span class="provider gpt"><span class="dot"></span>OpenAI Codex 专用</span></td>
<td>272K</td>
<td><span class="badge-ok">OK</span></td>
</tr>
<tr>
<td><code>claude-opus-4-7</code></td>
<td><span class="provider claude"><span class="dot"></span>AnthropicClaude Pro / Max OAuth</span></td>
<td>200K</td>
<td><span class="badge-ok">OK</span></td>
</tr>
<tr>
<td><code>claude-sonnet-4-6</code></td>
<td><span class="provider claude"><span class="dot"></span>Anthropic</span></td>
<td>200K</td>
<td><span class="badge-ok">OK</span></td>
</tr>
<tr>
<td><code>gemini-2.5-pro</code></td>
<td><span class="provider gemini"><span class="dot"></span>GoogleCode Assist OAuth</span></td>
<td>1M</td>
<td><span class="badge-beta">BETA</span></td>
</tr>
<tr>
<td><code>gemini-2.5-flash</code></td>
<td><span class="provider gemini"><span class="dot"></span>Google</span></td>
<td>1M</td>
<td><span class="badge-beta">BETA</span></td>
</tr>
</tbody>
</table>
</div>
<p class="note">后端 pricing 表实时跟进 <code class="mono">model-price-repo</code>完整清单登录后在 <router-link to="/dashboard">控制台</router-link> 查看</p> <p class="note">后端 pricing 表实时跟进 <code class="mono">model-price-repo</code>完整清单登录后在 <router-link to="/dashboard">控制台</router-link> 查看</p>
</section> </section>
@@ -104,6 +253,24 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
<script setup lang="ts"> <script setup lang="ts">
// DocsView — public quickstart documentation // DocsView — public quickstart documentation
// Route: /docs (no auth required) // Route: /docs (no auth required)
async function copyCode(ev: MouseEvent) {
const button = ev.currentTarget as HTMLButtonElement
const panel = button.closest('.code-panel')
const codeEl = panel?.querySelector('pre code') as HTMLElement | null
if (!codeEl) return
try {
await navigator.clipboard.writeText(codeEl.innerText)
const original = button.innerHTML
button.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg> 已复制'
button.classList.add('copied')
setTimeout(() => {
button.innerHTML = original
button.classList.remove('copied')
}, 1500)
} catch (e) {
console.warn('Clipboard copy failed', e)
}
}
</script> </script>
<style scoped> <style scoped>
@@ -119,6 +286,9 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
position: relative; position: relative;
} }
/* nav brand SVG */
.brand svg { color: var(--cyan); flex-shrink: 0; }
.docs-hero { .docs-hero {
padding: 80px 24px 40px; padding: 80px 24px 40px;
text-align: center; text-align: center;
@@ -139,6 +309,8 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
margin: 48px 0; margin: 48px 0;
scroll-margin-top: 80px; scroll-margin-top: 80px;
} }
/* h2 with cyan left-accent bar */
.docs-section h2 { .docs-section h2 {
font-size: 22px; font-size: 22px;
font-weight: 700; font-weight: 700;
@@ -146,7 +318,20 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
color: var(--text-0); color: var(--text-0);
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
padding-bottom: 8px; padding-bottom: 8px;
padding-left: 14px;
position: relative;
} }
.docs-section h2::before {
content: '';
position: absolute;
left: 0;
top: 4px;
bottom: 10px;
width: 3px;
background: var(--cyan);
border-radius: 2px;
}
.docs-section p { .docs-section p {
color: var(--text-1); color: var(--text-1);
font-size: 14px; font-size: 14px;
@@ -164,20 +349,92 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
font-size: 13px; font-size: 13px;
color: var(--cyan); color: var(--cyan);
} }
.docs-section pre.mono {
background: var(--bg-code); /* code panel */
.code-panel {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--r-md); border-radius: var(--r-md);
background: var(--bg-code);
overflow: hidden;
margin: 12px 0;
}
.code-head {
display: flex;
align-items: center;
padding: 10px 14px;
gap: 12px;
background: var(--bg-1);
border-bottom: 1px solid var(--border);
}
.traffic { display: flex; gap: 6px; flex-shrink: 0; }
.traffic span {
width: 10px; height: 10px; border-radius: 50%;
}
.traffic span:nth-child(1) { background: #f87171; }
.traffic span:nth-child(2) { background: #fbbf24; }
.traffic span:nth-child(3) { background: #34d399; }
.code-tabs {
display: flex;
gap: 6px;
flex: 1;
min-width: 0;
}
.code-tabs .tab {
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-2);
padding: 2px 10px;
border: 1px solid var(--border);
border-radius: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 240px;
}
.code-tabs .tab.active {
color: var(--cyan);
background: rgba(34,211,238,0.1);
border-color: rgba(34,211,238,0.3);
}
.code-copy {
background: transparent;
border: 1px solid var(--border);
border-radius: 4px;
padding: 4px 8px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-3);
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
transition: color 0.15s, border-color 0.15s;
}
.code-copy:hover {
color: var(--cyan);
border-color: rgba(34,211,238,0.3);
}
.code-copy.copied {
color: var(--green, #34d399);
border-color: rgba(52,211,153,0.3);
}
.code-copy svg { flex-shrink: 0; }
.code-panel pre.mono {
margin: 0;
border: none;
border-radius: 0;
background: var(--bg-code);
padding: 16px; padding: 16px;
font-size: 13px; font-size: 13px;
line-height: 1.6; line-height: 1.6;
color: var(--text-1); color: var(--text-1);
overflow-x: auto; overflow-x: auto;
margin: 12px 0;
} }
.docs-section pre .str { color: var(--cyan); } .code-panel pre .str { color: var(--cyan); }
.docs-section pre .kw { color: var(--amber); } .code-panel pre .kw { color: var(--amber); }
.docs-section pre .cm { color: var(--text-3); } .code-panel pre .cm { color: var(--text-3); }
.callout { .callout {
padding: 16px 20px; padding: 16px 20px;
@@ -193,14 +450,80 @@ requires_openai_auth = <span class="kw">true</span></code></pre>
font-size: 14px; font-size: 14px;
} }
.model-list { list-style: none; padding: 0; } /* models table */
.model-list li { .table-wrap {
padding: 8px 0; border: 1px solid var(--border);
color: var(--text-1); border-radius: var(--r-md);
font-size: 14px; overflow: hidden;
border-bottom: 1px dashed var(--border); margin: 12px 0;
}
.models-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.models-table thead {
background: var(--bg-1);
}
.models-table th {
text-align: left;
padding: 10px 14px;
color: var(--text-3);
font-weight: 500;
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
border-bottom: 1px solid var(--border);
}
.models-table td {
padding: 12px 14px;
color: var(--text-1);
border-bottom: 1px solid rgba(30,41,59,0.5);
}
.models-table tbody tr:last-child td { border-bottom: none; }
.models-table code {
color: var(--cyan);
background: transparent;
padding: 0;
font-family: var(--font-mono);
font-size: 12px;
}
.models-table .provider {
display: inline-flex;
align-items: center;
gap: 6px;
}
.models-table .provider .dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.models-table .provider.gpt .dot { background: var(--p-gpt, #10a37f); }
.models-table .provider.claude .dot { background: var(--p-claude, #d97757); }
.models-table .provider.gemini .dot { background: var(--p-gemini, #4285f4); }
.badge-ok {
display: inline-block;
padding: 2px 8px;
font-size: 10px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--green, #34d399);
background: rgba(52,211,153,0.1);
border: 1px solid rgba(52,211,153,0.3);
border-radius: 4px;
}
.badge-beta {
display: inline-block;
padding: 2px 8px;
font-size: 10px;
font-weight: 600;
font-family: var(--font-mono);
color: var(--amber, #fbbf24);
background: rgba(251,191,36,0.1);
border: 1px solid rgba(251,191,36,0.3);
border-radius: 4px;
} }
.model-list li:last-child { border-bottom: none; }
/* container override (puro.css has 1100px/32px; we want narrower for docs readability) */ /* container override (puro.css has 1100px/32px; we want narrower for docs readability) */
.container { .container {