262 lines
13 KiB
Markdown
262 lines
13 KiB
Markdown
# Post Project 計劃(文案任務 Flow A 砍掉重做)
|
||
|
||
> 取代 `backend/docs/threads-automation-gap-plan.md` 中文案自動營運閉環的舊版 Flow A。Flow B(海巡獲客:brand / placement_topic / outreach_drafts / PatrolPage)不在本計劃範圍。
|
||
|
||
## 1. 動機
|
||
|
||
舊版文案任務(Flow A)流程過重:
|
||
|
||
- 一個任務跑 5 個 worker(analyze → expand-graph → scan-viral → matrix → copy-draft),加上 refill-publish-inventory 自動補庫存旁路。
|
||
- 中間產 research map、knowledge graph、scan_posts、相似帳號 / 受眾樣本等中介資料,前端被 pipeline 狀態綁住。
|
||
- 使用者真正要的是:
|
||
|
||
> 單純 po 文、相似貼文模仿、發想主題、AI 找網路資料、矩陣產 N 篇、定時發送、可選人設、可貼參考貼文讓 AI 學風格。
|
||
|
||
本計劃把 Flow A 整包砍掉重做,換成線性可操作的「發文計畫(Post Project)」。
|
||
|
||
## 2. 範圍
|
||
|
||
### 砍
|
||
|
||
- Persona 文案流(copy_mission / copy_draft 全部)
|
||
- refill-publish-inventory worker 與其 publish_inventory policy 接面
|
||
- 5 個文案 worker(analyze-copy-mission / expand-copy-mission-graph / generate-copy-matrix / generate-copy-draft / refill-publish-inventory)
|
||
- `library/copymission/`、`library/matrix/copy_generate.go`、`library/viral/mission_*.go`、`library/viral/research_map.go`
|
||
- prompt files `matrix_copy.*`
|
||
- 前端 MissionsPage + `pages/missions/`
|
||
- `copy_missions` / `copy_drafts` 兩個 MongoDB collection(drop)
|
||
|
||
### 不動
|
||
|
||
- Flow B(brand / placement_topic outreach_drafts、PatrolPage 海巡獲客)
|
||
- `outreach_drafts` model / library/outreach / `generate-outreach-draft` worker
|
||
- `knowledge_graph` model(brand / placement_topic 共用)
|
||
- `scan_post` 通用 viral 採樓能力(移除 `CopyMissionID` 欄位即可)
|
||
- `publish_queue` / `publish_analytics` 通用發布與量測
|
||
- scheduler / reaper / sweeper / token-sweeper / refresh-threads-token 通用 worker
|
||
- Persona model / API(沿用)
|
||
|
||
### 新增
|
||
|
||
- 兩個 collection:`post_projects` / `post_drafts`
|
||
- 一個 worker:`go-generate-post-matrix`
|
||
- 三個同步 LLM endpoint:research-topics / analyze-style / generate-matrix
|
||
- 一條線性的前端 wizard 頁面
|
||
- 三份 prompt files(post_matrix_generate / analyze_style / research_topics)
|
||
|
||
## 3. 資料模型
|
||
|
||
### post_projects
|
||
|
||
| 欄位 | 型態 | 說明 |
|
||
|------|------|------|
|
||
| `_id` | ObjectID | |
|
||
| `tenant_id` | string | |
|
||
| `owner_uid` | string | |
|
||
| `persona_id` | ObjectID | 關聯 personas |
|
||
| `label` | string | 使用者自訂專案名 |
|
||
| `topic` | string | 使用者選定 / 編輯後的主題描述 |
|
||
| `web_research_summary` | string | research-topics 回傳的摘要 |
|
||
| `web_research_refs` | []object | `[{url, title, snippet}]` |
|
||
| `style_profile` | object | analyze-style 回的結構化風格 |
|
||
| `style_summary` | string | 給前端顯示的風格短述 |
|
||
| `style_references` | []string | 使用者貼進來的參考貼文原文 |
|
||
| `status` | string | `topic_set` / `style_set` / `matrix_generating` / `drafts_ready` / `archived` |
|
||
| `matrix_draft_count` | int | 矩陣產生的草稿數 |
|
||
| `created_at` / `updated_at` | int64 | unix nanoseconds(UTC+0) |
|
||
|
||
`style_profile` 建議結構:
|
||
|
||
```json
|
||
{
|
||
"tone": "親切、口語、像跟朋友聊天",
|
||
"length": "短至中(80-180 字)",
|
||
"structure": ["hook", "故事", "takeaway", "互動問句"],
|
||
"emoji_usage": "少量,平均 1-2 顆,放結尾",
|
||
"hook_style": "提問或反差句",
|
||
"avoid": ["過度口號", "廉價 emoji 疊字"]
|
||
}
|
||
```
|
||
|
||
### post_drafts
|
||
|
||
| 欄位 | 型態 | 說明 |
|
||
|------|------|------|
|
||
| `_id` | ObjectID | |
|
||
| `tenant_id` | string | |
|
||
| `project_id` | ObjectID | 關聯 post_project |
|
||
| `persona_id` | ObjectID | |
|
||
| `topic` | string | snapshot 主題(生成時) |
|
||
| `angle` | string | AI 給該篇的角度標籤 |
|
||
| `hook` | string | AI 給的 hook |
|
||
| `text` | string | 草稿本文 |
|
||
| `rationale` | string | AI 說為何這樣寫 |
|
||
| `sources` | []object | `[{url, title}]` 引用網路資料 |
|
||
| `status` | string | `draft` / `scheduled` / `published` |
|
||
| `publish_queue_id` | ObjectID? | 排入佇列後回填 |
|
||
| `scheduled_at` | int64? | 排程時間 |
|
||
| `published_post_id` | string? | 發布後 Threads post id |
|
||
| `created_at` / `updated_at` | int64 | unix nanoseconds |
|
||
|
||
### publish_queue.queue_item 改欄位
|
||
|
||
- `copy_mission_id` / `copy_draft_id` → `post_project_id` / `post_draft_id`(可空,舊資料不再解析)
|
||
- 既有 outreach 排程不受影響(Flow B 不靠這兩欄)
|
||
|
||
### publish_inventory policy
|
||
|
||
- 移除 `copy_mission_id` 欄位(refill worker 已砍)
|
||
- publish_inventory 對 outreach 是否仍有用需確認;若只服務文案 refill 則整個 model 一併砍。階段 1 解耦時若發現 inventory 僅供 refill,連 collection 一起 drop。
|
||
|
||
## 4. API
|
||
|
||
新 `generate/api/post_project.api`,import 至 `gateway.api`。
|
||
|
||
```
|
||
Prefix: /api/v1/personas (scope) 與 /api/v1/post-projects (資源)
|
||
|
||
POST /api/v1/personas/:personaId/post-projects
|
||
GET /api/v1/personas/:personaId/post-projects
|
||
GET /api/v1/post-projects/:projectId
|
||
PATCH /api/v1/post-projects/:projectId (label / topic / status)
|
||
DELETE /api/v1/post-projects/:projectId
|
||
|
||
POST /api/v1/post-projects/:projectId/research-topics sync: { seed } -> { suggestions[], refs[] }
|
||
POST /api/v1/post-projects/:projectId/analyze-style sync: { references:[{text}] } -> { style_profile, style_summary }
|
||
POST /api/v1/post-projects/:projectId/generate-matrix async job: enqueue generate-post-matrix
|
||
POST /api/v1/post-projects/:projectId/drafts/:draftId/regenerate async job: 重生單篇
|
||
|
||
GET /api/v1/post-projects/:projectId/drafts
|
||
PATCH /api/v1/post-projects/:projectId/drafts/:draftId (text / angle / sources)
|
||
DELETE /api/v1/post-projects/:projectId/drafts/:draftId
|
||
|
||
POST /api/v1/post-projects/:projectId/drafts/schedule body: [{draft_id, send_at}] -> publish_queue.Create
|
||
```
|
||
|
||
Envelope:`code/message/data/error`;列表用 `page/pageSize` + `pagination/list`。
|
||
|
||
## 5. Worker
|
||
|
||
`internal/worker/job/generate_post_matrix.go`
|
||
|
||
- `workerType = "go-generate-post-matrix"`
|
||
- `step = "post_matrix_generate"`
|
||
- 讀 `project.topic` + `project.style_profile` + `persona` + `project.web_research_refs`
|
||
- 組 prompt → LLM 一次吐 N 篇(N=5 預設)
|
||
- 寫 N 個 `post_drafts` doc
|
||
- 更新 `project.status = drafts_ready`、`matrix_draft_count = N`
|
||
- 過程中 `RefreshRunLock` + `Heartbeat`,不做 cancel 自行設狀態
|
||
|
||
regenerate 走同一個 worker 同一 template,payload 帶 `target_draft_id`:
|
||
- 有 → 刪舊 draft / 重生一篇覆蓋
|
||
- 無 → 全新矩陣
|
||
|
||
## 6. Library
|
||
|
||
新 `internal/library/post_matrix/`(取代 `library/copymission/`):
|
||
|
||
- `analyze_style.go`:貼文 → LLM → 吐 `style_profile` JSON + `style_summary`
|
||
- `research_topics.go`:seed → 內部 websearch(`node-tool-keyword-search` 或 `library/viral/crawler_search`) → LLM 摘要 + 主題候選
|
||
- `generate.go`:組 prompt → LLM → N 篇 post_drafts
|
||
|
||
prompt files(`internal/library/prompt/files/`):
|
||
|
||
- `post_matrix_generate.system.md` / `.user.md`
|
||
- `analyze_style.system.md` / `.user.md`
|
||
- `research_topics.system.md` / `.user.md`
|
||
|
||
## 7. 前端
|
||
|
||
### 7.1 新頁面 `web/src/pages/PostProjectsPage.tsx`
|
||
|
||
取代 MissionsPage,5-step wizard(沿用 `PageTitle` / `Card` / `Field` / `Input` / `Textarea` / `Button` / `Badge`):
|
||
|
||
1. **人設**:下拉選 Persona
|
||
2. **主題**:
|
||
- Textarea 輸入 seed 或想法
|
||
- 按鈕「給我主題靈感」→ `research-topics`(同步) → 顯示 3-5 張主題卡(含摘要 + 來源)
|
||
- 點卡片自動填 Textarea,或手動編輯
|
||
- 「再查一次」換一批
|
||
3. **風格**(可選):
|
||
- Textarea 貼 1~N 篇參考貼文(空行分隔)
|
||
- 按鈕「分析風格」→ `analyze-style`(同步) → 顯示 `style_profile` 條列
|
||
- 可重貼、重新分析
|
||
4. **矩陣**:
|
||
- 按鈕「產生 5 篇草稿」→ `generate-matrix` job → polling 進度
|
||
- 顯示 N 張草稿卡(Hook / Text / Angle / Sources)
|
||
- 每張可:編輯 / 刪除 / 重生單篇
|
||
5. **排程**:
|
||
- 勾選草稿 → 展開 datetime-local 輸入
|
||
- 「排入佇列」→ `drafts/schedule`
|
||
- 顯示排程清單(沿用 publish_queue 顯示元件)
|
||
|
||
### 7.2 路由 / 導覽
|
||
|
||
- `App.tsx`:`case "posts": return <PostProjectsPage />`,加進 `validPages` / `accountRequiredPages`
|
||
- `acAssets.ts` `navApps`:把原 `missions` 換成 `posts`(label「發文計畫」,sublabel `POST`)
|
||
- `mobileNavKeys` 同步替換
|
||
- `AcIcon`:`mission` 換 `posts` SVG(鉛筆 + 對話框 stack 線條稿)
|
||
- `validPages` / `accountRequiredPages` 移除 `"missions"`
|
||
|
||
### 7.3 API client
|
||
|
||
`api/haixun.ts`:
|
||
|
||
- 刪 types `CopyMission`、`ContentInboxItem`、`CopyMissionID` 等相關
|
||
- 刪 endpoints `copyMissions` / `createCopyMission` / `inspireCopyMission` / `startCopyMissionAnalyze` / `startCopyMissionMatrix` / `scheduleMissionDrafts` / persona copy-drafts 系列
|
||
- 新 types `PostProject` / `PostDraft` / `StyleProfile`
|
||
- 新 endpoints `createPostProject` / `listPostProjects` / `researchTopics` / `analyzeStyle` / `generateMatrix` / `regenerateDraft` / `listPostDrafts` / `updatePostDraft` / `deletePostDraft` / `schedulePostDrafts`
|
||
- 保留 outreach_drafts endpoints(Flow B)
|
||
|
||
## 8. Infra / deploy
|
||
|
||
`deploy/docker-compose.yml`:
|
||
|
||
- 刪 `worker-copy-mission-analyze` / `worker-copy-mission-expand-graph` / `worker-copy-matrix` / `worker-copy-draft` / `worker-refill-publish-inventory` 五個 service
|
||
- 新 `worker-post-matrix`:`HAIXUN_JOB_WORKER_TYPE=go-generate-post-matrix` / `HAIXUN_JOB_WORKER_ID=go-generate-post-matrix`
|
||
- 保留:`worker-scan-viral`、`worker-outreach-draft`、其餘通用 worker
|
||
|
||
## 9. Migration
|
||
|
||
`internal/bootstrap/init.go` 加 idempotent migration block:
|
||
|
||
- `db.RunCommand({dropIndexes: "publish_queue"})` 必要時重 EnsureIndexes
|
||
- 寫一個 `tool` subcommand(沿用 `backend/cmd/tool`)或 init runtime 做一次性:
|
||
- drop `copy_missions` collection(忽略 not exist)
|
||
- drop `copy_drafts` collection(忽略 not exist)
|
||
- rename `publish_queue.copy_mission_id` → `post_project_id`、`copy_draft_id` → `post_draft_id`(對存量資料,若有人 queue 還沒發)
|
||
- 移除 `publish_inventory.policy.copy_mission_id` 欄位(或整 drop `publish_inventories` collection,階段 1 確認後決定)
|
||
- 新 EnsureIndexes `post_projects`(tenant+owner+status)、`post_drafts`(project_id+status)
|
||
|
||
執行:首次 `make prod-install` 跑 init,後續 `make prod-rebuild` 即可。
|
||
|
||
## 10. 執行階段
|
||
|
||
| 階段 | 動作 | 驗證 |
|
||
|------|------|------|
|
||
| **1. 後端解耦** | scan_viral 拔 CopyMission/CopyDraft 依賴;刪 refill_publish_inventory.go;service_context 對應移除;publish_queue / publish_inventory entity 改欄位 | `go test ./...` Flow B 仍綠 |
|
||
| **2. 刪除 Flow A 後端** | api / handler / logic / model / worker template / library / prompt 全砍;`make gen-api` 重產 | `go test ./...` 通過 |
|
||
| **3. 前端清 Flow A** | MissionsPage / missions 目錄 / App.tsx / Layout / acAssets / haixun.ts 清 off | `make web-build` 通過 |
|
||
| **4. Infra 清理** | docker-compose.yml 刪 5 個 worker service | `docker compose config` 過 |
|
||
| **5. 新後端實作** | post_project / post_draft model + usecase + repository;post_project.api + generate_post_matrix.go worker + library/post_matrix + prompt files;job template 註冊 | `go test ./...` 通過 |
|
||
| **6. 新前端** | PostProjectsPage + 5-step wizard;AcIcon 新 icon | `make web-build` 通過 |
|
||
| **7. DB migration** | bootstrap init drop / rename / EnsureIndexes | `docker run init` 過 |
|
||
| **8. 煙霧測試** | 開 project → 靈感 → 風格 → 矩陣 → 排程 → sweeper 發;Flow B outreach 仍可;JobsPage 顯示新 type | 部署在 dev 環境實測 |
|
||
|
||
每階段結束跑 `go mod tidy && make fmt && go test ./... && make web-build`。
|
||
|
||
## 11. 風險 / 開放問題
|
||
|
||
1. **websearch 串接**:`library/viral/crawler_search.go` 與 `node-tool-keyword-search` 服務是否適合 research-topics 用?實作時驗證呼叫 path;不順則直接打內部 node-tool-keyword-search HTTP。
|
||
2. **publish_queue rename 對存量資料**:先做 null-tolerant(舊資料視為已發 / 忽略),降低遷移負擔;正式上線前可一次性 rename script。
|
||
3. **publish_inventory 是否完全沒用**:階段 1 確認;若 Flow B 不依賴,整包砍。
|
||
4. **style_profile 形態**:採 JSON 結構化(語氣 / 長度 / 結構 / emoji / hook_style / avoid),方便前端顯示;prompt 需校調測試。
|
||
5. **矩陣批次數**:MVP 固定 N=5;後續可加 `count` query 參數,受 persona 配額上限保護。
|
||
6. **regenerate 是否保留 sources**:MVP 整篇重來,不保留;後續可加「保留 sources」選項。
|
||
|
||
## 12. 不在本計劃範圍
|
||
|
||
- Flow B 重做(若日後需要另開計劃)
|
||
- Persona model 改造(沿用)
|
||
- publish_queue_sweeper 行為修改(沿用)
|
||
- A/B 測試、自動成效優化(屬另一個閉環計劃) |