diff --git a/README.md b/README.md index 592ed05..94864e8 100644 --- a/README.md +++ b/README.md @@ -61,10 +61,14 @@ docker compose up -d --build ```bash docker compose ps docker compose logs -f backend +./scripts/demo_smoke.sh +SMOKE_AUDIO=1 ./scripts/demo_smoke.sh docker compose down docker compose down -v ``` +`scripts/demo_smoke.sh` 会检查健康状态、本地登录、统一生成、资产重试、故事列表和 Provider 能力分层。默认跳过 TTS;演示前需要验证语音链路时使用 `SMOKE_AUDIO=1`。 + ## 手动开发 后端: @@ -138,6 +142,9 @@ npm run build - `docs/product/job-search-relaunch-prd.md`:求职版产品重启 PRD - `docs/product/unified-generation-workflow-prd.md`:统一生成工作流 PRD - `docs/planning/week-1-execution-backlog.md`:短期执行 backlog +- `docs/planning/week-2-execution-backlog.md`:下一阶段执行 backlog +- `docs/planning/demo-checklist.md`:求职演示检查清单 +- `docs/planning/week-1-sprint-review.md`:Week 1 复盘总结 - `docs/technical/memory-system-dev.md`:记忆系统技术说明 - `docs/technical/provider-routing.md`:Provider 能力与路由策略说明 diff --git a/docs/README.md b/docs/README.md index 6ef68e1..3235799 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,16 @@ ## Planning - `planning/week-1-execution-backlog.md` - 当前短期执行 backlog。用于决定下一步先做什么,以及如何拆成可交付任务。 + Week 1 执行 backlog。用于说明求职版重启的第一阶段范围、进展和交付状态。 + +- `planning/week-2-execution-backlog.md` + Week 2 执行 backlog。用于继续推进演示闭环、前端状态体验和面试材料。 + +- `planning/demo-checklist.md` + 求职演示检查清单。用于演示前确认 Docker 环境、核心链路、话术和风险预案。 + +- `planning/week-1-sprint-review.md` + Week 1 复盘总结。用于沉淀已完成成果、剩余缺口和下一阶段建议。 ## Technical diff --git a/docs/planning/demo-checklist.md b/docs/planning/demo-checklist.md new file mode 100644 index 0000000..44cab2d --- /dev/null +++ b/docs/planning/demo-checklist.md @@ -0,0 +1,139 @@ +# DreamWeaver 求职演示 Checklist + +**目标**: 演示前用 5-10 分钟确认本地 Docker 环境、核心生成链路和讲解材料处于可展示状态。 + +--- + +## 1. 演示前准备 + +### 环境检查 + +- [ ] 已拉取最新 `main` +- [ ] `backend/.env` 存在,且不包含需要提交的真实密钥 +- [ ] `DEBUG=true` +- [ ] `ENABLE_DEMO_PROVIDERS=true` +- [ ] `SECRET_KEY` 已设置 +- [ ] Docker Desktop 正常运行 + +### 启动命令 + +```bash +docker compose up -d --build +docker compose ps +``` + +### 入口 + +- 用户端:http://localhost:52080 +- 本地登录:http://localhost:52080/auth/dev/signin +- Admin:http://localhost:52888 +- 后端健康:http://localhost:52000/health +- Admin 后端健康:http://localhost:52800/health + +--- + +## 2. 自动 smoke 检查 + +默认检查健康、登录、普通故事、绘本、图片补全和 Provider 能力分层: + +```bash +./scripts/demo_smoke.sh +``` + +需要检查 TTS 音频时: + +```bash +SMOKE_AUDIO=1 ./scripts/demo_smoke.sh +``` + +通过标准: + +- [ ] backend health 返回 `ok` +- [ ] admin-backend health 返回 `ok` +- [ ] dev login 能拿到 session +- [ ] `/api/generations` 能生成普通故事 +- [ ] 普通故事封面 retry 后 `image_status=ready` +- [ ] `/api/generations` 能生成绘本 +- [ ] 绘本图片 retry 后 `image_status=ready` +- [ ] `/admin/providers/capabilities` 返回 `text/image/tts/storybook` +- [ ] 如果启用 `SMOKE_AUDIO=1`,音频 retry 后 `audio_status=ready` + +--- + +## 3. 手动演示路径 + +### 路径 A: 普通故事 + +1. 打开用户端。 +2. 使用本地登录。 +3. 创建普通故事: + - 关键词:`月亮船,小狐狸,学会道歉` + - 教育主题:`真诚表达` +4. 进入故事详情页。 +5. 展示: + - 标题和正文 + - 封面状态 + - 音频状态 + - 资产补全/重试入口 +6. 点击音频播放,说明音频缓存复用。 + +### 路径 B: 绘本 + +1. 创建绘本: + - 关键词:`彩虹邮局,小刺猬,练习说谢谢` + - 教育主题:`感恩表达` +2. 进入绘本阅读器。 +3. 翻页展示多页文本和插图。 +4. 刷新页面或复制 URL,说明绘本已经支持按 ID 恢复。 + +### 路径 C: Admin/Provider + +1. 打开 Admin。 +2. 说明管理端不是用户主链路,而是产品拥有者维护供应链路的辅助能力。 +3. 通过接口或页面说明: + - Capability: `text/image/tts/storybook` + - Provider: 具体供应商配置 + - Adapter: API 调用实现 + - Routing Policy: 优先级/成本/延迟/轮询 + +--- + +## 4. 3 分钟讲解结构 + +### 0:00 - 0:40 产品定位 + +DreamWeaver 是面向 3-8 岁亲子场景的个性化 AI 绘本与陪伴式讲述产品。它不是只生成一次性故事,而是围绕孩子档案、成长主题和故事宇宙,生成可回看、可补全、可聆听的故事体验。 + +### 0:40 - 1:30 用户闭环 + +用户选择孩子档案,输入主题或教育目标,生成故事或绘本。主内容先可读,封面、插图、语音作为资产补全。即使图片或音频失败,故事仍然可读,并且可以后续重试。 + +### 1:30 - 2:20 系统设计 + +系统收敛到统一 Generation Workflow:上下文准备、主记录保存、资产补全、状态回写和后处理。Provider 体系拆成 Capability、Provider、Adapter、Routing Policy,用户不需要理解模型供应商,只感知稳定的产品能力。 + +### 2:20 - 3:00 取舍与下一步 + +求职版优先稳定闭环和可解释性,不做支付、多租户和复杂监控。下一步会继续打磨前端状态体验、旧 API 兼容策略和 generation job 是否落库。 + +--- + +## 5. 风险预案 + +| 风险 | 现场处理 | +| --- | --- | +| 网络导致 TTS 失败 | 说明音频是可恢复资产,不阻塞故事阅读;使用已缓存样本或跳过 TTS | +| 图片 provider 失败 | 展示 degraded completed 与 retry 机制 | +| Docker 冷启动慢 | 演示前提前运行 smoke 脚本并保持容器运行 | +| Admin 页面不适合主展示 | 只用 Provider 分层说明辅助讲系统设计 | +| 面试官追问生产部署 | 明确当前是求职版 MVP,本轮重点是产品闭环和系统边界 | + +--- + +## 6. 演示完成标准 + +- [ ] 面试官能在 30 秒内理解产品定位。 +- [ ] 能现场看到普通故事和绘本结果。 +- [ ] 能解释失败降级和资产重试。 +- [ ] 能解释为什么 Provider 分层是产品设计,而不是单纯技术炫技。 +- [ ] 能说明下一步计划,而不是让项目停在 demo。 diff --git a/docs/planning/week-1-execution-backlog.md b/docs/planning/week-1-execution-backlog.md index 9039cc0..8c851c3 100644 --- a/docs/planning/week-1-execution-backlog.md +++ b/docs/planning/week-1-execution-backlog.md @@ -37,7 +37,7 @@ ## 2.1 Current Progress Snapshot -**Updated**: 2026-04-18 morning +**Updated**: 2026-04-18 evening ### What Has Been Completed @@ -75,17 +75,20 @@ - Provider Router 专注运行时 failover、熔断、成本和指标记录 - 新增 `/admin/providers/capabilities` 展示能力分层 - 新增 `docs/technical/provider-routing.md` 作为术语表和分层说明 +- 已产出 Week 2 执行 backlog:`docs/planning/week-2-execution-backlog.md` +- 已产出求职演示 checklist:`docs/planning/demo-checklist.md` +- 已产出 Week 1 Sprint Review:`docs/planning/week-1-sprint-review.md` +- 已新增本地演示 smoke 脚本:`scripts/demo_smoke.sh` ### What Is In Progress -- 统一状态模型与统一外部 API 已落地,内部 service workflow 已开始收束公共步骤 -- 旧生成 API 仍保留为兼容层,后续需要继续降低重复实现 -- 资产补全已经具备统一重试入口首版,封面/绘本插图/音频已有 asset completion helper 与结果对象;后续需要评估是否落库为 generation job +- Week 2 前端状态体验、旧 API 兼容策略和 generation job 是否落库将在下一阶段继续推进 ### What Is Still Pending -- Week 2 可直接执行的开发任务表 -- 演示 checklist 与最终收尾策略 +- Week 2 任务执行 +- 3 分钟项目讲解稿 +- 前端关键状态体验打磨 ### Remote Checkpoint Scope @@ -156,9 +159,9 @@ **Acceptance Criteria** -- [ ] 输出一句话产品定位 -- [ ] 输出 3 条核心价值主张 -- [ ] 明确哪些功能是本轮保留,哪些是延后 +- [x] 输出一句话产品定位 +- [x] 输出 3 条核心价值主张 +- [x] 明确哪些功能是本轮保留,哪些是延后 ### Story B: 明确统一生成工作流 @@ -170,7 +173,7 @@ - [x] 定义统一状态模型 - [x] 明确故事与绘本的共同链路和差异 -- [ ] 明确失败降级与重试原则 +- [x] 明确失败降级与重试原则 ### Story C: 识别并拆解关键阻塞项 @@ -180,7 +183,7 @@ **Acceptance Criteria** -- [ ] 明确 admin-frontend 的处理方案 +- [x] 明确 admin-frontend 的处理方案 - [x] 明确 Storybook 恢复方案 - [x] 明确 Provider 重构边界 @@ -194,12 +197,12 @@ | W1-02 | Product | 梳理本轮 In Scope / Out of Scope | 范围清单 | P0 | 0.5d | Done | | W1-03 | Product / System | 盘点现有生成路径:普通故事、完整生成、绘本生成 | 现状流程图或对照表 | P0 | 0.5d | Done | | W1-04 | Product / System | 定义统一 Generation Workflow 状态模型 | 状态流转说明 | P0 | 1.0d | Done | -| W1-05 | Product / Backend | 定义统一工作流下的 API / 数据结构影响 | 接口与模型变更清单 | P0 | 0.5d | In Progress | +| W1-05 | Product / Backend | 定义统一工作流下的 API / 数据结构影响 | 接口与模型变更清单 | P0 | 0.5d | Done | | W1-06 | Product / Backend | 梳理 Provider 概念层:Capability / Provider / Routing Policy | 分层图与术语表 | P1 | 0.5d | Done | | W1-07 | Product / Frontend | 梳理 Storybook 当前问题与恢复方案 | 恢复方案说明 | P0 | 0.5d | Done | | W1-08 | Product / Frontend | 确认 admin 前端是修复、裁剪还是暂时降级 | 决策记录 | P0 | 0.5d | Done | -| W1-09 | Planning | 产出 Week 2 开发任务清单 | 下周 backlog | P1 | 0.5d | In Progress | -| W1-10 | Review | 形成求职演示版检查清单 | 演示清单 | P1 | 0.5d | Pending | +| W1-09 | Planning | 产出 Week 2 开发任务清单 | 下周 backlog | P1 | 0.5d | Done | +| W1-10 | Review | 形成求职演示版检查清单 | 演示清单 | P1 | 0.5d | Done | --- @@ -301,11 +304,11 @@ 只有满足以下条件,Week 1 才视为完成: -- [ ] 产品定位能用 30 秒讲清楚 -- [ ] 统一生成工作流的状态模型已明确 -- [ ] 关键阻塞项均有明确处理方案,不处于“再看看” -- [ ] Week 2 有可直接执行的任务表 -- [ ] 所有本周产出都已沉淀为书面文档 +- [x] 产品定位能用 30 秒讲清楚 +- [x] 统一生成工作流的状态模型已明确 +- [x] 关键阻塞项均有明确处理方案,不处于“再看看” +- [x] Week 2 有可直接执行的任务表 +- [x] 所有本周产出都已沉淀为书面文档 --- diff --git a/docs/planning/week-1-sprint-review.md b/docs/planning/week-1-sprint-review.md new file mode 100644 index 0000000..39e183b --- /dev/null +++ b/docs/planning/week-1-sprint-review.md @@ -0,0 +1,96 @@ +# DreamWeaver 求职版重启:Week 1 Sprint Review + +**Date**: 2026-04-18 +**Theme**: 产品聚焦与核心生成链路收敛 + +--- + +## 1. Sprint Summary + +Week 1 将 DreamWeaver 从“功能很多但主线分散”的项目,推进为“围绕个性化 AI 绘本与陪伴式讲述”的求职版 MVP 基础。核心变化不是增加更多功能,而是收敛产品叙事、统一生成入口、补齐可恢复能力、梳理 Provider 分层,并让本地 Docker 演示具备可重复验证的基础。 + +--- + +## 2. Completed Outcomes + +### Product + +- 明确产品定位:面向 3-8 岁亲子场景的个性化 AI 绘本与陪伴式讲述产品。 +- 明确三条核心价值: + - 个性化连续性 + - 陪伴式体验 + - 生成稳定性 +- 明确求职版不追求“功能最多”,而追求“价值闭环清楚、系统设计可解释、演示链路稳定”。 + +### Generation Workflow + +- 新增统一生成入口: + - `POST /api/generations` + - `GET /api/generations/{id}` + - `POST /api/generations/{id}/retry-assets` +- 普通故事和绘本创建入口已切到统一 API。 +- Storybook 阅读器支持按 ID 恢复。 +- 故事与绘本详情支持统一状态字段: + - `generation_status` + - `image_status` + - `audio_status` + - `last_error` +- 服务层抽出公共步骤: + - 上下文准备 + - 主记录保存 + - 封面补全 + - 绘本插图补全 + - 音频缓存与生成 +- 引入 `AssetCompletionResult` 表达资产补全结果。 + +### Provider + +- 新增 `provider_policy.py`,将 Capability / 默认 Provider / Routing Strategy / API key ref 映射从 runtime router 中拆出。 +- Provider Router 继续负责 failover、熔断、成本、指标和 adapter 执行。 +- 新增 `/admin/providers/capabilities`,用于展示能力分层。 +- 新增 `docs/technical/provider-routing.md`,沉淀术语表和面试表达口径。 + +### Engineering + +- 本地 Docker Compose 入口已收敛为一个。 +- 后端全量 ruff 通过。 +- 后端测试从 53 个增长到 71 个并通过。 +- 用户端和 admin 前端均可 Docker 构建。 +- 已新增演示 smoke 脚本和演示 checklist。 + +--- + +## 3. What Changed In The Product Story + +之前的 DreamWeaver 容易被理解成“会调很多模型的儿童故事工具”。Week 1 之后,项目故事可以变成: + +> DreamWeaver 把 AI 文本、绘本、图片和语音能力收敛成一条亲子阅读工作流。主内容优先可读,封面、插图和语音作为可恢复资产补全;Provider 体系隐藏在产品能力背后,通过 Capability 和 Routing Policy 保证生成稳定性。 + +这个表达更接近 AI 产品经理需要讲清楚的内容:用户问题、产品闭环、系统边界、失败恢复和取舍。 + +--- + +## 4. Remaining Gaps + +- 前端状态体验仍可继续强化,尤其是生成中、部分完成、失败重试的文案和 CTA。 +- 旧生成 API 仍作为兼容层保留,需要 Week 2 明确 deprecation 策略。 +- `AssetCompletionResult` 目前是服务层对象,是否落库成 generation job 需要继续评估。 +- 记忆系统和成长时间线仍是后续增强点,不应抢占演示闭环优先级。 + +--- + +## 5. Week 2 Recommendation + +Week 2 应优先做“演示稳定 + 前端状态体验 + 面试材料”,而不是继续深挖底层架构。建议顺序: + +1. 固化 smoke 脚本和演示 checklist。 +2. 打磨故事详情和绘本阅读器状态体验。 +3. 明确旧 API 兼容层策略。 +4. 输出 3 分钟项目讲解稿。 +5. 连续跑 3 次本地演示回归。 + +--- + +## 6. Sprint Verdict + +Week 1 达成目标。项目已经具备求职版 MVP 的基础形态:产品定位清楚、核心生成链路可跑、状态模型可解释、Provider 分层可讲、Docker 本地演示可验证。 diff --git a/docs/planning/week-2-execution-backlog.md b/docs/planning/week-2-execution-backlog.md new file mode 100644 index 0000000..9222381 --- /dev/null +++ b/docs/planning/week-2-execution-backlog.md @@ -0,0 +1,161 @@ +# DreamWeaver 求职版重启:Week 2 执行 Backlog + +**Version**: 1.0 +**Date**: 2026-04-18 +**Sprint Length**: 5 个工作日 +**Sprint Theme**: 演示闭环、前端状态体验与求职表达打磨 + +--- + +## 1. Sprint Executive Summary + +Week 1 已经完成产品主线收敛、统一生成入口、Storybook 恢复、资产补全、Provider 分层和本地 Docker 可运行基础。Week 2 不应重新发散到“更多功能”,而应围绕求职演示版完成三件事: + +1. 把用户端关键体验打磨到能稳定演示。 +2. 把统一生成工作流的代码与说明继续收束。 +3. 把演示脚本、项目讲解和风险预案沉淀成可复用材料。 + +Week 2 的目标不是做“完整商业产品”,而是做出一个面试时能自然讲清楚的 AI 产品案例:用户价值明确、核心链路能跑、失败降级能解释、系统设计有取舍。 + +--- + +## 2. Sprint Goal + +在 5 个工作日内,将 DreamWeaver 推进到“可连续演示、可复盘讲解、可继续扩展”的求职版 MVP。 + +### Success Definition + +- 用户端能完成:创建故事/绘本 -> 查看详情/阅读绘本 -> 补全封面/语音 -> 回到故事库。 +- 演示前可以用 smoke 脚本快速确认 Docker 环境可用。 +- 关键页面能表达生成状态、部分完成、失败重试和资产就绪状态。 +- 面试讲解材料能覆盖产品定位、用户价值、生成工作流、Provider 分层和工程取舍。 +- Week 2 结束时,主仓库只保留与演示和短期迭代有关的文档与入口。 + +--- + +## 3. Scope + +### In Scope + +- 用户端生成与结果页状态体验 +- 故事库与绘本阅读器的演示稳定性 +- 音频播放和缓存体验验证 +- 演示脚本与 smoke 自动化 +- Provider 分层和 Generation Workflow 的讲解材料 +- 小范围测试补齐与回归验证 + +### Out of Scope + +- 会员、支付、商业化 +- 多租户 Provider 市场 +- 大规模视觉重做 +- 复杂 generation job 落库 +- 生产级部署、高可用、监控大盘 +- 新增大量第三方 Provider + +--- + +## 4. Priorities + +| Priority | Item | Why It Matters | +| --- | --- | --- | +| P0 | 演示链路稳定 | 面试现场不能依赖临场修复 | +| P0 | 前端状态表达 | AI 产品必须让用户理解等待、失败和恢复 | +| P0 | 演示脚本与 smoke 检查 | 保证每次展示前可快速复验 | +| P1 | Generation Workflow 继续收束 | 强化系统设计表达力 | +| P1 | Provider 分层讲解材料 | 体现 AIPM 对多模型能力的产品化理解 | +| P1 | 测试覆盖补齐 | 提升项目成熟度信号 | +| P2 | 局部 UI 视觉优化 | 只做服务演示理解的优化 | + +--- + +## 5. Week 2 Backlog + +| ID | Workstream | Task | Output | Priority | Estimate | Status | +| --- | --- | --- | --- | --- | --- | --- | +| W2-01 | Demo | 固化本地 Docker smoke 脚本 | `scripts/demo_smoke.sh` | P0 | 0.5d | Done | +| W2-02 | Demo | 形成求职演示 checklist | `docs/planning/demo-checklist.md` | P0 | 0.5d | Done | +| W2-03 | Planning | 输出 Week 2 执行 backlog | 当前文档 | P0 | 0.5d | Done | +| W2-04 | Product | 写 3 分钟项目讲解稿 | 面试口径:产品、工作流、Provider、取舍 | P0 | 0.5d | Pending | +| W2-05 | Frontend | 打磨创建弹窗的状态文案 | 用户知道正在生成故事/绘本/资产 | P0 | 0.5d | Pending | +| W2-06 | Frontend | 强化故事详情页资产状态与重试 CTA | 图片/音频失败时可理解、可操作 | P0 | 1.0d | Pending | +| W2-07 | Frontend | 强化绘本阅读器降级态 | 缺图、失败、加载中不出现空白体验 | P0 | 1.0d | Pending | +| W2-08 | Backend | 梳理旧生成 API 兼容层策略 | 保留/标记 deprecated/迁移计划 | P1 | 0.5d | Pending | +| W2-09 | Backend | 判断 generation job 是否需要落库 | ADR 或技术说明 | P1 | 0.5d | Pending | +| W2-10 | QA | 补前端关键路径构建与 smoke 验证记录 | Docker build + smoke 输出 | P1 | 0.5d | Pending | +| W2-11 | Docs | 输出 Week 1 Sprint Review | `docs/planning/week-1-sprint-review.md` | P1 | 0.5d | Done | +| W2-12 | Docs | 更新 README 的演示前检查流程 | README 本地演示说明 | P1 | 0.5d | Done | + +--- + +## 6. Suggested Execution Sequence + +### Day 1: 演示可复验 + +- 完成 smoke 脚本 +- 完成演示 checklist +- 完成 Week 1 Sprint Review +- 确认 Docker 环境能一键跑通 + +### Day 2: 用户端状态体验 + +- 优化创建弹窗生成状态 +- 优化故事详情页图片/音频状态与重试 +- 保证失败时用户有明确下一步 + +### Day 3: 绘本阅读体验 + +- 优化绘本阅读器缺图/失败/加载态 +- 验证按 ID 恢复、刷新恢复、故事库进入 +- 补关键前端构建验证 + +### Day 4: 系统设计收尾 + +- 明确旧 API 兼容层策略 +- 决定 generation job 是否落库 +- 整理 Provider 和 Generation Workflow 的讲解图/话术 + +### Day 5: 求职演示彩排 + +- 按 checklist 连续跑 3 次 +- 输出 3 分钟项目讲解稿 +- 记录风险预案和下一阶段计划 + +--- + +## 7. Definition of Done + +- [ ] smoke 脚本能在本地 Docker 栈中完成健康检查、登录、生成、重试和读回验证。 +- [ ] 用户端主链路可手动演示,不需要打开数据库或日志解释状态。 +- [ ] 故事和绘本的主要失败降级态有清楚展示和重试方式。 +- [ ] README、docs index、演示 checklist 与当前代码一致。 +- [ ] 面试讲解能在 3 分钟内说明产品价值、技术工作流和取舍。 +- [ ] 全量后端测试、ruff、Docker build 在演示前可通过。 + +--- + +## 8. Risks + +| Risk | Likelihood | Impact | Mitigation | +| --- | --- | --- | --- | +| 前端状态优化变成视觉重做 | Medium | High | 只改状态、文案、CTA,不做大规模改版 | +| 继续深挖后端抽象导致演示延期 | Medium | High | generation job 先做决策,不急着落库 | +| TTS provider 网络不稳定 | Medium | Medium | smoke 脚本支持跳过音频,演示前优先用缓存 | +| Demo 数据不足 | Medium | Medium | smoke 脚本自动生成故事与绘本样本 | +| 面试讲解过技术化 | Medium | High | 讲解顺序固定为用户问题 -> 产品方案 -> 系统取舍 | + +--- + +## 9. Week 2 Output Package + +Week 2 结束时,应能交付: + +- 本地 Docker demo +- smoke 脚本 +- 演示 checklist +- 3 分钟项目讲解稿 +- Week 1 Sprint Review +- Week 2 完成记录 +- 核心链路测试结果 + +这套材料的目标是让项目从“我做过很多功能”变成“我能用产品经理方式定义问题、收敛范围、推动工程实现并验证结果”。 diff --git a/scripts/demo_smoke.sh b/scripts/demo_smoke.sh new file mode 100755 index 0000000..5bbb7ca --- /dev/null +++ b/scripts/demo_smoke.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -euo pipefail + +APP_URL="${APP_URL:-http://localhost:52080}" +BACKEND_URL="${BACKEND_URL:-http://localhost:52000}" +ADMIN_BACKEND_URL="${ADMIN_BACKEND_URL:-http://localhost:52800}" +ADMIN_AUTH="${ADMIN_AUTH:-admin:admin}" +SMOKE_AUDIO="${SMOKE_AUDIO:-0}" + +COOKIE_JAR="$(mktemp "${TMPDIR:-/tmp}/dreamweaver-cookie.XXXXXX")" +cleanup() { + rm -f "$COOKIE_JAR" +} +trap cleanup EXIT + +need_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +need_cmd curl +need_cmd jq + +say() { + printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$*" +} + +post_json() { + local url="$1" + local payload="$2" + curl -fsS -b "$COOKIE_JAR" -H 'Content-Type: application/json' -d "$payload" "$url" +} + +get_json() { + local url="$1" + curl -fsS -b "$COOKIE_JAR" "$url" +} + +assert_jq() { + local json="$1" + local filter="$2" + local message="$3" + if ! jq -e "$filter" >/dev/null <<<"$json"; then + echo "Assertion failed: $message" >&2 + echo "$json" | jq '.' >&2 + exit 1 + fi +} + +say "Checking backend health" +curl -fsS "$BACKEND_URL/health" | jq -e '.status == "ok"' >/dev/null +curl -fsS "$ADMIN_BACKEND_URL/health" | jq -e '.status == "ok"' >/dev/null + +say "Logging in with dev auth" +curl -fsS -c "$COOKIE_JAR" -o /dev/null -L "$APP_URL/auth/dev/signin" +session_json="$(get_json "$APP_URL/auth/session")" +assert_jq "$session_json" '.user.id == "github:dev_user_001"' "dev session should be active" + +say "Checking provider capability policy" +capabilities_json="$(curl -fsS -u "$ADMIN_AUTH" "$ADMIN_BACKEND_URL/admin/providers/capabilities")" +assert_jq "$capabilities_json" 'map(.capability) | sort == ["image","storybook","text","tts"]' "capabilities should include text/image/tts/storybook" + +say "Generating text story without assets" +story_json="$(post_json "$APP_URL/api/generations" '{ + "output_mode": "story", + "type": "keywords", + "data": "金色书签, 小鹿, 学会复盘", + "education_theme": "复盘与成长", + "generate_images": false +}')" +story_id="$(jq -r '.id' <<<"$story_json")" +assert_jq "$story_json" '.mode == "generated" and .generation_status == "narrative_ready"' "story should be readable before assets" +echo "$story_json" | jq '{id,title,mode,generation_status,image_status,audio_status}' + +say "Retrying story cover image" +story_image_json="$(post_json "$APP_URL/api/generations/$story_id/retry-assets" '{"assets":["image"]}')" +assert_jq "$story_image_json" '.image_status == "ready" and (.image_url != null)' "story cover should be ready after retry" +echo "$story_image_json" | jq '{id,title,generation_status,image_status,audio_status}' + +if [[ "$SMOKE_AUDIO" == "1" ]]; then + say "Retrying story audio" + story_audio_json="$(post_json "$APP_URL/api/generations/$story_id/retry-assets" '{"assets":["audio"]}')" + assert_jq "$story_audio_json" '.audio_status == "ready"' "story audio should be ready after retry" + audio_probe="$(curl -fsS -b "$COOKIE_JAR" -o /tmp/dreamweaver-smoke-audio.mp3 -w '%{http_code} %{content_type} %{size_download}' "$APP_URL/api/audio/$story_id")" + if [[ "$audio_probe" != 200\ audio/mpeg* ]]; then + echo "Unexpected audio response: $audio_probe" >&2 + exit 1 + fi + echo "$story_audio_json" | jq '{id,title,generation_status,image_status,audio_status}' +else + say "Skipping audio smoke; set SMOKE_AUDIO=1 to include TTS" +fi + +say "Generating storybook without images" +storybook_json="$(post_json "$APP_URL/api/generations" '{ + "output_mode": "storybook", + "type": "keywords", + "data": "彩虹邮局, 小刺猬, 练习说谢谢", + "education_theme": "感恩表达", + "generate_images": false, + "page_count": 6 +}')" +storybook_id="$(jq -r '.id' <<<"$storybook_json")" +assert_jq "$storybook_json" '.mode == "storybook" and .image_status == "not_requested" and (.pages | length) >= 4' "storybook should be readable before images" +echo "$storybook_json" | jq '{id,title,mode,generation_status,image_status,audio_status,pages:(.pages | length)}' + +say "Retrying storybook images" +storybook_image_json="$(post_json "$APP_URL/api/generations/$storybook_id/retry-assets" '{"assets":["image"]}')" +assert_jq "$storybook_image_json" '.image_status == "ready" and (.pages | length) >= 4 and ([.pages[] | select(.image_url != null)] | length) == (.pages | length)' "storybook images should be ready after retry" +echo "$storybook_image_json" | jq '{id,title,generation_status,image_status,audio_status,pages:(.pages | length), ready_pages:([.pages[] | select(.image_url != null)] | length)}' + +say "Checking story list" +list_json="$(get_json "$APP_URL/api/stories?limit=5")" +assert_jq "$list_json" "map(.id) | index($story_id) != null" "story list should include generated story" +assert_jq "$list_json" "map(.id) | index($storybook_id) != null" "story list should include generated storybook" +echo "$list_json" | jq '.[] | {id,title,mode,generation_status,image_status,audio_status}' + +say "DreamWeaver demo smoke passed"