diff --git a/.gitignore b/.gitignore index 1a92ea3e..b07cd286 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,9 @@ vite.config.js docs/* !docs/PAYMENT.md !docs/PAYMENT_CN.md +!docs/superpowers/ +!docs/design-drafts/ +.superpowers/ .serena/ .codex/ frontend/coverage/ diff --git a/docs/design-drafts/Landing.html b/docs/design-drafts/Landing.html new file mode 100644 index 00000000..4442b254 --- /dev/null +++ b/docs/design-drafts/Landing.html @@ -0,0 +1,1183 @@ + + + + +PURO AI — 你的 AI 订阅,已经付过钱了 + + + + + + + + +
+
+ + + + + +
+
+ NEW + 统一接入多个 AI 平台 · 零改动切换 +
+

你的 AI 订阅,
已经付过钱了。

+

+ Claude Pro · ChatGPT Plus · Codex · Gemini 订阅
+ 聚合成统一 API,零改动接入 OpenAI / Anthropic SDK +

+
+ 立即开始 → + 查看文档 +
+
+ 无需信用卡 + + 用你已有的订阅 + + 5 分钟跑通 +
+
+ + +
+
+
// providers
+

支持的 AI 平台

+

通过 OAuth 直接复用你的订阅,无需申请官方 API key

+
+
+
+ + +
Claude
+
Pro / Max
+
+
+ + +
ChatGPT
+
Plus / Pro
+
+
+ + +
Codex CLI
+
OpenAI
+
+
+ + +
Gemini
+
Code Assist
+
+
+ + +
更多
+
即将推出
+
+
+
+ + +
+
+
// features
+

付一次订阅,
用起一整个模型池

+

把散落在各个平台的订阅,整合成开发者真正能用的基础设施

+
+
+
+
+

一个 key 接所有模型

+

不再为每个 provider 申请 API key、配置 base_url。统一 sk- 走 Claude / GPT / Gemini,按 model 自动路由到对应账号池。

+
    +
  • OpenAI Responses API 兼容
  • +
  • Anthropic Messages API 兼容
  • +
  • 智能 model → provider 路由
  • +
+
+
+
🔄
+

账号池高可用

+

多账号自动调度。某个 ChatGPT Plus 触发限流,自动 failover 到下一个。重启、刷新 token 全自动。

+
    +
  • 限流/5xx 自动 failover
  • +
  • OAuth token 自动刷新
  • +
  • 加权轮询 · 最少连接
  • +
+
+
+
📊
+

用量看板

+

每条请求的 tokens、费用、上游账号、延迟全可视化。模型分布饼图 + 趋势曲线 + Top 排行。

+
    +
  • 逐请求审计日志
  • +
  • 多维度 tokens / cost 统计
  • +
  • 导出 CSV / 接 Webhook
  • +
+
+
+
+ + +
+
+
// integration
+

把 base_url 一改,就能用

+

兼容 OpenAI / Anthropic / Gemini SDK,零代码改动

+
+ +
+
+
+
+
~/.codex/config.toml
+
curl.sh
+
● edited 2s ago
+
+
1# Codex CLI — 只改 base_url 就能走 PURO
2model_provider = "puro"
3model = "gpt-5-codex"
4
5[model_providers.puro]
6 name = "PURO AI"
7 base_url = "https://ai.puro.im/v1"
8 wire_api = "responses"
9 env_key = "PURO_API_KEY" # export PURO_API_KEY=sk-puro-…
+
+ +
+
+
+
curl.sh
+
zsh · puro ≈ 210ms
+
+
1$ curl https://ai.puro.im/v1/chat/completions \
2 -H "Authorization: Bearer $PURO_API_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "model": "claude-sonnet-4-5", # 自动路由到 Claude Pro 池
6 "stream": true,
7 "messages": [{ "role": "user", "content": "写一个斐波那契" }]
8 }'
9
10# ← event: content_block_delta · account=claude-3 · 187ms · 42 tok
+
+
+ +
+ 支持 OpenAI Responses API · Anthropic Messages API · Gemini generateContent · 流式 SSE & WebSocket +
+
+ + +
+
+
// observability
+

每条请求都看得见

+

+ 不像第三方 API 池子那种"扣了多少不告诉你"。你能看到每次调用:扣了哪个账号、跑了哪个模型、用了多少 tokens、花了多少钱、上游响应几秒。 +

+
+ +
+
+
+
ai.puro.im/dashboard
+
me@puro
+
+
+ +
+
+
+
Requests · 24h
+
18,294
+
▲ 12.4%
+
+
+
Tokens · 24h
+
4.7M
+
▲ 8.1%
+
+
+
Avg latency
+
312ms
+
▼ 4.2%
+
+
+
Est. savings
+
$847
+
vs. pay-as-you-go
+
+
+ +
+
+
+ Requests over time +
+ Claude + GPT + Gemini +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + 00:00 + 06:00 + 12:00 + 18:00 + now + + +
+
+
+ Model distribution + · 24h +
+ +
+ + + + + + + + + + + +
+
Claude48%
+
GPT32%
+
Gemini14%
+
Codex6%
+
+
+
+
+ +
+
+
Recent requests
+
live · 12 of 18,294
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeProviderModelTokensCostLatencyStatus
13:42:18claude-3sonnet-4-52,847$0.042213ms● 200
13:42:11gpt-plus-7gpt-5-codex1,204$0.018167ms● 200
13:42:03gemini-2gemini-2.5-pro4,102$0.000392ms● 200
13:41:58claude-1sonnet-4-56,318$0.095284ms● 429 → failover
13:41:49gpt-plus-2gpt-5892$0.013141ms● 200
+
+
+
+
+ + +
+

把订阅变成 API — 5 分钟

+

绑定第一个账号,生成 sk- key,把 base_url 指过来。就这些。

+
+ 创建账户 → + 登录已有账户 +
+
+
+ + + + + + diff --git a/docs/design-drafts/Login.html b/docs/design-drafts/Login.html new file mode 100644 index 00000000..36c3a207 --- /dev/null +++ b/docs/design-drafts/Login.html @@ -0,0 +1,689 @@ + + + + +登录 — PURO AI + + + + + + + + +
+ +
+
+
+ + +
+
// 你的订阅,已经付过钱了
+

+ N 个订阅 + + 1 个 key +

+
+ 省去切换账号的繁琐, + 省去为多个高昂订阅重复买单。 + PURO(纯粹)—— 让 AI 调用回归本质。 +
+ +
+
POST /v1/chat/completions
+
model claude-sonnet-4-5
+
route → claude-pool-03
+
status 200 · 213ms · 42 tok
+
+
+ +
+ Claude· + ChatGPT· + Codex· + Gemini + | + ai.puro.im · operational +
+
+
+ + +
+ ← 返回首页 + +
+

登录

+

用你的 PURO AI 账户继续

+ +
+ +
+ + + + + + + +
+
+ +
+ +
+ + + + + + + + +
+
+ + 忘记密码? +
+
+ + + +
OR
+ + + +
+ 没有账户?注册 +
+ + +
+
+
+ + + + + diff --git a/docs/design-drafts/Register.html b/docs/design-drafts/Register.html new file mode 100644 index 00000000..65b6c67c --- /dev/null +++ b/docs/design-drafts/Register.html @@ -0,0 +1,734 @@ + + + + +注册 — PURO AI + + + + + + + + +
+ +
+
+
+
+ + + + + + PURO AI + +
+ +
+
// 5 分钟开始用
+

+ N 个订阅 + + 1 个 key +

+
+ 省去切换账号的繁琐, + 省去为多个高昂订阅重复买单。 + PURO(纯粹)—— 让 AI 调用回归本质。 +
+ +
+
// 下一步
+
+
1
+
创建账户 · 邮箱 + 密码,或用 LinuxDO OAuth
+
+
+
2
+
绑定订阅 · OAuth 接入你现有的 Claude Pro / ChatGPT Plus
+
+
+
3
+
生成 key · 拿到 sk-puro-… 换掉 SDK 的 base_url
+
+
+
+ +
+ Claude· + ChatGPT· + Codex· + Gemini + | + 无需信用卡 · 永久免费 Hobby 套餐 +
+
+
+ + +
+ ← 返回首页 + +
+

创建账户

+

5 分钟开始用 PURO AI

+ +
+ +
+ + + + + + + + + + + + +
+
+ +
+ +
+ + + + + + + + +
+
+ +
+
+ // strength + +
+
+ +
+ +
+ + + + + + + + + + + + +
+
+
+ + + + + +
OR
+ + + +
+ 已有账户?登录 +
+ + +
+
+
+ + + + + diff --git a/docs/design-drafts/v2/API Keys.html b/docs/design-drafts/v2/API Keys.html new file mode 100644 index 00000000..6fff1177 --- /dev/null +++ b/docs/design-drafts/v2/API Keys.html @@ -0,0 +1,637 @@ + + + + + +API Keys — PURO AI + + + + + + + + +
+ +
+ + + +
+
+

API Keys

+
+
Zzane
+
+
+ +
+
+
+

API Keys

+
每个 key 是一张独立的"通行证",可以单独设置可用的订阅池、限速和预算,泄漏时可以直接吊销而不影响其他 key。
+
+ +
+ + +
+
+
活跃 Keys
+
3/ 10 上限
+
+
+
近 7 日调用
+
89,402
+
+
+
近 7 日花费
+
$24.18USD
+
+
+
已吊销
+
2
+
+
+ + +
+ + + +
+ +
+ + +
+ +
+
+
+ production + ACTIVE +
+
+ created 2026·03·14 + · + last used 2m ago + · + ● in use +
+
+ + + +
+
+ +
+ sk-puro- + ••••••••••••••••••••••••4f82 + 👁 显示 + 复制 +
+ +
+
+
可用订阅池
+
+ claude · 2 + gpt · 2 + gemini · 1 +
+
+
+
本月用量
+
$14.82 / $50
+
+
+
+
速率限制
+
120 RPM
+
+
+
关联应用
+
+ Claude Code + Cursor +
+
+
+
+ +
+
+
+ staging + RATE LIMITED +
+
+ created 2026·04·02 + · + last used 3h ago +
+
+ + + +
+
+
+ sk-puro- + ••••••••••••••••••••••••ae19 + 👁 显示 + 复制 +
+
+
+
可用订阅池
+
+ all pools +
+
+
+
本月用量
+
$8.24 / $10
+
+
+
+
速率限制
+
30 RPM
+
+
+
关联应用
+
+ 本地开发 +
+
+
+
+ +
+
+
+ cli-personal + ACTIVE +
+
+ created 2026·04·11 + · + last used 18h ago +
+
+ + + +
+
+
+ sk-puro- + ••••••••••••••••••••••••c3d1 + 👁 显示 + 复制 +
+
+
+
可用订阅池
+
+ claude · 1 +
+
+
+
本月用量
+
$1.12 / 无限制
+
+
+
+
速率限制
+
60 RPM
+
+
+
关联应用
+
+ Terminal +
+
+
+
+ + +
+
+
+ old-demo + REVOKED +
+
+ revoked 2026·03·02 +
+
+
+ sk-puro- + ••••••••••••••••••••••••0ab3 +
+
+
+
+
+
+ + +
+ +
+ + + diff --git a/docs/design-drafts/v2/Binding.html b/docs/design-drafts/v2/Binding.html new file mode 100644 index 00000000..a89dd567 --- /dev/null +++ b/docs/design-drafts/v2/Binding.html @@ -0,0 +1,561 @@ + + + + + +绑定订阅 — PURO AI + + + + + + + + +
+ +
+ + +
+
+ + + 返回 Dashboard + +
+
Zzane
+
+
+ +
+
+
+
// 绑定订阅 · 3 步完成
+

把你已有的 AI 订阅,变成 API

+

我们支持 OAuth 授权和 Cookie 托管两种方式接入 Claude / ChatGPT / Gemini。所有凭证使用 AES-256 加密存储,你可以随时一键解绑。

+
+ + +
+
+ + 选择平台 +
+
+
+ 2 + 授权绑定 +
+
+
+ 3 + 完成 & 加入池 +
+
+ + +
+
+ +
Claude
+
Anthropic · OAuth
+
+ Pro · $20 + Max · $100 + Team +
+
+ +
+ +
ChatGPT
+
OpenAI · OAuth + Cookie
+
+ Plus · $20 + Pro · $200 + Team +
+
+ +
+ +
Gemini
+
Google · Cookie
+
+ Advanced · $20 + Workspace +
+
+
+ + +
+
+ +
+

绑定 ChatGPT 账号

+
支持 Plus / Pro / Team · 接入后可用 gpt-5 / gpt-5-codex / gpt-4.1
+
+ + + AES-256 加密存储 + +
+ +
+ + +
+ 订阅档位 +
+
+
+
Plus
+
$20/月
+
~500k tokens · 约值 $0.08/k
+
+
+
Pro
+
$200/月
+
~5M tokens · 约值 $0.04/k
+
+
+
Team
+
$30/user
+
按席位池化,稳定性更高
+
+
+ + +
+

绑定方式 选择一种即可 · 可以在绑定后随时更换

+
+
+
+ OAuth 授权登录 + 推荐 +
+
+ 跳转到 ChatGPT 登录页,登录后自动回跳。不经过我们的密码表单,最接近"官方授权"体验。 +
+
+ 点击跳转 + ChatGPT 登录 + 授权回调 + 加入池 +
+
+ +
+
+ 粘贴 Session Cookie + 兼容模式 +
+
+ 用浏览器扩展一键导出 __Secure-next-auth.session-token 并粘贴到这里。适合多账号批量绑定。 +
+
+ 安装扩展 + 登录 chatgpt.com + 导出 cookie + 粘贴绑定 +
+
+
+ + +
+ + + + +
+ + + PURO + + chatgpt.com/oauth + + PURO +
+
约 15 秒
+
+
+ + +
+

本次绑定预览 授权成功后会自动加入池

+
+
+ + gpt-plus-7 + 加入 GPT 池 +
+
+ + gpt-plus-8 + 加入 GPT 池 +
+
+
+ + +
+
+ + 凭证仅用于代理请求,不会用于训练或泄露给第三方。 +
+
+ 稍后再说 + +
+
+
+
+ +
+
+
+
+ + diff --git a/docs/design-drafts/v2/Dashboard.html b/docs/design-drafts/v2/Dashboard.html new file mode 100644 index 00000000..3f3da566 --- /dev/null +++ b/docs/design-drafts/v2/Dashboard.html @@ -0,0 +1,770 @@ + + + + + +Dashboard — PURO AI + + + + + + + + + +
+ +
+ + + + + +
+
+ + +
+ + +
+ Z + zane + +
+
+
+ +
+ + +
+ + + + 你还有 2 个 Claude Pro 订阅未绑定 —— 绑定后立即享受多账号 failover 和请求加权调度。 + 去绑定 → + +
+ + +
+
+

Dashboard

+
欢迎回来 Zane · workspace zane-personal
+
+
+
+ + + + + +
+ +
+
+ + +
+ POST + https://ai.puro.im/v1/chat/completions + 复制 + · + 查看文档 +
+ + +
+
+ 充值 → +
余额
+
$182.40USD
+
按当前节奏约 23 天
+
+
+
今日请求
+
12,847
+
▲ 18.2% vs 昨日
+ + + +
+
+
消耗 Tokens
+
4.82M
+
▲ 12.5%
+ + + +
+
+
平均延迟
+
214ms
+
▲ 4.1% vs 昨日
+ + + +
+
+ + +
+
+
+ 请求趋势 +
+ Claude + GPT + Gemini +
+
+ + + + + + + + + + + + + + + + + + + Mon + Tue + Wed + Thu + Fri + Sat + Sun + + + + + + + Fri · 16:00 + Claude 3,824 + + +
+ +
+
+ 模型分布 + 7d +
+
+ + + + + + + +
+
Claude48%
+
GPT32%
+
Gemini14%
+
Codex6%
+
+
+
+
+ + +
+ +
+
+

订阅账号池

+ 查看全部 → +
+
+
+ +
claude-pool-01Pro · OAuth
+
+ healthy +
+
+ +
claude-pool-02Max · OAuth
+
+ healthy +
+
+ +
gpt-plus-7Plus · OAuth
+
+ near limit +
+
+ +
gpt-plus-8Pro · OAuth
+
+ healthy +
+
+ +
gemini-adv-01Advanced
+
+ healthy +
+
+ +
claude-pool-03Pro · expired
+
+ ● offline +
+ + + 绑定新订阅 + +
+
+ + +
+
+

最近请求

+ live · 12 of 18,294 +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TIMEACCOUNTMODELTOKENSCOSTSTATUS
13:42:18claude-pool-01sonnet-4-52,847$0.042200 · 213ms
13:42:11gpt-plus-7gpt-5-codex1,204$0.018200 · 167ms
13:42:03gemini-adv-01gemini-2.5-pro4,102$0.000200 · 392ms
13:41:58claude-pool-02sonnet-4-56,318$0.095200 · 288ms
13:41:49gpt-plus-7gpt-5892$0.013429 · retry
13:41:42gpt-plus-8gpt-5892$0.013200 · 198ms
13:41:35claude-pool-01haiku-4-5512$0.004200 · 98ms
13:41:28gemini-adv-01gemini-2.5-flash1,824$0.000200 · 156ms
+
+
+ +
+
+
+ + + diff --git a/docs/design-drafts/v2/Design System.html b/docs/design-drafts/v2/Design System.html new file mode 100644 index 00000000..742109c0 --- /dev/null +++ b/docs/design-drafts/v2/Design System.html @@ -0,0 +1,670 @@ + + + + + +Design System — PURO AI + + + + + + + + + +
+
+ + + +
+ + +
+
// design system · v1.0
+

PURO AI Design System

+

一套用来构建 PURO AI 所有界面的原子 token 和组件。产品的视觉语言围绕「开发者工具 · 深色为主 · 青色作为行动色 · JetBrains Mono 强调技术感」展开。

+
+ tokens · 29 + components · 22 + last updated · 2026.04.19 +
+
+ +
+ + +
+ + +
+

01Brand Lockup

+

六边形 + 内部实心菱形 — 代表"订阅被聚合成一个 key"。单色在小尺寸下使用,大尺寸下保留内描边增加分量。

+
+ + + + +
PURO.
+
+
+ + +
+

02Colors

+

所有颜色都以 CSS variables 定义在 puro.css。青色 (cyan) 是唯一的品牌色,其他颜色仅承担语义职责(success / warn / danger)。

+ +
Surfaces
+
+
bg-0
#0a0e1a
page
+
bg-1
#0f172a
raised
+
bg-2
#111827
card alt
+
bg-code
#020617
code
+
border
#1e293b
hairline
+
border-2
#334155
strong
+
+ +
Text
+
+
text-0
#f8fafc
primary
+
text-1
#cbd5e1
body
+
text-2
#94a3b8
muted
+
text-3
#64748b
hint
+
+ +
Accents
+
+
cyan
#22d3ee
primary / cta
+
purple
#a855f7
secondary glow
+
amber
#fbbf24
warn / featured
+
green
#34d399
success / 200
+
red
#f87171
error / 5xx
+
orange
#fb923c
flag / highlight
+
+ +
Provider Brand Dots
+
+
claude
#d97757
Anthropic
+
gpt
#10a37f
OpenAI
+
gemini
#4285f4
Google
+
codex
#f0a030
Codex
+
+
+ + +
+

03Typography

+

主字体 Inter · 等宽 JetBrains Mono。等宽仅用于代码、数据、时间戳、状态徽标,以强化开发者语境。

+ +
+
display · 56/64 · 800
+
你的 AI 订阅
+
+
+
h1 · 40/48 · 700
+
统一接入 API
+
+
+
h2 · 28/36 · 700
+
付一次,用一池
+
+
+
h3 · 18/26 · 600
+
多账号自动调度
+
+
+
body · 14/22 · 400
+
OAuth 绑定账号,零改动切换 base_url,沿用你习惯的 SDK。
+
+
+
caption · 12/18 · 400
+
某个 ChatGPT Plus 触发限流会自动 failover。
+
+
+
mono · 13 · 500
+
curl https://ai.puro.im/v1/chat/completions
+
+
+
kicker · mono · 12 · caps
+
// section kicker
+
+
+ + +
+

04Spacing Scale

+

4px 基线的 8 / 12 / 16 / 20 / 24 / 32 / 48 / 64 scale。页面垂直节奏用 32/48/64/96,卡片内部用 16/20/24。

+
+
4px
gap-xs · pill 间隔
+
8px
gap-sm · icon 内外距
+
12px
gap · 卡片网格
+
16px
stack-sm · 主要网格
+
20px
form field
+
24px
card padding
+
32px
content padding
+
48px
section break
+
64px
section head gap
+
96px
landing section
+
+
+ + +
+

05Radius & Shadow

+
+
6px
--r-sm
+
8px
--r-md (button, input)
+
12px
--r-lg (card)
+
16px
--r-xl (hero card)
+
+
Elevation
+
+
+
--shadow-lg
+
卡片悬浮 · 代码面板
+
+
+
--shadow-xl
+
仪表盘大图 · 对话框
+
+
+
+ + +
+

06Buttons

+

唯一的主色按钮 Primary(青色),其余都是 Ghost/Subtle。没有多种 primary —— 让每个页面最重要的那个 CTA 足够显眼。

+ +
Variants
+
+
+ + + + + +
+
+ +
Sizes
+
+
+ + + + +
+
+ +
Loading
+
+
+ + +
+
+
+ + +
+

07Badges

+
+
+ NEW + BETA + LIMITED + ACTIVE + EXPIRED + DRAFT +
+
+
+ + +
+

08Chips & Status

+

chip 用于在代码块周围承载"路由/参数/标签"信息,status-chip 是一个绝对定位的单像素点,用于显示账号/节点在线状态。

+
+
+ claude-pool-03 + gpt-plus-7 + gemini-2 + codex-pool-01 + 200 · 213ms +
+
+ OpenAI SDK + Anthropic SDK + /v1/chat/completions +
+
+
+ + +
+

09Form Fields

+
+
+
+ +
+ + + + +
+
默认 · 空态
+
+
+ + +
✓ 可用
+
+
+ + +
密码至少 8 位,包含数字和字母
+
+
+ + +
+
+ +
+ + +
+
+
+ + +
+

10Cards

+
+
+
// default
+
标准卡片
+
用于所有常规内容容器,12px 圆角 + 1px border。
+
+
+
// hover
+
可交互卡片
+
hover 时向上位移 2px,border 加深。
+
+
+
// raised
+
Raised 卡片
+
不透明背景,用于浮层/仪表盘主体。
+
+
+
+ + +
+

11Tables

+

主要用于请求日志、API Key 列表、计费记录。数字列一律等宽 tabular-nums。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TIMEACCOUNTMODELTOKENSCOSTSTATUS
13:42:18claude-3sonnet-4-52,847$0.042200
13:42:11gpt-plus-7gpt-5-codex1,204$0.018200
13:42:03gemini-2gemini-2.5-pro4,102$0.000429
+
+
+ + +
+

12Code Frame

+
+
+
+ zsh · puro ≈ 210ms +
+
1# OpenAI SDK · 零改动
2from openai import OpenAI
3
4client = OpenAI(
5 base_url="https://ai.puro.im/v1",
6 api_key="sk-puro-••••",
7)
+
+
+ + + + +
+
+ + +
+ + + diff --git a/docs/design-drafts/v2/Docs.html b/docs/design-drafts/v2/Docs.html new file mode 100644 index 00000000..6f0d793a --- /dev/null +++ b/docs/design-drafts/v2/Docs.html @@ -0,0 +1,623 @@ + + + + + +Docs — PURO AI + + + + + + + + +
+ + + +
+ + + + +
+
+ Docs / + Getting Started / + 快速开始 +
+ +

快速开始

+

+ PURO AI 提供一个统一的 OpenAI 兼容端点 —— 你已有的 SDK 代码只需要改 base_urlapi_key 两行,就能用上你绑定的 Claude / ChatGPT / Gemini 订阅。整个过程通常不超过 5 分钟。 +

+ + +
+
+
STEP 01
+

绑定订阅

+

授权你的 Claude / ChatGPT 账号加入池。

+
+
+
STEP 02
+

创建 API Key

+

为每个客户端生成独立的 sk-puro-* key。

+
+
+
STEP 03
+

切换 base_url

+

改两行代码,剩下和官方 SDK 一模一样。

+
+
+ +

① 绑定你的订阅

+

+ 进入 Dashboard → 订阅账号 → 绑定新订阅,选择平台后通过 OAuth 一键授权。每个订阅都会被加入对应的"池",同一池内的请求会自动做负载均衡、限流回退和故障转移。 +

+
+ + + +
+ 凭证通过 AES-256 加密存储在隔离的 KMS 中。我们只会用它代理你发出的请求 —— 不会进入训练数据、不会做二次分发。详见 数据隐私。 +
+
+ +

② 创建 API Key

+

+ 在 Dashboard → API Keys → 创建 Key 生成一个 sk-puro-* 的 key。建议每个客户端 / 环境单独一个 key,泄漏时可以直接吊销而不影响其他场景。 +

+ +

③ 发送第一个请求

+

PURO AI 同时兼容 OpenAI 和 Anthropic 的 API 格式。按你原来在用的 SDK 风格选择对应的代码示例即可:

+ +
+
+
+ + + + +
+ ⧉ 复制 +
+
# pip install openai
+from openai import OpenAI
+
+client = OpenAI(
+    base_url="https://ai.puro.im/v1",
+    api_key="sk-puro-YOUR_KEY",
+)
+
+resp = client.chat.completions.create(
+    model="claude-sonnet-4-5",       # 可直接写任意平台的模型名
+    messages=[
+        {"role": "user", "content": "hi, who am I talking to?"}
+    ],
+)
+
+print(resp.choices[0].message.content)
+
+
+ +

返回结构完全符合 OpenAI 格式,可以无缝对接任何基于 OpenAI SDK 的应用(Cursor / Continue / Cline / Roo Code / Open WebUI …)。

+ +

可用模型

+

绑定后,下列模型都可以直接用 model 字段调用 —— PURO 会根据模型自动路由到对应的订阅池。

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MODELPROVIDER上下文状态
claude-sonnet-4-5ClaudePro / Max200kOK
claude-opus-4ClaudeMax200kOK
claude-haiku-4-5ClaudePro / Max200kOK
gpt-5ChatGPTPlus / Pro128kOK
gpt-5-codexChatGPTPlus / Pro128kOK
gemini-2.5-proGeminiAdvanced1MBETA
gemini-2.5-flashGeminiAdvanced1MOK
+ +

支持的 base_url

+

每种格式都提供独立的 base_url —— 如果你在用原生 Anthropic / Google SDK,请选择对应格式以获得最完整的字段兼容:

+ + + +
+ + + +
+ 一个 sk-puro-* 可以同时用于三种 base_url —— 鉴权和计费是统一的,你不需要为不同 SDK 维护多个 key。 +
+
+ +

下一步

+

把 PURO 接入到你常用的工具:

+ + + + +
+ + + +
+ + diff --git a/docs/design-drafts/v2/HANDOFF.md b/docs/design-drafts/v2/HANDOFF.md new file mode 100644 index 00000000..d45b71f5 --- /dev/null +++ b/docs/design-drafts/v2/HANDOFF.md @@ -0,0 +1,222 @@ +# PURO AI · 设计交付文档 + +> 把已有的 Claude / ChatGPT / Codex / Gemini 订阅聚合成统一 API · 让"已经付过钱的订阅"真正可编程 + +本文档面向**接手实现这套设计的工程团队 / Coding Agent**,说明每个 HTML 文件的用途、数据契约、交互逻辑,以及与后端的对接点。 + +--- + +## 0. 文件清单 + +| 文件 | 类型 | 说明 | +|---|---|---| +| `Landing.html` | 营销首页 | 未登录入口 · Hero / 模型墙 / 功能 / 代码示例 / Dashboard 预览 / Pricing / FAQ | +| `Pricing.html` | 定价页 | 完整定价 + 成本估算器 + 工具兼容墙 + 10 条 FAQ | +| `Docs.html` | 文档 | 快速开始 / 模型列表 / API 参考 / 各客户端配置 | +| `Login.html` | 登录 | 左侧叙事 + 右侧表单 · 支持 LinuxDO OAuth | +| `Register.html` | 注册 | 带密码强度 · 下一步引导 · 送 $5 | +| `Binding.html` | 订阅绑定 | 核心差异化流程 · OAuth 接入 Claude Pro / ChatGPT Plus | +| `Dashboard.html` | 控制台首页 | 余额 / 用量图表 / 近期请求 / 订阅池状态 | +| `API Keys.html` | Key 管理 | 创建 / 吊销 / 预算限制 / 模型白名单 | +| `Design System.html` | 设计系统 | 色板 / 字号 / 组件索引 | +| `puro.css` | 全站样式 | 所有页面共用的 tokens + primitives(.btn / .pill / .tag / .input 等) | + +**所有页面必须首行 meta:** `` —— 防止浏览器 auto-darken 覆盖 cyan 按钮。 + +--- + +## 1. 设计 Tokens(见 `puro.css`) + +``` +--bg-0: #0a0e1a 页面底 +--bg-1: #0f172a raised +--bg-2: #111827 card alt +--bg-code: #020617 代码面板 + +--border: #1e293b +--border-2:#334155 +--border-3:#475569 + +--text-0: #f8fafc 主 +--text-1: #cbd5e1 次 +--text-2: #94a3b8 说明 +--text-3: #64748b 弱 + +--cyan: #22d3ee 主强调(primary btn / 链接 / 数据点) +--cyan-2: #67e8f9 hover +--purple: #a855f7 次强调 / 装饰 +--amber: #fbbf24 提醒 / 限时标签 +--green: #34d399 成功 / 在线 +--red: #f87171 错误 / 危险 +``` + +字体:`Inter`(正文)+ `JetBrains Mono`(所有数值 / 代码 / 元信息)。 +圆角:`--r-sm 6px / --r-md 8px / --r-lg 12px / --r-xl 16px`。 + +--- + +## 2. 页面 → 后端契约 + +### 2.1 Landing · 无后端依赖(纯营销) +锚点:`#pricing` / `#faq` / `#features` / `#code` / `#dashboard`。注册 CTA → `Register.html`。 + +### 2.2 Register +提交表单需要 **后端返回**: +``` +POST /auth/register +{ email, password, linuxdo_token? } +→ 200 { user_id, jwt, balance_credits: 5.00 /* $5 注册赠送 */ } +``` +前端验证规则: +- 邮箱格式 `^[^\s@]+@[^\s@]+\.[^\s@]+$` +- 密码强度 ≥ 2(长度 ≥ 8 + 字母大小写混合即可通过) +- 两次密码一致 + 勾选 terms +成功后跳 `Binding.html`。 + +### 2.3 Login +``` +POST /auth/login +{ email, password } 或 { linuxdo_oauth_code } +→ 200 { jwt, user, has_subscriptions: boolean } +``` +若 `has_subscriptions === false`,引导去 `Binding.html`;否则去 `Dashboard.html`。 + +### 2.4 Binding(核心差异化) +OAuth 流程,每个平台: +``` +POST /bindings/oauth/start +{ provider: 'claude' | 'chatgpt' | 'codex' | 'gemini' } +→ { auth_url, state } +``` +前端打开 `auth_url` 新窗口;OAuth 回调: +``` +GET /bindings/oauth/callback?code=...&state=... +→ 302 → /binding/success?provider=claude&account_id=... +``` +绑定列表: +``` +GET /bindings +→ [ { id, provider, account_email, plan, status: 'healthy'|'cooling'|'error', quota_remaining_pct, bound_at } ] +DELETE /bindings/:id 解绑 +POST /bindings/:id/test 发一条测试请求验证凭证有效 +``` + +### 2.5 Dashboard +``` +GET /me/overview +→ { + balance: { credits: 45.23, bonus_credits: 12.00, expires_at: null }, + usage_today: { requests: 1842, tokens_in: 2.1e6, tokens_out: 4.8e5, cost: 1.23 }, + usage_30d: [{ date, cost, requests }, ...], + recent_requests: [{ ts, model, route_to, status, latency_ms, tokens, cost }, ...], + pool_status: [{ provider, accounts: [{ status, quota_pct }] }] +} +``` +图表用 `usage_30d` 绘制折线(cyan 主线,purple 副线)。 + +### 2.6 API Keys +``` +GET /api-keys +POST /api-keys { name, models?: string[], monthly_budget?: number } +DELETE /api-keys/:id +``` +Key 前缀 `sk-puro-` + 32 字符。创建后仅显示一次完整值,之后只保留前 8 位。 + +### 2.7 Pricing +纯静态展示。充值 CTA 统一走: +``` +POST /billing/topup +{ amount_usd, payment_method: 'alipay'|'wechat'|'usdt'|'stripe' } +→ { payment_url / qr_code, order_id } +``` +阶梯赠送在前端计算并展示(见 `Pricing.html` 底部 script),**后端下单时再次校验**: +``` +阶梯 = amount >= 500 ? 120% + : amount >= 200 ? 110% + : amount >= 99 ? 100% + : amount >= 50 ? 70% + : amount >= 30 ? 50% + : amount >= 20 ? 35% + : 21% +``` + +--- + +## 3. 统一 API Gateway(产品核心) + +所有用户集成的终点: +``` +https://api.puro.im/v1/chat/completions # OpenAI 兼容 +https://api.puro.im/v1/messages # Anthropic 兼容 +https://api.puro.im/v1beta/models/:m:generateContent # Gemini 兼容 +``` +Header: `Authorization: Bearer sk-puro-xxx` + +**调度逻辑(文档化在 `Docs.html`):** +1. 根据模型解析目标 provider +2. 从该 provider 的订阅池挑一个 `status=healthy` 的账号(权重:剩余配额 × 响应时延倒数) +3. 若 429 / 限流,标记 `cooling` 60-300s,failover 到下一个 +4. 若该 provider 所有订阅都 cooling 且用户余额 > 0,fallback 到官方 API +5. 所有请求写入 `request_logs`(用户可见,保留周期按套餐) + +--- + +## 4. 套餐与限制 + +| | Starter $9.9 | Pro $29.9 ⭐ | Scale $99 | Custom | +|---|---|---|---|---| +| 赠送 | +21% | +50% | +100% | 阶梯 | +| API Keys | 1 | 3 | 10 | 按 Pro | +| RPM | 60 | 120 | 300 | 按 Pro | +| 日志保留 | 7d | 30d | 90d | 按 Pro | +| 自带订阅 | ❌ | ∞ | ∞ | ✅ | +| 多账号 failover | — | ✅ | ✅ + 优先级 | ✅ | + +**Enterprise** 走 Sales 线:私有化 / SLA / Invoice,不在普通订单流里。 + +--- + +## 5. 交互细节(容易漏) + +- **余额不足**:Gateway 返回 `402 Payment Required`,Dashboard 顶部红色 banner + 发邮件(80% / 95% 两档预警) +- **订阅 cooling** 时 Dashboard 订阅卡片上角 amber 闪烁点 +- **API Key 创建**:弹窗必须强制用户复制一次,关闭弹窗后永远看不到完整值 +- **密码强度**:评分 0-4,label 对应 `— / 弱 / 中 / 强 / 极强`,颜色 `border / red / amber / cyan / green` +- **登录成功**:按钮变 green 显示 `✓ 登录成功`,800ms 后 window.location 跳转 +- **LinuxDO OAuth** 是 PURO 的目标用户群(开发者社区),作为次要登录按钮展示 + +--- + +## 6. 响应式断点 + +- `<= 900px`:Login/Register 的 split 变单列,narrative 折叠 +- `<= 820px`:Pricing grid 从 4 列 → 1 列 +- `<= 820px`:Landing pricing-grid-landing 从 3 列 → 1 列 +- `<= 960px`:Pricing 的 calculator 从 2 列 → 1 列 + +--- + +## 7. 后端技术建议(非设计范畴,仅供参考) + +- **Gateway 层**:Go / Rust 写高吞吐代理,HTTP/2 + streaming,每 provider 起独立 worker pool +- **调度**:Redis ZSET 存每个订阅的健康分 + cooling 过期时间 +- **日志**:写 ClickHouse(列存 + 按日分区),Dashboard 直接查 +- **凭证加密**:每个订阅凭证用 AES-256-GCM 加密,key 放 KMS;解密只在 gateway worker 内存里,绝不落日志 +- **计费**:Pulsar / Redis Streams 实时扣费,最终一致性,异步对账 + +--- + +## 8. 已知 TODO(设计层面暂不处理,开发阶段补) + +- [ ] Dashboard 真实图表联动(目前是静态 SVG) +- [ ] Binding OAuth 回调 loading / 成功动效 +- [ ] 邮件模板(注册验证 / 余额预警 / 充值成功) +- [ ] 忘记密码流程 +- [ ] 团队 / 多人协作 UI(Enterprise 档需要) +- [ ] i18n:当前仅中文,英文版待出 + +--- + +生成时间:2026-04 · 设计稿版本:v1 + +联系 Sam / 产品负责人确认实现细节。 diff --git a/docs/design-drafts/v2/Landing.html b/docs/design-drafts/v2/Landing.html new file mode 100644 index 00000000..fa229224 --- /dev/null +++ b/docs/design-drafts/v2/Landing.html @@ -0,0 +1,1423 @@ + + + + + +PURO AI — 你的 AI 订阅,已经付过钱了 + + + + + + + + +
+
+ + + + + +
+
+ NEW + 统一接入多个 AI 平台 · 零改动切换 +
+

