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
AIAzure OpenAI(gpt-5.4 chat + gpt-image-2 image,resley-sweden-ext.openai.azure.com
DBmvp-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 970f8f5docs/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_assetsec_pubaccountswritingKitId FK。内容中心 server components(列表/详情默认 RSC,编辑器 'use client'),文章 CRUD + 状态机 draft/unsent/sent

Phase 2.2(04-28 早)— 文章编辑器(CC 干)

文件内容
actions.ts3 server actions(createArticleAction / updateArticleAction / setArticleStatusAction),每个第一行 requireUserId()
content/new/page.tsx0/1/多公众号三种分支
content/[id]/edit/page.tsx服务端取详情 + 校验 + Toaster
_components/Editor.tsxclient 编辑器,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范围
M1brief → 选题 → 标题 → 大纲 → 正文 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)
M5systemPrompt 补 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 → 出图 → 自动 ![](url) 插入
M4 一键全文配图/api/ai/auto-illustrate SSE 流式,按 skill §6 规则筛段(首段必插、紧跟 H2 跳过、末段不插、已含 ![] 跳过、cap 6、串行),进度 modal
M5 Image Studio CTAsidebar 底部 + 4 处出图位置都引流
M6 系统设置新建 /dashboard/settings,nav 不再灰,3 卡片(个人 / 写作偏好 / 模型只读)
M7 新建文章 UX单账号直接 submit 跳编辑器,删冗余中间页
M8 删除草稿列表 hover 🗑 + 编辑器顶部红字按钮,二次确认 cascade 删

关键约束

  • Azure gpt-image-2 v1 surface(api-key header + 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 反色版。流程:

  1. gpt-image-2 生成原画 → vision 校准 bbox(232-642 为 echo 主体,220-807 含回声重影)
  2. trim 后 paste 回正方形画布(视觉居中靠画布对称解决,不靠图片 bbox,否则笔画分布不对称会偏左)
  3. 黑笔画自动反色为奶白色 → 黑底页面用 -dark.png
  4. 透明 PNG 必须 <Image unoptimized><img>,否则 next/image 转 lossy WebP 丢 alpha

部署踩的 6+ 个坑(已固化进 deployer

  1. pnpm-workspace.yamlpackages → 当空 workspace 拒装
  2. 服务器拉 registry.npmjs.org 超时 → 加 .npmrc 走 npmmirror
  3. drizzle.config.ts 写死读 .env.local(部署机只有 .env)→ 加 .env fallback
  4. postgres 库不接受 ?schema=public 连接参数
  5. pm2 restart --update-env 不刷新缓存 → 必须 pm2 delete && pm2 start
  6. inventory 推荐端口 3794 被占(旧 echo 进程?)→ 换 3795
  7. manifest.build 整条 chain 被 deployer 忽略,只跑 pnpm run build —— 把 pnpm migrate && pnpm seed 移进 package.json 的 build script 头部
  8. build 头部加 rm -f .env.local,避免本地 .env.local 被打进生产镜像
  9. 第一次生产跑 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.mdImage 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选题、内容结构、配图

相关页面