Merge pull request 'fix: remove iShare references from frontend' (#4) from fix/remove-ishare-refs into main
Some checks failed
Some checks failed
This commit is contained in:
168
LOCAL_SETUP_NOTES.md
Normal file
168
LOCAL_SETUP_NOTES.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# Sub2API 本地开发环境搭建记录
|
||||||
|
|
||||||
|
> 2026-04-19 @ macOS (darwin/arm64)
|
||||||
|
|
||||||
|
## 一、环境依赖
|
||||||
|
|
||||||
|
| 项 | 版本 | 说明 |
|
||||||
|
|---|---|---|
|
||||||
|
| Go | 1.24.3(本机)→ 1.26.2(auto via GOTOOLCHAIN) | go.mod 要求 1.26.2,靠 Go 1.21+ 的 GOTOOLCHAIN 机制自动下载 |
|
||||||
|
| Node | v24.13.0 | ≥18 即可 |
|
||||||
|
| pnpm | v10.33.0 | 现装:`npm install -g pnpm` |
|
||||||
|
| Docker | OrbStack | 已跑 mysql8@3306,端口冲突规避见下 |
|
||||||
|
| 端口占用 | 8080 / 5433 / 6380 均空闲 | 5432/6379 留给可能的其他 PG/Redis |
|
||||||
|
|
||||||
|
## 二、部署步骤(实际执行)
|
||||||
|
|
||||||
|
### 1. 拉源码
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Wei-Shaw/sub2api.git /Users/mini/Work/dev/sub2api
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 起依赖容器(非默认端口)
|
||||||
|
```bash
|
||||||
|
docker run -d --name sub2api-pg \
|
||||||
|
-e POSTGRES_PASSWORD=devpass -e POSTGRES_DB=sub2api \
|
||||||
|
-p 5433:5432 postgres:15
|
||||||
|
|
||||||
|
docker run -d --name sub2api-redis \
|
||||||
|
-p 6380:6379 redis:7
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 安装 pnpm(首次)
|
||||||
|
```bash
|
||||||
|
npm install -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 构建前端
|
||||||
|
```bash
|
||||||
|
cd /Users/mini/Work/dev/sub2api/frontend
|
||||||
|
pnpm install # ~9 秒
|
||||||
|
pnpm run build # ~8 秒,产物输出到 ../backend/internal/web/dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 构建后端(⚠️ 必带 `-tags embed`)
|
||||||
|
```bash
|
||||||
|
cd /Users/mini/Work/dev/sub2api/backend
|
||||||
|
go build -tags embed -o sub2api ./cmd/server
|
||||||
|
# 产物:105MB Mach-O 64-bit arm64
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 生成 config.yaml
|
||||||
|
```bash
|
||||||
|
cp /Users/mini/Work/dev/sub2api/deploy/config.example.yaml \
|
||||||
|
/Users/mini/Work/dev/sub2api/backend/config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
修改四处(见下方问题点 Issue #1 说明 sslmode):
|
||||||
|
|
||||||
|
| 字段 | 原值 | 改为 |
|
||||||
|
|---|---|---|
|
||||||
|
| `database.port` | 5432 | **5433** |
|
||||||
|
| `database.password` | `"your_secure_password_here"` | `"devpass"` |
|
||||||
|
| `database.sslmode` | `"prefer"` | **`"disable"`** |
|
||||||
|
| `redis.port` | 6379 | **6380** |
|
||||||
|
| `jwt.secret` | `"change-this-..."` | `openssl rand -hex 32` 产出的 64 位 hex |
|
||||||
|
|
||||||
|
### 7. 启动 + 验证
|
||||||
|
```bash
|
||||||
|
cd /Users/mini/Work/dev/sub2api/backend
|
||||||
|
nohup ./sub2api > /tmp/sub2api.log 2>&1 &
|
||||||
|
# 等 5-10 秒让服务完成 pricing 数据下载
|
||||||
|
curl -si http://localhost:8080 | head
|
||||||
|
# 期望:HTTP/1.1 200 OK,HTML 含 <title>Sub2API - AI API Gateway</title>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、遇到的问题与解法
|
||||||
|
|
||||||
|
### Issue #1 — `sslmode: "prefer"` 启动失败
|
||||||
|
**现象**:后端启动立即退出,日志:
|
||||||
|
```
|
||||||
|
Failed to initialize application: acquire migrations lock:
|
||||||
|
pq: unsupported sslmode "prefer"; only "require" (default),
|
||||||
|
"verify-full", "verify-ca", and "disable" supported
|
||||||
|
```
|
||||||
|
|
||||||
|
**根因**:`config.example.yaml` 默认的 `sslmode: "prefer"` 是 libpq(C 驱动)的模式,Go 的 `lib/pq` 不支持。
|
||||||
|
|
||||||
|
**解法**:本地 Docker Postgres 没配 SSL,改成 `disable`:
|
||||||
|
```yaml
|
||||||
|
database:
|
||||||
|
sslmode: "disable"
|
||||||
|
```
|
||||||
|
生产若走带 SSL 的 PG,用 `require` 或 `verify-full`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #2 — go.mod 要求 Go 1.26.2,本机只有 1.24.3
|
||||||
|
**现象**:首次 `go build` 触发:
|
||||||
|
```
|
||||||
|
go: downloading go1.26.2 (darwin/arm64)
|
||||||
|
```
|
||||||
|
|
||||||
|
**根因**:`backend/go.mod` 第一行 `go 1.26.2` 写死。
|
||||||
|
|
||||||
|
**解法**:**无需手动升级 Go**。Go 1.21+ 的 GOTOOLCHAIN 机制会自动下载指定版本并透明切换。首次 build 比较慢(下载 toolchain + 全部依赖),后续会缓存。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #3 — frontend 构建产物路径是相对路径到 backend
|
||||||
|
**现象**:`pnpm run build` 的日志显示产物写到 `../backend/internal/web/dist/`。
|
||||||
|
|
||||||
|
**说明**:**这是预期行为**。Vite 配置把输出指向 backend 的 embed 目录,配合 `go build -tags embed` 把 dist 打进 Go 二进制。所以:
|
||||||
|
- 每次改前端代码都要重新 `pnpm run build` 然后 `go build -tags embed`
|
||||||
|
- 如果 `go build` 时忘了 `-tags embed`,后端启动后访问 `/` 会 404
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Issue #4 — 日志文件 `/app/data/logs/sub2api.log` 写入失败
|
||||||
|
**现象**:启动日志里有 WARN:
|
||||||
|
```
|
||||||
|
日志文件输出初始化失败,降级为仅标准输出
|
||||||
|
path=/app/data/logs/sub2api.log err=mkdir /app: read-only file system
|
||||||
|
```
|
||||||
|
|
||||||
|
**根因**:默认配置指向容器内路径 `/app/data/logs/`,本地裸跑在 macOS 上 `/app` 不可写。
|
||||||
|
|
||||||
|
**影响**:无功能影响,只是降级到 stdout。我们用 `nohup ./sub2api > /tmp/sub2api.log 2>&1 &` 已经把 stdout 重定向了,日志照样完整。
|
||||||
|
|
||||||
|
**若要消除 WARN**:修改 config 里 `logging.file_path`(或等同字段)指向本地可写路径,如 `/tmp/sub2api/logs/sub2api.log`,并 `mkdir -p` 目录。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、当前状态
|
||||||
|
|
||||||
|
```
|
||||||
|
Backend PID: 26921
|
||||||
|
HTTP: 200 @ http://localhost:8080
|
||||||
|
页面: Sub2API - AI API Gateway(Setup Wizard 入口)
|
||||||
|
Pricing: 已下载 177 个模型价格
|
||||||
|
Containers: sub2api-pg (Up), sub2api-redis (Up)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、下次重启命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动依赖(容器如果 stopped)
|
||||||
|
docker start sub2api-pg sub2api-redis
|
||||||
|
|
||||||
|
# 启动后端
|
||||||
|
cd /Users/mini/Work/dev/sub2api/backend
|
||||||
|
nohup ./sub2api > /tmp/sub2api.log 2>&1 &
|
||||||
|
|
||||||
|
# 停止
|
||||||
|
pkill -f "/Users/mini/Work/dev/sub2api/backend/sub2api"
|
||||||
|
docker stop sub2api-pg sub2api-redis
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、清理重来
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pkill -f "/Users/mini/Work/dev/sub2api/backend/sub2api"
|
||||||
|
docker rm -f sub2api-pg sub2api-redis
|
||||||
|
rm -rf /Users/mini/Work/dev/sub2api
|
||||||
|
```
|
||||||
1064
backend/config.prod.yaml
Normal file
1064
backend/config.prod.yaml
Normal file
File diff suppressed because it is too large
Load Diff
BIN
backend/sub2api-linux
Executable file
BIN
backend/sub2api-linux
Executable file
Binary file not shown.
1262
docs/superpowers/plans/2026-04-20-portal-i18n-pricing.md
Normal file
1262
docs/superpowers/plans/2026-04-20-portal-i18n-pricing.md
Normal file
File diff suppressed because it is too large
Load Diff
445
docs/superpowers/plans/fidelity-delta-report.md
Normal file
445
docs/superpowers/plans/fidelity-delta-report.md
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
# PURO AI Fidelity Delta Report
|
||||||
|
|
||||||
|
> Compare: `docs/design-drafts/v2/{Landing,Login,Register}.html` vs `frontend/src/views/{landing,auth}/*.vue`
|
||||||
|
> Generated: 2026-04-19
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. LandingView
|
||||||
|
|
||||||
|
**Zip:** `docs/design-drafts/v2/Landing.html`
|
||||||
|
**Vue:** `frontend/src/views/landing/LandingView.vue`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Copy / Text Deltas
|
||||||
|
|
||||||
|
| # | Zip (HTML) | Vue (SFC) | Severity | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| L-T1 | Hero eyebrow: `<span class="badge">NEW</span> 统一接入多个 AI 平台 · 零改动切换` | Hero eyebrow: `ChatGPT Plus · Claude Pro · Codex · Gemini` (plain pill, no badge) | **important** | Zip has a highlighted NEW badge + tagline; Vue shows only a product-list pill with no "NEW" call-out and no `统一接入…` text |
|
||||||
|
| L-T2 | Hero sub: `聚合成统一 API,零改动接入 <span class="pill">OpenAI</span> / <span class="pill">Anthropic</span> SDK` — pill is a code-styled inline token | Vue: `聚合成统一 API,零改动接入 OpenAI / Anthropic SDK` — plain text, no pill styling | **cosmetic** | Inline `<span class="pill">` with monospace border lost |
|
||||||
|
| L-T3 | Hero CTA buttons: `立即开始 →` (primary) + `查看文档` (ghost) | Hero CTA buttons: `登录 →` (primary) + `联系咨询` (ghost) | **important** | Primary button label changed from "立即开始" to "登录"; secondary changed from "查看文档" to "联系咨询". Zip routes unauthenticated users to Register first; Vue routes to Login |
|
||||||
|
| L-T4 | Hero micro-text: `无需信用卡 · 用你已有的订阅 · 5 分钟跑通` (three dot-separated items, each a `<span>`) | Hero micro-text: `已验证可用 Codex CLI · Claude Code · curl · 服务器出口新加坡` (single plain string) | **important** | Entirely different social-proof claims; zip emphasises zero-friction onboarding, Vue emphasises tech compatibility |
|
||||||
|
| L-T5 | Model-wall section kicker: `// providers` | Vue model-wall section kicker: `支持的 AI 平台` (Chinese, no `//` prefix) | **cosmetic** | Zip uses monospace `// providers` pattern; Vue switches to Chinese heading |
|
||||||
|
| L-T6 | Model-wall subtitle: `通过 OAuth 直接复用你的订阅,无需申请官方 API key` | Vue sub: `无需申请官方 API key,也无需切换账号` | **cosmetic** | Minor copy rewrite; same meaning |
|
||||||
|
| L-T7 | Features section title: `付一次订阅,<br>用起一整个模型池` | Vue section title: `一套 key,三件武器` | **important** | Substantially different headline; zip's "pay once" framing vs Vue's "one key, three weapons" |
|
||||||
|
| L-T8 | Features section sub: `把散落在各个平台的订阅,整合成开发者真正能用的基础设施` | Vue: *(sub entirely absent)* | **important** | Vue omits the features section subtitle entirely |
|
||||||
|
| L-T9 | Feature card 2 copy: `某个 ChatGPT Plus 触发限流,自动 failover 到下一个。重启、刷新 token 全自动。` | Vue: `某个上游触发限流 / 冷却时,流量切到下一个健康账号,token 刷新全自动。` | **cosmetic** | Light rewrite, same meaning |
|
||||||
|
| L-T10 | Feature bullets (all 3 cards): 3-bullet lists under a dashed-border divider | Vue: *bullets entirely absent* from all feature cards | **important** | All 9 feature bullet items (`OpenAI Responses API 兼容`, `限流/5xx 自动 failover`, etc.) are missing |
|
||||||
|
| L-T11 | Code demo section kicker: `// integration` | Vue: `快速接入` | **cosmetic** | |
|
||||||
|
| L-T12 | Dashboard section sub (long): `不像第三方 API 池子那种"扣了多少不告诉你"。你能看到每次调用:扣了哪个账号、跑了哪个模型、用了多少 tokens、花了多少钱、上游响应几秒。` | Vue: `不像第三方 API 池子那种"扣了多少不告诉你"——扣哪个账号、跑哪个模型、用了多少 tokens、上游响应几秒,一目了然。` | **cosmetic** | Condensed; "花了多少钱" detail removed from Vue version |
|
||||||
|
| L-T13 | CTA banner: `把订阅变成 API — 5 分钟` + `绑定第一个账号,生成 sk- key,把 base_url 指过来。就这些。` | *CTA banner entirely absent from Vue* | **important** | The mid-page conversion CTA section is completely missing from Vue |
|
||||||
|
| L-T14 | Pricing section (full 3-tier grid + `pricing-more-link`) | *Pricing section entirely absent from Vue* | **important** | Full pricing section with 3 tiers ($9.9 / $29.9 / $99), all tier copy, and "查看完整定价" link is missing |
|
||||||
|
| L-T15 | FAQ section (6 expandable `<details>` + "查看全部 10 个问题" link) | *FAQ section entirely absent from Vue* | **important** | All FAQ content missing |
|
||||||
|
| L-T16 | Footer brand tagline: `把多个 AI 订阅聚合成统一 API。让「已经付过钱」的订阅真正为你工作。` | Vue: `Self-hosted on puro.im` | **important** | Zip has Chinese product tagline; Vue shows English self-hosted note |
|
||||||
|
| L-T17 | Footer meta: `© 2026 puro.im · Built with ♥ in Shanghai` | Vue: `© 2026 puro.im · MIT License\nfork of Wei-Shaw/sub2api` | **cosmetic** | Different but both acceptable |
|
||||||
|
| L-T18 | Footer "产品" column: 文档 / 定价 / FAQ / 功能 | Vue "产品" column: 文档 / 更新日志 | **important** | Vue removes 定价/FAQ/功能 links |
|
||||||
|
| L-T19 | Footer "账户" column: 登录 / 注册 / Dashboard / 绑定订阅 | Vue footer has "联系" column (admin@puro.im / git.puro.im) instead of "账户" column | **important** | Entire "账户" column replaced with "联系" column |
|
||||||
|
| L-T20 | Footer system-status line: `all systems operational` (with green dot) | *Absent from Vue footer* | **cosmetic** | |
|
||||||
|
| L-T21 | Stat card labels (dashboard): `Requests · 24h`, `Tokens · 24h`, `Avg latency`, `Est. savings` | Vue: `今日请求`, `输入 Tokens`, `输出 Tokens`, `今日费用` | **important** | Different stat names and dimensions (4th stat is "savings" in zip vs "cost" in Vue; zip has 1 tokens stat vs Vue has 2 separate I/O token stats) |
|
||||||
|
| L-T22 | Log table columns: Time / Provider / Model / Tokens / Cost / Latency / Status | Vue: 时间 / 模型 / 上游 / 状态 / 用量 (5 cols, Chinese, Cost and Latency absent) | **important** | Zip has 7-column table with Cost + Latency; Vue drops those two columns |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure Deltas
|
||||||
|
|
||||||
|
| # | What zip has | Vue status | Severity |
|
||||||
|
|---|---|---|---|
|
||||||
|
| L-S1 | Nav links include `#pricing` and `#faq` anchor links | Vue nav only has `#features` + `/docs` (no pricing/faq) | **important** |
|
||||||
|
| L-S2 | Hero: `<span class="hero-micro">` with 3 `<span>` children and `.dot` separators | Vue: single plain string in `.hero-micro`, no separators | **cosmetic** |
|
||||||
|
| L-S3 | Model cards: icon logo (`<div class="model-logo">` + inline SVG) + name + tag stacked vertically with green status chip | Vue: colored dot + name + meta in a row (horizontal), no status chip, no SVG logos | **important** |
|
||||||
|
| L-S4 | Code demo: single-column with TWO separate `code-frame` panels, each with traffic-light dots + tabs + `● edited 2s ago` / `zsh · puro ≈ 210ms` labels | Vue: two-column `code-demo` grid with simplified `code-block` panels (no tab row, no traffic dots) | **important** |
|
||||||
|
| L-S5 | Dashboard: browser-chrome header (`dash-head` with url bar `ai.puro.im/dashboard` + lock icon + `me@puro` label) | Vue: minimal `dash-header` with title + three dots, no URL bar | **important** |
|
||||||
|
| L-S6 | Dashboard: left sidebar (`dash-side`) with WORKSPACE and SETTINGS nav groups | Vue: sidebar entirely absent | **important** |
|
||||||
|
| L-S7 | Dashboard chart area: `chart-grid` (2fr + 1fr) with line chart SVG + donut chart SVG side-by-side | Vue: single `chart-card` with a simplified `<polyline>` chart only, no donut | **important** |
|
||||||
|
| L-S8 | Donut chart: full SVG with 4 arc segments (Claude 48% / GPT 32% / Gemini 14% / Codex 6%) + legend | Vue: absent | **important** |
|
||||||
|
| L-S9 | CTA banner section (mid-page, after dashboard) | Vue: absent | **important** |
|
||||||
|
| L-S10 | Pricing section (3-tier grid) | Vue: absent | **important** |
|
||||||
|
| L-S11 | FAQ section (`<details>` accordion, 6 items) | Vue: absent | **important** |
|
||||||
|
| L-S12 | Footer: 4-column grid (2fr brand + 3×1fr cols) with `footer-brand` description paragraph | Vue: 4-column grid present but content differs (see L-T16/T18/T19) | **cosmetic** (structure match, content differs) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Visuals / SVG
|
||||||
|
|
||||||
|
| # | Delta | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| L-V1 | Nav brand: inline SVG hexagon (`<path d="M12 2L21 7V17L12 22L3 17V7L12 2Z">` + inner fill path) | Vue: `⬢` Unicode emoji | **important** — SVG renders crisp at all sizes; emoji rendering varies by OS/font |
|
||||||
|
| L-V2 | Footer brand: same inline SVG hexagon | Vue: `⬢` emoji | **important** |
|
||||||
|
| L-V3 | Model-card logos: unique inline SVG per provider (Claude chevron, GPT circle, Codex bracket, Gemini star, "..." dots) | Vue: colored 10px `<div class="model-dot">` only | **important** |
|
||||||
|
| L-V4 | Dashboard line-chart: SVG with gradient fill area, cyan/purple/amber stroke lines, x-axis time labels (`00:00`…`now`) | Vue: simplified `<polyline>` without fill area or x-axis labels | **important** |
|
||||||
|
| L-V5 | Hero eyebrow: `.badge` span with cyan tinted background for "NEW" | Vue: no badge element | **important** |
|
||||||
|
| L-V6 | Pricing tier-flag chips (STARTER / ◆ 推荐 / ⚡ 限时) with color variants (`muted` / default / `amber`) | Vue: absent (no pricing section) | n/a (section missing) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing CSS (zip has, Vue scoped missing)
|
||||||
|
|
||||||
|
| # | Missing rule / component | Impact |
|
||||||
|
|---|---|---|
|
||||||
|
| L-C1 | `.model-logo` (icon container with border + bg), `.status-chip` (green dot + glow pulse), `.model-card.disabled` opacity | Model wall looks flat vs. designed appearance |
|
||||||
|
| L-C2 | `.hero-eyebrow .badge` (cyan tinted pill) | Missing "NEW" badge styling |
|
||||||
|
| L-C3 | `.hero-micro .dot` (4px separator dots) | Separator dots between hero micro items |
|
||||||
|
| L-C4 | `.code-tabs`, `.tab`, `.tab.active`, `.tab-dot`, `.traffic` | Full code-frame tab-bar styling missing |
|
||||||
|
| L-C5 | `.dash-head .url` (address-bar mockup with lock icon pseudo) | Dashboard browser-chrome bar |
|
||||||
|
| L-C6 | `.dash-side`, `.side-group`, `.side-label`, `.side-item`, `.side-item.active` | Sidebar nav entirely unstyled |
|
||||||
|
| L-C7 | `.chart-grid` 2:1 layout, `.chart-title .legend`, `.sw` (legend swatch) | Chart section layout and legend |
|
||||||
|
| L-C8 | `.cta-banner` and all sub-rules | Mid-page CTA banner |
|
||||||
|
| L-C9 | `.pricing-grid-landing`, `.tier-l`, `.tier-l-flag`, `.tier-l-price`, `.tier-l-credit`, `.tier-l-discount`, `.tier-l-feats`, `.pricing-more-link` | Entire pricing card system |
|
||||||
|
| L-C10 | `.faq-l` + `summary`, `::after`, `[open]` variants, `.faq-answer` | FAQ accordion |
|
||||||
|
| L-C11 | `.section-kicker` with `// ` monospace prefix convention (zip uses `letter-spacing: 0.15em`) | Vue uses 0.12em and different style |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing Scripts / Interactivity
|
||||||
|
|
||||||
|
| # | What zip has | Vue status | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| L-I1 | Code-frame tab switching: clicking `.tab` elements switches displayed code block | Vue code demo has no tab interaction (static 2 panels) | **interactive** |
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. LoginView
|
||||||
|
|
||||||
|
**Zip:** `docs/design-drafts/v2/Login.html`
|
||||||
|
**Vue:** `frontend/src/views/auth/LoginView.vue`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Copy / Text Deltas
|
||||||
|
|
||||||
|
| # | Zip (HTML) | Vue (SFC) | Severity | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| LN-T1 | Narrative headline: `<span class="amber">N</span> 个订阅 → <span class="cyan">1</span> 个 key` (N is amber-colored variable) | Vue: `<span class="num-5">5</span> 个订阅 → <span class="num-1">1</span> 个 key` | **important** | Zip uses abstract "N" (variable quantity); Vue hardcodes "5". This was the specific discrepancy the user noticed |
|
||||||
|
| LN-T2 | Narrative kicker: `// 你的订阅,已经付过钱了` | Vue: absent (no kicker element in narrative) | **important** | |
|
||||||
|
| LN-T3 | n-sub text: `省去切换账号的繁琐,` / `省去为多个高昂订阅重复买单。` / `PURO(纯粹)—— 让 AI 调用回归本质。` | Vue: `省去切换账号的繁琐,` / `省去为多个高昂订阅重复买单。` / `PURO(纯粹)—— 让 AI 调用回归本质。` (via `auth-narrative-tagline`) | **cosmetic** | Full-width vs half-width punctuation (`,` vs `,`, `()` vs `()`); same meaning otherwise |
|
||||||
|
| LN-T4 | Form title: `登录` (h1) + sub: `用你的 PURO AI 账户继续` | Vue: `{{ t('auth.puroLoginTitle') }}` + `{{ t('auth.puroLoginSub') }}` — actual rendered text depends on i18n key values | **important** | Need to verify i18n values match; design source has specific Chinese copy |
|
||||||
|
| LN-T5 | "Remember me" checkbox: `记住我` | Vue: absent (no remember-me UI) | **cosmetic** | |
|
||||||
|
| LN-T6 | "Forgot password" link: `忘记密码?` (always visible, inline with remember-me) | Vue: only shown `v-if="passwordResetEnabled && !backendModeEnabled"` | **cosmetic** | Conditioned; zip always shows it |
|
||||||
|
| LN-T7 | Submit button text: `登录 →` | Vue: i18n key `t('auth.signIn')` / `t('auth.signingIn')` | **cosmetic** | Likely matches; verify arrow `→` is included |
|
||||||
|
| LN-T8 | LinuxDO button: `使用 LinuxDO 登录` with custom orange gradient `.linuxdo-ico` "L" badge | Vue: delegated to `<LinuxDoOAuthSection>` component | **cosmetic** | Functionally equivalent; visual parity depends on component implementation |
|
||||||
|
| LN-T9 | Footer link: `没有账户?<a>注册</a>` | Vue: i18n `t('auth.dontHaveAccount')` + `t('auth.signUp')` | **cosmetic** | Verify i18n values |
|
||||||
|
| LN-T10 | Legal line: `登录即表示你同意 <a>服务条款</a> 与 <a>隐私政策</a>` | Vue: **absent** | **important** | No terms/privacy consent text in Vue login form |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure Deltas
|
||||||
|
|
||||||
|
| # | What zip has | Vue status | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| LN-S1 | `route-demo` block: live routing demo panel showing `POST /v1/chat/completions`, `model: claude-sonnet-4-5`, `route → claude-pool-03`, `status 200 · 213ms · 42 tok` | Vue: **entirely absent** | **important** |
|
||||||
|
| LN-S2 | `n-bottom` bar: provider list (`Claude · ChatGPT · Codex · Gemini`) + `live · ai.puro.im · operational` green-dot status | Vue: replaced by simpler `auth-narrative-foot` = `Claude · ChatGPT · Codex · Gemini` only; no live status | **important** |
|
||||||
|
| LN-S3 | `back-home` link (`← 返回首页`) absolute-positioned top-right of form panel | Vue: absent | **cosmetic** |
|
||||||
|
| LN-S4 | Password field: eye toggle with two SVG states (open / closed-with-slash), both embedded inline | Vue: `<Icon name="eye">` / `<Icon name="eyeOff">` (component-based) | **cosmetic** — functionally equivalent |
|
||||||
|
| LN-S5 | `remember-me` checkbox with custom styled box + check mark | Vue: absent | **cosmetic** |
|
||||||
|
| LN-S6 | Login form `autocomplete="off" novalidate` attribute | Vue: `autocomplete` per-field, no `novalidate` (uses Vue validation) | **cosmetic** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Visuals / SVG
|
||||||
|
|
||||||
|
| # | Delta | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| LN-V1 | Brand in narrative: inline SVG hexagon (same double-path as Landing) | Vue: `⬢` emoji | **important** |
|
||||||
|
| LN-V2 | `route-demo` styling: pill-inline badges with cyan/amber/green border-tinted backgrounds, green dot-pulse status | Vue: absent (whole block missing) | n/a (structure missing) |
|
||||||
|
| LN-V3 | `n-bottom .live .dot` — 5px green dot with `box-shadow` glow | Vue: absent | **important** |
|
||||||
|
| LN-V4 | Narrative background: `narrative::before` pseudo with dual radial-gradient overlay (cyan + purple) | Vue: handled by `AuthLayout` — parity depends on that component | unknown — needs AuthLayout check |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing CSS (zip has, Vue scoped missing)
|
||||||
|
|
||||||
|
| # | Missing rule | Impact |
|
||||||
|
|---|---|---|
|
||||||
|
| LN-C1 | `.route-demo` and all its `.row`, `.k`, `.v`, `.pill-inline` variants | Route demo panel |
|
||||||
|
| LN-C2 | `.n-bottom` and `.n-bottom .live`, `.n-bottom .sep` | Bottom status bar |
|
||||||
|
| LN-C3 | `.n-kicker` | Kicker line above headline |
|
||||||
|
| LN-C4 | `.check` custom checkbox (`.box`, `:checked ~ .box::after`) | Remember-me checkbox |
|
||||||
|
| LN-C5 | `.legal` legal notice line (monospace, dashed link underlines) | Terms/privacy link |
|
||||||
|
| LN-C6 | `.back-home` | Return-to-home link |
|
||||||
|
| LN-C7 | `@media (max-width: 900px)` hides `.route-demo` and `.n-bottom` | Responsive rule |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing Scripts / Interactivity
|
||||||
|
|
||||||
|
| # | Zip has | Vue status | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| LN-I1 | Password eye-toggle: vanilla JS swaps `pw.type` + toggles two SVG icons by `display` | Vue: reactive `showPassword` ref + Icon component. Functionally equivalent | **none** — Vue solution is better |
|
||||||
|
| LN-I2 | Login submit: loading spinner class, success state (`✓ 登录成功` label text change + green background), redirect to Dashboard.html | Vue: `isLoading` spinner, redirect via `router.push('/dashboard')`; no "✓ 登录成功" button text mutation | **cosmetic** — success toast used instead |
|
||||||
|
| LN-I3 | Basic non-empty check only (`if (!form.email.value || !form.password.value)`) | Vue: full regex email validation + min-length password validation + Turnstile + 2FA flow | **none** — Vue is more complete |
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. RegisterView
|
||||||
|
|
||||||
|
**Zip:** `docs/design-drafts/v2/Register.html`
|
||||||
|
**Vue:** `frontend/src/views/auth/RegisterView.vue`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Copy / Text Deltas
|
||||||
|
|
||||||
|
| # | Zip (HTML) | Vue (SFC) | Severity | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| R-T1 | Narrative kicker: `// 5 分钟开始用` | Vue: absent (no kicker) | **important** | |
|
||||||
|
| R-T2 | Narrative headline: `<span class="amber">N</span> 个订阅 → <span class="cyan">1</span> 个 key` | Vue: `<span class="num-5">5</span> 个订阅 → <span class="num-1">1</span> 个 key` | **important** | Same as Login: "N" vs hardcoded "5" |
|
||||||
|
| R-T3 | Form title: `创建账户` (h1) + sub: `注册即送 <b style="color:var(--cyan)">$5</b> 测试积分` | Vue: `{{ t('auth.puroRegisterTitle') }}` + `{{ t('auth.puroRegisterSub') }}` | **important** | The "$5 bonus credit" hook in the sub-title is a key conversion element; Vue moves it to i18n |
|
||||||
|
| R-T4 | Password placeholder: `至少 8 位,含字母与数字` | Vue: `{{ t('auth.createPasswordPlaceholder') }}` | **cosmetic** | Verify i18n value matches |
|
||||||
|
| R-T5 | `bonus-note` block: green-bordered callout `+$5 完成注册即送 $5 测试积分 —— 够你跑几万次 Claude 请求。` | Vue: **absent** | **important** | High-conversion element entirely missing |
|
||||||
|
| R-T6 | Steps panel title: `// 下一步` with 3 numbered onboarding steps (创建账户 → 绑定订阅 → 生成 key) | Vue: **absent** | **important** | The onboarding journey explainer is missing |
|
||||||
|
| R-T7 | `n-bottom` provider list + `live · ai.puro.im · operational` | Vue: `auth-narrative-foot` = `Claude · ChatGPT · Codex · Gemini` only | **important** | Live status indicator missing |
|
||||||
|
| R-T8 | `back-home` link: `← 返回首页` | Vue: absent | **cosmetic** | |
|
||||||
|
| R-T9 | Footer: `已有账户?<a>登录</a>` | Vue: i18n `t('auth.alreadyHaveAccount')` + `t('auth.signIn')` | **cosmetic** | Verify i18n values |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure Deltas
|
||||||
|
|
||||||
|
| # | What zip has | Vue status | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| R-S1 | Onboarding `.steps` panel (3-step card in the narrative: 1. Create account, 2. Bind subscription, 3. Generate key) | Vue: **entirely absent** | **important** |
|
||||||
|
| R-S2 | Email field: inline validation green checkmark (`valid-ico` SVG) appears when email format is valid | Vue: shows `input-error-text` below field on error; no inline success icon on valid state during typing | **cosmetic** |
|
||||||
|
| R-S3 | Password field: 4-bar strength meter (`pw-strength` div) + text label (`// strength · 弱/中/强/极强`) | Vue: no strength meter; only a static `input-hint` paragraph | **important** |
|
||||||
|
| R-S4 | Confirm-password field with: `valid-ico` checkmark + `match-hint` (`// matched` / `// passwords do not match`) | Vue: no confirm-password field at all | **important** |
|
||||||
|
| R-S5 | Terms checkbox (`我已阅读并同意 服务条款 与 隐私政策`) — submit button disabled until checked | Vue: no terms checkbox | **important** |
|
||||||
|
| R-S6 | `bonus-note` callout block | Vue: absent | **important** |
|
||||||
|
| R-S7 | Password confirm field activates submit only when: email valid + score ≥ 2 + passwords match + terms checked | Vue: password min-length 6, no confirm field, no terms gate | **important** |
|
||||||
|
| R-S8 | Vue-only: invitation code field (conditional on `invitationCodeEnabled`) | Zip: absent | n/a (Vue enhancement) |
|
||||||
|
| R-S9 | Vue-only: promo code field (conditional on `promoCodeEnabled`) | Zip: absent | n/a (Vue enhancement) |
|
||||||
|
| R-S10 | Vue-only: Turnstile CAPTCHA widget | Zip: absent | n/a (Vue enhancement) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Visuals / SVG
|
||||||
|
|
||||||
|
| # | Delta | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| R-V1 | Brand in narrative: inline SVG hexagon | Vue: `⬢` emoji | **important** |
|
||||||
|
| R-V2 | Steps panel: numbered circles (`step-num`) with cyan border and active fill | Vue: absent | **important** |
|
||||||
|
| R-V3 | `bonus-note` callout: green border `rgba(52,211,153,0.2)` + `+$5` chip in green background | Vue: absent | **important** |
|
||||||
|
| R-V4 | Password strength bars: 4 `<span class="bar">` elements that fill red/amber/cyan/green based on `data-score` | Vue: absent | **important** |
|
||||||
|
| R-V5 | Match-hint monospace text (`// matched` in green / `// passwords do not match` in red) | Vue: absent | **important** |
|
||||||
|
| R-V6 | `valid-ico` green SVG checkmark inside input-wrap when field is valid | Vue: absent | **cosmetic** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing CSS (zip has, Vue scoped missing)
|
||||||
|
|
||||||
|
| # | Missing rule | Impact |
|
||||||
|
|---|---|---|
|
||||||
|
| R-C1 | `.steps`, `.steps-title`, `.step`, `.step-num`, `.step.active .step-num`, `.step-text .k` | Onboarding steps panel |
|
||||||
|
| R-C2 | `.pw-strength[data-score]` + `.bar` + 4 score-tinted variants | Password strength meter |
|
||||||
|
| R-C3 | `.pw-hint[data-score]` + `.val` color variants | Strength label |
|
||||||
|
| R-C4 | `.match-hint`, `.match-hint.mismatch`, `.match-hint.ok` | Confirm-password match feedback |
|
||||||
|
| R-C5 | `.valid-ico` + `.input-wrap input.ok ~ .valid-ico` | Inline valid checkmark |
|
||||||
|
| R-C6 | `.bonus-note` + `.bonus-note .emoji` | Registration bonus callout |
|
||||||
|
| R-C7 | `.n-bottom` + live-dot styles (same as Login) | Bottom status bar |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing Scripts / Interactivity
|
||||||
|
|
||||||
|
| # | Zip has | Vue status | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| R-I1 | Password strength scorer (`scorePw()`) — counts: ≥8 chars, mixed case, digit, special/long; maps score 0-4 to `['—','弱','中','强','极强']` | Vue: no strength meter, only min-length check | **interactive** |
|
||||||
|
| R-I2 | Real-time confirm-password match checker with `// matched` / `// passwords do not match` feedback | Vue: no confirm-password field | **interactive** |
|
||||||
|
| R-I3 | Email inline validation (adds `.ok` class → shows green checkmark) | Vue: error shown on submit, not live | **interactive** |
|
||||||
|
| R-I4 | Submit button gated on: email `.ok` + `score >= 2` + passwords match + terms checkbox checked | Vue: submit gated only on loading + optional Turnstile | **interactive** |
|
||||||
|
| R-I5 | Success animation: button text changes to `✓ 注册成功,正在跳转...` + green background, then redirects | Vue: toast notification + router.push — no button animation | **cosmetic** |
|
||||||
|
|
||||||
|
---
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. DocsView
|
||||||
|
|
||||||
|
**Zip:** `docs/design-drafts/v2/Docs.html`
|
||||||
|
**Vue:** `frontend/src/views/docs/DocsView.vue`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Copy / Text Deltas
|
||||||
|
|
||||||
|
| # | Zip (HTML) | Vue (SFC) | Severity | Notes |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| D-T1 | Page title / h1: `快速开始` + lede: `PURO AI 提供一个统一的 OpenAI 兼容端点 —— 你已有的 SDK 代码只需要改 base_url 和 api_key 两行…整个过程通常不超过 5 分钟。` | Vue h1: `快速接入 PURO AI` + subtitle: `三步走:拿 key → 配 base_url → 发请求` | **important** | Zip's lede is a full paragraph explaining the product value and 5-minute setup claim; Vue's subtitle is a terse 3-step summary. Different framing. |
|
||||||
|
| D-T2 | Step-card labels: `STEP 01 / 绑定订阅`, `STEP 02 / 创建 API Key`, `STEP 03 / 切换 base_url` with description paragraphs | Vue: no step-card grid at all | **important** | The `quick-grid` 3-card visual summary is entirely absent from Vue |
|
||||||
|
| D-T3 | Section "① 绑定你的订阅": full paragraph about Dashboard → OAuth flow + KMS callout `凭证通过 AES-256 加密存储在隔离的 KMS 中…` | Vue: Section "1. 获取 API key": `当前 PURO AI 不开放自助注册付费。联系管理员获取:admin@puro.im` + note `未来通过 iShare 入口开放订阅购买。` | **important** | Completely different content: zip describes the self-serve OAuth flow; Vue describes an invite-only alpha state. Vue reflects current operational reality; zip is the target product state. |
|
||||||
|
| D-T4 | Section "② 创建 API Key": instructions for `Dashboard → API Keys → 创建 Key`, advice to create per-client keys for safe revocation | Vue: no dedicated "create API key" section | **important** | Zip's key-management guidance (per-client keys, revoke without affecting others) is absent from Vue |
|
||||||
|
| D-T5 | Section "③ 发送第一个请求": prose introducing OpenAI + Anthropic format support, code-panel with Python/Node/cURL/Anthropic SDK tabs | Vue: "2. Codex CLI 接入", "3. Claude Code 接入", "4. curl 直连测试" — three separate sections each with a plain `<pre>` block | **important** | Zip organises code samples under one tabbed panel; Vue splits them into three separate sections with different tool focus (Codex CLI is a Vue-only section) |
|
||||||
|
| D-T6 | Codex CLI section: **absent from zip** | Vue: Section "2. Codex CLI 接入" with `~/.codex/config.toml` and `~/.codex/auth.json` config samples | n/a (Vue enhancement) | Vue-only section targeting Codex CLI users; not in zip |
|
||||||
|
| D-T7 | Models table: `claude-sonnet-4-5`, `claude-opus-4`, `claude-haiku-4-5`, `gpt-5`, `gpt-5-codex`, `gemini-2.5-pro`, `gemini-2.5-flash` with PROVIDER / 池 / 上下文 / 状态 columns | Vue model-list: `gpt-5.4`, `gpt-5.4-codex`, `claude-opus-4-7`, `claude-sonnet-4-6`, `gemini-2.5-pro`, `gemini-2.5-flash` (plain `<ul>`) | **important** | Model names differ (e.g. `claude-sonnet-4-5` vs `claude-sonnet-4-6`; `gpt-5` vs `gpt-5.4`); zip has `claude-haiku-4-5` which Vue omits; zip has rich table structure vs Vue's plain list |
|
||||||
|
| D-T8 | Section "支持的 base_url": three URLs (`/v1`, `/anthropic`, `/google`) with explanations + amber callout about unified key across all formats | Vue: no dedicated base_url section; curl examples hard-code `/responses` and `/v1/messages` endpoints | **important** | The `/anthropic` and `/google` base_url variants and the "one key for all formats" callout are absent from Vue |
|
||||||
|
| D-T9 | Section "下一步": integration links for Claude Code, Cursor, Cline/Roo Code, Continue | Vue: no "next steps" section | **important** | Onward-journey links that guide users to integration docs are absent |
|
||||||
|
| D-T10 | Breadcrumb trail: `Docs / Getting Started / 快速开始` (monospace, with cyan `.current` span) | Vue: absent | **cosmetic** | |
|
||||||
|
| D-T11 | Nav "active" link: `文档` has `.active` class in zip nav | Vue nav: no `.active` state on current-page link | **cosmetic** | |
|
||||||
|
| D-T12 | Nav links: 产品 / 定价 / 文档 / 设计系统 | Vue nav links: 首页 / `#codex` / `#claude-code` / `#curl` (in-page anchors) | **important** | Zip nav is a proper site-level navigation; Vue nav links to in-page anchors only and removes Pricing + Design System entries |
|
||||||
|
| D-T13 | Nav CTA: `Dashboard` (ghost) + `开始使用` (primary) | Vue nav CTA: `登录 →` (primary only, no ghost button) | **important** | Zip has two CTA buttons (authenticated + unauthenticated states); Vue has one |
|
||||||
|
| D-T14 | Left sidebar brand-line: `// docs · v1` (monospace kicker) | Vue: no sidebar at all | **important** | The `// docs · v1` kicker text is a brand-pattern element; entire sidebar is missing |
|
||||||
|
| D-T15 | Right TOC label: `On this page` (monospace uppercase) with 6 anchor links | Vue: no right-column TOC | **important** | |
|
||||||
|
| D-T16 | Page-footer nav links: `← 上一页 / 产品概念` and `下一页 → / 绑定你的订阅` | Vue: no page-footer nav | **important** | Prev/next doc navigation absent |
|
||||||
|
| D-T17 | Section "6. 问题反馈" + callout with admin@puro.im | Vue has equivalent "6. 问题反馈" section (same email) | — | This matches; no delta |
|
||||||
|
| D-T18 | Callout (cyan) security note with SVG lock icon: `凭证通过 AES-256 加密存储在隔离的 KMS 中…` | Vue callout: plain `<a>` email link inside div, no icon | **important** | Zip callout has inline SVG icon + richer copy; Vue callout is a minimal email link only |
|
||||||
|
| D-T19 | Callout (amber, warning) key portability note: `一个 sk-puro-* 可以同时用于三种 base_url…` | Vue: no amber/warning callout variant | **important** | Amber callout variant used in zip for advisory messages is entirely absent from Vue |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Structure Deltas
|
||||||
|
|
||||||
|
| # | What zip has | Vue status | Severity |
|
||||||
|
|---|---|---|---|
|
||||||
|
| D-S1 | 3-column grid layout: `docs-layout` (240px left nav + 1fr main + 220px right TOC) | Vue: single-column `.container` with centred `.docs-body`; no sidebar, no TOC column | **important** |
|
||||||
|
| D-S2 | Sticky left sidebar (`docs-nav`) with `brand-line`, search `<input>`, 4 nav-section groups (Getting Started / API Reference / Integrations / Advanced) | Vue: absent | **important** |
|
||||||
|
| D-S3 | Sticky right TOC (`docs-toc`) with `On this page` label + 6 anchor links (some `.sub` indented) | Vue: absent | **important** |
|
||||||
|
| D-S4 | Breadcrumb bar (`docs-crumbs`) above h1 | Vue: absent | **cosmetic** |
|
||||||
|
| D-S5 | Quick-start step-card grid (`quick-grid`) — 3 cards with monospace STEP 01/02/03 labels | Vue: absent | **important** |
|
||||||
|
| D-S6 | Code panel (`code-panel`) with `panel-tabs` bar (Python / Node.js / cURL / Anthropic SDK tabs) + `copy-code` button | Vue: plain `<pre class="mono">` blocks with no chrome, no tab switching, no copy button | **important** |
|
||||||
|
| D-S7 | Models table (`models-table`) — `<table>` with thead/tbody, provider badge spans (`.provider.claude/gpt/gemini` + `.dot`), status badges (`.badge.green/.amber`) | Vue: `<ul class="model-list">` plain list | **important** |
|
||||||
|
| D-S8 | Two distinct callout variants: default cyan and `.amber` warning, each with inline SVG icon (`<svg>` lock / triangle) | Vue: single callout style with no icon and no amber variant | **important** |
|
||||||
|
| D-S9 | Page-footer prev/next nav (`page-foot` with two `foot-link` cards) | Vue: absent | **important** |
|
||||||
|
| D-S10 | Nav `docs-top` with `backdrop-filter: blur(12px)` sticky bar + brand SVG hex | Vue: plain `.nav` (no `docs-top` class, no sticky backdrop, `⬢` emoji brand) | **important** |
|
||||||
|
| D-S11 | Left nav search input (`nav-search` with SVG magnifier pseudo-element) | Vue: absent | **cosmetic** |
|
||||||
|
| D-S12 | `h2` headings with `::before` 3px cyan vertical bar accent | Vue `h2`: `border-bottom: 1px solid var(--border)` only — no left-bar accent | **important** |
|
||||||
|
| D-S13 | `<section id="models">` — Vue-only "5. 支持的模型" section maps to zip's models table | Equivalent content present but rendered as list vs table (see D-T7) | — |
|
||||||
|
| D-S14 | Vue-only: `docs-hero` centered section above `docs-body` | Zip: no separate hero section; content starts directly with breadcrumbs + h1 inside `docs-body` | n/a (Vue adds hero) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Visuals / SVG
|
||||||
|
|
||||||
|
| # | Delta | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| D-V1 | Nav brand: inline SVG hexagon (same double-path as Landing/Login/Register: outer stroke + inner filled path) | Vue: `⬢` Unicode emoji | **important** |
|
||||||
|
| D-V2 | Left nav SVG search icon (inline `url("data:image/svg+xml...")` magnifier in `::before` pseudo) | Vue: no sidebar/search | n/a (structure missing) |
|
||||||
|
| D-V3 | `h2::before` pseudo-element: 3px × 22px cyan vertical accent bar | Vue `h2`: plain bottom border only | **important** |
|
||||||
|
| D-V4 | Code syntax token spans: `.com` (grey italic comments), `.kw` (pink keywords), `.str` (green strings), `.fn` (amber functions), `.prop` (blue properties), `.num` (orange numbers) | Vue pre blocks: `.str` (cyan), `.kw` (amber), `.cm` (grey) — only 3 token types, different colour mapping | **cosmetic** |
|
||||||
|
| D-V5 | Models table: `.provider` span with 6px `.dot` circle (colour per provider) + "Claude / ChatGPT / Gemini" text | Vue model list: no provider badge, provider shown as inline text only | **important** |
|
||||||
|
| D-V6 | Models table: `.badge.green` ("OK") and `.badge.amber` ("BETA") status chips | Vue: no status badges | **important** |
|
||||||
|
| D-V7 | Callout icons: inline SVG lock (14×14) for cyan callout; inline SVG triangle-warning for amber callout | Vue: no icon in callouts | **important** |
|
||||||
|
| D-V8 | Quick-grid cards: `::before` / hover `translateY(-2px)` lift animation | Vue: no step cards | n/a (structure missing) |
|
||||||
|
| D-V9 | `docs-nav-item.active`: cyan text + `rgba(34,211,238,0.06)` tinted background | Vue: no sidebar nav | n/a (structure missing) |
|
||||||
|
| D-V10 | `docs-toc a.active`: cyan text + left `2px solid var(--cyan)` border + `rgba(34,211,238,0.03)` bg | Vue: no TOC | n/a (structure missing) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing CSS (zip has, Vue scoped missing)
|
||||||
|
|
||||||
|
| # | Missing rule / component | Impact |
|
||||||
|
|---|---|---|
|
||||||
|
| D-C1 | `.docs-layout` 3-column grid, `.docs-nav`, `.docs-nav .brand-line`, `.docs-nav-section`, `.docs-nav-label`, `.docs-nav-item`, `.docs-nav-item.active` | Entire left sidebar nav system |
|
||||||
|
| D-C2 | `.docs-nav .nav-search` + `input` + `::before` SVG pseudo | Sidebar search bar |
|
||||||
|
| D-C3 | `.docs-toc`, `.docs-toc-label`, `.docs-toc a`, `.docs-toc a.active`, `.docs-toc a.sub` | Right-column TOC |
|
||||||
|
| D-C4 | `.docs-crumbs`, `.docs-crumbs .sep`, `.docs-crumbs .current` | Breadcrumb bar |
|
||||||
|
| D-C5 | `.docs-top` (sticky backdrop-blur nav variant) | Docs-specific nav bar |
|
||||||
|
| D-C6 | `.docs-body h2::before` (3px cyan left-bar accent pseudo-element) | h2 section accent bar; Vue uses bottom-border instead |
|
||||||
|
| D-C7 | `.code-panel`, `.code-panel .panel-tabs`, `.tabs-inner button`, `.tabs-inner button.active`, `.copy-code`, `.copy-code:hover` | Tabbed code panel chrome |
|
||||||
|
| D-C8 | `.code-panel pre .com`, `.kw`, `.str`, `.fn`, `.prop`, `.num` (6 token colour rules) | Full syntax highlighting palette (Vue has only 3 token types) |
|
||||||
|
| D-C9 | `.quick-grid`, `.quick-card`, `.quick-card:hover`, `.quick-card .num`, `.quick-card h4`, `.quick-card p` | Step-card quick-start grid |
|
||||||
|
| D-C10 | `.models-table`, `.models-table th`, `.models-table td`, `.models-table tr:hover`, `.models-table .mono` | Styled models table |
|
||||||
|
| D-C11 | `.callout .icon` (flex + cyan colour), `.callout.amber`, `.callout.amber .icon` | Callout icon + amber variant |
|
||||||
|
| D-C12 | `.docs-body h2` flex + `gap: 12px` + `align-items: center` | h2 layout for left-bar + text alignment |
|
||||||
|
| D-C13 | `.page-foot`, `.foot-link`, `.foot-link:hover`, `.foot-link .dir`, `.foot-link .title`, `.foot-link.next` | Prev/next page footer nav |
|
||||||
|
| D-C14 | `.tabs`, `.tab`, `.tab:hover`, `.tab.active` (standalone tab-bar component) | Tab bar used in docs layout |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing Scripts / Interactivity
|
||||||
|
|
||||||
|
| # | What zip has | Vue status | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| D-I1 | Code-panel tab switching: clicking a `button` inside `.tabs-inner` shows/hides the corresponding `<pre>` block and toggles `.active` on the tab | Vue: all code blocks are always visible in separate `<section>` elements; no tab switching | **interactive** |
|
||||||
|
| D-I2 | Copy-to-clipboard: clicking `.copy-code` copies the current tab's `<pre>` text content, momentarily changes label to `✓ 복사됨` (or similar confirmation) | Vue: no copy button on any code block | **interactive** |
|
||||||
|
| D-I3 | TOC active-link tracking: scrolling the page updates which `.docs-toc a` has `.active` class (IntersectionObserver or scroll listener implied) | Vue: no TOC | n/a (structure missing) |
|
||||||
|
| D-I4 | Sidebar nav active-item tracking: clicked nav item gets `.active` class with cyan highlight | Vue: no sidebar | n/a (structure missing) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Recommended Fix List
|
||||||
|
|
||||||
|
| Priority | ID | Fix | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | D-S1 / D-S2 / D-S3 | Implement 3-column `docs-layout` with sticky left sidebar and right TOC | Structural foundation; all other doc-nav deltas depend on this. Estimate: 3–4h |
|
||||||
|
| 2 | D-S6 / D-I1 / D-I2 | Replace plain `<pre>` blocks with `.code-panel` component: tab bar (Python/Node/cURL/Anthropic SDK), copy-to-clipboard button, traffic-light dots | Highest developer-credibility element on the page. Port from zip JS or use Vue `ref`+`v-show`. Estimate: 2h |
|
||||||
|
| 3 | D-S7 / D-V5 / D-V6 | Replace `<ul class="model-list">` with `<table class="models-table">` including provider badge spans and OK/BETA status chips | Table format communicates provider, pool, context and status at a glance. Estimate: 1h |
|
||||||
|
| 4 | D-V1 / D-V3 | Replace `⬢` emoji brand with inline SVG hexagon (same as Landing fix); add `h2::before` cyan accent bar via CSS | Both are cosmetic but high-frequency — visible on every section heading. Estimate: 0.5h |
|
||||||
|
| 5 | D-T3 / D-T8 | Rewrite Section 1 to reflect OAuth subscription-binding flow (once feature exists); add `支持的 base_url` section with three endpoint variants and amber "one key for all formats" callout | Content alignment with zip product vision. May be deferred until backend supports self-serve OAuth. Estimate: 1h when ready |
|
||||||
|
| 6 | D-T9 / D-S9 | Add "下一步" integration-links section + `page-foot` prev/next navigation | Keeps users moving through the docs funnel. Estimate: 0.5h |
|
||||||
|
| 7 | D-S5 / D-C9 | Add `quick-grid` three-step card summary below the lede | Visual orientation aid above the long-form content. Estimate: 0.5h |
|
||||||
|
| 8 | D-V7 / D-C11 | Add SVG icon support to callout component (lock icon for info, triangle for amber/warning), add `.callout.amber` variant | Required to port the base_url portability warning. Estimate: 0.5h |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
### Total Deltas Found
|
||||||
|
|
||||||
|
| Category | Important | Cosmetic | Interactive |
|
||||||
|
|---|---|---|---|
|
||||||
|
| LandingView | 15 | 7 | 1 |
|
||||||
|
| LoginView | 5 | 9 | 0 (Vue is better) |
|
||||||
|
| RegisterView | 9 | 6 | 4 |
|
||||||
|
| DocsView | 16 | 3 | 2 |
|
||||||
|
| **Total** | **45** | **25** | **7** |
|
||||||
|
|
||||||
|
**Grand total: 77 deltas** (45 important + 25 cosmetic + 7 interactive)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Top 10 Most Impactful User-Visible Deltas
|
||||||
|
|
||||||
|
1. **L-S9 / L-S10 / L-S11 — Landing: CTA banner + Pricing section + FAQ entirely missing**
|
||||||
|
`LandingView.vue` — These three sections are the primary conversion funnel below the fold. The design shows a compelling mid-page CTA (`把订阅变成 API — 5 分钟`), a full 3-tier pricing grid, and 6 FAQ items. All are absent. Without them, the landing page ends abruptly after the dashboard mockup.
|
||||||
|
|
||||||
|
2. **LN-T1 / R-T2 — Login + Register narrative: "N" hardcoded as "5"**
|
||||||
|
`LoginView.vue` lines 8-9, `RegisterView.vue` lines 8-9 — Zip uses abstract variable `N` (amber) to convey "however many subscriptions you have". Vue hardcodes `5`, which is factually wrong for users with 1–4 or more than 5 subscriptions.
|
||||||
|
|
||||||
|
3. **L-S6 / L-S7 — Dashboard mockup: no sidebar, no donut chart**
|
||||||
|
`LandingView.vue` — The zip's dashboard mockup has a sidebar nav (7 menu items) and a 2-panel chart area (line chart + model-distribution donut). Vue shows only a line chart and no sidebar. This is visible in the hero social-proof section used to sell the product.
|
||||||
|
|
||||||
|
4. **R-S3 / R-S4 / R-S5 — Register: no password strength meter, no confirm-password, no terms checkbox**
|
||||||
|
`RegisterView.vue` — Three mutually reinforcing UX elements from the zip are absent. The strength meter educates users; the confirm field prevents typos; the terms gate is a legal/trust signal. Combined, their absence degrades register-page quality significantly.
|
||||||
|
|
||||||
|
5. **R-T5 / R-S6 — Register: `+$5 bonus-note` callout missing**
|
||||||
|
`RegisterView.vue` — The green-bordered "完成注册即送 $5 测试积分 —— 够你跑几万次 Claude 请求" callout is a direct conversion driver. It's visible below the submit button in the zip; entirely absent in Vue.
|
||||||
|
|
||||||
|
6. **R-S1 — Register: onboarding steps panel missing**
|
||||||
|
`RegisterView.vue` — The narrative left-panel in zip shows a 3-step numbered checklist (create account → bind subscription → generate key). This explains what happens *after* registration and reduces drop-off. Vue shows a generic headline only.
|
||||||
|
|
||||||
|
7. **L-V1 / LN-V1 / R-V1 — SVG hexagon logo replaced by ⬢ emoji everywhere**
|
||||||
|
`LandingView.vue`, `LoginView.vue`, `RegisterView.vue` — The double-path SVG hex (`stroke` outline + inner `fill`) renders as a crisp cyan icon. The `⬢` Unicode character renders inconsistently across macOS/Windows/mobile and lacks the inner fill detail. Affects brand recognition.
|
||||||
|
|
||||||
|
8. **L-S3 / L-V3 — Model wall: SVG logos replaced by color dots**
|
||||||
|
`LandingView.vue` — Each model card in the zip has a distinct inline SVG icon (Claude chevron, GPT circle-crosshair, Codex bracket, Gemini star, dots). Vue renders only 10px color dots. On a section designed to communicate provider breadth, icon identity matters.
|
||||||
|
|
||||||
|
9. **LN-S1 — Login: route-demo panel entirely absent**
|
||||||
|
`LoginView.vue` — The live-routing demonstration block (`POST /v1/chat/completions → claude-pool-03 → 200 OK · 213ms`) is the most concrete product proof-point on the login page. It communicates technical credibility to developer users seeing the login page for the first time. Entirely missing from Vue.
|
||||||
|
|
||||||
|
10. **L-T7 / L-T8 / L-T10 — Landing features: different headline, no sub, no bullets**
|
||||||
|
`LandingView.vue` — Section headline changed from `付一次订阅,用起一整个模型池` to `一套 key,三件武器`. The subtitle and all 9 feature bullet items (API compatibility claims, failover details, export options) are absent. Together these make the features section significantly less informative.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Estimated Work
|
||||||
|
|
||||||
|
| Scope | Est. hours |
|
||||||
|
|---|---|
|
||||||
|
| Fix "N vs 5" in Login + Register narrative (L-T1 / R-T2) | 0.25h |
|
||||||
|
| Restore SVG hexagon logo across all 3 views | 0.5h |
|
||||||
|
| Restore Landing: CTA banner + Pricing section + FAQ | 4–6h |
|
||||||
|
| Restore Landing: model-card SVG logos + status chips | 1.5h |
|
||||||
|
| Restore Landing: code-frame tabs + traffic lights | 1h |
|
||||||
|
| Restore Landing: dashboard sidebar + donut chart | 2h |
|
||||||
|
| Restore Login: route-demo panel + n-bottom status bar | 1.5h |
|
||||||
|
| Restore Login: remember-me checkbox + legal notice | 0.5h |
|
||||||
|
| Restore Register: strength meter + confirm field + terms checkbox | 2h |
|
||||||
|
| Restore Register: steps panel + bonus-note callout | 1h |
|
||||||
|
| CSS parity (Landing/Login/Register) | 2–3h |
|
||||||
|
| Docs: 3-column layout + sticky sidebar + right TOC | 3–4h |
|
||||||
|
| Docs: tabbed code-panel + copy-to-clipboard | 2h |
|
||||||
|
| Docs: models table with provider/status badges | 1h |
|
||||||
|
| Docs: SVG hex + h2 accent bar + callout icon/amber variant | 1h |
|
||||||
|
| Docs: base_url section + page-footer nav + quick-grid | 1h |
|
||||||
|
| **Total (all important + interactive deltas)** | **~25–30h** |
|
||||||
|
| **Top 5 only (Landing CTA/pricing/FAQ + Docs layout + code-panel)** | **~12–14h** |
|
||||||
@@ -5921,7 +5921,6 @@ export default {
|
|||||||
getKey: {
|
getKey: {
|
||||||
heading: '1. Get your API key',
|
heading: '1. Get your API key',
|
||||||
desc: 'PURO AI is currently invite-only. Contact the admin to get access:',
|
desc: 'PURO AI is currently invite-only. Contact the admin to get access:',
|
||||||
note: 'Subscription self-purchase via the iShare portal is coming soon.',
|
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
heading: '2. Codex CLI setup',
|
heading: '2. Codex CLI setup',
|
||||||
|
|||||||
@@ -6114,7 +6114,6 @@ export default {
|
|||||||
getKey: {
|
getKey: {
|
||||||
heading: '1. 获取 API key',
|
heading: '1. 获取 API key',
|
||||||
desc: '当前 PURO AI 不开放自助注册付费。联系管理员获取:',
|
desc: '当前 PURO AI 不开放自助注册付费。联系管理员获取:',
|
||||||
note: '未来通过 iShare 入口开放订阅购买。',
|
|
||||||
},
|
},
|
||||||
codex: {
|
codex: {
|
||||||
heading: '2. Codex CLI 接入',
|
heading: '2. Codex CLI 接入',
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
<div class="callout">
|
<div class="callout">
|
||||||
<a href="mailto:admin@puro.im">admin@puro.im</a>
|
<a href="mailto:admin@puro.im">admin@puro.im</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="note">{{ $t('docs.sections.getKey.note') }}</p>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="codex" class="docs-section">
|
<section id="codex" class="docs-section">
|
||||||
|
|||||||
Reference in New Issue
Block a user