你的 AI 订阅,
已经付过钱了。

+

+ Claude Pro · ChatGPT Plus · Codex · Gemini 订阅
+ 聚合成统一 API,零改动接入 OpenAI / Anthropic SDK +

+
+ 立即开始 → + 查看文档 +
+
+ 无需信用卡 + + 用你已有的订阅 + + 5 分钟跑通 +
+
+ + +
+
+
// providers
+

支持的 AI 平台

+

通过 OAuth 直接复用你的订阅,无需申请官方 API key

+
+
+
+ + +
Claude
+
Pro / Max
+
+
+ + +
ChatGPT
+
Plus / Pro
+
+
+ + +
Codex CLI
+
OpenAI
+
+
+ + +
Gemini
+
Code Assist
+
+
+ + +
更多
+
即将推出
+
+
+
+ + +
+
+
// features
+

付一次订阅,
用起一整个模型池

+

把散落在各个平台的订阅,整合成开发者真正能用的基础设施

+
+
+
+
+

一个 key 接所有模型

+

不再为每个 provider 申请 API key、配置 base_url。统一 sk- 走 Claude / GPT / Gemini,按 model 自动路由到对应账号池。

+
    +
  • OpenAI Responses API 兼容
  • +
  • Anthropic Messages API 兼容
  • +
  • 智能 model → provider 路由
  • +
+
+
+
🔄
+

账号池高可用

+

多账号自动调度。某个 ChatGPT Plus 触发限流,自动 failover 到下一个。重启、刷新 token 全自动。

+
    +
  • 限流/5xx 自动 failover
  • +
  • OAuth token 自动刷新
  • +
  • 加权轮询 · 最少连接
  • +
+
+
+
📊
+

用量看板

+

每条请求的 tokens、费用、上游账号、延迟全可视化。模型分布饼图 + 趋势曲线 + Top 排行。

+
    +
  • 逐请求审计日志
  • +
  • 多维度 tokens / cost 统计
  • +
  • 导出 CSV / 接 Webhook
  • +
+
+
+
+ + +
+
+
// integration
+

把 base_url 一改,就能用

+

兼容 OpenAI / Anthropic / Gemini SDK,零代码改动

+
+ +
+
+
+
+
~/.codex/config.toml
+
curl.sh
+
● edited 2s ago
+
+
1# Codex CLI — 只改 base_url 就能走 PURO
2model_provider = "puro"
3model = "gpt-5-codex"
4
5[model_providers.puro]
6 name = "PURO AI"
7 base_url = "https://ai.puro.im/v1"
8 wire_api = "responses"
9 env_key = "PURO_API_KEY" # export PURO_API_KEY=sk-puro-…
+
+ +
+
+
+
curl.sh
+
zsh · puro ≈ 210ms
+
+
1$ curl https://ai.puro.im/v1/chat/completions \
2 -H "Authorization: Bearer $PURO_API_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "model": "claude-sonnet-4-5", # 自动路由到 Claude Pro 池
6 "stream": true,
7 "messages": [{ "role": "user", "content": "写一个斐波那契" }]
8 }'
9
10# ← event: content_block_delta · account=claude-3 · 187ms · 42 tok
+
+
+ +
+ 支持 OpenAI Responses API · Anthropic Messages API · Gemini generateContent · 流式 SSE & WebSocket +
+
+ + +
+
+
// observability
+

每条请求都看得见

+

+ 不像第三方 API 池子那种"扣了多少不告诉你"。你能看到每次调用:扣了哪个账号、跑了哪个模型、用了多少 tokens、花了多少钱、上游响应几秒。 +

+
+ +
+
+
+
ai.puro.im/dashboard
+
me@puro
+
+
+ +
+
+
+
Requests · 24h
+
18,294
+
▲ 12.4%
+
+
+
Tokens · 24h
+
4.7M
+
▲ 8.1%
+
+
+
Avg latency
+
312ms
+
▼ 4.2%
+
+
+
Est. savings
+
$847
+
vs. pay-as-you-go
+
+
+ +
+
+
+ Requests over time +
+ Claude + GPT + Gemini +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + 00:00 + 06:00 + 12:00 + 18:00 + now + + +
+
+
+ Model distribution + · 24h +
+ +
+ + + + + + + + + + + +
+
Claude48%
+
GPT32%
+
Gemini14%
+
Codex6%
+
+
+
+
+ +
+
+
Recent requests
+
live · 12 of 18,294
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TimeProviderModelTokensCostLatencyStatus
13:42:18claude-3sonnet-4-52,847$0.042213ms● 200
13:42:11gpt-plus-7gpt-5-codex1,204$0.018167ms● 200
13:42:03gemini-2gemini-2.5-pro4,102$0.000392ms● 200
13:41:58claude-1sonnet-4-56,318$0.095284ms● 429 → failover
13:41:49gpt-plus-2gpt-5892$0.013141ms● 200
+
+
+
+
+ + +
+

把订阅变成 API — 5 分钟

+

绑定第一个账号,生成 sk- key,把 base_url 指过来。就这些。

+
+ 创建账户 → + 登录已有账户 +
+
+
+ + +
+
+
// pricing
+

充一次,全平台通用

+

积分永不过期 · 同一份余额可跑 Claude / ChatGPT / Gemini · 支付宝 / 微信 / USDT

