Files
sub2api/frontend/src/views/docs/DocsView.vue

544 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="puro-page">
<div class="bg-glow soft"></div>
<nav class="nav">
<div class="container nav-inner">
<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">
<router-link to="/">{{ $t('docs.nav.products') }}</router-link>
<router-link to="/pricing">{{ $t('docs.nav.pricing') }}</router-link>
<router-link to="/docs" class="active">{{ $t('docs.nav.docs') }}</router-link>
</div>
<div class="nav-cta">
<PuroLocaleSwitcher />
<router-link to="/login" class="btn btn-ghost">{{ $t('docs.nav.login') }}</router-link>
<router-link to="/register" class="btn btn-primary">{{ $t('docs.nav.signup') }}</router-link>
</div>
</div>
</nav>
<section class="docs-hero container">
<h1>{{ $t('docs.hero.title') }}</h1>
<p class="subtitle">{{ $t('docs.hero.subtitle') }}</p>
</section>
<div class="container docs-body">
<section id="get-key" class="docs-section">
<h2>{{ $t('docs.sections.getKey.heading') }}</h2>
<p>{{ $t('docs.sections.getKey.desc') }}</p>
<div class="callout">
<a href="mailto:admin@puro.im">admin@puro.im</a>
</div>
<p class="note">{{ $t('docs.sections.getKey.note') }}</p>
</section>
<section id="codex" class="docs-section">
<h2>{{ $t('docs.sections.codex.heading') }}</h2>
<p>{{ $t('docs.sections.codex.configIntro') }}</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>
{{ $t('docs.sections.codex.copy') }}
</button>
</div>
<pre class="mono"><code>model_provider = <span class="str">"OpenAI"</span>
model = <span class="str">"gpt-5.4"</span>
wire_api = <span class="str">"responses"</span>
[model_providers.OpenAI]
name = <span class="str">"OpenAI"</span>
base_url = <span class="str">"https://ai.puro.im"</span>
wire_api = <span class="str">"responses"</span>
requires_openai_auth = <span class="kw">true</span></code></pre>
</div>
<p>{{ $t('docs.sections.codex.authIntro') }}</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>
{{ $t('docs.sections.codex.copy') }}
</button>
</div>
<pre class="mono"><code>{
<span class="str">"OPENAI_API_KEY"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span>
}</code></pre>
</div>
<p>{{ $t('docs.sections.codex.verifyIntro') }}</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>
{{ $t('docs.sections.codex.copy') }}
</button>
</div>
<pre class="mono"><code><span class="cm">$</span> codex exec --sandbox read-only <span class="str">"say hi"</span></code></pre>
</div>
</section>
<section id="claude-code" class="docs-section">
<h2>{{ $t('docs.sections.claudeCode.heading') }}</h2>
<p>{{ $t('docs.sections.claudeCode.configIntro') }}</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>
{{ $t('docs.sections.claudeCode.copy') }}
</button>
</div>
<pre class="mono"><code>{
<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>
}</code></pre>
</div>
<p class="note">{{ $t('docs.sections.claudeCode.note') }}</p>
</section>
<section id="curl" class="docs-section">
<h2>{{ $t('docs.sections.curl.heading') }}</h2>
<p>{{ $t('docs.sections.curl.openaiIntro') }}</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>
{{ $t('docs.sections.curl.copy') }}
</button>
</div>
<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">"Content-Type: application/json"</span> \
-d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre>
</div>
<p>{{ $t('docs.sections.curl.anthropicIntro') }}</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>
{{ $t('docs.sections.curl.copy') }}
</button>
</div>
<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">"Content-Type: application/json"</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>
</div>
</section>
<section id="models" class="docs-section">
<h2>{{ $t('docs.sections.models.heading') }}</h2>
<div class="table-wrap">
<table class="models-table mono">
<thead>
<tr>
<th>{{ $t('docs.sections.models.colModel') }}</th>
<th>{{ $t('docs.sections.models.colPlatform') }}</th>
<th>{{ $t('docs.sections.models.colContext') }}</th>
<th>{{ $t('docs.sections.models.colStatus') }}</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>{{ $t('docs.sections.models.codexDedicated') }}</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>
<i18n-t tag="p" class="note" keypath="docs.sections.models.note">
<template #repo><code class="mono">{{ $t('docs.sections.models.noteRepo') }}</code></template>
<template #dashboard><router-link to="/dashboard">{{ $t('docs.sections.models.noteDashboard') }}</router-link></template>
</i18n-t>
</section>
<section id="feedback" class="docs-section">
<h2>{{ $t('docs.sections.feedback.heading') }}</h2>
<p>{{ $t('docs.sections.feedback.desc') }}</p>
<div class="callout">
<a href="mailto:admin@puro.im">admin@puro.im</a>
</div>
</section>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import PuroLocaleSwitcher from '@/components/puro/PuroLocaleSwitcher.vue'
const { t } = useI18n()
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> ${t('docs.sections.codex.copied')}`
button.classList.add('copied')
setTimeout(() => {
button.innerHTML = original
button.classList.remove('copied')
}, 1500)
} catch (e) {
console.warn('Clipboard copy failed', e)
}
}
</script>
<style scoped>
/* =============================================================
* DocsView — component-local styles
* Globals from puro.css (scoped to .puro-page) provide:
* - .nav, .nav-inner, .brand, .hex, .nav-links, .nav-cta (nav base)
* - .mono, .btn, .btn-primary, .container (primitives)
* ============================================================= */
.puro-page {
min-height: 100vh;
position: relative;
}
/* nav brand SVG */
.brand svg { color: var(--cyan); flex-shrink: 0; }
.docs-hero {
padding: 80px 24px 40px;
text-align: center;
}
.docs-hero h1 {
font-size: clamp(32px, 4vw, 48px);
font-weight: 800;
letter-spacing: -0.03em;
margin-bottom: 12px;
}
.docs-hero .subtitle {
color: var(--text-2);
font-size: 16px;
}
.docs-body { padding-bottom: 80px; }
.docs-section {
margin: 48px 0;
scroll-margin-top: 80px;
}
/* h2 with cyan left-accent bar */
.docs-section h2 {
font-size: 22px;
font-weight: 700;
margin-bottom: 14px;
color: var(--text-0);
border-bottom: 1px solid var(--border);
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 {
color: var(--text-1);
font-size: 14px;
line-height: 1.8;
margin: 12px 0;
}
.docs-section .note {
color: var(--text-3);
font-size: 13px;
}
.docs-section code.mono {
background: var(--bg-1);
padding: 2px 6px;
border-radius: var(--r-sm);
font-size: 13px;
color: var(--cyan);
}
/* code panel */
.code-panel {
border: 1px solid var(--border);
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;
font-size: 13px;
line-height: 1.6;
color: var(--text-1);
overflow-x: auto;
}
.code-panel pre .str { color: var(--cyan); }
.code-panel pre .kw { color: var(--amber); }
.code-panel pre .cm { color: var(--text-3); }
.callout {
padding: 16px 20px;
background: var(--bg-1);
border: 1px solid var(--border-2);
border-left: 3px solid var(--cyan);
border-radius: var(--r-md);
margin: 12px 0;
}
.callout a {
color: var(--cyan);
font-family: var(--font-mono);
font-size: 14px;
}
/* models table */
.table-wrap {
border: 1px solid var(--border);
border-radius: var(--r-md);
overflow: hidden;
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;
}
/* container override (puro.css has 1100px/32px; we want narrower for docs readability) */
.container {
max-width: 860px;
margin: 0 auto;
padding: 0 24px;
position: relative;
z-index: 2;
}
</style>