Initial commit: clean project structure
- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+) - Frontend: Vue 3 + TypeScript + Pinia + Tailwind - Admin Frontend: separate Vue 3 app for management - Docker Compose: 9 services orchestration - Specs: design prototypes, memory system PRD, product roadmap Cleanup performed: - Removed temporary debug scripts from backend root - Removed deprecated admin_app.py (embedded UI) - Removed duplicate docs from admin-frontend - Updated .gitignore for Vite cache and egg-info
This commit is contained in:
130
.claude/specs/product-roadmap/PRODUCT-VISION.md
Normal file
130
.claude/specs/product-roadmap/PRODUCT-VISION.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# DreamWeaver 产品愿景与全流程规划
|
||||
|
||||
## 一、产品定位
|
||||
|
||||
### 1.1 愿景
|
||||
**梦语织机** - 为 3-8 岁儿童打造的 AI 故事创作平台,让每个孩子都能拥有专属的成长故事。
|
||||
|
||||
### 1.2 核心价值
|
||||
| 维度 | 价值主张 |
|
||||
|------|----------|
|
||||
| 个性化 | 基于关键词/主角定制,每个故事独一无二 |
|
||||
| 教育性 | 融入成长主题(勇气、友谊、诚实等) |
|
||||
| 沉浸感 | AI 封面 + 语音朗读,多感官体验 |
|
||||
| 亲子互动 | 家长参与创作,增进亲子关系 |
|
||||
|
||||
### 1.3 目标用户
|
||||
**主要用户:家长(25-40岁)**
|
||||
- 需求:为孩子找到有教育意义的睡前故事
|
||||
- 痛点:市面故事千篇一律,缺乏个性化
|
||||
- 场景:睡前、旅途、周末亲子时光
|
||||
|
||||
**次要用户:幼儿园/早教机构**
|
||||
- 需求:批量生成教学故事素材
|
||||
- 痛点:内容制作成本高
|
||||
|
||||
---
|
||||
|
||||
## 二、竞品分析
|
||||
|
||||
| 产品 | 优势 | 劣势 | 我们的差异化 |
|
||||
|------|------|------|--------------|
|
||||
| 凯叔讲故事 | 内容丰富、品牌知名 | 无个性化、订阅贵 | AI 个性化生成 |
|
||||
| 喜马拉雅儿童 | 海量音频、多平台 | 内容同质化 | 用户参与创作 |
|
||||
| ChatGPT | AI 能力强 | 非儿童专属、无配套 | 垂直场景优化 |
|
||||
| Midjourney | 图像质量高 | 无故事整合 | 故事+图像+音频一体 |
|
||||
|
||||
---
|
||||
|
||||
## 三、产品路线图
|
||||
|
||||
### Phase 1: MVP 完善 ✅ 已完成
|
||||
- [x] 关键词生成故事
|
||||
- [x] 故事润色增强
|
||||
- [x] AI 封面生成
|
||||
- [x] 语音朗读
|
||||
- [x] 故事收藏管理
|
||||
- [x] OAuth 登录
|
||||
- [x] 工程鲁棒性改进
|
||||
|
||||
### Phase 2: 体验增强
|
||||
| 功能 | 优先级 | 用户价值 |
|
||||
|------|--------|----------|
|
||||
| 故事编辑 | P0 | 用户可修改 AI 生成内容 |
|
||||
| 角色定制 | P0 | 孩子成为故事主角 |
|
||||
| 故事续写 | P1 | 形成系列故事 |
|
||||
| 多语言支持 | P1 | 英文故事学习 |
|
||||
| 故事分享 | P1 | 社交传播 |
|
||||
|
||||
### Phase 3: 供应商平台化
|
||||
| 功能 | 优先级 | 技术价值 |
|
||||
|------|--------|----------|
|
||||
| 供应商管理后台 | P0 | 可视化配置 AI 供应商 |
|
||||
| 适配器插件化 | P0 | 新供应商零代码接入 |
|
||||
| 供应商健康监控 | P1 | 自动故障转移 |
|
||||
| A/B 测试框架 | P1 | 供应商效果对比 |
|
||||
| 成本分析面板 | P2 | API 调用成本追踪 |
|
||||
|
||||
### Phase 4: 社区与增长
|
||||
| 功能 | 优先级 | 增长价值 |
|
||||
|------|--------|----------|
|
||||
| 故事广场 | P0 | 内容发现 |
|
||||
| 点赞/收藏 | P0 | 社区互动 |
|
||||
| 创作者主页 | P1 | 用户留存 |
|
||||
| 故事模板 | P1 | 降低创作门槛 |
|
||||
|
||||
### Phase 5: 商业化
|
||||
| 功能 | 优先级 | 商业价值 |
|
||||
|------|--------|----------|
|
||||
| 会员订阅 | P0 | 核心收入 |
|
||||
| 故事导出 | P0 | 增值服务 |
|
||||
| 实体书打印 | P1 | 高客单价 |
|
||||
| API 开放 | P2 | B 端收入 |
|
||||
|
||||
---
|
||||
|
||||
## 四、核心指标 (KPIs)
|
||||
|
||||
### 4.1 用户指标
|
||||
| 指标 | 定义 | 目标 |
|
||||
|------|------|------|
|
||||
| DAU | 日活跃用户 | Phase 2: 1000+ |
|
||||
| 留存率 | 次日/7日/30日 | 40%/25%/15% |
|
||||
| 创作转化率 | 访问→创作 | 30%+ |
|
||||
|
||||
### 4.2 业务指标
|
||||
| 指标 | 定义 | 目标 |
|
||||
|------|------|------|
|
||||
| 故事生成量 | 日均生成数 | 5000+ |
|
||||
| 分享率 | 故事被分享比例 | 10%+ |
|
||||
| 付费转化率 | 免费→付费 | 5%+ |
|
||||
|
||||
### 4.3 技术指标
|
||||
| 指标 | 定义 | 目标 |
|
||||
|------|------|------|
|
||||
| API 成功率 | 供应商调用成功率 | 99%+ |
|
||||
| 响应时间 | 故事生成 P95 | <30s |
|
||||
| 成本/故事 | 单个故事 API 成本 | <$0.05 |
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与应对
|
||||
|
||||
| 风险 | 影响 | 概率 | 应对策略 |
|
||||
|------|------|------|----------|
|
||||
| AI 生成内容不当 | 高 | 中 | 内容审核 + 家长控制 + 敏感词过滤 |
|
||||
| API 成本过高 | 高 | 中 | 多供应商比价 + 缓存优化 + 分级限流 |
|
||||
| 供应商服务中断 | 高 | 低 | 多供应商冗余 + 自动故障转移 |
|
||||
| 用户增长缓慢 | 中 | 中 | 社区运营 + 分享裂变 + SEO |
|
||||
| 竞品模仿 | 低 | 高 | 快速迭代 + 深耕垂直 + 数据壁垒 |
|
||||
|
||||
---
|
||||
|
||||
## 六、下一步讨论议题
|
||||
|
||||
1. **供应商平台化架构** - 如何设计插件化的适配器系统?
|
||||
2. **Phase 2 功能优先级** - 先做哪个功能?
|
||||
3. **技术选型** - nanobanana vs flux vs 其他图像供应商?
|
||||
4. **商业模式** - 免费/付费边界在哪里?
|
||||
|
||||
请确认以上产品愿景是否符合预期,我们再深入讨论供应商平台化的技术架构。
|
||||
677
.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md
Normal file
677
.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md
Normal file
@@ -0,0 +1,677 @@
|
||||
# RFC: 供应商平台化架构设计
|
||||
|
||||
## 背景
|
||||
|
||||
### 当前问题
|
||||
1. **硬编码适配器**: `gemini`, `flux`, `minimax` 写死在代码中
|
||||
2. **新供应商需改代码**: 接入 nanobanana 等新供应商需要修改 `provider_router.py`
|
||||
3. **无法动态切换**: 供应商故障时需要重启服务
|
||||
4. **缺乏监控**: 不知道哪个供应商更快、更便宜、更稳定
|
||||
|
||||
### 目标
|
||||
- **零代码接入**: 通过后台配置即可接入新供应商
|
||||
- **动态切换**: 运行时切换供应商,无需重启
|
||||
- **智能路由**: 基于成本、延迟、成功率自动选择最优供应商
|
||||
- **可观测性**: 供应商健康状态、成本、性能一目了然
|
||||
|
||||
---
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Admin Dashboard │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ 供应商管理 │ │ 健康监控 │ │ 成本分析 │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Provider Router │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 路由策略: Priority → Weight → Health → Cost │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┬───────────┬───────────┬───────────────────┐ │
|
||||
│ │ Adapter │ Adapter │ Adapter │ Adapter │ │
|
||||
│ │ Registry │ Factory │ Health │ Metrics │ │
|
||||
│ └───────────┴───────────┴───────────┴───────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ Text Adapters │ │ Image Adapters│ │ TTS Adapters │
|
||||
├───────────────┤ ├───────────────┤ ├───────────────┤
|
||||
│ • Gemini │ │ • Flux │ │ • Minimax │
|
||||
│ • OpenAI │ │ • Nanobanana │ │ • ElevenLabs │
|
||||
│ • Claude │ │ • DALL-E │ │ • Azure TTS │
|
||||
│ • Qwen │ │ • Midjourney │ │ • Google TTS │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
### 2. 核心组件
|
||||
|
||||
#### 2.1 Adapter 接口定义
|
||||
|
||||
```python
|
||||
# 统一适配器接口
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TypeVar, Generic
|
||||
from pydantic import BaseModel
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class AdapterConfig(BaseModel):
|
||||
"""适配器配置基类"""
|
||||
api_key: str
|
||||
api_base: str | None = None
|
||||
model: str | None = None
|
||||
timeout_ms: int = 60000
|
||||
max_retries: int = 3
|
||||
|
||||
class BaseAdapter(ABC, Generic[T]):
|
||||
"""适配器基类"""
|
||||
|
||||
# 适配器元信息
|
||||
adapter_type: str # text / image / tts
|
||||
adapter_name: str # gemini / flux / minimax
|
||||
|
||||
def __init__(self, config: AdapterConfig):
|
||||
self.config = config
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, **kwargs) -> T:
|
||||
"""执行适配器逻辑"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def health_check(self) -> bool:
|
||||
"""健康检查"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def estimated_cost(self) -> float:
|
||||
"""预估单次调用成本 (USD)"""
|
||||
pass
|
||||
```
|
||||
|
||||
#### 2.2 适配器注册表
|
||||
|
||||
```python
|
||||
# 适配器注册表 - 支持动态注册
|
||||
class AdapterRegistry:
|
||||
"""适配器注册表"""
|
||||
|
||||
_adapters: dict[str, type[BaseAdapter]] = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, adapter_type: str, adapter_name: str):
|
||||
"""装饰器: 注册适配器"""
|
||||
def decorator(adapter_class: type[BaseAdapter]):
|
||||
key = f"{adapter_type}:{adapter_name}"
|
||||
cls._adapters[key] = adapter_class
|
||||
return adapter_class
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def get(cls, adapter_type: str, adapter_name: str) -> type[BaseAdapter] | None:
|
||||
key = f"{adapter_type}:{adapter_name}"
|
||||
return cls._adapters.get(key)
|
||||
|
||||
@classmethod
|
||||
def list_adapters(cls, adapter_type: str | None = None) -> list[str]:
|
||||
"""列出所有已注册的适配器"""
|
||||
if adapter_type:
|
||||
return [k for k in cls._adapters if k.startswith(f"{adapter_type}:")]
|
||||
return list(cls._adapters.keys())
|
||||
```
|
||||
|
||||
#### 2.3 适配器实现示例
|
||||
|
||||
```python
|
||||
# 图像适配器示例: Nanobanana
|
||||
@AdapterRegistry.register("image", "nanobanana")
|
||||
class NanobananapAdapter(BaseAdapter[str]):
|
||||
adapter_type = "image"
|
||||
adapter_name = "nanobanana"
|
||||
|
||||
async def execute(self, prompt: str, **kwargs) -> str:
|
||||
"""生成图片,返回 URL"""
|
||||
async with httpx.AsyncClient(timeout=self.config.timeout_ms / 1000) as client:
|
||||
response = await client.post(
|
||||
f"{self.config.api_base}/generate",
|
||||
json={"prompt": prompt, "model": self.config.model},
|
||||
headers={"Authorization": f"Bearer {self.config.api_key}"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()["image_url"]
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
# 简单的健康检查
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5) as client:
|
||||
response = await client.get(f"{self.config.api_base}/health")
|
||||
return response.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@property
|
||||
def estimated_cost(self) -> float:
|
||||
return 0.02 # $0.02 per image
|
||||
```
|
||||
|
||||
#### 2.4 智能路由器
|
||||
|
||||
```python
|
||||
class ProviderRouter:
|
||||
"""智能供应商路由器"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self._health_cache: dict[str, tuple[bool, float]] = {} # adapter_key -> (healthy, last_check)
|
||||
|
||||
async def route(
|
||||
self,
|
||||
provider_type: str,
|
||||
strategy: str = "priority", # priority / cost / latency / round_robin
|
||||
**kwargs
|
||||
):
|
||||
"""路由到最优供应商"""
|
||||
providers = await self._get_enabled_providers(provider_type)
|
||||
|
||||
if not providers:
|
||||
raise ValueError(f"No {provider_type} providers configured")
|
||||
|
||||
# 按策略排序
|
||||
sorted_providers = self._sort_by_strategy(providers, strategy)
|
||||
|
||||
errors = []
|
||||
for provider in sorted_providers:
|
||||
# 检查健康状态
|
||||
if not await self._is_healthy(provider):
|
||||
continue
|
||||
|
||||
try:
|
||||
adapter = self._create_adapter(provider)
|
||||
result = await adapter.execute(**kwargs)
|
||||
|
||||
# 记录成功指标
|
||||
await self._record_metrics(provider, success=True)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"{provider.name}: {e}")
|
||||
await self._record_metrics(provider, success=False, error=str(e))
|
||||
continue
|
||||
|
||||
raise ValueError(f"All providers failed: {' | '.join(errors)}")
|
||||
|
||||
def _sort_by_strategy(self, providers: list[Provider], strategy: str) -> list[Provider]:
|
||||
if strategy == "priority":
|
||||
return sorted(providers, key=lambda p: (-p.priority, -p.weight))
|
||||
elif strategy == "cost":
|
||||
return sorted(providers, key=lambda p: self._get_estimated_cost(p))
|
||||
elif strategy == "latency":
|
||||
return sorted(providers, key=lambda p: self._get_avg_latency(p))
|
||||
else:
|
||||
return providers
|
||||
```
|
||||
|
||||
### 3. 数据模型扩展
|
||||
|
||||
```sql
|
||||
-- 供应商表 (已有,需扩展)
|
||||
ALTER TABLE providers ADD COLUMN api_key_ref VARCHAR(100); -- 密钥引用 (从 secrets 表获取)
|
||||
ALTER TABLE providers ADD COLUMN request_schema JSONB; -- 请求参数 schema
|
||||
ALTER TABLE providers ADD COLUMN response_parser VARCHAR(200); -- 响应解析规则
|
||||
|
||||
-- 供应商指标表 (新增)
|
||||
CREATE TABLE provider_metrics (
|
||||
id SERIAL PRIMARY KEY,
|
||||
provider_id VARCHAR(36) REFERENCES providers(id),
|
||||
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
success BOOLEAN,
|
||||
latency_ms INTEGER,
|
||||
cost_usd DECIMAL(10, 6),
|
||||
error_message TEXT,
|
||||
request_id VARCHAR(100)
|
||||
);
|
||||
|
||||
-- 供应商健康状态表 (新增)
|
||||
CREATE TABLE provider_health (
|
||||
provider_id VARCHAR(36) PRIMARY KEY REFERENCES providers(id),
|
||||
is_healthy BOOLEAN DEFAULT TRUE,
|
||||
last_check TIMESTAMP WITH TIME ZONE,
|
||||
consecutive_failures INTEGER DEFAULT 0,
|
||||
last_error TEXT
|
||||
);
|
||||
|
||||
-- 密钥管理表 (新增)
|
||||
CREATE TABLE provider_secrets (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
encrypted_value TEXT NOT NULL, -- 加密存储
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4. Admin Dashboard 功能
|
||||
|
||||
#### 4.1 供应商管理
|
||||
- 供应商列表 (启用/禁用/删除)
|
||||
- 新增供应商 (选择适配器类型 + 配置参数)
|
||||
- 编辑供应商 (修改优先级/权重/超时等)
|
||||
- 测试连接 (验证 API Key 有效性)
|
||||
|
||||
#### 4.2 健康监控
|
||||
- 实时健康状态 (绿/黄/红)
|
||||
- 成功率趋势图
|
||||
- 延迟分布图
|
||||
- 故障告警配置
|
||||
|
||||
#### 4.3 成本分析
|
||||
- 按供应商统计调用量
|
||||
- 按供应商统计成本
|
||||
- 成本趋势图
|
||||
- 预算告警
|
||||
|
||||
#### 4.4 A/B 测试
|
||||
- 创建实验 (供应商 A vs B)
|
||||
- 流量分配 (50/50 或自定义)
|
||||
- 效果对比 (成功率/延迟/成本)
|
||||
|
||||
---
|
||||
|
||||
## 实现路径
|
||||
|
||||
### 阶段 1: 适配器抽象 (基础) - ✅ 已完成
|
||||
|
||||
| 任务 | 状态 | 文件 |
|
||||
|------|------|------|
|
||||
| 定义 `BaseAdapter` 接口 | ✅ | `services/adapters/base.py` |
|
||||
| 实现 `AdapterRegistry` 注册表 | ✅ | `services/adapters/registry.py` |
|
||||
| 重构 GeminiAdapter | ✅ | `services/adapters/text/gemini.py` |
|
||||
| 重构 FluxAdapter | ✅ | `services/adapters/image/flux.py` |
|
||||
| 重构 MinimaxAdapter | ✅ | `services/adapters/tts/minimax.py` |
|
||||
| 重构 `ProviderRouter` 使用新接口 | ✅ | `services/provider_router.py` |
|
||||
|
||||
### 阶段 2: 新供应商接入 (扩展) - 待开始
|
||||
1. 实现 Nanobanana 适配器
|
||||
2. 实现 OpenAI/Claude 文本适配器
|
||||
3. 实现 ElevenLabs TTS 适配器
|
||||
4. 验证零代码接入流程
|
||||
|
||||
### 阶段 3: 监控与分析 (可观测) - 待开始
|
||||
1. 实现指标收集
|
||||
2. 实现健康检查
|
||||
3. 实现成本追踪
|
||||
4. Admin Dashboard 开发
|
||||
|
||||
### 阶段 4: 智能路由 (优化) - 待开始
|
||||
1. 实现多种路由策略
|
||||
2. 实现自动故障转移
|
||||
3. 实现 A/B 测试框架
|
||||
|
||||
---
|
||||
|
||||
## 并行执行与容错设计
|
||||
|
||||
### 问题
|
||||
|
||||
当前串行流程存在两个问题:
|
||||
1. **等待时间长**: 故事(3-5s) → 封面(5-10s) → 音频(3-5s) = 总计 11-20s
|
||||
2. **单点失败**: 某一步502/超时导致整个流程失败
|
||||
|
||||
### 方案 1: 并行执行
|
||||
|
||||
```python
|
||||
async def generate_story_full(keywords: list[str]) -> StoryResult:
|
||||
# Step 1: 故事生成(必须先完成,后续依赖它)
|
||||
story = await generate_story_content(keywords)
|
||||
|
||||
# Step 2: 图片和音频并行执行
|
||||
image_task = asyncio.create_task(generate_image(story.summary))
|
||||
audio_task = asyncio.create_task(text_to_speech(story.content))
|
||||
|
||||
# 等待两者完成,互不阻塞
|
||||
image_result, audio_result = await asyncio.gather(
|
||||
image_task, audio_task,
|
||||
return_exceptions=True # 一个<E4B880><E4B8AA><EFBFBD>败不影响另一个
|
||||
)
|
||||
|
||||
return StoryResult(
|
||||
story=story,
|
||||
image_url=image_result if not isinstance(image_result, Exception) else None,
|
||||
audio_url=audio_result if not isinstance(audio_result, Exception) else None,
|
||||
errors={
|
||||
"image": str(image_result) if isinstance(image_result, Exception) else None,
|
||||
"audio": str(audio_result) if isinstance(audio_result, Exception) else None,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**时间对比:**
|
||||
```
|
||||
串行: 3s + 8s + 4s = 15s
|
||||
并行: 3s + max(8s, 4s) = 11s (节省 27%)
|
||||
```
|
||||
|
||||
### 方案 2: 部分成功处理
|
||||
|
||||
**核心原则: 部分成功 > 全部失败**
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class StoryResult:
|
||||
story: Story # 核心,必须成功
|
||||
image_url: str | None = None # 增强,可降级
|
||||
audio_url: str | None = None # 增强,可降级
|
||||
errors: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def is_complete(self) -> bool:
|
||||
return self.image_url is not None and self.audio_url is not None
|
||||
|
||||
@property
|
||||
def failed_components(self) -> list[str]:
|
||||
return [k for k, v in self.errors.items() if v is not None]
|
||||
```
|
||||
|
||||
**降级策略:**
|
||||
|
||||
| 组件 | 失败时降级方案 | 用户体验 |
|
||||
|------|---------------|---------|
|
||||
| 故事 | 无降级,整体失败 | 显示错误,提示重试 |
|
||||
| 封面 | 使用默认封面图 | 显示占位图 + "重新生成"按钮 |
|
||||
| 音频 | 不生成音频 | 隐藏播放按钮 + "生成语音"按钮 |
|
||||
|
||||
### 方案 3: 流式返回 (SSE)
|
||||
|
||||
**为什么用 SSE:**
|
||||
- 用户无需等待全部完成
|
||||
- 每完成一步立即展示
|
||||
- 比 WebSocket 简单,HTTP 兼容性好
|
||||
|
||||
**后端实现:**
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/api/generate/stream")
|
||||
async def generate_story_stream(
|
||||
request: GenerateRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
async def event_generator():
|
||||
# 1. 立即返回任务ID
|
||||
story_id = str(uuid.uuid4())
|
||||
yield {"event": "started", "data": json.dumps({"story_id": story_id})}
|
||||
|
||||
# 2. 生成故事
|
||||
try:
|
||||
story = await generate_story_content(request.keywords)
|
||||
yield {"event": "story_ready", "data": json.dumps({
|
||||
"title": story.title,
|
||||
"content": story.content,
|
||||
})}
|
||||
except Exception as e:
|
||||
yield {"event": "story_failed", "data": json.dumps({"error": str(e)})}
|
||||
return
|
||||
|
||||
# 3. 并行生成图片和音频
|
||||
async def gen_image():
|
||||
try:
|
||||
url = await generate_image(story.summary)
|
||||
yield {"event": "image_ready", "data": json.dumps({"image_url": url})}
|
||||
except Exception as e:
|
||||
yield {"event": "image_failed", "data": json.dumps({"error": str(e)})}
|
||||
|
||||
async def gen_audio():
|
||||
try:
|
||||
url = await text_to_speech(story.content)
|
||||
yield {"event": "audio_ready", "data": json.dumps({"audio_url": url})}
|
||||
except Exception as e:
|
||||
yield {"event": "audio_failed", "data": json.dumps({"error": str(e)})}
|
||||
|
||||
# 并行执行,逐个yield结果
|
||||
tasks = [gen_image(), gen_audio()]
|
||||
for coro in asyncio.as_completed([t.__anext__() for t in tasks]):
|
||||
result = await coro
|
||||
yield result
|
||||
|
||||
yield {"event": "complete", "data": json.dumps({"story_id": story_id})}
|
||||
|
||||
return EventSourceResponse(event_generator())
|
||||
```
|
||||
|
||||
**前端实现:**
|
||||
|
||||
```typescript
|
||||
const eventSource = new EventSource('/api/generate/stream', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ keywords }),
|
||||
});
|
||||
|
||||
eventSource.addEventListener('started', (e) => {
|
||||
const { story_id } = JSON.parse(e.data);
|
||||
showLoading('正在创作故事...');
|
||||
});
|
||||
|
||||
eventSource.addEventListener('story_ready', (e) => {
|
||||
const { title, content } = JSON.parse(e.data);
|
||||
renderStory(title, content);
|
||||
showLoading('正在生成封面和语音...');
|
||||
});
|
||||
|
||||
eventSource.addEventListener('image_ready', (e) => {
|
||||
const { image_url } = JSON.parse(e.data);
|
||||
renderCover(image_url);
|
||||
});
|
||||
|
||||
eventSource.addEventListener('image_failed', (e) => {
|
||||
showRetryButton('image');
|
||||
});
|
||||
|
||||
eventSource.addEventListener('audio_ready', (e) => {
|
||||
const { audio_url } = JSON.parse(e.data);
|
||||
enablePlayButton(audio_url);
|
||||
});
|
||||
|
||||
eventSource.addEventListener('complete', () => {
|
||||
eventSource.close();
|
||||
hideLoading();
|
||||
});
|
||||
```
|
||||
|
||||
**用户体验时间线:**
|
||||
```
|
||||
0s → 显示"正在创作..."
|
||||
3s → 故事文本渲染,显示"正在生成封面和语音..."
|
||||
3-7s → 音频就绪,播放按钮可用
|
||||
3-11s → 封面就绪,图片显示
|
||||
11s → 完成
|
||||
```
|
||||
|
||||
### 方案 4: 断点续传 (可选)
|
||||
|
||||
适用于网络不稳定场景,支持刷新页面后继续:
|
||||
|
||||
```python
|
||||
class StoryWorkflowState(Base):
|
||||
__tablename__ = "story_workflow_states"
|
||||
|
||||
story_id: Mapped[str] = mapped_column(String(36), primary_key=True)
|
||||
status: Mapped[str] = mapped_column(String(20)) # pending/story_done/image_done/audio_done/complete
|
||||
story_content: Mapped[str | None] = mapped_column(Text)
|
||||
image_url: Mapped[str | None] = mapped_column(String(500))
|
||||
audio_url: Mapped[str | None] = mapped_column(String(500))
|
||||
last_error: Mapped[str | None] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, onupdate=datetime.utcnow)
|
||||
|
||||
async def resume_workflow(story_id: str) -> StoryResult:
|
||||
state = await get_workflow_state(story_id)
|
||||
|
||||
if state.status == "story_done":
|
||||
# 从图片+音频生成继续
|
||||
return await generate_image_and_audio(state)
|
||||
elif state.status == "image_done":
|
||||
# 只需要生成音频
|
||||
return await generate_audio_only(state)
|
||||
elif state.status == "audio_done":
|
||||
# 只需要生成图片
|
||||
return await generate_image_only(state)
|
||||
else:
|
||||
return StoryResult.from_state(state)
|
||||
```
|
||||
|
||||
### 推荐实现顺序
|
||||
|
||||
| 优先级 | 方案 | 收益 | 复杂度 | 状态 |
|
||||
|--------|------|------|--------|------|
|
||||
| P0 | 并行执行 | 节省 27% 时间 | 低 | ✅ 已完成 |
|
||||
| P0 | 部分成功 | 提升容错性 | 低 | ✅ 已完成 |
|
||||
| P1 | SSE 流式返回 | 体验大幅提升 | 中 | 待开始 |
|
||||
| P2 | 断点续传 | 极端场景保障 | 高 | 待开始 |
|
||||
|
||||
**P0 实现详情:**
|
||||
- 新增 API: `POST /api/generate/full`
|
||||
- 文件: `api/stories.py:113-189`
|
||||
- 响应模型: `FullStoryResponse` (含 `errors` 字段标识失败组件)
|
||||
|
||||
---
|
||||
|
||||
## 待决策清单
|
||||
|
||||
> **使用说明**: 在每个决策的 `[ ]` 中填入你的选择(如 `[x]` 或 `[B]`),确认后删除未选中的选项。
|
||||
|
||||
---
|
||||
|
||||
### 决策 1: 适配器配置存储
|
||||
|
||||
**问题**: 适配器的配置信息(API地址、模型名、超时等)存在哪里?
|
||||
|
||||
| 选项 | 方案 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| [ ] A | 全部存数据库 | 完全动态,运行时可改 | 需要管理界面,初始化复杂 |
|
||||
| [ ] B | 代码定义 + DB配置 | 平衡,核心逻辑在代码,参数可调 | 新适配器仍需改代码 |
|
||||
| [ ] C | 配置文件 (YAML/JSON) | 简单,版本控制友好 | 改配置需重启 |
|
||||
|
||||
**推荐**: B(代码定义适配器类,DB存储启用状态/优先级/API Key引用)
|
||||
|
||||
---
|
||||
|
||||
### 决策 2: 密钥管理
|
||||
|
||||
**问题**: API Key 等敏感信息如何存储?
|
||||
|
||||
| 选项 | 方案 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| [ ] A | 环境变量 | 简单,当前方式 | 多供应商时env膨胀,改key需重启 |
|
||||
| [ ] B | 数据库加密存储 | 动态管理,支持多key | 需要加密方案,安全风险 |
|
||||
| [ ] C | 外部密钥服务 (Vault/AWS Secrets) | 企业级安全 | 复杂,增加依赖 |
|
||||
|
||||
**推荐**: A(当前阶段),后期可迁移到B
|
||||
|
||||
---
|
||||
|
||||
### 决策 3: 图像供应商优先级
|
||||
|
||||
**问题**: 接入多个图像供应商后,默认使用哪个?
|
||||
|
||||
| 选项 | 供应商 | 特点 | 预估成本 |
|
||||
|------|--------|------|----------|
|
||||
| [ ] 1 | Nanobanana | 新兴,据说效果好 | 待调研 |
|
||||
| [ ] 2 | Flux (当前) | 稳定,已接入 | ~$0.03/张 |
|
||||
| [ ] 3 | DALL-E 3 | OpenAI出品,质量高 | ~$0.04/张 |
|
||||
| [ ] 4 | Midjourney | 艺术风格强 | API受限 |
|
||||
|
||||
**推荐**: 先调研Nanobanana,效果好则替换Flux
|
||||
|
||||
---
|
||||
|
||||
### 决策 4: 文本供应商优先级
|
||||
|
||||
**问题**: 故事生成使用哪个LLM?
|
||||
|
||||
| 选项 | 供应商 | 特点 | 预估成本 |
|
||||
|------|--------|------|----------|
|
||||
| [ ] 1 | Gemini (当前) | 免费额度大,中文好 | 免费/低成本 |
|
||||
| [ ] 2 | OpenAI GPT-4o | 质量稳定 | ~$0.01/1K tokens |
|
||||
| [ ] 3 | Claude | 创意写作强 | ~$0.015/1K tokens |
|
||||
| [ ] 4 | Qwen (通义千问) | 国内,中文优化 | 待调研 |
|
||||
|
||||
**推荐**: Gemini为主,OpenAI备用
|
||||
|
||||
---
|
||||
|
||||
### 决策 5: TTS供应商优先级
|
||||
|
||||
**问题**: 语音合成使用哪个服务?
|
||||
|
||||
| 选项 | 供应商 | 特点 | 预估成本 |
|
||||
|------|--------|------|----------|
|
||||
| [ ] 1 | Minimax (当前) | 中文效果好,已接入 | ~$0.01/1K字符 |
|
||||
| [ ] 2 | ElevenLabs | 英文最佳,多语言 | ~$0.03/1K字符 |
|
||||
| [ ] 3 | Azure TTS | 稳定,多语言 | ~$0.016/1K字符 |
|
||||
| [ ] 4 | Google TTS | 便宜 | ~$0.004/1K字符 |
|
||||
|
||||
**推荐**: Minimax为主(中文场景)
|
||||
|
||||
---
|
||||
|
||||
### 决策 6: Admin Dashboard 技术栈
|
||||
|
||||
**问题**: 供应商管理后台用什么技术?
|
||||
|
||||
| 选项 | 方案 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| [ ] A | 复用 Vue 前端 | 技术栈统一,复用组件 | 需要自己写UI |
|
||||
| [ ] B | React Admin | 成熟的Admin框架 | 引入新技术栈 |
|
||||
| [ ] C | 现成方案 (AdminJS/Retool) | 开发快 | 定制性差,可能收费 |
|
||||
|
||||
**推荐**: A(在现有Vue项目中加 `/admin` 路由)
|
||||
|
||||
---
|
||||
|
||||
### 决策 7: Phase 2 功能优先级
|
||||
|
||||
**问题**: 体验增强阶段先做哪个功能?
|
||||
|
||||
| 选项 | 功能 | 用户价值 | 开发复杂度 |
|
||||
|------|------|----------|------------|
|
||||
| [ ] 1 | 故事编辑 | 高(用户可修改AI内容) | 中 |
|
||||
| [ ] 2 | 角色定制 | 高(孩子成为主角) | 低 |
|
||||
| [ ] 3 | 故事分享 | 高(增长引擎) | 中 |
|
||||
| [ ] 4 | 故事续写 | 中(延长使用时长) | 中 |
|
||||
|
||||
**推荐**: 2 → 1 → 3 → 4(角色定制最快出效果)
|
||||
|
||||
---
|
||||
|
||||
### 决策 8: 并行与容错实现顺序
|
||||
|
||||
**问题**: 并行执行、部分成功、SSE、断点续传,先做哪些?
|
||||
|
||||
| 选项 | 方案 | 说明 |
|
||||
|------|------|------|
|
||||
| [ ] A | P0先做 | 先实现并行+部分成功,快速见效 |
|
||||
| [ ] B | P0+P1一起 | 并行+部分成功+SSE,体验完整 |
|
||||
| [ ] C | 只做SSE | 跳过简单方案,直接上流式 |
|
||||
|
||||
**推荐**: A(先P0,验证后再做SSE)
|
||||
|
||||
---
|
||||
|
||||
## 确认后删除此区块
|
||||
|
||||
确认所有决策后,可以删除未选中的选项,保留最终方案作为实现依据。
|
||||
169
.claude/specs/product-roadmap/ROADMAP.md
Normal file
169
.claude/specs/product-roadmap/ROADMAP.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# DreamWeaver 产品路线图
|
||||
|
||||
## 产品愿景
|
||||
|
||||
**梦语织机** - 为 3-8 岁儿童打造的 AI 故事创作平台,让每个孩子都能拥有专属的成长故事。
|
||||
|
||||
### 核心价值主张
|
||||
- **个性化**: 基于关键词生成独一无二的故事
|
||||
- **教育性**: 融入成长主题(勇气、友谊、诚实等)
|
||||
- **沉浸感**: AI 封面 + 语音朗读,多感官体验
|
||||
- **亲子互动**: 家长参与创作,增进亲子关系
|
||||
|
||||
---
|
||||
|
||||
## 用户画像
|
||||
|
||||
### 主要用户:家长(25-40岁)
|
||||
- **需求**: 为孩子找到有教育意义的睡前故事
|
||||
- **痛点**: 市面故事千篇一律,缺乏个性化
|
||||
- **场景**: 睡前、旅途、周末亲子时光
|
||||
|
||||
### 次要用户:幼儿园/早教机构
|
||||
- **需求**: 批量生成教学故事素材
|
||||
- **痛点**: 内容制作成本高
|
||||
- **场景**: 课堂教学、活动策划
|
||||
|
||||
---
|
||||
|
||||
## 功能规划
|
||||
|
||||
### Phase 1: MVP 完善(当前)
|
||||
> 目标:核心体验闭环,用户可完整使用
|
||||
|
||||
| 功能 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 关键词生成故事 | ✅ 已完成 | 输入关键词,AI 生成故事 |
|
||||
| 故事润色增强 | ✅ 已完成 | 用户提供草稿,AI 润色 |
|
||||
| AI 封面生成 | ✅ 已完成 | 根据故事生成插画 |
|
||||
| 语音朗读 | ✅ 已完成 | TTS 朗读故事 |
|
||||
| 故事收藏管理 | ✅ 已完成 | 保存、查看、删除 |
|
||||
| OAuth 登录 | ✅ 已完成 | GitHub/Google 登录 |
|
||||
|
||||
### Phase 2: 体验增强
|
||||
> 目标:提升用户粘性,增加互动性
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| **故事编辑** | P0 | 用户可修改 AI 生成的故事内容 |
|
||||
| **角色定制** | P0 | 输入孩子姓名/性别,成为故事主角 |
|
||||
| **故事续写** | P1 | 基于已有故事继续创作下一章 |
|
||||
| **多语言支持** | P1 | 英文故事生成(已有 i18n 基础) |
|
||||
| **故事分享** | P1 | 生成分享图片/链接 |
|
||||
| **收藏夹/标签** | P2 | 故事分类管理 |
|
||||
|
||||
### Phase 3: 社区与增长
|
||||
> 目标:构建用户社区,实现自然增长
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| **故事广场** | P0 | 公开优质故事,用户可浏览 |
|
||||
| **点赞/收藏** | P0 | 社区互动基础 |
|
||||
| **故事模板** | P1 | 预设故事框架(冒险/友谊/成长) |
|
||||
| **创作者主页** | P1 | 展示用户创作的故事集 |
|
||||
| **评论系统** | P2 | 用户交流反馈 |
|
||||
|
||||
### Phase 4: 商业化
|
||||
> 目标:建立可持续商业模式
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| **会员订阅** | P0 | 免费/基础/高级三档 |
|
||||
| **故事导出** | P0 | PDF/电子书格式导出 |
|
||||
| **实体书打印** | P1 | 对接印刷服务,生成实体绘本 |
|
||||
| **API 开放** | P2 | 为 B 端客户提供 API |
|
||||
| **企业版** | P2 | 幼儿园/早教机构定制 |
|
||||
|
||||
---
|
||||
|
||||
## 技术架构演进
|
||||
|
||||
### 当前架构 (Phase 1)
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Vue 3 │────▶│ FastAPI │────▶│ PostgreSQL │
|
||||
│ Frontend │ │ Backend │ │ (Neon) │
|
||||
└─────────────┘ └──────┬──────┘ └─────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Gemini │ │ Minimax │ │ Flux │
|
||||
│ (Text) │ │ (TTS) │ │ (Image) │
|
||||
└────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
### Phase 2 架构演进
|
||||
```
|
||||
新增组件:
|
||||
- Redis: 缓存 + 会话 + Rate Limit
|
||||
- Celery: 异步任务队列(图片/音频生成)
|
||||
- S3/OSS: 静态资源存储
|
||||
```
|
||||
|
||||
### Phase 3 架构演进
|
||||
```
|
||||
新增组件:
|
||||
- Elasticsearch: 故事全文搜索
|
||||
- CDN: 静态资源加速
|
||||
- 消息队列: 社区通知推送
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 里程碑规划
|
||||
|
||||
### M1: MVP 完善 ✅
|
||||
- [x] 核心功能闭环
|
||||
- [x] 工程鲁棒性改进
|
||||
- [x] 测试覆盖
|
||||
|
||||
### M2: 体验增强
|
||||
- [ ] 故事编辑功能
|
||||
- [ ] 角色定制(孩子成为主角)
|
||||
- [ ] 故事续写
|
||||
- [ ] 多语言支持
|
||||
- [ ] 分享功能
|
||||
|
||||
### M3: 社区上线
|
||||
- [ ] 故事广场
|
||||
- [ ] 用户互动(点赞/收藏)
|
||||
- [ ] 创作者主页
|
||||
|
||||
### M4: 商业化
|
||||
- [ ] 会员体系
|
||||
- [ ] 故事导出
|
||||
- [ ] 实体书打印
|
||||
|
||||
---
|
||||
|
||||
## 竞品分析
|
||||
|
||||
| 产品 | 优势 | 劣势 | 我们的差异化 |
|
||||
|------|------|------|--------------|
|
||||
| 凯叔讲故事 | 内容丰富、品牌知名 | 无个性化、订阅贵 | AI 个性化生成 |
|
||||
| 喜马拉雅儿童 | 海量音频、多平台 | 内容同质化 | 用户参与创作 |
|
||||
| ChatGPT | AI 能力强 | 非儿童专属、无配套 | 垂直场景优化 |
|
||||
|
||||
---
|
||||
|
||||
## 风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对策略 |
|
||||
|------|------|----------|
|
||||
| AI 生成内容不当 | 高 | 内容审核 + 家长控制 |
|
||||
| API 成本过高 | 中 | 缓存优化 + 分级限流 |
|
||||
| 用户增长缓慢 | 中 | 社区运营 + 分享裂变 |
|
||||
| 竞品模仿 | 低 | 快速迭代 + 深耕垂直 |
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
**Phase 2 优先实现功能:**
|
||||
|
||||
1. **故事编辑** - 用户体验核心痛点
|
||||
2. **角色定制** - 差异化竞争力
|
||||
3. **故事分享** - 自然增长引擎
|
||||
|
||||
是否需要我为这些功能生成详细的技术规格文档?
|
||||
Reference in New Issue
Block a user