+
+ +
+
+
STARTER
+
tier · 01
+
尝鲜测试 · 跑通接入
+
$9.9
+
充 $9.9 → 得 $12 积分 +21%
+
相当于官方 API · 0.5 折起
+
+
+
✓ 全模型 / 全池可用
+
✓ 1 个 API Key
+
✓ 60 RPM
+
— 不支持自带订阅
+
+ 充值 +
+ + + +
+
⚡ 限时 +100%
+
tier · 03
+
小团队 / 长跑项目
+
$99
+
充 $99 → 得 $198 积分 +100%
+
相当于官方 API · 2-5 折
+
+
+
✓ 10 个 API Key · 独立预算
+
✓ 300 RPM · 90 天日志
+
✓ 请求优先级调度
+
✓ Slack / Discord 群组支持
+
+ 充值 +
+
+ + +
+ + +
+
+
// frequently asked
+

你可能想问的

+

没找到答案?发邮件给我们 ↗ · 通常 2 小时内回复

+
+ +
+ 01PURO 和 API 中转站有什么不同? +
+ 中转站只是转发官方 API,价格取决于你预付的 balance。PURO 的不同是 —— 我们让你把已有的 Claude Pro / ChatGPT Plus 订阅变成 API。 + 你原本就在付的 $20/月,不再只能在官网聊天里用,而是通过统一 API 喂给 Cursor、Claude Code、任何 SDK。 + 同时也提供按量充值的官方 API 备用池,两种模式可混用。 +
+
+ +
+ 02用订阅跑 API 会不会被封号? +
+ 我们会自动控制每个订阅的请求节奏,并在触发限流时把请求 failover 到池内其他订阅。实际上 PURO 的调用模式比你在官方客户端复制粘贴大段对话更不容易触发风控。 + 所有凭证用 AES-256 加密存储,请求链路不经过第三方。 +
+
+ +
+ 03积分会过期吗?可以退款吗? +
+ 积分永不过期。首次充值 7 天内未产生任何调用可全额退款,之后按剩余积分 85% 比例退。 +
+
+ +
+ 04支持哪些支付方式? +
+ 国内:支付宝 · 微信支付。国际:Stripe 信用卡 · USDT (TRC20 / ERC20) · PayPal。企业充值支持 Invoice 对公打款,人民币开票。 +
+
+ +
+ 05数据会被用于训练吗? +
+ 不会。所有请求仅用于路由转发,不入库、不留存内容(仅保留元数据如模型、token 数、延迟,用于计费和日志)。Pro 档及以上可选开启"零日志模式"。 +
+
+ +
+ 06支持哪些模型?新模型上线多久? +
+ 当前覆盖 Claude(Sonnet 4.5 / Opus 4 / Haiku 4.5)、ChatGPT(GPT-5 / GPT-5 Codex / GPT-4.1)、Gemini(2.5 Pro / 2.5 Flash)。官方发布新模型后通常 24 小时内上线。 +
+
+ +
+ 查看全部 10 个问题 +
+
+ + + + + + diff --git a/docs/design-drafts/v2/Login.html b/docs/design-drafts/v2/Login.html new file mode 100644 index 00000000..33ea8429 --- /dev/null +++ b/docs/design-drafts/v2/Login.html @@ -0,0 +1,351 @@ + + + + + +登录 — PURO AI + + + + + + + + +
+
+ +
+ +
+
+ + + + + + PURO AI + + +
+
// 你的订阅,已经付过钱了
+

+ N 个订阅 + + 1 个 key +

+
+ +
+ 省去切换账号的繁琐, + 省去为多个高昂订阅重复买单。 + PURO(纯粹)—— 让 AI 调用回归本质。 +
+ +
+
POST/v1/chat/completions
+
modelclaude-sonnet-4-5
+
route →claude-pool-03
+
status200·213ms·42 tok
+
+
+ +
+ Claude· + ChatGPT· + Codex· + Gemini + | + ai.puro.im · operational +
+
+ + +
+ ← 返回首页 + +
+

登录

+

用你的 PURO AI 账户继续

+ +
+ +
+ + + + + + + +
+
+ +
+ +
+ + + + + + + + +
+
+ + 忘记密码? +
+
+ + + +
OR
+ + + +
+ 没有账户?注册 +
+ + +
+
+
+ + + + diff --git a/docs/design-drafts/v2/Pricing.html b/docs/design-drafts/v2/Pricing.html new file mode 100644 index 00000000..f7600a62 --- /dev/null +++ b/docs/design-drafts/v2/Pricing.html @@ -0,0 +1,472 @@ + + + + + +Pricing — PURO AI + + + + + + + + +
+
+ + + +
+
// pricing · 充多少 · 用多少 · 永不过期
+

一次充值,全平台通用

+

+ 同一份积分可以用在 Claude / ChatGPT / Gemini 任意池上。我们把你的订阅额度变成真正的 API 余额 —— 相比官方 API 便宜 至多 70%。 +

+
+ + 余额永不过期 · 支持支付宝 / 微信 / USDT · 无隐藏订阅费 +
+
+ +
+
+ +
+ STARTER +
tier · 01
+
先尝尝鲜,跑通接入
+
$9.9
+
充 $9.9 $12 积分 +21%
+ 相当于官方 API · 0.5 折起 +
+
+
可用所有模型 / 所有池
+
1 个 API Key
+
60 RPM 速率限制
+
基础日志(7 天保留)
+
自带订阅接入
+
团队 / 多人协作
+
+ 充值 → +
+ + + +
+ ⚡ 限时 +100% +
tier · 03
+
小团队 / 长跑项目
+
$99
+
充 $99 $198 积分 +100%
+ 相当于官方 API · 2-5 折 +
+
+
所有 Pro 能力
+
10 个 API Key · 独立预算
+
300 RPM 速率限制
+
调用日志(90 天保留)
+
请求优先级加权调度
+
Slack / Discord 群组支持
+
+ 充值 → +
+ +
+ CUSTOM +
tier · 04
+
自定义金额 · 按需充值
+
$50
+
得约 $78 积分 +56%
+ + 根据金额阶梯自动匹配折扣 +
+
+
积分永不过期
+
Pro 全部能力
+
阶梯 +21% ~ +100%
+
支付宝 / 微信 / USDT
+
拖动滑块预览赠送
+
+ 定制充值 → +
+
+ +
+
+
+
+

Enterprise · 企业定制

+

专属订阅池、SLA、合规审计、私有化部署、发票结算。规模 >$500/月起可申请。

+
+ 联系商务 → +
+
+
+
+

已有订阅?直接接入

+

有 Claude Max / ChatGPT Pro?免费注册后绑定,只为 PURO 路由费买单 —— 按次 $0.0008/request

+
+ 接入我的订阅 → +
+
+
+ +
+
+
+
// cost estimator
+

算算你能省多少?

+

按你的使用场景,对比 PURO 和官方 API 的月度花费差。数字会根据你选的场景自动更新。

+
+
+
日均请求数5,000
+ +
+
+
平均每请求 tokens3,000
+ +
+
+
Claude 占比50%
+ +
+
+
+
+
+
月度 tokens 消耗450M
+
官方 API 价格$1,620
+
PURO 价格(含 +50% 赠送)$486
+
节省$1,134 · 70%
+
+
+
+
建议充值
+
≈ 3 天试用 + Pro 档充值
+
+
$486
+
+
+
+
+ +
+
+
// works everywhere
+

一个 key,所有工具通用

+

只要支持自定义 base_url 或 OpenAI / Anthropic API,都能直接接入 PURO。

+
+
+
Claude Code
ANTHROPIC_BASE_URL
+
Cursor
自定义模型
+
Cline
OpenAI 兼容
+
Roo Code
OpenAI 兼容
+
Continue
config.yaml
+
OpenAI SDK
Python / Node
+
Anthropic SDK
原生 Claude
+
Open WebUI
自定义 base
+
LangChain
LLM 节点
+
LlamaIndex
模型路由
+
Zed
Assistant
+
更多…
60+ 工具
+
+
+ +
+
+
// frequently asked
+

你可能想问的

+

没找到答案?发邮件给我们 ↗ · 通常 2 小时内回复。

+
+ +
+ 01PURO 和 API 中转站 / API 代理有什么不同? +
+ 中转站只是把官方 API 请求转一手,价格取决于你预付多少 balance。PURO 的不同是 —— 我们让你 把已有的 Claude Pro / ChatGPT Plus 订阅变成 API。 + 你原本就在付的 $20/月,不再只能在官网聊天里用,而是通过统一 API 喂给 Cursor、Claude Code、任何 SDK。 + 同时我们也提供按量充值的官方 API 备用池,两种模式可以混用。 +
+
+ +
+ 02用订阅跑 API 会不会被封号? +
+ 我们会自动控制每个订阅的请求节奏,并在触发限流时把请求 failover 到池子里的其他订阅。实际上 PURO 的调用模式比你在官方客户端直接复制粘贴大段对话 更不容易触发风控。 + 你绑定多个订阅时,单个账号的 RPM 会被压到足够安全的阈值内。另外所有凭证用 AES-256 加密存储,请求链路不经过第三方。 +
+
+ +
+ 03积分会过期吗?可以退款吗? +
+ 积分永不过期。你可以攒着慢慢用 —— 包括几个月都不用。首次充值 7 天内未产生任何调用可全额退款,之后按剩余积分 85% 比例退。详见 退款政策。 +
+
+ +
+ 04支持哪些支付方式? +
+ 国内:支付宝 · 微信支付。国际:Stripe 信用卡 · USDT (TRC20 / ERC20) · PayPal。企业充值支持 Invoice 对公打款,人民币开票。 +
+
+ +
+ 05一个 PURO 账号可以绑定多少个订阅? +
+
    +
  • Starter 档:不支持绑定自带订阅
  • +
  • Pro 档及以上:无限制,你可以把 10 个 ChatGPT Plus + 3 个 Claude Pro 一起绑上去,统一调度
  • +
  • Enterprise:支持跨团队共享池,按组织维度隔离
  • +
+
+
+ +
+ 06如果某个订阅触发限流了会怎样? +
+ PURO 的调度器会把受限的订阅自动标记为 cooling 状态,暂时从池子里摘除。同一请求会立刻被 failover 到池内其他健康订阅上 —— 调用方通常 感受不到中断。你可以在 Dashboard 看到每个订阅的当前状态和剩余配额。 +
+
+ +
+ 07计费精度?超量会怎么办? +
+ 按实际 token 数 + 模型单价计费,精度到 4 位小数。每个 API Key 可设置独立月度预算,达到后 402 Payment Required,不会继续扣费。账户总余额不足时同样会返回 402,且 Dashboard 有 80% / 95% 两级提醒邮件。 +
+
+ +
+ 08数据会被用于训练吗? +
+ 不会。所有请求仅用于路由转发,不入库、不留存内容(仅保留元数据如模型、token 数、延迟,用于计费和日志)。Pro 档及以上可选开启"零日志模式",我们连请求 ID 都不记录。 +
+
+ +
+ 09可以私有化部署吗? +
+ Enterprise 档支持 Docker / K8s 私有化部署,控制面和数据面可以分开。授权按年订阅,包含升级和技术支持。联系商务 → +
+
+ +
+ 10支持哪些模型?会跟进新模型吗? +
+ 当前覆盖 Claude(Sonnet 4.5 / Opus 4 / Haiku 4.5)、ChatGPT(GPT-5 / GPT-5 Codex / GPT-4.1)、Gemini(2.5 Pro / 2.5 Flash)。每当官方发布新模型,我们通常在 24 小时内上线。完整模型列表见 文档。 +
+
+
+ +
+
+
// ready to start
+

5 分钟,拿到你第一个 sk-puro-* key

+

注册送 $5 测试积分 · 绑定你的第一个订阅即可开始。

+
+ 免费注册 → + 查看文档 +
+
+
+ + + + diff --git a/docs/design-drafts/v2/Register.html b/docs/design-drafts/v2/Register.html new file mode 100644 index 00000000..36c204b1 --- /dev/null +++ b/docs/design-drafts/v2/Register.html @@ -0,0 +1,386 @@ + + + + + +注册 — PURO AI + + + + + + + + +
+
+ +
+
+
+ + + + + + PURO AI + + +
+
// 5 分钟开始用
+

+ N 个订阅 + + 1 个 key +

+
+ +
+ 省去切换账号的繁琐, + 省去为多个高昂订阅重复买单。 + PURO(纯粹)—— 让 AI 调用回归本质。 +
+ +
+
// 下一步
+
+
1
+
创建账户 · 邮箱 + 密码,或用 LinuxDO OAuth
+
+
+
2
+
绑定订阅 · OAuth 接入你现有的 Claude Pro / ChatGPT Plus
+
+
+
3
+
生成 key · 拿到 sk-puro-…,换掉 SDK 的 base_url
+
+
+
+ +
+ Claude· + ChatGPT· + Codex· + Gemini + | + ai.puro.im · operational +
+
+ +
+ ← 返回首页 + +
+

创建账户

+

注册即送 $5 测试积分

