docs: package week 4 demo and architecture
This commit is contained in:
@@ -151,9 +151,12 @@ npm run build
|
||||
- `docs/planning/week-2-execution-backlog.md`:下一阶段执行 backlog
|
||||
- `docs/planning/week-2-to-4-execution-backlog.md`:Week 2 到 Week 4 总执行 backlog
|
||||
- `docs/planning/demo-checklist.md`:求职演示检查清单
|
||||
- `docs/planning/demo-package.md`:求职版 Demo 包装页
|
||||
- `docs/planning/demo-validation-log.md`:本地 Docker 演示验证记录
|
||||
- `docs/planning/interview-pitch.md`:3 分钟项目讲解稿
|
||||
- `docs/planning/week-1-sprint-review.md`:Week 1 复盘总结
|
||||
- `docs/planning/week-4-sprint-review.md`:Week 4 复盘和生产化 backlog
|
||||
- `docs/technical/architecture.md`:求职版架构说明
|
||||
- `docs/technical/api-compatibility.md`:旧生成 API 兼容层策略
|
||||
- `docs/technical/generation-job-state.md`:Generation Job 状态落库决策
|
||||
- `docs/technical/memory-system-dev.md`:记忆系统技术说明
|
||||
|
||||
@@ -87,6 +87,9 @@ const currentStoryId = computed(() => {
|
||||
return Number.isFinite(parsed) ? parsed : null
|
||||
})
|
||||
const storybookTraceId = computed(() => storybook.value?.id ?? currentStoryId.value)
|
||||
const readingPositionKey = computed(() => (
|
||||
storybookTraceId.value ? `dreamweaver:admin-storybook:${storybookTraceId.value}:page` : null
|
||||
))
|
||||
|
||||
function goHome() {
|
||||
store.clearStorybook()
|
||||
@@ -105,6 +108,22 @@ function prevPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function restoreReadingPosition() {
|
||||
const key = readingPositionKey.value
|
||||
if (!key || !totalPages.value) {
|
||||
currentPageIndex.value = -1
|
||||
return
|
||||
}
|
||||
|
||||
const savedValue = Number(window.localStorage.getItem(key))
|
||||
if (!Number.isFinite(savedValue)) {
|
||||
currentPageIndex.value = -1
|
||||
return
|
||||
}
|
||||
|
||||
currentPageIndex.value = Math.min(Math.max(savedValue, -1), totalPages.value - 1)
|
||||
}
|
||||
|
||||
async function loadStorybook() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
@@ -125,6 +144,7 @@ async function loadStorybook() {
|
||||
}
|
||||
|
||||
if (cachedStorybook?.id === storyId) {
|
||||
restoreReadingPosition()
|
||||
loading.value = false
|
||||
await generationTraceRef.value?.refresh()
|
||||
return
|
||||
@@ -155,6 +175,7 @@ async function loadStorybook() {
|
||||
last_error: detail.last_error,
|
||||
retryable_assets: detail.retryable_assets,
|
||||
})
|
||||
restoreReadingPosition()
|
||||
await generationTraceRef.value?.refresh()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '绘本加载失败'
|
||||
@@ -208,6 +229,13 @@ watch(
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(currentPageIndex, (pageIndex) => {
|
||||
const key = readingPositionKey.value
|
||||
if (key) {
|
||||
window.localStorage.setItem(key, String(pageIndex))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
- `planning/demo-checklist.md`
|
||||
求职演示检查清单。用于演示前确认 Docker 环境、核心链路、话术和风险预案。
|
||||
|
||||
- `planning/demo-package.md`
|
||||
求职版 Demo 包装页。用于面试前快速准备演示路径、讲解锚点和失败预案。
|
||||
|
||||
- `planning/demo-validation-log.md`
|
||||
本地 Docker 演示验证记录。用于说明最近一次构建、smoke 和手动检查结果。
|
||||
|
||||
@@ -33,8 +36,14 @@
|
||||
- `planning/week-1-sprint-review.md`
|
||||
Week 1 复盘总结。用于沉淀已完成成果、剩余缺口和下一阶段建议。
|
||||
|
||||
- `planning/week-4-sprint-review.md`
|
||||
Week 4 复盘总结。用于说明求职版 Demo 包装、当前状态和生产化 backlog。
|
||||
|
||||
## Technical
|
||||
|
||||
- `technical/architecture.md`
|
||||
求职版架构说明。用于解释产品闭环、后端分层、状态模型和可观测性。
|
||||
|
||||
- `technical/memory-system-dev.md`
|
||||
记忆系统技术说明。用于后续继续做孩子档案、故事宇宙和个性化生成。
|
||||
|
||||
|
||||
66
docs/planning/demo-package.md
Normal file
66
docs/planning/demo-package.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# DreamWeaver 求职版 Demo 包装
|
||||
|
||||
这份文档用于演示前 5 分钟快速准备,也可以作为面试官追问时的项目导航。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话定位
|
||||
|
||||
DreamWeaver 是面向 3-8 岁亲子场景的个性化 AI 绘本与陪伴式讲述产品。它把孩子档案、故事宇宙、故事生成、绘本插图、语音缓存、阅读事件和成长记忆串成一个可恢复的阅读闭环。
|
||||
|
||||
---
|
||||
|
||||
## 2. 演示前检查
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
./scripts/demo_smoke.sh
|
||||
```
|
||||
|
||||
需要验证语音链路时:
|
||||
|
||||
```bash
|
||||
SMOKE_AUDIO=1 ./scripts/demo_smoke.sh
|
||||
```
|
||||
|
||||
演示入口:
|
||||
|
||||
- 用户端:`http://localhost:52080`
|
||||
- 本地登录:`http://localhost:52080/auth/dev/signin`
|
||||
- 管理端:`http://localhost:52888`
|
||||
- 后端健康:`http://localhost:52000/health`
|
||||
|
||||
---
|
||||
|
||||
## 3. 主演示路径
|
||||
|
||||
1. 使用本地登录进入用户端。
|
||||
2. 创建普通故事,说明主内容优先可读。
|
||||
3. 打开故事详情页,展示 `partial_ready`、封面补全、音频缓存状态和生成轨迹。
|
||||
4. 补全封面或音频,说明资产失败不会覆盖正文。
|
||||
5. 创建绘本,进入绘本阅读器。
|
||||
6. 刷新页面或重新进入绘本,说明按 ID 恢复和阅读位置恢复。
|
||||
7. 回到故事库,展示跨故事 Provider 运营摘要。
|
||||
8. 打开孩子时间线,展示阅读事件和记忆沉淀。
|
||||
|
||||
---
|
||||
|
||||
## 4. 面试讲解锚点
|
||||
|
||||
- **产品判断**:求职版不追求功能越多,而是围绕亲子阅读闭环收敛。
|
||||
- **AI 不确定性处理**:主内容和资产拆开,图片/音频失败不阻塞阅读。
|
||||
- **Provider 产品化**:用户看到稳定能力,系统内部用 Capability / Provider / Adapter / Routing Policy 管供应链。
|
||||
- **可观测性**:generation job/event 让生成过程、失败恢复和 Provider 成本可解释。
|
||||
- **可继续生产化**:前端已有轮询形态,后端已有任务事件模型,下一步可以迁移到 worker。
|
||||
|
||||
---
|
||||
|
||||
## 5. 失败预案
|
||||
|
||||
| 风险 | 现场处理 |
|
||||
| --- | --- |
|
||||
| TTS 网络失败 | 说明音频是可恢复资产,展示缓存状态或跳过语音 |
|
||||
| 图片生成失败 | 展示 `degraded_completed` 与资源重试 |
|
||||
| Docker 冷启动慢 | 演示前先跑 smoke 并保持容器运行 |
|
||||
| Provider 追问过深 | 回到 Capability / Provider / Adapter / Routing Policy 四层解释 |
|
||||
| 生产化追问 | 说明下一步是 worker 化、监控告警、密钥治理和 Provider analytics 扩展 |
|
||||
@@ -14,6 +14,8 @@
|
||||
- 音频缓存治理首版已验证:`GET /api/audio/{story_id}/status` 查询状态不触发生成,`DELETE /api/audio/{story_id}/cache` 可清理缓存并让音频重新进入可补全状态。
|
||||
- 时间线联动已验证:阅读事件会生成更完整的 recent_story 记忆,孩子时间线会展示阅读记录和记忆沉淀。
|
||||
- `./scripts/demo_smoke.sh` 已覆盖音频缓存状态查询。
|
||||
- Week 4 Demo 包装已完成:新增架构说明、Demo 包装文档、Week 4 sprint review,用户端和管理端绘本阅读器支持阅读位置恢复。
|
||||
- Week 4 最终回归通过:后端全量测试 85 passed,ruff 通过,用户端/管理端构建通过,`docker compose up -d --build` 和 `./scripts/demo_smoke.sh` 通过。
|
||||
- 后端新增 `partial_ready`、`text_status` 与迁移 `0012_story_text_status` 后,`backend/.venv/bin/python -m pytest backend/tests -q` 通过,82 个测试通过。
|
||||
- `backend/.venv/bin/python -m ruff check backend/app backend/tests backend/alembic/versions/0012_add_story_text_status_and_partial_ready.py` 通过。
|
||||
- 用户端与管理端 `npm run build` 均通过。
|
||||
|
||||
@@ -61,11 +61,11 @@ Week 2 已完成演示闭环、统一生成工作流、generation job/event、
|
||||
|
||||
| ID | Workstream | Task | Output | Priority | Status |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| W4-01 | Frontend | 结果页与阅读页体验收尾 | 故事详情、绘本阅读、故事库状态一致 | P0 | Pending |
|
||||
| W4-02 | Docs | 架构图与系统说明 | `docs/technical/architecture.md` 或 Mermaid 图 | P0 | Pending |
|
||||
| W4-03 | Demo | 求职版 Demo 包装 | 演示路径、风险预案、检查命令一页化 | P0 | Pending |
|
||||
| W4-04 | QA | 全量回归与验证记录 | pytest、ruff、前端 build、Docker smoke | P0 | Pending |
|
||||
| W4-05 | Product | 项目复盘与下一阶段路线 | Week 4 review + production backlog | P1 | Pending |
|
||||
| W4-01 | Frontend | 结果页与阅读页体验收尾 | 故事详情音频缓存、绘本阅读位置恢复、故事库状态一致 | P0 | Done |
|
||||
| W4-02 | Docs | 架构图与系统说明 | `docs/technical/architecture.md` | P0 | Done |
|
||||
| W4-03 | Demo | 求职版 Demo 包装 | `docs/planning/demo-package.md` | P0 | Done |
|
||||
| W4-04 | QA | 全量回归与验证记录 | pytest、ruff、前端 build、Docker smoke | P0 | Done |
|
||||
| W4-05 | Product | 项目复盘与下一阶段路线 | `docs/planning/week-4-sprint-review.md` | P1 | Done |
|
||||
|
||||
---
|
||||
|
||||
|
||||
69
docs/planning/week-4-sprint-review.md
Normal file
69
docs/planning/week-4-sprint-review.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# DreamWeaver Week 4 Sprint Review
|
||||
|
||||
**Date**: 2026-04-18
|
||||
**Theme**: 求职版 Demo 包装、结果体验收尾与生产化路线
|
||||
|
||||
---
|
||||
|
||||
## 1. 本阶段完成
|
||||
|
||||
- Week 2-4 总 backlog 已固化,Week 2 全部完成。
|
||||
- Week 3 已补齐音频缓存治理首版:
|
||||
- 音频缓存状态查询
|
||||
- 音频缓存清理
|
||||
- 故事详情页展示缓存大小和更新时间
|
||||
- Week 3 已补齐时间线与记忆联动:
|
||||
- 阅读事件进入孩子成长时间线
|
||||
- 阅读事件生成的 `recent_story` 记忆带上故事模式、封面、阅读时长和来源
|
||||
- 时间线能展示阅读记录与记忆沉淀
|
||||
- Week 4 已补齐绘本阅读位置恢复。
|
||||
- Week 4 已输出架构说明和 Demo 包装文档。
|
||||
|
||||
---
|
||||
|
||||
## 2. 当前项目状态
|
||||
|
||||
DreamWeaver 已经具备求职演示所需的完整闭环:
|
||||
|
||||
`孩子档案 -> 输入主题 -> 生成故事/绘本 -> 资产补全 -> 语音缓存 -> 阅读记录 -> 记忆沉淀 -> 成长时间线 -> 复用上下文生成新故事`
|
||||
|
||||
同时具备可解释的系统设计:
|
||||
|
||||
- 统一生成入口
|
||||
- 统一状态模型
|
||||
- generation job/event
|
||||
- Provider failover 和聚合指标
|
||||
- 跨故事 Provider analytics
|
||||
- 前端生成轨迹和自动轮询形态
|
||||
|
||||
---
|
||||
|
||||
## 3. 验证状态
|
||||
|
||||
最近一轮验证包括:
|
||||
|
||||
- 后端全量测试:85 passed
|
||||
- 后端 ruff:通过
|
||||
- 用户端生产构建:通过
|
||||
- 管理端生产构建:通过
|
||||
- Docker 全栈重建:通过
|
||||
- demo smoke:通过
|
||||
|
||||
---
|
||||
|
||||
## 4. 生产化 Backlog
|
||||
|
||||
| Priority | Task | Why |
|
||||
| --- | --- | --- |
|
||||
| P0 | 将同步生成迁移到 Celery worker | 支持真实长任务、断点恢复和后台进度 |
|
||||
| P0 | Provider analytics 加入时间窗口和失败原因 | 让运营分析可用于成本与稳定性决策 |
|
||||
| P1 | 音频缓存过期策略与后台清理 | 控制磁盘占用和缓存生命周期 |
|
||||
| P1 | 生成任务取消与重试队列 | 防止重复任务和用户误触造成浪费 |
|
||||
| P1 | 监控告警与结构化 dashboard | 上线前需要可观测性闭环 |
|
||||
| P2 | 更细粒度叙事风格与音色策略 | 扩展体验,但不影响当前求职版主线 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 面试表达
|
||||
|
||||
这个项目最重要的不是“接了几个 AI API”,而是把模型的不确定性变成了可恢复、可解释、可追踪的产品体验。主内容优先保存,资产独立补全,状态明确可读,Provider 调用可观测,阅读行为还能反哺记忆和下一次生成。
|
||||
107
docs/technical/architecture.md
Normal file
107
docs/technical/architecture.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# DreamWeaver 求职版架构说明
|
||||
|
||||
这份说明用于面试和复盘场景,帮助快速解释 DreamWeaver 如何把多模型能力收敛成稳定的亲子阅读产品闭环。
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品闭环
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["孩子档案 / 故事宇宙"] --> B["输入主题或教育目标"]
|
||||
B --> C["统一生成入口 /api/generations"]
|
||||
C --> D["文本故事或绘本结构"]
|
||||
D --> E["主记录保存到 stories"]
|
||||
E --> F["封面 / 插图 / 音频资产补全"]
|
||||
F --> G["故事详情 / 绘本阅读器"]
|
||||
G --> H["阅读事件"]
|
||||
H --> I["记忆沉淀与成长时间线"]
|
||||
I --> A
|
||||
```
|
||||
|
||||
核心取舍是主内容优先可读,封面、插图和音频作为可恢复资产补全。这样 AI 资产失败不会摧毁用户已经得到的故事。
|
||||
|
||||
---
|
||||
|
||||
## 2. 后端分层
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
FE["Vue 用户端 / 管理端"] --> API["FastAPI API 层"]
|
||||
API --> WF["story_service 统一工作流"]
|
||||
WF --> JOB["generation_jobs / events"]
|
||||
WF --> ST["story_status 状态推导"]
|
||||
WF --> MEM["memory_service 记忆上下文"]
|
||||
WF --> ROUTER["provider_router"]
|
||||
ROUTER --> POLICY["provider_policy"]
|
||||
ROUTER --> ADAPTER["text / image / tts / storybook adapters"]
|
||||
WF --> DB["PostgreSQL: stories, profiles, universes, reading_events, memory_items"]
|
||||
WF --> CACHE["音频文件缓存"]
|
||||
```
|
||||
|
||||
职责边界:
|
||||
|
||||
- API 层负责认证、限流、兼容 header 和响应模型。
|
||||
- `story_service` 负责产品工作流:上下文准备、主记录保存、资产补全、状态回写。
|
||||
- `story_status` 负责把文本、图片、音频状态推导为统一 `generation_status`。
|
||||
- `generation_jobs` 负责生成任务、事件流、进度摘要和 Provider 聚合。
|
||||
- `provider_router` 负责运行时调用、failover、指标和成本记录。
|
||||
- 前端只消费标准状态和 `retryable_assets`,不自行猜测重试逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 3. 状态模型
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> narrative_ready
|
||||
narrative_ready --> partial_ready: 主内容可读且资产待补全
|
||||
partial_ready --> assets_generating: 发起封面 / 插图 / 音频
|
||||
assets_generating --> completed: 所需资产完成
|
||||
assets_generating --> degraded_completed: 资产失败但主内容可读
|
||||
degraded_completed --> assets_generating: 用户重试失败资产
|
||||
completed --> assets_generating: 用户清理或重做资产
|
||||
narrative_ready --> failed: 主内容生成失败
|
||||
```
|
||||
|
||||
关键语义:
|
||||
|
||||
- `text_status` 只表达主文本或绘本结构是否可读。
|
||||
- `partial_ready` 表示主内容可读,资产仍可补全。
|
||||
- `degraded_completed` 表示主内容可读,但至少一个资产失败。
|
||||
- `retryable_assets` 由后端标准返回,前端按字段展示 CTA。
|
||||
|
||||
---
|
||||
|
||||
## 4. 可观测性
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
G["生成 / 重试请求"] --> J["generation_jobs"]
|
||||
J --> E["generation_job_events"]
|
||||
E --> T["任务详情时间线"]
|
||||
E --> S["单故事 Provider stats"]
|
||||
E --> A["跨故事 Provider analytics"]
|
||||
T --> UI["故事详情 / 绘本阅读"]
|
||||
S --> UI
|
||||
A --> LIB["故事库运营摘要"]
|
||||
```
|
||||
|
||||
当前已经能说明:
|
||||
|
||||
- 某个故事来自哪次生成任务。
|
||||
- 每次任务执行到哪一步。
|
||||
- Provider 是否成功、耗时多少、预估成本多少。
|
||||
- 失败资源是否可以单独重试。
|
||||
|
||||
---
|
||||
|
||||
## 5. 当前边界与下一步
|
||||
|
||||
当前仍是求职版 MVP,不引入复杂工作流引擎。下一步生产化优先级:
|
||||
|
||||
1. 把同步生成迁移到后台 worker。
|
||||
2. 基于现有 job 查询和前端轮询展示真实异步进度。
|
||||
3. 扩展 Provider analytics 的时间窗口、失败原因和跨用户维度。
|
||||
4. 为音频缓存增加过期策略和后台清理任务。
|
||||
5. 补充部署、监控告警和密钥治理策略。
|
||||
@@ -87,6 +87,9 @@ const currentStoryId = computed(() => {
|
||||
return Number.isFinite(parsed) ? parsed : null
|
||||
})
|
||||
const storybookTraceId = computed(() => storybook.value?.id ?? currentStoryId.value)
|
||||
const readingPositionKey = computed(() => (
|
||||
storybookTraceId.value ? `dreamweaver:storybook:${storybookTraceId.value}:page` : null
|
||||
))
|
||||
|
||||
function goHome() {
|
||||
store.clearStorybook()
|
||||
@@ -105,6 +108,22 @@ function prevPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function restoreReadingPosition() {
|
||||
const key = readingPositionKey.value
|
||||
if (!key || !totalPages.value) {
|
||||
currentPageIndex.value = -1
|
||||
return
|
||||
}
|
||||
|
||||
const savedValue = Number(window.localStorage.getItem(key))
|
||||
if (!Number.isFinite(savedValue)) {
|
||||
currentPageIndex.value = -1
|
||||
return
|
||||
}
|
||||
|
||||
currentPageIndex.value = Math.min(Math.max(savedValue, -1), totalPages.value - 1)
|
||||
}
|
||||
|
||||
async function loadStorybook() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
@@ -125,6 +144,7 @@ async function loadStorybook() {
|
||||
}
|
||||
|
||||
if (cachedStorybook?.id === storyId) {
|
||||
restoreReadingPosition()
|
||||
loading.value = false
|
||||
await generationTraceRef.value?.refresh()
|
||||
return
|
||||
@@ -155,6 +175,7 @@ async function loadStorybook() {
|
||||
last_error: detail.last_error,
|
||||
retryable_assets: detail.retryable_assets,
|
||||
})
|
||||
restoreReadingPosition()
|
||||
await generationTraceRef.value?.refresh()
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '绘本加载失败'
|
||||
@@ -208,6 +229,13 @@ watch(
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(currentPageIndex, (pageIndex) => {
|
||||
const key = readingPositionKey.value
|
||||
if (key) {
|
||||
window.localStorage.setItem(key, String(pageIndex))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user