Echo(公众号写作工作台)
多公众号写作管理工具,给 resley 自己用,公众号「5 分钟 AI」。线上 echo.mvp.restry.cn。
概述
2026-04-25 在 fries-mac 这边一天内从设计 logo 到部署完上线 Phase 1。04-27 完成 Phase 2.0/2.1(schema + 内容中心)。04-28 一天密集 push 完 Phase 2.2/2.3 全套(编辑器 + 5 步 wizard + 改写双版本 + 公众号排版 + 真出图 + 系统设置 + 删除 + UserBadge + 移动端 + UI-SPEC.md),从骨架升到产品级。
架构
| 项 | 值 |
|---|---|
| 域名 | echo.mvp.restry.cn |
| 端口 | 3795(部署连撞 3794/3795) |
| 部署 | 走 deployer |
| 本地路径 | ~/projects/echo/ |
| 仓库 | https://github.com/Restry/echo(私有,04-27 新建) |
| 技术栈 | Next.js 16 + React 19 + Drizzle + NextAuth v5 + PostgreSQL |
| AI | Azure OpenAI(gpt-5.4 chat + gpt-image-2 image,resley-sweden-ext.openai.azure.com) |
| DB | mvp-deployer 共享 PG 上独立 echo 库(owner image_studio) |
| 表 | 9 张:ec_users / ec_articles / ec_pubaccounts / ec_sessions / ec_accounts_oauth / ec_settings / ec_verification_tokens / ec_writing_kits / ec_article_assets(统一 ec_ 前缀) |
| 管理员 | 无管理员概念,普通用户即可(/register 自助注册 + 微信扫码 + 微信内 OAuth) |
信息架构(4 个一级页面)
/login 登录页(邮箱密码 + 微信扫码 + 微信内 OAuth)
/dashboard
├─ /accounts 账号管理(公众号档案 CRUD)
├─ /content 内容中心(核心)
│ └─ /:id/edit 文章编辑器
└─ /settings 系统设置(个人 / 写作偏好 / AI 模型只读)
侧边栏:Logo · 账号管理 · 内容中心 · 系统设置 · UserBadge · 退出 · Image Studio CTA。 移动端:sidebar 收起,顶部 header 露出 logo / UserBadge / 退出。
完整 UI 规范:docs/UI-SPEC.md(commit 970f8f5,docs/business-flow.excalidraw 配套业务流程图)。
Phase 1(04-25)— 骨架
logo / 注册 / 公众号 CRUD / 7 张表 schema。Bug 三件:logo 容器 ghost “o” 切断、Base UI MenuItem onSelect 不触发 AlertDialog、postgres URL ?schema=public 报错。
Phase 2.0 / 2.1(04-27)— 内容中心 + writing kits
drizzle 加 ec_writing_kits(含 5 个 5min-ai seed:product-card / style-fingerprint v3 / 选题 / 内容结构 / 配图)+ ec_article_assets;ec_pubaccounts 加 writingKitId FK。内容中心 server components(列表/详情默认 RSC,编辑器 'use client'),文章 CRUD + 状态机 draft/unsent/sent。
Phase 2.2(04-28 早)— 文章编辑器(CC 干)
| 文件 | 内容 |
|---|---|
actions.ts | 3 server actions(createArticleAction / updateArticleAction / setArticleStatusAction),每个第一行 requireUserId() |
content/new/page.tsx | 0/1/多公众号三种分支 |
content/[id]/edit/page.tsx | 服务端取详情 + 校验 + Toaster |
_components/Editor.tsx | client 编辑器,500ms 防抖保存、blur 立即 flush、AI 改写 stub、状态切换、资产卡 |
Phase 2.3(04-28 下午)— 5 步 wizard + A/B 改写 + 排版 + 封面 prompt
派 CC proc_7065f3ebaec2(high effort, 400 turns),HEAD 64f3568:
| M | 范围 |
|---|---|
| M1 | brief → 选题 → 标题 → 大纲 → 正文 4 步 wizard(4 个 SSE API + draft-prompts.ts + AiDraftWizard.tsx) |
| M2 | 改写选区出 A/B 双版本(rewrite route 加 variants=2,UI 双栏 diff,真并行流) |
| M3 | 公众号排版导出(wechat-format.ts:h2/h3 → 红 span #D0021B、消除列表平铺、文末追加 CTA、modal 复制) |
| M4 | 封面 prompt 生成(按 skill §9 模板,含「文章封面:」前缀 + 16:9 + 2K) |
| M5 | systemPrompt 补 contentCalendar(漏了的第 5 个 KitDoc 字段)+ 移动端紧凑控制条按钮露出 |
E2E 5 件套全 PASS(CC 用 browser-agent 端口 4821 真浏览器跑)。
Phase 2.3+(04-28 晚)— 真出图 + 系统设置 + 删除(M1-M8)
派 CC proc_32ab57bc61f3,HEAD be8bdb0,上线 commit fdae06b。
| M | 内容 |
|---|---|
| M1 图片存储基建 | data/article-images/<aid>/<uuid>.png + 鉴权 GET API route |
| M2 封面图直出 | azure-image.ts + /api/ai/cover-image,CoverPromptDialog 加「用此 prompt 出图」,写 coverUrl,编辑器顶部预览 |
| M3 段落配图 | /api/ai/paragraph-image,编辑器「🖼 为这段配图」按钮,AI 生英文 prompt → 出图 → 自动  插入 |
| M4 一键全文配图 | /api/ai/auto-illustrate SSE 流式,按 skill §6 规则筛段(首段必插、紧跟 H2 跳过、末段不插、已含 ![] 跳过、cap 6、串行),进度 modal |
| M5 Image Studio CTA | sidebar 底部 + 4 处出图位置都引流 |
| M6 系统设置 | 新建 /dashboard/settings,nav 不再灰,3 卡片(个人 / 写作偏好 / 模型只读) |
| M7 新建文章 UX | 单账号直接 submit 跳编辑器,删冗余中间页 |
| M8 删除草稿 | 列表 hover 🗑 + 编辑器顶部红字按钮,二次确认 cascade 删 |
关键约束:
- Azure gpt-image-2 v1 surface(
api-keyheader +api-version=preview) maxDuration=600+runtime=nodejs- 单张 5min,必 8 次指数退避
- 不动 schema(coverUrl 已存在)
E2E 7 项 5 ✅ + 2 ⚠(T3 confirm dialog 没弹 / T7 删后没 redirect 不报 404)→ hotfix c6fd21d:原生 window.confirm() + router.replace + refresh。
UserBadge + 移动端 header(04-28 19:24,commit 8052769)
根因:dashboard/layout.tsx 左 sidebar 底部只渲染 session.user.email,对微信扫码用户 email=NULL 直接空白;移动端 sidebar hidden md:flex 整个隐藏。修:
UserBadge组件(头像 + 昵称 + 邮箱降级)- 桌面 sidebar 底部 email → UserBadge
- 移动端 dashboard 顶部加 header(logo + UserBadge compact + 退出 icon button)
session-callbacks.ts:18已是if (token.uid && (!token.name || !token.picture)),能命中 undefined 和 null
SSE bug 修:addEventListener("confirmed", ...) → onmessage(commit be8bdb0 前)
微信扫码登录页面卡住不跳。根因:WxScanLogin.tsx 用了命名事件 addEventListener("confirmed", ...),但 wx-gateway 推的是默认 message 事件(data: {"status":"confirmed",...} 不带 event: 行)→ 浏览器永不触发。
实际抓 SSE 流:data: {"status":"pending"} 全是默认通道。skill 文档原本就写 es.onmessage,echo 那版当时 CC 写错。修复改 es.onmessage + 解析 data.status 派发 scanned/confirmed/expired/timeout。
Logo 制作流程(gpt-image-2 + vision 校准)
资产在 ~/projects/echo/public/,含 wordmark / wordmark-tight / icon 各自的 -dark.png 反色版。流程:
- gpt-image-2 生成原画 → vision 校准 bbox(232-642 为 echo 主体,220-807 含回声重影)
- trim 后 paste 回正方形画布(视觉居中靠画布对称解决,不靠图片 bbox,否则笔画分布不对称会偏左)
- 黑笔画自动反色为奶白色 → 黑底页面用
-dark.png - 透明 PNG 必须
<Image unoptimized>或<img>,否则 next/image 转 lossy WebP 丢 alpha
部署踩的 6+ 个坑(已固化进 deployer)
pnpm-workspace.yaml缺packages→ 当空 workspace 拒装- 服务器拉
registry.npmjs.org超时 → 加.npmrc走 npmmirror drizzle.config.ts写死读.env.local(部署机只有.env)→ 加.envfallbackpostgres库不接受?schema=public连接参数pm2 restart --update-env不刷新缓存 → 必须pm2 delete && pm2 start- inventory 推荐端口 3794 被占(旧 echo 进程?)→ 换 3795
manifest.build整条 chain 被 deployer 忽略,只跑pnpm run build—— 把pnpm migrate && pnpm seed移进 package.json 的buildscript 头部- build 头部加
rm -f .env.local,避免本地.env.local被打进生产镜像 - 第一次生产跑 migration 时 DB 已有同名表 → drizzle
__drizzle_migrations历史表里伪插一条0000_init的 sha256(按文件实际内容计算),让后续 migration 跳过 init
Skill wechat-article-crayon 加 5min-ai 子目录(04-27)
Echo 同号是「5 分钟 AI」,跟 ChainThink 币圈号属不同账号风格,不能共用 references。新建 references/5min-ai/:
| 文件 | 内容 |
|---|---|
product-card.md | Image Studio 产品要素(含定价 ¥1=10 积分 → Image2 ¥0.4/张、Image1.5 ¥0.2/张) |
style-fingerprint.md | 卡兹克风 v3:10 条硬规则 + 5 软建议 + 禁用清单 + 招牌开头/结尾模板 ×3 |
topic-pool.md / content-structure.md / content-calendar.md | 选题、内容结构、配图 |
相关页面
- deployer — 部署平台
- wx-gateway — 微信登录 + access_token 集中
- image-studio — 同 deployer 上 04-25 同日上线的姐妹项目
- fries-mac — 开发主机
- research-fries — 风格指纹研究