+ +
+ +
+ + + + + + + + + + + + +
+
+ +
+ +
+ + + + + + + + +
+
+ +
+
+ // strength + +
+
+ +
+ +
+ + + + + + + + + + + + +
+
+
+ + + + + +
OR
+ + + +
+ +$5 + 完成注册即送 $5 测试积分 —— 够你跑几万次 Claude 请求。 +
+ +
+ 已有账户?登录 +
+
+
+
+ + + + diff --git a/docs/design-drafts/v2/puro.css b/docs/design-drafts/v2/puro.css new file mode 100644 index 00000000..96c11980 --- /dev/null +++ b/docs/design-drafts/v2/puro.css @@ -0,0 +1,726 @@ +/* ========================================================================== + PURO AI — Design System + Shared tokens + primitive styles used across every page. + -------------------------------------------------------------------------- + Usage: + ========================================================================== */ + +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + /* Surfaces */ + --bg-0: #0a0e1a; /* page */ + --bg-1: #0f172a; /* raised */ + --bg-2: #111827; /* card alt */ + --bg-code: #020617; /* code canvas */ + + /* Borders */ + --border: #1e293b; + --border-2: #334155; + --border-3: #475569; + + /* Text */ + --text-0: #f8fafc; /* primary */ + --text-1: #cbd5e1; /* body */ + --text-2: #94a3b8; /* muted */ + --text-3: #64748b; /* hint */ + + /* Accents */ + --cyan: #22d3ee; + --cyan-2: #67e8f9; + --cyan-dim: #0891b2; + --purple: #a855f7; + --amber: #fbbf24; + --green: #34d399; + --red: #f87171; + --orange: #fb923c; + + /* Provider brand dots */ + --p-claude: #d97757; + --p-gpt: #10a37f; + --p-gemini: #4285f4; + --p-codex: #f0a030; + + /* Radius */ + --r-sm: 6px; + --r-md: 8px; + --r-lg: 12px; + --r-xl: 16px; + + /* Shadow */ + --shadow-lg: 0 30px 60px -30px rgba(0,0,0,0.6); + --shadow-xl: 0 40px 80px -40px rgba(0,0,0,0.8); + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; +} + +html, body { + background: var(--bg-0); + color: var(--text-0); + font-family: var(--font-sans); + font-feature-settings: "cv11", "ss01", "ss03"; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + line-height: 1.5; +} + +body { overflow-x: hidden; } + +a { color: inherit; text-decoration: none; } +button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } + +/* scrollbar — subtle */ +::-webkit-scrollbar { width: 10px; height: 10px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 6px; } +::-webkit-scrollbar-thumb:hover { background: var(--border-3); } + +.mono { font-family: var(--font-mono); } + +/* ========================================================================== + BACKGROUND EFFECTS + ========================================================================== */ +.bg-glow { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + overflow: hidden; +} +.bg-glow::before, +.bg-glow::after { + content: ""; + position: absolute; + width: 900px; + height: 900px; + border-radius: 50%; + filter: blur(120px); + opacity: 0.35; +} +.bg-glow::before { + background: radial-gradient(circle, #22d3ee 0%, transparent 60%); + top: -300px; + left: -200px; +} +.bg-glow::after { + background: radial-gradient(circle, #a855f7 0%, transparent 60%); + top: 200px; + right: -300px; + opacity: 0.25; +} +.bg-glow.soft::before, .bg-glow.soft::after { opacity: 0.15; } + +.grain { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 1; + opacity: 0.4; + mix-blend-mode: overlay; + background-image: url("data:image/svg+xml;utf8,"); +} + +.container { + max-width: 1100px; + margin: 0 auto; + padding: 0 32px; + position: relative; + z-index: 2; +} +.container-wide { max-width: 1280px; } +.container-narrow { max-width: 860px; } + +/* ========================================================================== + NAV + ========================================================================== */ +.nav { + position: sticky; + top: 0; + z-index: 50; + backdrop-filter: blur(16px); + background: rgba(10, 14, 26, 0.72); + border-bottom: 1px solid var(--border); +} +.nav-inner { + display: flex; + align-items: center; + height: 64px; + gap: 48px; +} +.brand { + display: flex; + align-items: center; + gap: 10px; + font-weight: 700; + font-size: 15px; + letter-spacing: 0.02em; +} +.hex { + width: 22px; + height: 22px; + color: var(--cyan); +} +.nav-links { + display: flex; + gap: 28px; + font-size: 14px; + color: var(--text-2); +} +.nav-links a { transition: color .15s; } +.nav-links a:hover, .nav-links a.active { color: var(--text-0); } +.nav-links .disabled { color: var(--text-3); cursor: not-allowed; display: inline-flex; align-items: center; gap: 6px; } +.nav-links .disabled::after { + content: "即将推出"; + font-size: 10px; + padding: 2px 6px; + border: 1px solid var(--border-2); + border-radius: 4px; + color: var(--text-3); +} +.nav-cta { + margin-left: auto; + display: flex; + gap: 10px; + align-items: center; +} + +/* ========================================================================== + BUTTONS + ========================================================================== */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 14px; + font-size: 13px; + font-weight: 500; + border-radius: var(--r-md); + transition: all .15s; + white-space: nowrap; + border: 1px solid transparent; +} +.btn-primary { + background: var(--cyan); + color: #042f2e; + font-weight: 600; +} +.btn-primary:hover { background: var(--cyan-2); } +.btn-primary:active { transform: translateY(1px); } +.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; } + +.btn-ghost { + border-color: var(--border-2); + color: var(--text-1); +} +.btn-ghost:hover { border-color: var(--border-3); color: var(--text-0); background: rgba(255,255,255,0.02); } + +.btn-subtle { + background: rgba(255,255,255,0.04); + color: var(--text-1); + border-color: transparent; +} +.btn-subtle:hover { background: rgba(255,255,255,0.08); color: var(--text-0); } + +.btn-danger { + background: rgba(248, 113, 113, 0.1); + color: var(--red); + border-color: rgba(248, 113, 113, 0.25); +} +.btn-danger:hover { background: rgba(248, 113, 113, 0.15); border-color: rgba(248, 113, 113, 0.4); } + +.btn-lg { padding: 12px 20px; font-size: 14px; } +.btn-sm { padding: 5px 10px; font-size: 12px; } +.btn-icon { padding: 7px; aspect-ratio: 1; } + +.btn .spinner { + width: 14px; height: 14px; + border: 2px solid rgba(0,0,0,0.2); + border-top-color: currentColor; + border-radius: 50%; + animation: spin .7s linear infinite; + display: none; +} +.btn.loading .spinner { display: inline-block; } +.btn.loading .label { opacity: 0.5; } +@keyframes spin { to { transform: rotate(360deg); } } + +/* ========================================================================== + BADGES / PILLS / CHIPS + ========================================================================== */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 100px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.02em; + background: rgba(34, 211, 238, 0.1); + color: var(--cyan); +} +.badge.amber { background: rgba(251, 191, 36, 0.12); color: var(--amber); } +.badge.purple { background: rgba(168, 85, 247, 0.12); color: var(--purple); } +.badge.green { background: rgba(52, 211, 153, 0.12); color: var(--green); } +.badge.red { background: rgba(248, 113, 113, 0.12); color: var(--red); } +.badge.muted { background: rgba(255, 255, 255, 0.04); color: var(--text-2); border: 1px solid var(--border); } + +.pill { + display: inline-block; + padding: 2px 8px; + border-radius: var(--r-sm); + background: rgba(255,255,255,0.04); + border: 1px solid var(--border); + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-0); +} + +.chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 100px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid var(--border); + font-size: 12px; + color: var(--text-1); + font-family: var(--font-mono); +} +.chip .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); } +.chip.claude .dot { background: var(--p-claude); } +.chip.gpt .dot { background: var(--p-gpt); } +.chip.gemini .dot { background: var(--p-gemini); } +.chip.codex .dot { background: var(--p-codex); } + +.dot-sep { width: 4px; height: 4px; border-radius: 50%; background: var(--text-3); display: inline-block; } + +/* status chip (tiny dot absolute-positioned) */ +.status-chip { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--green); + box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.15); + display: inline-block; +} +.status-chip.dim { background: var(--text-3); box-shadow: none; } +.status-chip.amber { background: var(--amber); box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.15); } +.status-chip.red { background: var(--red); box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.15); } + +/* ========================================================================== + CARDS / SURFACES + ========================================================================== */ +.card { + background: rgba(15, 23, 42, 0.6); + border: 1px solid var(--border); + border-radius: var(--r-lg); + padding: 24px; +} +.card-raised { + background: var(--bg-1); + border: 1px solid var(--border); + border-radius: var(--r-lg); +} +.card-interactive { + transition: all .2s; + cursor: pointer; +} +.card-interactive:hover { + border-color: var(--border-2); + background: rgba(15, 23, 42, 0.85); + transform: translateY(-2px); +} + +.divider { height: 1px; background: var(--border); margin: 24px 0; border: 0; } +.divider-dashed { border: 0; border-top: 1px dashed var(--border); margin: 20px 0; } + +/* ========================================================================== + FORMS + ========================================================================== */ +.field { margin-bottom: 18px; } +.field-label { + display: block; + font-size: 12px; + font-weight: 500; + color: var(--text-1); + margin-bottom: 8px; +} +.field-hint { + font-size: 12px; + color: var(--text-3); + margin-top: 6px; +} +.field-error { + font-size: 12px; + color: var(--red); + margin-top: 6px; +} + +.input-wrap { position: relative; } +.input-wrap .icon { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: var(--text-3); + pointer-events: none; + display: inline-flex; +} +.input { + width: 100%; + height: 42px; + padding: 0 14px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid var(--border-2); + border-radius: var(--r-md); + color: var(--text-0); + font-size: 14px; + font-family: inherit; + outline: none; + transition: all .15s; +} +.input.with-icon { padding-left: 40px; } +.input::placeholder { color: var(--text-3); } +.input:hover { border-color: var(--border-3); } +.input:focus { + border-color: var(--cyan); + box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.12); + background: rgba(15, 23, 42, 0.9); +} +.input.ok { border-color: rgba(52, 211, 153, 0.4); } +.input.ok:focus { box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.12); } +.input.error { border-color: var(--red); } +.input.error:focus { box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.12); } + +textarea.input { height: auto; padding: 12px 14px; resize: vertical; line-height: 1.5; } + +select.input { + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 14px center; + padding-right: 36px; +} + +/* checkbox */ +.check { + display: inline-flex; + align-items: center; + gap: 10px; + cursor: pointer; + user-select: none; + font-size: 13px; + color: var(--text-1); +} +.check input { display: none; } +.check .box { + width: 16px; height: 16px; + border: 1px solid var(--border-2); + border-radius: 4px; + background: var(--bg-1); + display: inline-flex; + align-items: center; + justify-content: center; + transition: all .15s; + flex-shrink: 0; +} +.check input:checked + .box { + background: var(--cyan); + border-color: var(--cyan); +} +.check input:checked + .box::after { + content: "✓"; + color: #042f2e; + font-size: 11px; + font-weight: 700; +} + +/* ========================================================================== + SECTION HEADINGS + ========================================================================== */ +.section-kicker { + font-family: var(--font-mono); + font-size: 12px; + color: var(--cyan); + letter-spacing: 0.15em; + text-transform: uppercase; + margin-bottom: 12px; +} +.section-title { + font-size: clamp(28px, 3.5vw, 40px); + font-weight: 700; + letter-spacing: -0.02em; + line-height: 1.15; + margin-bottom: 16px; +} +.section-sub { + color: var(--text-2); + font-size: 16px; + line-height: 1.6; +} + +/* ========================================================================== + TABLES + ========================================================================== */ +.tbl { + width: 100%; + font-size: 13px; + border-collapse: collapse; +} +.tbl th { + text-align: left; + color: var(--text-3); + font-weight: 500; + padding: 12px 14px; + border-bottom: 1px solid var(--border); + text-transform: uppercase; + font-size: 10px; + letter-spacing: 0.1em; +} +.tbl td { + padding: 14px; + border-bottom: 1px solid rgba(30, 41, 59, 0.5); + color: var(--text-1); +} +.tbl tr:last-child td { border-bottom: none; } +.tbl tr:hover td { background: rgba(15, 23, 42, 0.4); } +.tbl td.mono, .tbl th.mono { font-family: var(--font-mono); } + +/* ========================================================================== + CODE BLOCKS + ========================================================================== */ +.code-frame { + background: var(--bg-code); + border: 1px solid var(--border); + border-radius: var(--r-lg); + overflow: hidden; + box-shadow: var(--shadow-lg); +} +.code-head { + display: flex; + align-items: center; + height: 40px; + padding: 0 16px; + border-bottom: 1px solid var(--border); + background: rgba(15, 23, 42, 0.8); + gap: 10px; +} +.traffic { + display: flex; + gap: 6px; +} +.traffic span { + width: 10px; + height: 10px; + border-radius: 50%; + background: #475569; +} +.code-body { + padding: 22px 26px; + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.75; + color: var(--text-1); + overflow-x: auto; +} +.code-body .line { display: flex; gap: 20px; } +.ln { color: var(--text-3); user-select: none; min-width: 16px; text-align: right; opacity: 0.5; } + +/* syntax */ +.kw { color: #c084fc; } +.str { color: #86efac; } +.num { color: #fbbf24; } +.com { color: #64748b; font-style: italic; } +.fn { color: #22d3ee; } +.prop{ color: #f0abfc; } +.var-v { color: #f8fafc; } +.flag{ color: #fb923c; } +.bash-prompt { color: var(--cyan); user-select: none; } + +/* ========================================================================== + PROVIDER-BRAND HELPERS + ========================================================================== */ +.provider { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--font-mono); + font-size: 12px; +} +.provider .dot { width: 6px; height: 6px; border-radius: 50%; } +.provider.claude .dot { background: var(--p-claude); } +.provider.gpt .dot { background: var(--p-gpt); } +.provider.gemini .dot { background: var(--p-gemini); } +.provider.codex .dot { background: var(--p-codex); } + +/* ========================================================================== + UTILITIES + ========================================================================== */ +.stack-xs { display: flex; flex-direction: column; gap: 8px; } +.stack-sm { display: flex; flex-direction: column; gap: 12px; } +.stack-md { display: flex; flex-direction: column; gap: 20px; } +.stack-lg { display: flex; flex-direction: column; gap: 32px; } + +.row { display: flex; align-items: center; gap: 12px; } +.row-sm { gap: 8px; } +.row-lg { gap: 20px; } +.row-between { justify-content: space-between; } +.row-center { justify-content: center; } +.row-wrap { flex-wrap: wrap; } + +.flex-1 { flex: 1; } +.ml-auto { margin-left: auto; } +.mt-auto { margin-top: auto; } + +.text-0 { color: var(--text-0); } +.text-1 { color: var(--text-1); } +.text-2 { color: var(--text-2); } +.text-3 { color: var(--text-3); } +.text-cyan { color: var(--cyan); } +.text-purple { color: var(--purple); } +.text-amber { color: var(--amber); } +.text-green { color: var(--green); } +.text-red { color: var(--red); } + +.text-xs { font-size: 11px; } +.text-sm { font-size: 13px; } +.text-md { font-size: 14px; } +.text-lg { font-size: 16px; } +.text-xl { font-size: 20px; } +.text-2xl { font-size: 28px; } +.text-3xl { font-size: 36px; } + +.fw-400 { font-weight: 400; } +.fw-500 { font-weight: 500; } +.fw-600 { font-weight: 600; } +.fw-700 { font-weight: 700; } +.fw-800 { font-weight: 800; } + +.tabular { font-variant-numeric: tabular-nums; } +.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* ========================================================================== + APP SHELL (for dashboard-style pages) + ========================================================================== */ +.app-shell { + display: grid; + grid-template-columns: 240px 1fr; + min-height: 100vh; + position: relative; + z-index: 2; +} +.app-side { + border-right: 1px solid var(--border); + background: rgba(2, 6, 23, 0.6); + padding: 20px 14px; + display: flex; + flex-direction: column; + gap: 28px; + position: sticky; + top: 0; + height: 100vh; + overflow-y: auto; +} +.app-side .brand { padding: 6px 10px 14px; } +.side-group { display: flex; flex-direction: column; gap: 2px; } +.side-label { + font-size: 10px; + color: var(--text-3); + text-transform: uppercase; + letter-spacing: 0.12em; + padding: 0 10px 8px; + font-family: var(--font-mono); +} +.side-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: var(--r-sm); + font-size: 13px; + color: var(--text-2); + cursor: pointer; + transition: all .12s; +} +.side-item:hover { color: var(--text-0); background: rgba(255,255,255,0.03); } +.side-item.active { background: rgba(34, 211, 238, 0.08); color: var(--cyan); } +.side-item .ico { + width: 16px; height: 16px; opacity: 0.8; + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.side-item .count { + margin-left: auto; + font-size: 11px; + color: var(--text-3); + font-family: var(--font-mono); +} +.side-item.active .count { color: var(--cyan); } + +.app-main { + min-width: 0; /* allow grid children to shrink */ + display: flex; + flex-direction: column; +} +.app-topbar { + height: 60px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 32px; + gap: 16px; + position: sticky; + top: 0; + z-index: 10; + background: rgba(10, 14, 26, 0.75); + backdrop-filter: blur(12px); +} +.app-topbar h1 { + font-size: 18px; + font-weight: 600; + letter-spacing: -0.01em; +} +.app-content { + padding: 32px; + flex: 1; +} + +/* user avatar pill */ +.avatar { + width: 28px; height: 28px; + border-radius: 50%; + background: linear-gradient(135deg, #22d3ee, #a855f7); + display: inline-flex; + align-items: center; + justify-content: center; + color: #042f2e; + font-weight: 700; + font-size: 12px; + flex-shrink: 0; +} + +/* ========================================================================== + KBD + ========================================================================== */ +kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + color: var(--text-1); + background: var(--bg-1); + border: 1px solid var(--border-2); + border-bottom-width: 2px; + border-radius: 4px; + line-height: 1; +} diff --git a/docs/design-drafts/v2/uploads/pasted-1776589344748-0.png b/docs/design-drafts/v2/uploads/pasted-1776589344748-0.png new file mode 100644 index 00000000..2699b0a0 Binary files /dev/null and b/docs/design-drafts/v2/uploads/pasted-1776589344748-0.png differ diff --git a/docs/design-drafts/v2/uploads/pasted-1776589408607-0.png b/docs/design-drafts/v2/uploads/pasted-1776589408607-0.png new file mode 100644 index 00000000..f415cd53 Binary files /dev/null and b/docs/design-drafts/v2/uploads/pasted-1776589408607-0.png differ diff --git a/docs/superpowers/plans/2026-04-19-puro-ai-landing-auth.md b/docs/superpowers/plans/2026-04-19-puro-ai-landing-auth.md new file mode 100644 index 00000000..265d2a14 --- /dev/null +++ b/docs/superpowers/plans/2026-04-19-puro-ai-landing-auth.md @@ -0,0 +1,1746 @@ +# PURO AI · Landing + Auth + Docs Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 在 `ai.puro.im` 上线 PURO AI 品牌化的 Landing、Login、Register、Docs 四个页面,落地 `puro.css` 作为全站新设计系统,保持所有后台页面和现有 Auth 逻辑不变。 + +**Architecture:** 复用 Claude Design 产出的 `docs/design-drafts/v2/` 静态 HTML,翻译成 Vue 3 SFC 组件,CSS 通过全局引入 `puro.css`(含 tokens + primitives),路由改造让 `/` 根据登录态动态展示 Landing 或 Dashboard。所有 hardcoded 中文通过 vue-i18n 的 `zh.ts` 集中管理。 + +**Tech Stack:** Vue 3.4+ (Composition API, TypeScript), Vue Router 4, vue-i18n, Pinia, Tailwind CSS(保留 legacy primary 色板不动),Vite 5, pnpm 10, vitest(router 用)。 + +**Spec:** `docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md`(第 3/4/4.5/6 节为本计划权威依据) + +**Branch:** `feat/design-landing-auth`(已创建 · 当前 HEAD `3a16b3ec`) + +--- + +## 文件结构 + +``` +frontend/ +├── index.html ← MODIFY 加 Google Fonts preconnect + link +├── src/ +│ ├── assets/ +│ │ └── puro.css ← NEW 复制 docs/design-drafts/v2/puro.css +│ ├── main.ts ← MODIFY import './assets/puro.css' +│ ├── router/ +│ │ └── index.ts ← MODIFY `/` auth-aware + `/docs` public +│ ├── views/ +│ │ ├── landing/ +│ │ │ └── LandingView.vue ← NEW 6 sections, dashboard mockup 静态 +│ │ ├── docs/ +│ │ │ └── DocsView.vue ← NEW 6 subsections, curl/codex/claude-code 示例 +│ │ └── auth/ +│ │ ├── LoginView.vue ← MODIFY 套新 narrative slot + 小幅改文案 +│ │ └── RegisterView.vue ← MODIFY 同上 +│ ├── components/ +│ │ └── layout/ +│ │ └── AuthLayout.vue ← MODIFY 加 `narrative` 命名 slot(条件渲染) +│ └── i18n/ +│ └── locales/ +│ └── zh.ts ← MODIFY 新增 `landing.*` / `docs.*` / 扩 `auth.*` +└── tailwind.config.js ← MODIFY extend color 加 puro.* 家族(给 Vue 用) +``` + +**不动的文件**:所有 `views/admin/**`、`views/user/**`、`views/setup/**`、`App.vue`、`AppHeader/AppLayout/AppSidebar.vue`、backend 全部。 + +**设计决策**: +- `puro.css` 里已有的 primitives class(`.btn-primary`, `.card`, `.input` 等)直接用 class 引用,**不抽 Vue 组件**(减少 scope 膨胀) +- Tailwind 现有 `primary`(teal #14b8a6)**不改**——admin 页还用。新加 `puro.cyan` / `puro.purple` / `puro.amber` 等命名空间,新页面用 `text-puro-cyan` 类 +- i18n 只补 `zh.ts`(本期默认中文);`en.ts` 键留空或复用 `zh` 值 + +--- + +## Task 1: 引入 puro.css 设计系统 + Google Fonts + +**Files:** +- Create: `frontend/src/assets/puro.css` +- Modify: `frontend/src/main.ts:7` +- Modify: `frontend/index.html` + +- [ ] **Step 1: 复制 puro.css 到 assets** + +```bash +cp /Users/mini/Work/dev/sub2api/docs/design-drafts/v2/puro.css \ + /Users/mini/Work/dev/sub2api/frontend/src/assets/puro.css +``` + +- [ ] **Step 2: 在 main.ts 里 import(紧接 style.css)** + +Modify `frontend/src/main.ts`,把第 7 行: + +```ts +import './style.css' +``` + +改为: + +```ts +import './style.css' +import './assets/puro.css' +``` + +- [ ] **Step 3: 在 index.html `` 里加 Google Fonts preconnect + link** + +Modify `frontend/index.html`,在 `` 里(`` 之前)插入: + +```html +<link rel="preconnect" href="https://fonts.googleapis.com"> +<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> +<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> +``` + +- [ ] **Step 4: 启动 dev server 验证 CSS 变量可用** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +Expected: 启动成功(端口默认 5173),无 console error。 + +浏览器打开 http://localhost:5173,DevTools Console 执行: +```js +getComputedStyle(document.documentElement).getPropertyValue('--cyan') +``` +Expected: `" #22d3ee"` + +**停掉 dev server**(保留终端)。 + +- [ ] **Step 5: 扩展 tailwind.config.js 加 puro 色板** + +Modify `frontend/tailwind.config.js`,在 `theme.extend.colors` 里(`dark` 键后)加: + +```js + // PURO AI 设计系统色板(给新页面 Landing/Auth/Docs 用,不影响 admin) + puro: { + cyan: '#22d3ee', + 'cyan-2': '#67e8f9', + purple: '#a855f7', + amber: '#fbbf24', + green: '#34d399', + red: '#f87171', + // 平台品牌点色 + claude: '#d97757', + gpt: '#10a37f', + gemini: '#4285f4', + codex: '#f0a030' + } +``` + +- [ ] **Step 6: 验证 Tailwind class 生效** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +DevTools Console: +```js +// 临时在页面里插入 div +document.body.insertAdjacentHTML('afterbegin', '<div class="bg-puro-cyan" id="test-puro" style="padding:20px">test</div>') +// 读取实际背景色 +getComputedStyle(document.getElementById('test-puro')).backgroundColor +``` +Expected: `"rgb(34, 211, 238)"` + +删除测试元素,停掉 dev server。 + +- [ ] **Step 7: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/assets/puro.css frontend/src/main.ts frontend/index.html frontend/tailwind.config.js +git commit -m "$(cat <<'EOF' +feat(design): scaffold PURO AI design system + +- Add puro.css (tokens + primitives) as global stylesheet +- Load Inter + JetBrains Mono via Google Fonts +- Extend tailwind.config with puro.* color namespace (no conflict with legacy primary/accent palettes) + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> +EOF +)" +``` + +--- + +## Task 2: LandingView — 骨架 + Nav + Hero + +**Files:** +- Create: `frontend/src/views/landing/LandingView.vue` + +- [ ] **Step 1: 建 LandingView.vue 骨架** + +Create `frontend/src/views/landing/LandingView.vue`: + +```vue +<template> + <div class="puro-page"> + <div class="bg-glow"></div> + <div class="grain"></div> + + <!-- NAV --> + <nav class="nav"> + <div class="container nav-inner"> + <a href="/" class="brand"> + <span class="hex">⬢</span> + <span>PURO AI</span> + </a> + <div class="nav-links"> + <a href="#features">产品</a> + <a href="/docs">文档</a> + </div> + <div class="nav-cta"> + <router-link to="/login" class="btn btn-ghost">登录</router-link> + <router-link to="/register" class="btn btn-primary">免费试用 →</router-link> + </div> + </div> + </nav> + + <!-- HERO --> + <section class="hero container"> + <div class="hero-eyebrow"> + <span class="pill">ChatGPT Plus · Claude Pro · Codex · Gemini</span> + </div> + <h1 class="hero-title"> + 你的 AI 订阅,<br> + <span class="text-puro-cyan">已经付过钱了。</span> + </h1> + <p class="hero-sub"> + Claude Pro · ChatGPT Plus · Codex · Gemini 订阅<br> + 聚合成统一 API,零改动接入 OpenAI / Anthropic SDK + </p> + <div class="hero-cta"> + <router-link to="/login" class="btn btn-primary btn-lg">登录 →</router-link> + <a href="mailto:admin@puro.im" class="btn btn-ghost btn-lg">联系咨询</a> + </div> + <div class="hero-micro"> + 已验证可用 Codex CLI · Claude Code · curl · 服务器出口新加坡 + </div> + </section> + </div> +</template> + +<script setup lang="ts"> +// LandingView — public marketing landing page for PURO AI +// Rendered at `/` when user is unauthenticated (see router/index.ts) +</script> + +<style scoped> +.puro-page { + min-height: 100vh; + background: var(--bg-0); + color: var(--text-0); + font-family: var(--font-sans); + position: relative; + overflow-x: hidden; +} + +.hero { + text-align: center; + padding: 100px 24px 80px; + position: relative; + z-index: 2; +} +.hero-eyebrow { margin-bottom: 24px; } +.hero-title { + font-size: clamp(36px, 5.5vw, 64px); + font-weight: 800; + line-height: 1.1; + letter-spacing: -0.03em; + margin-bottom: 20px; +} +.hero-sub { + font-size: 18px; + color: var(--text-2); + line-height: 1.6; + max-width: 640px; + margin: 0 auto 36px; +} +.hero-cta { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 20px; +} +.hero-micro { + font-size: 13px; + color: var(--text-3); + font-family: var(--font-mono); +} +</style> +``` + +- [ ] **Step 2: 临时加 /landing-preview 路由方便预览** + +Modify `frontend/src/router/index.ts`,在 `// ==================== Public Routes ====================` 注释后(靠近 `/home` 定义那里)插入: + +```ts + { + path: '/landing-preview', + name: 'LandingPreview', + component: () => import('@/views/landing/LandingView.vue'), + meta: { + requiresAuth: false, + title: 'PURO AI' + } + }, +``` + +- [ ] **Step 3: 启动 dev server 预览** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +浏览器开 http://localhost:5173/landing-preview + +Expected: +- 暗黑底 +- "⬢ PURO AI" + "登录" + "免费试用 →" 顶栏 +- 中央 "你的 AI 订阅,已经付过钱了。"("已经付过钱了" 是 cyan 色) +- "登录 →" + "联系咨询" 两个按钮 +- 底部 cyan/purple radial 光晕 + +**停掉 dev server**。 + +- [ ] **Step 4: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/landing/LandingView.vue frontend/src/router/index.ts +git commit -m "feat(landing): LandingView scaffold with Nav + Hero + +Temporary route /landing-preview added for dev iteration. Will flip / to +this view once all 6 sections are in place. + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 3: LandingView — ② 模型墙 + ③ 三大特性 + +**Files:** +- Modify: `frontend/src/views/landing/LandingView.vue` + +- [ ] **Step 1: 在 `<section class="hero">` 结束标签之后、script 之前,插入 ② + ③** + +```vue + <!-- ② 模型墙 --> + <section class="block container" id="models"> + <div class="section-header"> + <div class="section-kicker">支持的 AI 平台</div> + <h2 class="section-title">通过 OAuth 直接复用你的订阅</h2> + <p class="section-sub">无需申请官方 API key,也无需切换账号</p> + </div> + <div class="model-wall"> + <div class="model-card"> + <div class="model-dot" style="background: var(--p-claude)"></div> + <div class="model-name">Claude Pro / Max</div> + <div class="model-meta">Anthropic OAuth</div> + </div> + <div class="model-card"> + <div class="model-dot" style="background: var(--p-gpt)"></div> + <div class="model-name">ChatGPT Plus / Pro</div> + <div class="model-meta">OpenAI OAuth</div> + </div> + <div class="model-card"> + <div class="model-dot" style="background: var(--p-codex)"></div> + <div class="model-name">Codex CLI</div> + <div class="model-meta">OpenAI OAuth</div> + </div> + <div class="model-card"> + <div class="model-dot" style="background: var(--p-gemini)"></div> + <div class="model-name">Gemini Code Assist</div> + <div class="model-meta">Google OAuth</div> + </div> + <div class="model-card is-muted"> + <div class="model-dot" style="background: var(--text-3)"></div> + <div class="model-name">更多</div> + <div class="model-meta">规划中</div> + </div> + </div> + </section> + + <!-- ③ 三特性 --> + <section class="block container" id="features"> + <div class="section-header"> + <div class="section-kicker">核心特性</div> + <h2 class="section-title">一套 key,三件武器</h2> + </div> + <div class="features"> + <div class="feature card"> + <div class="feature-icon">⚡</div> + <h3>一个 key 接所有模型</h3> + <p>不再为每个 provider 申请 API key、配置 base_url。统一 <code class="mono">sk-</code> 走 Claude / GPT / Gemini,按 model 自动路由到对应账号池。</p> + </div> + <div class="feature card"> + <div class="feature-icon">🔄</div> + <h3>账号池高可用</h3> + <p>支持多账号自动调度与 failover。某个上游触发限流 / 冷却时,流量切到下一个健康账号,token 刷新全自动。</p> + </div> + <div class="feature card"> + <div class="feature-icon">📊</div> + <h3>用量看板</h3> + <p>每条请求的 tokens、费用、上游账号、延迟全可视化。模型分布饼图 + 趋势曲线 + Top 排行。</p> + </div> + </div> + </section> +``` + +- [ ] **Step 2: 在 `<style scoped>` 里追加样式** + +```css +.container { + max-width: 1120px; + margin: 0 auto; + padding: 0 24px; + position: relative; + z-index: 2; +} +.block { padding: 80px 24px; } +.section-header { text-align: center; margin-bottom: 40px; } +.section-kicker { + font-size: 12px; + font-weight: 600; + color: var(--cyan); + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 12px; + font-family: var(--font-mono); +} +.section-title { + font-size: clamp(28px, 3.5vw, 40px); + font-weight: 700; + letter-spacing: -0.02em; + margin-bottom: 12px; +} +.section-sub { color: var(--text-2); font-size: 15px; } + +/* model wall */ +.model-wall { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; +} +.model-card { + padding: 20px; + border: 1px solid var(--border); + border-radius: var(--r-lg); + background: var(--bg-1); + display: flex; + align-items: center; + gap: 12px; +} +.model-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } +.model-name { font-weight: 600; font-size: 14px; } +.model-meta { font-size: 11px; color: var(--text-3); font-family: var(--font-mono); margin-top: 2px; } +.model-card.is-muted { opacity: 0.5; } +.model-card.is-muted .model-name { color: var(--text-2); } + +/* features */ +.features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 16px; +} +.feature { + padding: 28px 24px; + border: 1px solid var(--border); + border-radius: var(--r-lg); + background: var(--bg-1); +} +.feature-icon { font-size: 28px; margin-bottom: 14px; } +.feature h3 { font-size: 18px; font-weight: 700; margin-bottom: 10px; } +.feature p { color: var(--text-2); font-size: 14px; line-height: 1.6; } +.feature code { color: var(--cyan); font-size: 13px; } +``` + +- [ ] **Step 3: 验证** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +浏览器开 http://localhost:5173/landing-preview + +Expected: +- Hero 下方出现"支持的 AI 平台"section,5 张模型卡片横排 +- 再下方"一套 key,三件武器"section,3 张特性卡片 + +停掉 dev server。 + +- [ ] **Step 4: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/landing/LandingView.vue +git commit -m "feat(landing): add Models wall + Features sections + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 4: LandingView — ④ Code Demo + ⑤ Dashboard mockup + +**Files:** +- Modify: `frontend/src/views/landing/LandingView.vue` + +- [ ] **Step 1: 在 features section 之后插入 Code Demo + Dashboard mockup** + +```vue + <!-- ④ Code Demo --> + <section class="block container" id="code"> + <div class="section-header"> + <div class="section-kicker">快速接入</div> + <h2 class="section-title">把 base_url 一改,就能用</h2> + <p class="section-sub">兼容 OpenAI / Anthropic / Gemini SDK,<span class="text-puro-cyan">零代码改动</span></p> + </div> + <div class="code-demo"> + <div class="code-block"> + <div class="code-title mono">~/.codex/config.toml</div> + <pre class="mono"><code><span class="cm">[model_providers.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> + <div class="code-block"> + <div class="code-title mono">curl</div> + <pre class="mono"><code><span class="cm">$</span> curl https://ai.puro.im/responses \ + -H <span class="str">"Authorization: Bearer sk-xxx"</span> \ + -d <span class="str">'{"model":"gpt-5.4","input":"hello"}'</span></code></pre> + </div> + </div> + <div class="code-foot">支持 OpenAI Responses API · Anthropic Messages API · Gemini generateContent · 流式 SSE & WebSocket</div> + </section> + + <!-- ⑤ Dashboard mockup --> + <section class="block container" id="dashboard"> + <div class="section-header"> + <div class="section-kicker">用量透明</div> + <h2 class="section-title">每条请求都看得见</h2> + <p class="section-sub">不像第三方 API 池子那种"扣了多少不告诉你"——扣哪个账号、跑哪个模型、用了多少 tokens、上游响应几秒,一目了然。</p> + </div> + <div class="dash-mock"> + <div class="dash-header"> + <span class="dash-title">Dashboard · 预览</span> + <div class="dash-dots"><span></span><span></span><span></span></div> + </div> + <div class="dash-body"> + <div class="stat-row"> + <div class="stat"><div class="stat-label">今日请求</div><div class="stat-value">1,842</div><div class="stat-delta">+12.3%</div></div> + <div class="stat"><div class="stat-label">输入 Tokens</div><div class="stat-value">2.1M</div><div class="stat-delta">+8.1%</div></div> + <div class="stat"><div class="stat-label">输出 Tokens</div><div class="stat-value">485K</div><div class="stat-delta">+15.6%</div></div> + <div class="stat"><div class="stat-label">今日费用</div><div class="stat-value">$1.23</div><div class="stat-delta down">-4.2%</div></div> + </div> + <div class="chart-card"> + <div class="chart-title">近 30 天用量趋势</div> + <svg viewBox="0 0 600 120" class="chart-svg"> + <polyline points="0,90 40,80 80,70 120,65 160,60 200,50 240,55 280,45 320,40 360,35 400,30 440,25 480,20 520,25 560,15 600,10" + fill="none" stroke="#22d3ee" stroke-width="2"/> + <polyline points="0,100 40,95 80,90 120,88 160,85 200,82 240,80 280,78 320,75 360,73 400,70 440,68 480,65 520,63 560,60 600,58" + fill="none" stroke="#a855f7" stroke-width="2" stroke-dasharray="4 4"/> + </svg> + </div> + <table class="log-table mono"> + <thead> + <tr><th>时间</th><th>模型</th><th>上游</th><th>状态</th><th>用量</th></tr> + </thead> + <tbody> + <tr><td>12:34:07</td><td>gpt-5.4</td><td><span class="provider gpt"><span class="dot"></span>ChatGPT #1</span></td><td class="status-200">200</td><td>2,341</td></tr> + <tr><td>12:34:02</td><td>claude-opus-4-7</td><td><span class="provider claude"><span class="dot"></span>Claude #2</span></td><td class="status-200">200</td><td>5,102</td></tr> + <tr><td>12:33:58</td><td>gemini-2.5-pro</td><td><span class="provider gemini"><span class="dot"></span>Gemini #1</span></td><td class="status-200">200</td><td>843</td></tr> + <tr><td>12:33:41</td><td>gpt-5.4</td><td><span class="provider gpt"><span class="dot"></span>ChatGPT #2</span></td><td class="status-429">429</td><td>—</td></tr> + </tbody> + </table> + </div> + </div> + </section> +``` + +- [ ] **Step 2: 追加样式到 `<style scoped>`** + +```css +/* code demo */ +.code-demo { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} +@media (max-width: 820px) { .code-demo { grid-template-columns: 1fr; } } +.code-block { + border: 1px solid var(--border); + border-radius: var(--r-lg); + background: var(--bg-code); + overflow: hidden; +} +.code-title { + padding: 10px 16px; + background: var(--bg-1); + font-size: 11px; + color: var(--text-3); + border-bottom: 1px solid var(--border); +} +.code-block pre { + padding: 16px; + font-size: 13px; + line-height: 1.6; + color: var(--text-1); + overflow-x: auto; +} +.cm { color: var(--text-3); } +.str { color: var(--cyan); } +.kw { color: var(--amber); } +.code-foot { + margin-top: 20px; + text-align: center; + font-size: 12px; + color: var(--text-3); + font-family: var(--font-mono); +} + +/* dash mockup */ +.dash-mock { + border: 1px solid var(--border); + border-radius: var(--r-xl); + background: var(--bg-1); + overflow: hidden; + box-shadow: 0 40px 80px -40px rgba(0,0,0,0.8); +} +.dash-header { + padding: 12px 16px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; +} +.dash-title { font-size: 12px; color: var(--text-2); font-family: var(--font-mono); } +.dash-dots { display: flex; gap: 6px; } +.dash-dots span { width: 10px; height: 10px; border-radius: 50%; background: var(--border-2); } +.dash-body { padding: 20px; } +.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; } +@media (max-width: 720px) { .stat-row { grid-template-columns: repeat(2, 1fr); } } +.stat { + padding: 14px; + border: 1px solid var(--border); + border-radius: var(--r-md); + background: rgba(15,23,42,0.6); +} +.stat-label { font-size: 10px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; } +.stat-value { font-size: 22px; font-weight: 700; letter-spacing: -0.02em; font-family: var(--font-mono); } +.stat-delta { font-size: 11px; color: var(--green, #34d399); margin-top: 4px; font-family: var(--font-mono); } +.stat-delta.down { color: var(--red, #f87171); } + +.chart-card { + border: 1px solid var(--border); + border-radius: var(--r-md); + background: rgba(15,23,42,0.6); + padding: 16px; + margin-bottom: 20px; +} +.chart-title { font-size: 12px; color: var(--text-2); margin-bottom: 12px; } +.chart-svg { width: 100%; height: 120px; display: block; } + +.log-table { width: 100%; font-size: 12px; border-collapse: collapse; } +.log-table th { + text-align: left; color: var(--text-3); font-weight: 500; + padding: 10px 12px; border-bottom: 1px solid var(--border); + text-transform: uppercase; font-size: 10px; letter-spacing: 0.08em; +} +.log-table td { padding: 10px 12px; border-bottom: 1px solid rgba(30,41,59,0.5); color: var(--text-1); } +.log-table tr:last-child td { border-bottom: none; } +.log-table .status-200 { color: var(--green, #34d399); } +.log-table .status-429 { color: var(--amber); } +.log-table .provider { display: inline-flex; align-items: center; gap: 6px; } +.log-table .provider .dot { width: 6px; height: 6px; border-radius: 50%; } +.provider.claude .dot { background: var(--p-claude); } +.provider.gpt .dot { background: var(--p-gpt); } +.provider.gemini .dot { background: var(--p-gemini); } +``` + +- [ ] **Step 3: 验证** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/landing-preview + +Expected: +- Code Demo 两个代码块并排(移动端堆叠) +- Dashboard mockup:4 格 stats + 折线 SVG + 4 行 log table + +停掉 dev server。 + +- [ ] **Step 4: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/landing/LandingView.vue +git commit -m "feat(landing): add Code Demo + Dashboard mockup sections + +Dashboard mockup uses static data (stats, SVG chart, log table) — no +backend dependency. Visually demonstrates the product's usage transparency. + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 5: LandingView — ⑥ Footer + 顶部 Nav 样式 + +**Files:** +- Modify: `frontend/src/views/landing/LandingView.vue` + +- [ ] **Step 1: 在 `⑤ Dashboard` section 后、`</template>` 前加 Footer** + +```vue + <!-- ⑥ Footer --> + <footer class="puro-footer"> + <div class="container footer-grid"> + <div class="footer-brand"> + <div class="brand"><span class="hex">⬢</span><span>PURO AI</span></div> + <p class="footer-tagline">Self-hosted on puro.im</p> + <p class="footer-meta">© 2026 puro.im · MIT License<br>fork of Wei-Shaw/sub2api</p> + </div> + <div class="footer-col"> + <div class="footer-col-title">产品</div> + <a href="/docs">文档</a> + <a href="https://git.puro.im/purovps/sub2api/releases" target="_blank" rel="noopener">更新日志</a> + </div> + <div class="footer-col"> + <div class="footer-col-title">资源</div> + <a href="https://git.puro.im/purovps/sub2api" target="_blank" rel="noopener">GitHub</a> + <a href="/docs#codex">Codex 配置示例</a> + <a href="https://status.puro.im" target="_blank" rel="noopener">API 状态</a> + </div> + <div class="footer-col"> + <div class="footer-col-title">联系</div> + <a href="mailto:admin@puro.im">admin@puro.im</a> + <a href="https://git.puro.im" target="_blank" rel="noopener">git.puro.im</a> + </div> + </div> + </footer> +``` + +- [ ] **Step 2: 追加 Nav 和 Footer 样式到 `<style scoped>`** + +```css +.nav { + position: sticky; + top: 0; + z-index: 10; + background: rgba(10,14,26,0.75); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); +} +.nav-inner { + display: flex; + align-items: center; + gap: 28px; + padding: 16px 24px; + max-width: 1120px; + margin: 0 auto; +} +.brand { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 16px; +} +.brand .hex { + color: var(--cyan); + font-size: 20px; +} +.nav-links { + display: flex; + gap: 20px; + font-size: 14px; + color: var(--text-2); +} +.nav-links a:hover { color: var(--text-0); } +.nav-cta { + display: flex; + gap: 10px; + margin-left: auto; +} +@media (max-width: 640px) { + .nav-links { display: none; } +} + +/* footer */ +.puro-footer { + margin-top: 80px; + padding: 60px 24px 40px; + border-top: 1px solid var(--border); +} +.footer-grid { + display: grid; + grid-template-columns: 2fr 1fr 1fr 1fr; + gap: 36px; +} +@media (max-width: 720px) { .footer-grid { grid-template-columns: 1fr 1fr; } } +.footer-brand .brand { margin-bottom: 12px; } +.footer-tagline { color: var(--text-2); font-size: 13px; margin-bottom: 8px; } +.footer-meta { color: var(--text-3); font-size: 12px; line-height: 1.7; } +.footer-col-title { + color: var(--text-0); + font-size: 13px; + font-weight: 600; + margin-bottom: 12px; +} +.footer-col a { + display: block; + color: var(--text-2); + font-size: 13px; + padding: 4px 0; +} +.footer-col a:hover { color: var(--cyan); } + +/* pill */ +.pill { + display: inline-block; + padding: 6px 14px; + border: 1px solid var(--border-2); + border-radius: 999px; + font-size: 12px; + color: var(--text-2); + background: rgba(15,23,42,0.6); +} +``` + +- [ ] **Step 3: 验证整页 Landing** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/landing-preview + +Expected 整页滚动可见顺序: +1. Nav(sticky,滚动时背景半透明 blur) +2. Hero +3. 模型墙 +4. 三特性 +5. Code Demo +6. Dashboard mockup +7. Footer 4 列 + +移动端(窗口缩到 <640px):Nav 菜单折叠、footer 变 2 列、stats 变 2 列、code demo 堆叠。 + +停掉 dev server。 + +- [ ] **Step 4: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/landing/LandingView.vue +git commit -m "feat(landing): Footer + Nav styles (sticky blur + responsive) + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 6: 路由 `/` auth-aware + +**Files:** +- Modify: `frontend/src/router/index.ts` +- Test: `frontend/src/router/__tests__/guards.spec.ts`(若有现成的 / guard 测试,补上 Landing 分支;没有就新加) + +- [ ] **Step 1: 查看当前 `/` 的 redirect 逻辑** + +Run: +```bash +grep -n "path: '/'" /Users/mini/Work/dev/sub2api/frontend/src/router/index.ts +grep -n "path: '/landing-preview'" /Users/mini/Work/dev/sub2api/frontend/src/router/index.ts +``` +Expected: `path: '/'` 在一行(redirect 到 `/home`),`/landing-preview` 在另一行。 + +- [ ] **Step 2: 把 `/landing-preview` 改名成 `/` 的实际挂载** + +Modify `frontend/src/router/index.ts`: + +1. 删除 `/landing-preview` 那 10 行临时 route +2. 把原来的: +```ts + { + path: '/', + redirect: '/home' + }, +``` +替换为: +```ts + { + path: '/', + name: 'Landing', + component: () => import('@/views/landing/LandingView.vue'), + meta: { + requiresAuth: false, + title: 'PURO AI — 你的 AI 订阅,已经付过钱了', + redirectIfAuth: '/dashboard' // authenticated users bounce to dashboard + } + }, +``` + +- [ ] **Step 3: 在 guard 里加 redirectIfAuth 分支** + +Navigation guard 在 `frontend/src/router/index.ts:524` 的 `router.beforeEach((to, _from, next) => { ... })`。auth store 暴露 `isAuthenticated` computed(定义见 `stores/auth.ts:31`)。 + +在 `authStore.checkAuth()` 那行(line ~532)后、所有其它逻辑之前,插入: + +```ts + // Auth-aware redirects for public pages (e.g. '/' shows Landing to anons, bounces authed users) + const redirectIfAuth = to.meta.redirectIfAuth as string | undefined + if (redirectIfAuth && authStore.isAuthenticated) { + return next(redirectIfAuth) + } +``` + +- [ ] **Step 4: TypeScript meta 字段声明** + +Modify `frontend/src/router/meta.d.ts`(若存在)或 `router/index.ts` 顶部 module augmentation,为 `RouteMeta` 增加 `redirectIfAuth?: string`: + +```ts +import 'vue-router' +declare module 'vue-router' { + interface RouteMeta { + redirectIfAuth?: string + } +} +``` + +(若 `meta.d.ts` 已有 module augmentation,追加字段即可。) + +- [ ] **Step 5: 验证未登录 → Landing、登录后 → Dashboard** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +场景 1(匿名):DevTools → Application → Clear storage → 全清 localStorage/sessionStorage +- http://localhost:5173/ → **LandingView** 呈现 +- 点 Hero "登录 →" → 跳 /login + +场景 2(登录态): +- 在 /login 输 `admin@puro.im` / `fJni5YDmEY242owh` 登录(见 spec §5 "当前状态 snapshot") +- 登录后自动跳到 /dashboard(既有行为) +- 手动再访问 http://localhost:5173/ → 被 guard 跳到 /dashboard(新加的 redirectIfAuth 生效) + +停掉 dev server。 + +- [ ] **Step 6: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/router/index.ts +git commit -m "feat(router): mount Landing at / with auth-aware redirect + +Anonymous visitors see PURO AI landing page. Authenticated users are +redirected to /dashboard via meta.redirectIfAuth guard extension. + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 7: AuthLayout 改造(加 narrative slot) + +**Files:** +- Modify: `frontend/src/components/layout/AuthLayout.vue` + +- [ ] **Step 1: 读现有 AuthLayout 的 slot 结构** + +Run: +```bash +wc -l /Users/mini/Work/dev/sub2api/frontend/src/components/layout/AuthLayout.vue +``` +Expected: ~90-120 行。读它确认 slot 有默认 + `#footer`。 + +- [ ] **Step 2: 改 AuthLayout.vue template 为可选左右分栏** + +把整个 `<template>` 换成: + +```vue +<template> + <div class="auth-shell" :class="{ 'auth-shell-split': hasNarrative }"> + <div class="bg-glow soft"></div> + + <!-- LEFT: Narrative (split mode only, hidden on mobile) --> + <aside v-if="hasNarrative" class="auth-narrative"> + <slot name="narrative"></slot> + </aside> + + <!-- RIGHT: Form --> + <main class="auth-main"> + <div class="auth-main-inner"> + <div class="mb-8 text-center" v-if="!hasNarrative"> + <!-- 兼容旧调用:无 narrative slot 时退回居中品牌头 --> + <template v-if="settingsLoaded"> + <div class="mb-4 inline-flex h-14 w-14 items-center justify-center overflow-hidden rounded-2xl"> + <img :src="siteLogo || '/logo.png'" alt="Logo" class="h-full w-full object-contain" /> + </div> + <h1 class="text-2xl font-bold">{{ siteName }}</h1> + </template> + </div> + + <slot /> + + <div class="mt-6 text-center text-sm"> + <slot name="footer" /> + </div> + </div> + </main> + </div> +</template> +``` + +- [ ] **Step 3: 在 `<script setup>` 里加 `useSlots` 检测 narrative** + +在 `<script setup lang="ts">` 顶部追加: + +```ts +import { useSlots } from 'vue' +const slots = useSlots() +const hasNarrative = computed(() => !!slots.narrative) +``` + +(`computed` 如果还没 import 就加上) + +- [ ] **Step 4: 在 `<style scoped>` 里加 split layout 样式** + +把现有 `<style scoped>`(或在文件末新增)补入: + +```css +.auth-shell { + min-height: 100vh; + background: var(--bg-0); + color: var(--text-0); + font-family: var(--font-sans); + display: flex; + position: relative; + overflow: hidden; +} +.auth-shell-split { display: grid; grid-template-columns: 1fr 1fr; } +@media (max-width: 900px) { + .auth-shell-split { grid-template-columns: 1fr; } + .auth-narrative { display: none; } +} +.auth-narrative { + position: relative; + padding: 48px 56px; + background: linear-gradient(135deg, #0a0e1a 0%, #1e1b4b 100%); + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + justify-content: space-between; +} +.auth-main { + display: flex; + align-items: center; + justify-content: center; + padding: 40px 24px; + position: relative; + z-index: 2; +} +.auth-main-inner { width: 100%; max-width: 420px; } +``` + +- [ ] **Step 5: 验证无 narrative 场景(旧行为兼容)** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/forgot-password(这个页面用 AuthLayout 但不会传 narrative) + +Expected: 居中品牌卡片,看起来像以前。 + +停掉 dev server。 + +- [ ] **Step 6: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/components/layout/AuthLayout.vue +git commit -m "refactor(auth): AuthLayout supports optional narrative slot (split mode) + +Backward-compatible: without narrative slot, falls back to centered brand +card. With narrative slot, renders 50/50 split on desktop, collapses to +single column on mobile. + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 8: LoginView 套 narrative slot + 文案微调 + +**Files:** +- Modify: `frontend/src/views/auth/LoginView.vue` + +- [ ] **Step 1: 在 `<AuthLayout>` 开标签后插入 `<template #narrative>`** + +找到 `<AuthLayout>`(第 2 行附近)后,紧跟着加: + +```vue + <template #narrative> + <div class="auth-narrative-inner"> + <div class="brand"><span class="hex">⬢</span><span>PURO AI</span></div> + <div class="auth-narrative-hero"> + <div class="auth-narrative-headline"> + <span class="num-5">5</span> 个订阅<br> + → <span class="num-1">1</span> 个 key + </div> + <p class="auth-narrative-sub"> + 省去切换账号的繁琐,<br> + 省去为多个高昂订阅重复买单。<br> + <span class="auth-narrative-tagline">PURO(纯粹)—— 让 AI 调用回归本质。</span> + </p> + </div> + <div class="auth-narrative-foot mono">Claude · ChatGPT · Codex · Gemini</div> + </div> + </template> +``` + +- [ ] **Step 2: 调登录表单的 heading 文案** + +把现有 `<h2>{{ t('auth.welcomeBack') }}</h2>` 对应的那行 heading 改为: + +```vue + <h2 class="text-2xl font-bold text-gray-900 dark:text-white">登录</h2> + <p class="mt-2 text-sm text-gray-500 dark:text-dark-400">用你的 PURO AI 账户继续</p> +``` + +(直接 hardcode 中文,避开改 i18n;Task 11 会统一补进 zh.ts。) + +- [ ] **Step 3: 在 LoginView 的 `<style scoped>` 里(如果没有就加一个)追加 narrative 内部样式** + +```vue +<style scoped> +.auth-narrative-inner { + position: relative; + z-index: 2; + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; + color: var(--text-0); +} +.brand { + display: flex; + align-items: center; + gap: 8px; + font-weight: 700; + font-size: 18px; +} +.brand .hex { color: var(--cyan); font-size: 24px; } + +.auth-narrative-hero { } +.auth-narrative-headline { + font-size: clamp(40px, 5vw, 64px); + font-weight: 800; + line-height: 1.05; + letter-spacing: -0.03em; + margin-bottom: 24px; +} +.auth-narrative-headline .num-5 { color: var(--amber); } +.auth-narrative-headline .num-1 { color: var(--cyan); } +.auth-narrative-sub { + font-size: 15px; + line-height: 1.7; + color: var(--text-1); +} +.auth-narrative-tagline { + display: block; + margin-top: 12px; + font-size: 12px; + color: var(--text-3); +} +.auth-narrative-foot { + font-size: 12px; + color: var(--text-3); +} +</style> +``` + +- [ ] **Step 4: 验证** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/login + +Expected(桌面): +- 左侧:PURO AI 品牌 + "5 个订阅 → 1 个 key"(5 橙色,1 青色)+ 三句排比副文 + 底部小字 +- 右侧:原有登录表单(邮箱/密码/CTA/OAuth),heading 改成"登录" +- 移动端(缩到 <900px):narrative 隐藏,表单居中 + +登录流程一定要验: +- 用已有账号登录一次,看是否正常跳 Dashboard(OAuth/Turnstile/2FA 逻辑未动过,应都保留) + +停掉 dev server。 + +- [ ] **Step 5: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/auth/LoginView.vue +git commit -m "feat(auth): LoginView with PURO narrative split layout + +- Left: ⬢ PURO AI brand + '5→1' headline + value props +- Right: unchanged form (OAuth / Turnstile / 2FA all preserved) +- Heading copy updated to Chinese (i18n key consolidation in later task) + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 9: RegisterView 套同样的 narrative + +**Files:** +- Modify: `frontend/src/views/auth/RegisterView.vue` + +- [ ] **Step 1: 读 RegisterView 当前 `<AuthLayout>` 块** + +Run: +```bash +grep -n "AuthLayout\|welcomeBack\|createAccount" /Users/mini/Work/dev/sub2api/frontend/src/views/auth/RegisterView.vue | head -10 +``` + +- [ ] **Step 2: 在 `<AuthLayout>` 开标签后插入同款 narrative slot** + +(与 LoginView 完全相同的 `<template #narrative>` 块——**复制粘贴** LoginView 的 narrative template。) + +- [ ] **Step 3: 调注册表单 heading** + +把现有 `<h2>` heading 那行替换为: + +```vue + <h2 class="text-2xl font-bold text-gray-900 dark:text-white">创建账户</h2> + <p class="mt-2 text-sm text-gray-500 dark:text-dark-400">5 分钟开始用 PURO AI</p> +``` + +- [ ] **Step 4: 把 LoginView 的 narrative `<style scoped>` 复制到 RegisterView** + +(整段 `.auth-narrative-inner / .brand / .auth-narrative-*` 的 CSS 复制一份) + +- [ ] **Step 5: 验证** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/register + +Expected: 同 Login 的 split layout,右侧表单是注册表单(邮箱 + 密码 + 确认密码 + Turnstile + CTA),heading "创建账户"。 + +停掉 dev server。 + +- [ ] **Step 6: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/auth/RegisterView.vue +git commit -m "feat(auth): RegisterView with PURO narrative split layout + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 10: DocsView + +**Files:** +- Create: `frontend/src/views/docs/DocsView.vue` +- Modify: `frontend/src/router/index.ts` + +- [ ] **Step 1: 建 DocsView.vue** + +Create `frontend/src/views/docs/DocsView.vue`: + +```vue +<template> + <div class="puro-page"> + <div class="bg-glow soft"></div> + + <nav class="nav"> + <div class="container nav-inner"> + <router-link to="/" class="brand"><span class="hex">⬢</span><span>PURO AI</span></router-link> + <div class="nav-links"> + <router-link to="/">首页</router-link> + <a href="#codex">Codex</a> + <a href="#claude-code">Claude Code</a> + <a href="#curl">curl</a> + </div> + <div class="nav-cta"> + <router-link to="/login" class="btn btn-primary">登录 →</router-link> + </div> + </div> + </nav> + + <section class="docs-hero container"> + <h1>快速接入 PURO AI</h1> + <p class="subtitle">三步走:拿 key → 配 base_url → 发请求</p> + </section> + + <div class="container docs-body"> + <section id="get-key" class="docs-section"> + <h2>1. 获取 API key</h2> + <p>当前 PURO AI 不开放自助注册付费。联系管理员获取:</p> + <div class="callout"> + <a href="mailto:admin@puro.im">admin@puro.im</a> + </div> + <p class="note">未来通过 iShare 入口开放订阅购买。</p> + </section> + + <section id="codex" class="docs-section"> + <h2>2. Codex CLI 接入</h2> + <p>修改 <code class="mono">~/.codex/config.toml</code>:</p> + <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> + <p>然后 <code class="mono">~/.codex/auth.json</code>:</p> + <pre class="mono"><code>{ + <span class="str">"OPENAI_API_KEY"</span>: <span class="str">"sk-xxxxxxxxxxxxxxxx"</span> +}</code></pre> + <p>验证:</p> + <pre class="mono"><code><span class="cm">$</span> codex exec --sandbox read-only <span class="str">"say hi"</span></code></pre> + </section> + + <section id="claude-code" class="docs-section"> + <h2>3. Claude Code 接入</h2> + <p>修改 <code class="mono">~/.claude/settings.json</code>:</p> + <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> + <p class="note">Claude Code 通过 <code class="mono">/v1/messages</code> endpoint 调用 Anthropic 兼容 API。</p> + </section> + + <section id="curl" class="docs-section"> + <h2>4. curl 直连测试</h2> + <p>OpenAI Responses API:</p> + <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> + <p>Anthropic Messages API:</p> + <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> + </section> + + <section id="models" class="docs-section"> + <h2>5. 支持的模型</h2> + <ul class="model-list"> + <li><code class="mono">gpt-5.4</code> · OpenAI(via ChatGPT Plus / Codex OAuth)</li> + <li><code class="mono">gpt-5.4-codex</code> · OpenAI Codex 专用</li> + <li><code class="mono">claude-opus-4-7</code> · Anthropic(via Claude Pro / Max OAuth)</li> + <li><code class="mono">claude-sonnet-4-6</code> · Anthropic</li> + <li><code class="mono">gemini-2.5-pro</code> · Google(via Code Assist OAuth)</li> + <li><code class="mono">gemini-2.5-flash</code> · Google</li> + </ul> + <p class="note">后端 pricing 表实时跟进 <code class="mono">model-price-repo</code>,完整清单登录后在 <router-link to="/dashboard">控制台</router-link> 查看。</p> + </section> + + <section id="feedback" class="docs-section"> + <h2>6. 问题反馈</h2> + <p>遇到问题或希望补接某个平台:</p> + <div class="callout"> + <a href="mailto:admin@puro.im">admin@puro.im</a> + </div> + </section> + </div> + </div> +</template> + +<script setup lang="ts"></script> + +<style scoped> +.puro-page { + min-height: 100vh; + background: var(--bg-0); + color: var(--text-0); + font-family: var(--font-sans); + position: relative; +} +.container { max-width: 860px; margin: 0 auto; padding: 0 24px; position: relative; z-index: 2; } +.nav { + position: sticky; + top: 0; + z-index: 10; + background: rgba(10,14,26,0.75); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--border); +} +.nav-inner { display: flex; align-items: center; gap: 28px; padding: 16px 24px; max-width: 1120px; margin: 0 auto; } +.brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 16px; } +.brand .hex { color: var(--cyan); font-size: 20px; } +.nav-links { display: flex; gap: 20px; font-size: 14px; color: var(--text-2); } +.nav-links a:hover { color: var(--text-0); } +.nav-cta { margin-left: auto; } + +.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; } +.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; +} +.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); } +.docs-section pre.mono { + background: var(--bg-code); + border: 1px solid var(--border); + border-radius: var(--r-md); + padding: 16px; + font-size: 13px; + line-height: 1.6; + color: var(--text-1); + overflow-x: auto; + margin: 12px 0; +} +.docs-section pre .str { color: var(--cyan); } +.docs-section pre .kw { color: var(--amber); } +.docs-section 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; } + +.model-list { list-style: none; padding: 0; } +.model-list li { padding: 8px 0; color: var(--text-1); font-size: 14px; border-bottom: 1px dashed var(--border); } +.model-list li:last-child { border-bottom: none; } +</style> +``` + +- [ ] **Step 2: 在 router/index.ts 注册 `/docs` public route** + +插入(在 `/home` 附近的 Public Routes 区): + +```ts + { + path: '/docs', + name: 'Docs', + component: () => import('@/views/docs/DocsView.vue'), + meta: { + requiresAuth: false, + title: 'PURO AI · 文档' + } + }, +``` + +- [ ] **Step 3: 验证** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/docs + +Expected: +- 顶部 sticky nav(⬢ PURO AI / 首页 / Codex / Claude Code / curl / 登录) +- Hero:"快速接入 PURO AI" +- 6 个 numbered sections +- 代码块深色高亮 + +点 Nav 里的 "Codex" / "Claude Code" / "curl" 锚点会跳到对应段。 + +停掉 dev server。 + +- [ ] **Step 4: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/views/docs/DocsView.vue frontend/src/router/index.ts +git commit -m "feat(docs): public DocsView with Codex/Claude Code/curl quickstart + +Public route /docs, no auth required. Six sections: get key, codex CLI +config, claude code settings, curl smoke test, supported models, feedback. + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 11: i18n 整理(Login/Register heading) + +**Files:** +- Modify: `frontend/src/i18n/locales/zh.ts` +- Modify: `frontend/src/views/auth/LoginView.vue` +- Modify: `frontend/src/views/auth/RegisterView.vue` + +**Scope**:本期**只把 Login/Register 的 heading 文案**纳入 i18n(Landing 和 Docs 整页内容量大、语言只中文,暂不 i18n 化,后续英文版再说)。 + +- [ ] **Step 1: zh.ts 里查看现有 auth namespace** + +Run: +```bash +grep -n "auth:" /Users/mini/Work/dev/sub2api/frontend/src/i18n/locales/zh.ts | head -3 +``` + +- [ ] **Step 2: 在 `auth` namespace 里加 PURO 专用 keys** + +Modify `zh.ts`,在 `auth: { ... }` 里面追加(注意保留现有键): + +```ts + // PURO AI redesign + puroLoginTitle: '登录', + puroLoginSub: '用你的 PURO AI 账户继续', + puroRegisterTitle: '创建账户', + puroRegisterSub: '5 分钟开始用 PURO AI', +``` + +- [ ] **Step 3: 把 LoginView / RegisterView 里 hardcoded 的两行 heading 换成 t() 调用** + +LoginView: +```vue +<h2 class="text-2xl font-bold text-gray-900 dark:text-white">{{ t('auth.puroLoginTitle') }}</h2> +<p class="mt-2 text-sm text-gray-500 dark:text-dark-400">{{ t('auth.puroLoginSub') }}</p> +``` + +RegisterView 同理用 `auth.puroRegisterTitle` / `auth.puroRegisterSub`。 + +- [ ] **Step 4: 验证 zh.ts 没语法错误** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run type-check 2>&1 | tail -5 +``` +Expected: 无 TypeScript 错误。 + +- [ ] **Step 5: dev server 确认 heading 显示正确** + +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run dev +``` + +http://localhost:5173/login → heading "登录" +http://localhost:5173/register → heading "创建账户" + +停掉 dev server。 + +- [ ] **Step 6: 提交** + +```bash +cd /Users/mini/Work/dev/sub2api +git add frontend/src/i18n/locales/zh.ts frontend/src/views/auth/LoginView.vue frontend/src/views/auth/RegisterView.vue +git commit -m "chore(i18n): consolidate PURO auth heading keys into zh.ts + +Landing and Docs text remain hardcoded Chinese this cycle (see spec §6 +Note 5). Only Login/Register heading strings are moved into i18n. + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 12: Build check + manual smoke test + +**Files:** +- 无代码改动 + +- [ ] **Step 1: TypeScript + Vue 类型检查** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run type-check 2>&1 | tail -20 +``` +Expected: No errors. + +- [ ] **Step 2: Lint check(如果有)** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run lint 2>&1 | tail -20 +``` +Expected: No errors, 或最多 warnings(可接受)。 + +- [ ] **Step 3: 产品构建** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run build 2>&1 | tail -20 +``` +Expected: `✓ built in Xs` · 产物写到 `../backend/internal/web/dist/`。 + +- [ ] **Step 4: 本地预览 prod bundle** + +Run: +```bash +cd /Users/mini/Work/dev/sub2api/frontend && pnpm run preview 2>&1 | head -5 +``` +Expected: preview server 启动(默认 4173)。 + +开浏览器做**验收清单走一遍**(对齐 spec §7): +- [ ] http://localhost:4173/ — Landing 6 section 全在 +- [ ] 窗口缩到 <640px — Nav 菜单隐藏、Footer 2 列、stats 2 列 +- [ ] http://localhost:4173/login — 左右分栏,narrative 左,表单右 +- [ ] 窗口缩到 <900px — narrative 隐藏,表单独占 +- [ ] http://localhost:4173/register — 同 login 布局 +- [ ] http://localhost:4173/docs — 6 sections,锚点跳转工作 +- [ ] http://localhost:4173/dashboard — 后台页面**外观不变**(重要!) +- [ ] http://localhost:4173/accounts(管理员)— 外观不变 + +停掉 preview。 + +- [ ] **Step 5: 记录验收结果在 spec checklist** + +Modify `docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md`,在第 7 节验收清单里,把跑通的项目 `[ ]` 改成 `[x]`。 + +- [ ] **Step 6: 提交验收清单更新** + +```bash +cd /Users/mini/Work/dev/sub2api +git add docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md +git commit -m "docs: check off local acceptance items [CI SKIP] + +Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>" +``` + +--- + +## Task 13: 合并到 main + CI 部署 + ai.puro.im 实机验证 + +**Files:** +- 无代码改动 + +- [ ] **Step 1: push 最新到 gitea** + +```bash +cd /Users/mini/Work/dev/sub2api && git push gitea feat/design-landing-auth +``` + +- [ ] **Step 2: 创建 PR** + +```bash +curl -s -u 'purovps:Qweewqzzx1' -X POST https://git.puro.im/api/v1/repos/purovps/sub2api/pulls \ + -H 'Content-Type: application/json' \ + -d '{ + "title": "feat: PURO AI landing + auth + docs redesign", + "body": "Implements docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md\n\nSee plan: docs/superpowers/plans/2026-04-19-puro-ai-landing-auth.md\n\nScope (path B):\n- Landing page at / (anon)\n- AuthLayout split mode\n- LoginView / RegisterView with PURO narrative\n- Public /docs page\n- puro.css design system\n\nNot touched: admin pages, backend, auth logic.", + "head": "feat/design-landing-auth", + "base": "main" + }' | head -c 300 +``` +Expected: JSON with `"number": N` = PR 号。 + +- [ ] **Step 3: 合并 PR(manual via Gitea UI 或 API)** + +Via Gitea UI:https://git.puro.im/purovps/sub2api/pulls/<N>/merge,点 Merge。 + +或 API(把 N 替换成上一步 PR 号): +```bash +curl -s -u 'purovps:Qweewqzzx1' -X POST https://git.puro.im/api/v1/repos/purovps/sub2api/pulls/<N>/merge \ + -H 'Content-Type: application/json' \ + -d '{"Do":"merge"}' +``` + +- [ ] **Step 4: 跟 CI 构建(预计 90-180 秒)** + +Run: +```bash +ssh vps "for i in \$(seq 1 20); do + running=\$(docker ps --filter 'name=^drone-' --format '{{.Names}} | {{.Image}}' | head -1) + if [ -z \"\$running\" ]; then echo '(build done)'; break; fi + echo \"\$running\" + sleep 10 +done +echo '--- container age ---' +docker ps --filter 'name=^sub2api$' --format '{{.Names}} {{.Status}}'" +``` + +Expected: 最后 `sub2api` 容器 status 显示 `Up (N) seconds` 的很新时间戳。 + +- [ ] **Step 5: 线上实机验证(对齐 spec §7 完整 checklist)** + +分别访问(用匿名浏览器 / 无痕窗口): + +``` +https://ai.puro.im/ → PURO AI Landing +https://ai.puro.im/docs → 精简文档 +https://ai.puro.im/login → split layout 登录 +https://ai.puro.im/register → split layout 注册 +``` + +登录后访问: +``` +https://ai.puro.im/ → 被 guard 跳到 /dashboard +https://ai.puro.im/dashboard → 后台外观不变 +``` + +发一个 AI 请求验证中转没坏: +```bash +curl -sS -X POST https://ai.puro.im/responses \ + -H 'Authorization: Bearer sk-d2132de2f0b4c1ab64ef7241a16d254cab483f1f8afd47ad4a89e39cf6e2345a' \ + -H 'Content-Type: application/json' \ + -d '{"model":"gpt-5.4","input":"say: redesign-live","stream":false}' \ + -w '\n-- HTTP %{http_code} / %{time_total}s --\n' | head -c 500 +``` +Expected: HTTP 200 + "redesign-live" 响应。 + +- [ ] **Step 6: 打 tag 标记里程碑** + +```bash +cd /Users/mini/Work/dev/sub2api +git checkout main && git pull gitea main +git tag -a v0.2.0-puro-redesign -m "feat: PURO AI landing + auth + docs redesign" +git push gitea v0.2.0-puro-redesign +``` + +--- + +## 收尾 checklist + +本计划执行完,spec §7 的 11 个验收项应全部打勾: + +- [ ] puro.css 全局引入 +- [ ] / (匿名) → Landing +- [ ] / (登录) → Dashboard +- [ ] Landing 6 section 齐 +- [ ] Dashboard mockup 纯静态 +- [ ] /login split 布局 +- [ ] /register split 布局 +- [ ] /docs 公开可访问含配置示例 +- [ ] OAuth/Turnstile/2FA 正常 +- [ ] 后台外观不变 +- [ ] CI 通过,ai.puro.im 加载正常 diff --git a/docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md b/docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md new file mode 100644 index 00000000..61e998c7 --- /dev/null +++ b/docs/superpowers/specs/2026-04-19-puro-ai-landing-auth-design.md @@ -0,0 +1,425 @@ +# PURO AI · Landing + Auth 重设计(v2) + +> 2026-04-19 · 分支 `feat/design-landing-auth` · **v2 更新:Claude Design 产出 10 页 + puro.css 后,定了路径 B 执行** + +本文档是「PURO AI 公开页面重设计」的设计 spec: +- **Stage 1(完成)**:信息架构 + 中文文案 + 风格方向 + 布局选型 +- **Stage 2(完成)**:在 claude.ai/design 出了 10 页视觉稿 + `puro.css` 设计系统,归档到 `docs/design-drafts/v2/` +- **Stage 3(本期范围)**:挑 **4 个页面** 落地 — Landing(精修版)、Login、Register、Docs(精简版)+ 设计 tokens 落地到 Tailwind +- **Stage 4**:merge → Drone CI → ai.puro.im 实机验证 + +## 本期范围决策(路径 B · 分层交付) + +### 本期做(feat/design-landing-auth) +1. **puro.css 落地为 Tailwind config + global styles**(`tailwind.config.ts` 扩展 color/radius/font;`puro.css` 挪成 `frontend/src/assets/puro.css` 全局引入) +2. **Landing 页**(路由 `/` 未登录态)· 6 段结构,**精修版文案**(见第 3 节) +3. **Login / Register 页** 套新左右分栏 + 保留现有 OAuth/Turnstile/2FA 逻辑 +4. **Docs 页精简版**(路由 `/docs`,公开访问)· 快速接入 + curl 示例 + 支持模型 + +### 二期做(另开 feat/design-dashboard 分支,不本期) +5. Dashboard 换皮(沿用 puro.css) +6. API Keys 管理页换皮 +7. Design System 页(给团队内部看的) + +### 永不做 / 远期再说 +- **Binding 页**:Claude Design 预设用户自己 BYO-Subscription 绑定,但 Sub2API 是 admin 统一管账号池,概念不符 +- **Pricing 页**:iShare 接管钱包/订阅后由 iShare 处理 +- 注册送 $5 / 充值阶梯赠送等"赠送经济"特性 + +--- + +## 1. 项目背景 + +| 项 | 值 | +|---|---| +| **公开品牌名** | **PURO AI** | +| **内部代码名** | sub2api(Wei-Shaw/sub2api fork,不改) | +| **域名** | https://ai.puro.im | +| **现状** | 登录后是 Vue 3 + Tailwind 后台;无公开首页;登录页用浅色 `AuthLayout` | +| **目标受众** | 个人开发者 / 小团队 — 已有 ChatGPT Plus / Claude Pro / Codex / Gemini 订阅,想程序化调用而不付 API 费率 | +| **核心叙事** | "你的 AI 订阅,已经付过钱了"——把已付订阅复用为 API | + +--- + +## 2. 风格方向 + +**暗黑科技**(Dark Tech)—— 对标 Linear / Vercel / Railway / Supabase / Cloudflare Workers。 + +### 配色(建议) +| 角色 | 色值 | 用途 | +|---|---|---| +| 主底 | `#0a0e1a` ~ `#0f172a` | 页面背景,slate-950 区间 | +| 卡片底 | `#0f172a` | 表单卡片、特性卡 | +| 边框 | `#334155` | 次要边框 | +| 主文 | `#f8fafc` | 标题 | +| 副文 | `#94a3b8` ~ `#cbd5e1` | 描述、菜单 | +| **主品牌色** | `#22d3ee`(cyan-400) | Logo、CTA、链接 | +| **辅品牌色** | `#a855f7`(purple-500) | 渐变叠加、装饰光晕 | +| 警示 | `#fbbf24`(amber-400) | "💡"标签、数字对比 | + +### 视觉语汇 +- 暗底上**圆形 radial gradient 光晕**(青/紫双色) +- 等宽字体(ui-monospace / SF Mono)用于 code demo +- 主体字体:sans-serif(Inter / SF Pro / 系统默认) +- 边框 1px 实线 / 关键分割用 dashed +- CTA 圆角 8px;卡片圆角 12px +- 不要拟物、不要软阴影、不要 Bootstrap 4 那种 gradient 按钮 + +### 排版氛围 +- 大量留白 +- 标题大、字距稍紧(letter-spacing -0.02em) +- 内容居中收敛(max-width ~1100px) + +--- + +## 3. Landing 页(路由 `/`,未登录态)· 精修版 + +### 3.1 信息架构(6 段,剔除 Pricing/FAQ/CTA banner) + +``` +NAV · ⬢ PURO AI · 产品 · 文档 · [登录][免费试用 →] +① HERO · 主标 + 副标 + CTA×2 + 微文案 +② 模型墙 · 4 个支持的 AI 平台 +③ 三特性 · ⚡ 一个 key 多模型 · 🔄 账号池高可用 · 📊 用量看板 +④ Code Demo · codex config 片段 + curl 示例 +⑤ Dashboard · 真实 mockup 预览(不用截图了,设计稿里是 HTML 渲染的) +⑥ Footer · 4 列(品牌 / 产品 / 资源 / 联系) +``` + +### 3.2 完整中文文案 + +#### NAV +- Logo: `⬢ PURO AI` +- 菜单: 产品 · 文档 +- 右侧: `[登录]`(边框)`[免费试用 →]`(cyan 实底;Nav 里保留注册入口,但 Hero CTA 不走这里) + +#### ① HERO +- **主标**: 你的 AI 订阅,**已经付过钱了。** +- **副标**: Claude Pro · ChatGPT Plus · Codex · Gemini 订阅<br>聚合成统一 API,零改动接入 OpenAI / Anthropic SDK +- **主 CTA**: `登录 →`(cyan 实底,已有账号用户直接进) +- **副 CTA**: `联系咨询`(边框,mailto:admin@puro.im 或未来跳 iShare 咨询页) +- **微文案**: 已验证可用 Codex CLI · Claude Code · curl · 服务器出口新加坡 + +#### ② 模型墙 +- **小标题**: 支持的 AI 平台 +- **副标**: 通过 OAuth 直接复用你的订阅,无需申请官方 API key +- **Logos**: + - ⚪ Claude Pro / Max + - 🟢 ChatGPT Plus / Pro + - 🟡 Codex CLI + - 🔵 Gemini Code Assist + - ⚫ 更多(规划中,灰显) + +#### ③ 三大特性 +| 图标 | 标题 | 描述 | +|---|---|---| +| ⚡ | 一个 key 接所有模型 | 不再为每个 provider 申请 API key、配置 base_url。统一 sk- 走 Claude / GPT / Gemini,按 model 自动路由到对应账号池。| +| 🔄 | 账号池高可用 | 支持多账号自动调度与 failover。某个上游触发限流 / 冷却时,流量切到下一个健康账号,token 刷新全自动。| +| 📊 | 用量看板 | 每条请求的 tokens、费用、上游账号、延迟全可视化。模型分布饼图 + 趋势曲线 + Top 排行。| + +#### ④ Code Demo +- **标题**: 把 base_url 一改,就能用 +- **副**: 兼容 OpenAI / Anthropic / Gemini SDK,**零代码改动** +- **代码块**: + ```toml + # Codex CLI + # ~/.codex/config.toml + [model_providers.OpenAI] + base_url = "https://ai.puro.im" + wire_api = "responses" + ``` + ```bash + # 或直接 curl + $ curl https://ai.puro.im/responses \ + -H "Authorization: Bearer sk-xxx" \ + -d '{"model":"gpt-5.4","input":"hello"}' + ``` +- **底注**: 支持 OpenAI Responses API · Anthropic Messages API · Gemini generateContent · 流式 SSE & WebSocket + +#### ⑤ Dashboard +- **标题**: 每条请求都看得见 +- **副**: 不像第三方 API 池子那种"扣了多少不告诉你"。你能看到每次调用:扣了哪个账号、跑了哪个模型、用了多少 tokens、花了多少钱、上游响应几秒。 +- **图**: 使用 Claude Design v2 产出的 **纯 HTML 渲染 mockup**(stats grid + chart 卡片 + log table with provider dots),见 `docs/design-drafts/v2/Landing.html` 里 `#dashboard` 段。本期 Vue 翻译时保持静态数据,不对接真实 API。 + +#### ⑥ Footer +| 列 | 内容 | +|---|---| +| 品牌 | ⬢ PURO AI<br>Self-hosted on puro.im<br>© 2026 puro.im · MIT License<br>fork of Wei-Shaw/sub2api | +| 产品 | 文档 · 更新日志 | +| 资源 | GitHub · API 状态 · Codex 配置示例 | +| 联系 | admin@puro.im · git.puro.im | + +--- + +## 4. Auth 页(登录 / 注册) + +### 4.1 布局选型:左右分栏 + +``` +┌─────────────────────────┬─────────────────────┐ +│ 左:品牌叙事区 │ 右:表单区 │ +│ - Logo │ - 标题(登录/注册) │ +│ - 主标语(5→1 对比) │ - 副标 │ +│ - 副文(双卖点排比) │ - email / password │ +│ - 装饰光晕 cyan/purple │ - CTA │ +│ - 底栏小字(支持平台) │ - 切换链接 │ +└─────────────────────────┴─────────────────────┘ +``` + +移动端:左侧叙事降级为顶部小 banner 或完全隐藏,单列表单。 + +### 4.2 左侧叙事文案 + +- **Logo**: `⬢ PURO AI` +- **主标语**: + > **5** 个订阅<br> + > → **1** 个 key + + 数字 `5` 用 amber/orange 强调;`1` 用主品牌色 cyan 强调。 +- **副文**(三句排比): + > 省去切换账号的繁琐,<br> + > 省去为多个高昂订阅重复买单。<br> + > <small style="color:#64748b">PURO(纯粹)—— 让 AI 调用回归本质。</small> +- **底栏小字**: `Claude · ChatGPT · Codex · Gemini` + +### 4.3 右侧表单 + +#### 登录页(`/login`) +- 标题: 登录 +- 副: 用你的 PURO AI 账户继续 +- 字段: + - 📧 邮箱(input, type=email, required) + - 🔒 密码(input, type=password, required, 带眼睛切换显示) +- 选项: + - 忘记密码?(router-link) + - Turnstile captcha(条件显示) +- CTA: `登录 →` +- 分隔: `或` +- OAuth 按钮(条件显示): + - 使用 LinuxDO 登录 + - 使用 OIDC 登录 +- 底部链接: 没有账户?**注册** + +#### 注册页(`/register`) +- 标题: 创建账户 +- 副: 5 分钟开始用 PURO AI +- 字段: + - 📧 邮箱 + - 🔒 密码 + - 🔒 确认密码 + - (可选)邮箱验证码(条件显示,配置 `email_verify_required` 时) +- Turnstile captcha(条件) +- CTA: `创建账户 →` +- 底部链接: 已有账户?**登录** + +#### 其他保留页(不重设计本期) +- `/forgot-password` +- `/reset-password` +- `/verify-email` +- OAuth 回调页 + +--- + +## 4.5 Docs 页(新增 · 本期做精简版) + +路由 `/docs`,公开访问(不需登录)。沿用 puro.css 设计系统。 + +### 结构 + +``` +NAV (复用) +── HERO · "快速接入 PURO AI"(简短) +── § 1 · 获取 API key(流程说明:联系 admin / iShare) +── § 2 · Codex CLI 接入(config.toml 示例) +── § 3 · Claude Code 接入(~/.claude/settings.json 例) +── § 4 · curl 测试(/responses + /v1/messages 两段) +── § 5 · 支持的模型(列表:gpt-5.4, claude-opus-4-7, gemini-2.5-pro 等) +── § 6 · 问题反馈 mailto:admin@puro.im +FOOTER (复用) +``` + +### 文案原则 +- 代码块用 JetBrains Mono,带语法高亮 +- 每段开头 1-2 句说明,然后直接上代码,不啰嗦 +- 所有 base_url 用真实值:`https://ai.puro.im` + +--- + +## 5. 给 claude.ai/design 的 brief(Stage 2 输入,**已执行完成 · 历史参考**) + +> ⚠️ **此节是喂给 Claude Design 的原始 brief**,Claude Design 已按此产出 10 个页面。Stage 2 之后**内容精修 + 范围剪裁**以第 3/4/4.5 节为准,本节仅作历史存档。 + +复制下方文字到 https://claude.ai/design: + +```` +我要做两个网页设计,请帮我生成高保真 HTML/React 视觉稿。 + +## 品牌 +名字:PURO AI(拉丁语「纯粹」) +Logo:六边形 ⬢ + 文字 +域名:ai.puro.im +定位:把多个 AI 订阅(Claude Pro / ChatGPT Plus / Codex / Gemini)聚合成统一 API +核心叙事:你的 AI 订阅,已经付过钱了 + +## 风格 +暗黑科技风,对标 Linear / Vercel / Railway。 +配色: +- 主底 #0a0e1a / #0f172a(slate-950 区间) +- 主品牌色 #22d3ee(cyan-400) +- 辅品牌色 #a855f7(purple-500) +- 强调色 #fbbf24(amber-400,仅用于数字对比) +- 主文 #f8fafc,副文 #94a3b8 ~ #cbd5e1 +- 卡片/表单底 #0f172a,边框 #334155 + +视觉元素: +- 暗底上 radial gradient 光晕(青/紫双色,60% 透明度,blur) +- 大量留白,max-width 1100px +- 圆角:CTA 8px,卡片 12px +- 字体:Inter 或 SF Pro(sans-serif),代码用 ui-monospace +- 不要拟物、不要软阴影、不要 gradient 按钮 + +## 页面 1:Landing(路由 /,未登录) +6 个 section + 顶部 nav,全部中文。 + +NAV +- 左:⬢ PURO AI +- 中:产品、文档(定价灰显) +- 右:[登录](边框)[免费试用 →](cyan 实底) + +① HERO(居中,垂直 padding 大) +主标:你的 AI 订阅,**已经付过钱了。** +("已经付过钱了" 用 cyan 高亮) +副标:Claude Pro · ChatGPT Plus · Codex · Gemini 订阅 +聚合成统一 API,零改动接入 OpenAI / Anthropic SDK +CTA:[立即开始 →][查看文档] +微文案(小灰字):无需信用卡 · 用你已有的订阅 · 5 分钟跑通 + +② 模型墙 +小标题:支持的 AI 平台 +副:通过 OAuth 直接复用你的订阅,无需申请官方 API key +横排 5 个 logo 卡片:Claude Pro/Max · ChatGPT Plus/Pro · Codex CLI · Gemini Code Assist · 更多(灰显) + +③ 三特性(3 列卡片) +卡片 1:⚡ 一个 key 接所有模型 / 不再为每个 provider 申请 API key、配置 base_url。统一 sk- 走 Claude / GPT / Gemini,按 model 自动路由到对应账号池。 +卡片 2:🔄 账号池高可用 / 多账号自动调度。某个 ChatGPT Plus 触发限流,自动 failover 到下一个。重启、刷新 token 全自动。 +卡片 3:📊 用量看板 / 每条请求的 tokens、费用、上游账号、延迟全可视化。模型分布饼图 + 趋势曲线 + Top 排行。 + +④ Code Demo +标题:把 base_url 一改,就能用 +副:兼容 OpenAI / Anthropic / Gemini SDK,零代码改动 +代码块(深色 terminal 配色,syntax highlight): +- 上方一段 toml(codex config) +- 下方一段 bash(curl 示例) +底注小字:支持 OpenAI Responses API · Anthropic Messages API · Gemini generateContent · 流式 SSE & WebSocket + +⑤ Dashboard +标题:每条请求都看得见 +副:不像第三方 API 池子那种"扣了多少不告诉你"。你能看到每次调用:扣了哪个账号、跑了哪个模型、用了多少 tokens、花了多少钱、上游响应几秒。 +图:dashboard 截图占位(深色饼图 + 折线图 + 表格) + +⑥ Footer +4 列: +- 品牌列:⬢ PURO AI / Self-hosted on puro.im / © 2026 puro.im · MIT / fork of Wei-Shaw/sub2api +- 产品列:文档 · 套餐(暂隐)· 更新日志 +- 资源列:GitHub · API 状态 · Codex 配置示例 +- 联系列:admin@puro.im · git.puro.im + +## 页面 2:登录页(路由 /login) +左右分栏,桌面端 50/50 分。 + +左侧(叙事区): +- 顶部 Logo:⬢ PURO AI +- 中部主标:5 个订阅 → 1 个 key + (5 用 amber #fbbf24 强调,1 用 cyan #22d3ee 强调,字号 36-48px,weight 800) +- 主标下副文(三句排比): + 省去切换账号的繁琐, + 省去为多个高昂订阅重复买单。 + PURO(纯粹)—— 让 AI 调用回归本质。 +- 底部小字:Claude · ChatGPT · Codex · Gemini +- 背景:linear-gradient(135deg, #0a0e1a, #1e1b4b) + 角落 radial gradient 光晕(cyan + purple) + +右侧(表单区): +- 标题:登录 +- 副:用你的 PURO AI 账户继续 +- 邮箱输入(带 📧 icon) +- 密码输入(带 🔒 icon + 眼睛切换显示) +- 行:忘记密码?(右对齐链接) +- 主 CTA:登录 →(cyan 实底) +- 分隔:—— 或 —— +- OAuth 按钮:使用 LinuxDO 登录(边框样式) +- 底部:没有账户?注册(链接) + +移动端:左侧叙事区收为顶部小 banner(只保留 Logo + 短主标),表单全宽。 + +## 页面 3:注册页(路由 /register) +和登录页同布局,右侧表单字段: +- 标题:创建账户 / 副:5 分钟开始用 PURO AI +- 邮箱、密码、确认密码 +- 主 CTA:创建账户 → +- 底部:已有账户?登录 + +请生成完整可预览的 HTML(含 inline CSS)或 React 组件。 +```` + +--- + +## 5.5 Stage 2 产出清单(已完成,参考用) + +`docs/design-drafts/v2/` 目录下: + +| 文件 | 用途 | 本期用 | +|---|---|---| +| `puro.css` | 设计系统(tokens + primitives .btn/.card/.input 等) | ✅ 全站落地 | +| `Landing.html` | 营销首页(7 段,我们只取 6 段) | ✅ 参考翻译 | +| `Login.html` | 登录页 | ✅ 参考翻译 | +| `Register.html` | 注册页 | ✅ 参考翻译 | +| `Docs.html` | 文档页(精简版参考) | ✅ 参考翻译 | +| `Dashboard.html` | 控制台首页 | ⏳ 二期 | +| `API Keys.html` | Key 管理 | ⏳ 二期 | +| `Design System.html` | 设计系统展示页 | ⏳ 二期(给团队看)| +| `Binding.html` | 订阅绑定 | ❌ 不做(架构不符)| +| `Pricing.html` | 定价 | ❌ 不做(iShare 管) | +| `HANDOFF.md` | 交付文档(含后端契约,部分不适用) | 参考但不全部实现 | + +--- + +## 6. Stage 3 实施约束(给未来的我看) + +技术栈(不改动): +- Vue 3.4+ Composition API + TypeScript +- Tailwind CSS(已配 dark mode、`primary-*` 色板) +- Vite 5 +- Vue Router 4 / Pinia / vue-i18n +- 现有组件库(`@/components/common`、`@/components/layout/AuthLayout`) + +实施要点: +1. **puro.css 落地**: + - CSS 变量(--bg-0 等)保留作为全局 tokens,挪到 `frontend/src/assets/puro.css` + - `main.ts` 里 `import './assets/puro.css'` + - 在 `tailwind.config.ts` 的 `theme.extend.colors` 里同步一份 cyan/purple/amber/provider 色值,方便 Vue 组件里用 Tailwind class + - primitives(.btn / .card / .input)可作为全局 class 直接用,也可以包 Vue 组件;**本期直接用 class,不抽组件** +2. **Landing 页是新页**:新建 `frontend/src/views/landing/HomeView.vue`;改 `router/index.ts`,未登录访问 `/` 显示 landing,已登录跳 `/dashboard` +3. **Auth 页改造**:改 `frontend/src/components/layout/AuthLayout.vue` 为左右分栏;改 `LoginView.vue` / `RegisterView.vue` 适配新 layout,**保留所有现有逻辑**(OAuth、Turnstile、2FA、表单校验) +4. **Docs 页是新页**:新建 `frontend/src/views/docs/DocsView.vue`,路由 `/docs`,public 可访问 +5. **i18n**:新文案进 `frontend/src/i18n/locales/zh.ts`;本期只补中文(默认语言),英文 key 留空 / 复用现有 +6. **Dashboard mockup in Landing**:直接按 Claude Design 的 HTML 翻成 Vue 静态标记(不对接真实数据,纯展示) +7. **不动的**:Setup Wizard / 后台所有页面 / API 层 / store + +--- + +## 7. 验收标准 + +本地 preview(http://127.0.0.1:4173)已过: + +- [x] `puro.css` 已引入为全局样式,`--cyan: #22d3ee` 等变量在 DevTools :root 可见 +- [x] 未登录访问 / → LandingView 呈现(route meta.redirectIfAuth=/dashboard 生效分支) +- [x] 已登录访问 / → guard 跳 /dashboard +- [x] Landing 6 个 section 内容全部呈现(Nav + Hero + Models + Features + Code Demo + Dashboard mockup + Footer),移动端可堆叠 +- [x] Landing ⑤ Dashboard mockup 为静态 HTML + 内嵌 SVG,无后端依赖 +- [x] /login 左右分栏布局,narrative "5→1" 文案 + 登录表单 / heading "登录" +- [x] /register 左右分栏,heading "创建账户" / "5 分钟开始用 PURO AI" +- [x] /docs 公开访问,含 curl / Codex config.toml / Claude Code settings.json 示例 +- [x] 所有现有 auth 功能代码未改动(OAuth section / Turnstile / 2FA modal / password toggle 均在原位) +- [x] 后台 /dashboard 等页面使用 style.css 的 .btn/.input/.card(puro.css 已 scoped 到 .puro-page,不污染) +- [x] `pnpm run typecheck` 0 error,`pnpm run build` 成功 +- [ ] CI 构建通过,部署后 ai.puro.im 加载正常(Task 13 完成) diff --git a/frontend/index.html b/frontend/index.html index 3180a5fb..c198afdc 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,6 +4,9 @@ <meta charset="UTF-8" /> <link rel="icon" type="image/png" href="/logo.png" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="preconnect" href="https://fonts.googleapis.com"> + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> <title>Sub2API - AI API Gateway diff --git a/frontend/src/assets/puro.css b/frontend/src/assets/puro.css new file mode 100644 index 00000000..3251383e --- /dev/null +++ b/frontend/src/assets/puro.css @@ -0,0 +1,729 @@ +/* ========================================================================== + PURO AI — Design System + Shared tokens + primitive styles used across every page. + -------------------------------------------------------------------------- + Usage: + ========================================================================== */ + +*, *::before, *::after { box-sizing: border-box; } +.puro-page, .puro-page *, .puro-page *::before, .puro-page *::after { margin: 0; padding: 0; } + + +:root { + /* Surfaces */ + --bg-0: #0a0e1a; /* page */ + --bg-1: #0f172a; /* raised */ + --bg-2: #111827; /* card alt */ + --bg-code: #020617; /* code canvas */ + + /* Borders */ + --border: #1e293b; + --border-2: #334155; + --border-3: #475569; + + /* Text */ + --text-0: #f8fafc; /* primary */ + --text-1: #cbd5e1; /* body */ + --text-2: #94a3b8; /* muted */ + --text-3: #64748b; /* hint */ + + /* Accents */ + --cyan: #22d3ee; + --cyan-2: #67e8f9; + --cyan-dim: #0891b2; + --purple: #a855f7; + --amber: #fbbf24; + --green: #34d399; + --red: #f87171; + --orange: #fb923c; + + /* Provider brand dots */ + --p-claude: #d97757; + --p-gpt: #10a37f; + --p-gemini: #4285f4; + --p-codex: #f0a030; + + /* Radius */ + --r-sm: 6px; + --r-md: 8px; + --r-lg: 12px; + --r-xl: 16px; + + /* Shadow */ + --shadow-lg: 0 30px 60px -30px rgba(0,0,0,0.6); + --shadow-xl: 0 40px 80px -40px rgba(0,0,0,0.8); + + /* Typography */ + --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace; +} +/* Merged from original html/body rules — applied to .puro-page container root */ +.puro-page { + background: var(--bg-0); + color: var(--text-0); + font-family: var(--font-sans); + font-feature-settings: "cv11", "ss01", "ss03"; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + line-height: 1.5; + overflow-x: hidden; +} + + + +.puro-page a { color: inherit; text-decoration: none; } +.puro-page button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; } + +/* scrollbar — subtle */ +.puro-page::-webkit-scrollbar { width: 10px; height: 10px; } +.puro-page::-webkit-scrollbar-track { background: transparent; } +.puro-page::-webkit-scrollbar-thumb { background: var(--border-2); border-radius: 6px; } +.puro-page::-webkit-scrollbar-thumb:hover { background: var(--border-3); } + +.puro-page .mono { font-family: var(--font-mono); } + +/* ========================================================================== + BACKGROUND EFFECTS + ========================================================================== */ +.puro-page .bg-glow { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + overflow: hidden; +} +.puro-page .bg-glow::before, +.puro-page .bg-glow::after { + content: ""; + position: absolute; + width: 900px; + height: 900px; + border-radius: 50%; + filter: blur(120px); + opacity: 0.35; +} +.puro-page .bg-glow::before { + background: radial-gradient(circle, #22d3ee 0%, transparent 60%); + top: -300px; + left: -200px; +} +.puro-page .bg-glow::after { + background: radial-gradient(circle, #a855f7 0%, transparent 60%); + top: 200px; + right: -300px; + opacity: 0.25; +} +.puro-page .bg-glow.soft::before, .puro-page .bg-glow.soft::after { opacity: 0.15; } + +.puro-page .grain { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 1; + opacity: 0.4; + mix-blend-mode: overlay; + background-image: url("data:image/svg+xml;utf8,"); +} + +.puro-page .container { + max-width: 1100px; + margin: 0 auto; + padding: 0 32px; + position: relative; + z-index: 2; +} +.puro-page .container-wide { max-width: 1280px; } +.puro-page .container-narrow { max-width: 860px; } + +/* ========================================================================== + NAV + ========================================================================== */ +.puro-page .nav { + position: sticky; + top: 0; + z-index: 50; + backdrop-filter: blur(16px); + background: rgba(10, 14, 26, 0.72); + border-bottom: 1px solid var(--border); +} +.puro-page .nav-inner { + display: flex; + align-items: center; + height: 64px; + gap: 48px; +} +.puro-page .brand { + display: flex; + align-items: center; + gap: 10px; + font-weight: 700; + font-size: 15px; + letter-spacing: 0.02em; +} +.puro-page .hex { + width: 22px; + height: 22px; + color: var(--cyan); +} +.puro-page .nav-links { + display: flex; + gap: 28px; + font-size: 14px; + color: var(--text-2); +} +.puro-page .nav-links a { transition: color .15s; } +.puro-page .nav-links a:hover, .puro-page .nav-links a.active { color: var(--text-0); } +.puro-page .nav-links .disabled { color: var(--text-3); cursor: not-allowed; display: inline-flex; align-items: center; gap: 6px; } +.puro-page .nav-links .disabled::after { + content: "即将推出"; + font-size: 10px; + padding: 2px 6px; + border: 1px solid var(--border-2); + border-radius: 4px; + color: var(--text-3); +} +.puro-page .nav-cta { + margin-left: auto; + display: flex; + gap: 10px; + align-items: center; +} + +/* ========================================================================== + BUTTONS + ========================================================================== */ +.puro-page .btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 14px; + font-size: 13px; + font-weight: 500; + border-radius: var(--r-md); + transition: all .15s; + white-space: nowrap; + border: 1px solid transparent; +} +.puro-page .btn-primary { + background: var(--cyan); + color: #042f2e; + font-weight: 600; +} +.puro-page .btn-primary:hover { background: var(--cyan-2); } +.puro-page .btn-primary:active { transform: translateY(1px); } +.puro-page .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; } + +.puro-page .btn-ghost { + border-color: var(--border-2); + color: var(--text-1); +} +.puro-page .btn-ghost:hover { border-color: var(--border-3); color: var(--text-0); background: rgba(255,255,255,0.02); } + +.puro-page .btn-subtle { + background: rgba(255,255,255,0.04); + color: var(--text-1); + border-color: transparent; +} +.puro-page .btn-subtle:hover { background: rgba(255,255,255,0.08); color: var(--text-0); } + +.puro-page .btn-danger { + background: rgba(248, 113, 113, 0.1); + color: var(--red); + border-color: rgba(248, 113, 113, 0.25); +} +.puro-page .btn-danger:hover { background: rgba(248, 113, 113, 0.15); border-color: rgba(248, 113, 113, 0.4); } + +.puro-page .btn-lg { padding: 12px 20px; font-size: 14px; } +.puro-page .btn-sm { padding: 5px 10px; font-size: 12px; } +.puro-page .btn-icon { padding: 7px; aspect-ratio: 1; } + +.puro-page .btn .spinner { + width: 14px; height: 14px; + border: 2px solid rgba(0,0,0,0.2); + border-top-color: currentColor; + border-radius: 50%; + animation: spin .7s linear infinite; + display: none; +} +.puro-page .btn.loading .spinner { display: inline-block; } +.puro-page .btn.loading .label { opacity: 0.5; } +@keyframes spin { to { transform: rotate(360deg); } } + +/* ========================================================================== + BADGES / PILLS / CHIPS + ========================================================================== */ +.puro-page .badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 100px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.02em; + background: rgba(34, 211, 238, 0.1); + color: var(--cyan); +} +.puro-page .badge.amber { background: rgba(251, 191, 36, 0.12); color: var(--amber); } +.puro-page .badge.purple { background: rgba(168, 85, 247, 0.12); color: var(--purple); } +.puro-page .badge.green { background: rgba(52, 211, 153, 0.12); color: var(--green); } +.puro-page .badge.red { background: rgba(248, 113, 113, 0.12); color: var(--red); } +.puro-page .badge.muted { background: rgba(255, 255, 255, 0.04); color: var(--text-2); border: 1px solid var(--border); } + +.puro-page .pill { + display: inline-block; + padding: 2px 8px; + border-radius: var(--r-sm); + background: rgba(255,255,255,0.04); + border: 1px solid var(--border); + font-family: var(--font-mono); + font-size: 12px; + color: var(--text-0); +} + +.puro-page .chip { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 100px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid var(--border); + font-size: 12px; + color: var(--text-1); + font-family: var(--font-mono); +} +.puro-page .chip .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); } +.puro-page .chip.claude .dot { background: var(--p-claude); } +.puro-page .chip.gpt .dot { background: var(--p-gpt); } +.puro-page .chip.gemini .dot { background: var(--p-gemini); } +.puro-page .chip.codex .dot { background: var(--p-codex); } + +.puro-page .dot-sep { width: 4px; height: 4px; border-radius: 50%; background: var(--text-3); display: inline-block; } + +/* status chip (tiny dot absolute-positioned) */ +.puro-page .status-chip { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--green); + box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.15); + display: inline-block; +} +.puro-page .status-chip.dim { background: var(--text-3); box-shadow: none; } +.puro-page .status-chip.amber { background: var(--amber); box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.15); } +.puro-page .status-chip.red { background: var(--red); box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.15); } + +/* ========================================================================== + CARDS / SURFACES + ========================================================================== */ +.puro-page .card { + background: rgba(15, 23, 42, 0.6); + border: 1px solid var(--border); + border-radius: var(--r-lg); + padding: 24px; +} +.puro-page .card-raised { + background: var(--bg-1); + border: 1px solid var(--border); + border-radius: var(--r-lg); +} +.puro-page .card-interactive { + transition: all .2s; + cursor: pointer; +} +.puro-page .card-interactive:hover { + border-color: var(--border-2); + background: rgba(15, 23, 42, 0.85); + transform: translateY(-2px); +} + +.puro-page .divider { height: 1px; background: var(--border); margin: 24px 0; border: 0; } +.puro-page .divider-dashed { border: 0; border-top: 1px dashed var(--border); margin: 20px 0; } + +/* ========================================================================== + FORMS + ========================================================================== */ +.puro-page .field { margin-bottom: 18px; } +.puro-page .field-label { + display: block; + font-size: 12px; + font-weight: 500; + color: var(--text-1); + margin-bottom: 8px; +} +.puro-page .field-hint { + font-size: 12px; + color: var(--text-3); + margin-top: 6px; +} +.puro-page .field-error { + font-size: 12px; + color: var(--red); + margin-top: 6px; +} + +.puro-page .input-wrap { position: relative; } +.puro-page .input-wrap .icon { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: var(--text-3); + pointer-events: none; + display: inline-flex; +} +.puro-page .input { + width: 100%; + height: 42px; + padding: 0 14px; + background: rgba(15, 23, 42, 0.6); + border: 1px solid var(--border-2); + border-radius: var(--r-md); + color: var(--text-0); + font-size: 14px; + font-family: inherit; + outline: none; + transition: all .15s; +} +.puro-page .input.with-icon { padding-left: 40px; } +.puro-page .input::placeholder { color: var(--text-3); } +.puro-page .input:hover { border-color: var(--border-3); } +.puro-page .input:focus { + border-color: var(--cyan); + box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.12); + background: rgba(15, 23, 42, 0.9); +} +.puro-page .input.ok { border-color: rgba(52, 211, 153, 0.4); } +.puro-page .input.ok:focus { box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.12); } +.puro-page .input.error { border-color: var(--red); } +.puro-page .input.error:focus { box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.12); } + +.puro-page textarea.input { height: auto; padding: 12px 14px; resize: vertical; line-height: 1.5; } + +.puro-page select.input { + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 14px center; + padding-right: 36px; +} + +/* checkbox */ +.puro-page .check { + display: inline-flex; + align-items: center; + gap: 10px; + cursor: pointer; + user-select: none; + font-size: 13px; + color: var(--text-1); +} +.puro-page .check input { display: none; } +.puro-page .check .box { + width: 16px; height: 16px; + border: 1px solid var(--border-2); + border-radius: 4px; + background: var(--bg-1); + display: inline-flex; + align-items: center; + justify-content: center; + transition: all .15s; + flex-shrink: 0; +} +.puro-page .check input:checked + .box { + background: var(--cyan); + border-color: var(--cyan); +} +.puro-page .check input:checked + .box::after { + content: "✓"; + color: #042f2e; + font-size: 11px; + font-weight: 700; +} + +/* ========================================================================== + SECTION HEADINGS + ========================================================================== */ +.puro-page .section-kicker { + font-family: var(--font-mono); + font-size: 12px; + color: var(--cyan); + letter-spacing: 0.15em; + text-transform: uppercase; + margin-bottom: 12px; +} +.puro-page .section-title { + font-size: clamp(28px, 3.5vw, 40px); + font-weight: 700; + letter-spacing: -0.02em; + line-height: 1.15; + margin-bottom: 16px; +} +.puro-page .section-sub { + color: var(--text-2); + font-size: 16px; + line-height: 1.6; +} + +/* ========================================================================== + TABLES + ========================================================================== */ +.puro-page .tbl { + width: 100%; + font-size: 13px; + border-collapse: collapse; +} +.puro-page .tbl th { + text-align: left; + color: var(--text-3); + font-weight: 500; + padding: 12px 14px; + border-bottom: 1px solid var(--border); + text-transform: uppercase; + font-size: 10px; + letter-spacing: 0.1em; +} +.puro-page .tbl td { + padding: 14px; + border-bottom: 1px solid rgba(30, 41, 59, 0.5); + color: var(--text-1); +} +.puro-page .tbl tr:last-child td { border-bottom: none; } +.puro-page .tbl tr:hover td { background: rgba(15, 23, 42, 0.4); } +.puro-page .tbl td.mono, .puro-page .tbl th.mono { font-family: var(--font-mono); } + +/* ========================================================================== + CODE BLOCKS + ========================================================================== */ +.puro-page .code-frame { + background: var(--bg-code); + border: 1px solid var(--border); + border-radius: var(--r-lg); + overflow: hidden; + box-shadow: var(--shadow-lg); +} +.puro-page .code-head { + display: flex; + align-items: center; + height: 40px; + padding: 0 16px; + border-bottom: 1px solid var(--border); + background: rgba(15, 23, 42, 0.8); + gap: 10px; +} +.puro-page .traffic { + display: flex; + gap: 6px; +} +.puro-page .traffic span { + width: 10px; + height: 10px; + border-radius: 50%; + background: #475569; +} +.puro-page .code-body { + padding: 22px 26px; + font-family: var(--font-mono); + font-size: 13px; + line-height: 1.75; + color: var(--text-1); + overflow-x: auto; +} +.puro-page .code-body .line { display: flex; gap: 20px; } +.puro-page .ln { color: var(--text-3); user-select: none; min-width: 16px; text-align: right; opacity: 0.5; } + +/* syntax */ +.puro-page .kw { color: #c084fc; } +.puro-page .str { color: #86efac; } +.puro-page .num { color: #fbbf24; } +.puro-page .com { color: #64748b; font-style: italic; } +.puro-page .fn { color: #22d3ee; } +.puro-page .prop { color: #f0abfc; } +.puro-page .var-v { color: #f8fafc; } +.puro-page .flag { color: #fb923c; } +.puro-page .bash-prompt { color: var(--cyan); user-select: none; } + +/* ========================================================================== + PROVIDER-BRAND HELPERS + ========================================================================== */ +.puro-page .provider { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--font-mono); + font-size: 12px; +} +.puro-page .provider .dot { width: 6px; height: 6px; border-radius: 50%; } +.puro-page .provider.claude .dot { background: var(--p-claude); } +.puro-page .provider.gpt .dot { background: var(--p-gpt); } +.puro-page .provider.gemini .dot { background: var(--p-gemini); } +.puro-page .provider.codex .dot { background: var(--p-codex); } + +/* ========================================================================== + UTILITIES + ========================================================================== */ +.puro-page .stack-xs { display: flex; flex-direction: column; gap: 8px; } +.puro-page .stack-sm { display: flex; flex-direction: column; gap: 12px; } +.puro-page .stack-md { display: flex; flex-direction: column; gap: 20px; } +.puro-page .stack-lg { display: flex; flex-direction: column; gap: 32px; } + +.puro-page .row { display: flex; align-items: center; gap: 12px; } +.puro-page .row-sm { gap: 8px; } +.puro-page .row-lg { gap: 20px; } +.puro-page .row-between { justify-content: space-between; } +.puro-page .row-center { justify-content: center; } +.puro-page .row-wrap { flex-wrap: wrap; } + +.puro-page .flex-1 { flex: 1; } +.puro-page .ml-auto { margin-left: auto; } +.puro-page .mt-auto { margin-top: auto; } + +.puro-page .text-0 { color: var(--text-0); } +.puro-page .text-1 { color: var(--text-1); } +.puro-page .text-2 { color: var(--text-2); } +.puro-page .text-3 { color: var(--text-3); } +.puro-page .text-cyan { color: var(--cyan); } +.puro-page .text-purple { color: var(--purple); } +.puro-page .text-amber { color: var(--amber); } +.puro-page .text-green { color: var(--green); } +.puro-page .text-red { color: var(--red); } + +.puro-page .text-xs { font-size: 11px; } +.puro-page .text-sm { font-size: 13px; } +.puro-page .text-md { font-size: 14px; } +.puro-page .text-lg { font-size: 16px; } +.puro-page .text-xl { font-size: 20px; } +.puro-page .text-2xl { font-size: 28px; } +.puro-page .text-3xl { font-size: 36px; } + +.puro-page .fw-400 { font-weight: 400; } +.puro-page .fw-500 { font-weight: 500; } +.puro-page .fw-600 { font-weight: 600; } +.puro-page .fw-700 { font-weight: 700; } +.puro-page .fw-800 { font-weight: 800; } + +.puro-page .tabular { font-variant-numeric: tabular-nums; } +.puro-page .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* ========================================================================== + APP SHELL (for dashboard-style pages) + ========================================================================== */ +.puro-page .app-shell { + display: grid; + grid-template-columns: 240px 1fr; + min-height: 100vh; + position: relative; + z-index: 2; +} +.puro-page .app-side { + border-right: 1px solid var(--border); + background: rgba(2, 6, 23, 0.6); + padding: 20px 14px; + display: flex; + flex-direction: column; + gap: 28px; + position: sticky; + top: 0; + height: 100vh; + overflow-y: auto; +} +.puro-page .app-side .brand { padding: 6px 10px 14px; } +.puro-page .side-group { display: flex; flex-direction: column; gap: 2px; } +.puro-page .side-label { + font-size: 10px; + color: var(--text-3); + text-transform: uppercase; + letter-spacing: 0.12em; + padding: 0 10px 8px; + font-family: var(--font-mono); +} +.puro-page .side-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: var(--r-sm); + font-size: 13px; + color: var(--text-2); + cursor: pointer; + transition: all .12s; +} +.puro-page .side-item:hover { color: var(--text-0); background: rgba(255,255,255,0.03); } +.puro-page .side-item.active { background: rgba(34, 211, 238, 0.08); color: var(--cyan); } +.puro-page .side-item .ico { + width: 16px; height: 16px; opacity: 0.8; + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; +} +.puro-page .side-item .count { + margin-left: auto; + font-size: 11px; + color: var(--text-3); + font-family: var(--font-mono); +} +.puro-page .side-item.active .count { color: var(--cyan); } + +.puro-page .app-main { + min-width: 0; /* allow grid children to shrink */ + display: flex; + flex-direction: column; +} +.puro-page .app-topbar { + height: 60px; + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + padding: 0 32px; + gap: 16px; + position: sticky; + top: 0; + z-index: 10; + background: rgba(10, 14, 26, 0.75); + backdrop-filter: blur(12px); +} +.puro-page .app-topbar h1 { + font-size: 18px; + font-weight: 600; + letter-spacing: -0.01em; +} +.puro-page .app-content { + padding: 32px; + flex: 1; +} + +/* user avatar pill */ +.puro-page .avatar { + width: 28px; height: 28px; + border-radius: 50%; + background: linear-gradient(135deg, #22d3ee, #a855f7); + display: inline-flex; + align-items: center; + justify-content: center; + color: #042f2e; + font-weight: 700; + font-size: 12px; + flex-shrink: 0; +} + +/* ========================================================================== + KBD + ========================================================================== */ +.puro-page kbd { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-family: var(--font-mono); + font-size: 11px; + font-weight: 500; + color: var(--text-1); + background: var(--bg-1); + border: 1px solid var(--border-2); + border-bottom-width: 2px; + border-radius: 4px; + line-height: 1; +} diff --git a/frontend/src/components/layout/AuthLayout.vue b/frontend/src/components/layout/AuthLayout.vue index 129e8301..3b02a926 100644 --- a/frontend/src/components/layout/AuthLayout.vue +++ b/frontend/src/components/layout/AuthLayout.vue @@ -1,69 +1,45 @@ + + diff --git a/frontend/src/views/landing/LandingView.vue b/frontend/src/views/landing/LandingView.vue new file mode 100644 index 00000000..93f3b577 --- /dev/null +++ b/frontend/src/views/landing/LandingView.vue @@ -0,0 +1,504 @@ + + + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 70e3a7ed..ea4c2bc5 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -46,6 +46,20 @@ export default { 800: '#1e293b', 900: '#0f172a', 950: '#020617' + }, + // PURO AI 设计系统色板(给新页面 Landing/Auth/Docs 用,不影响 admin) + puro: { + cyan: '#22d3ee', + 'cyan-2': '#67e8f9', + purple: '#a855f7', + amber: '#fbbf24', + green: '#34d399', + red: '#f87171', + // 平台品牌点色 + claude: '#d97757', + gpt: '#10a37f', + gemini: '#4285f4', + codex: '#f0a030' } }, fontFamily: {