diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 818f54f..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "permissions": { - "allow": [ - "Skill(codex)", - "Bash(pip install:*)", - "Bash(alembic upgrade:*)", - "Bash(uvicorn:*)", - "Bash(npm run dev)", - "Bash(python:*)", - "Bash(ruff check:*)", - "Bash(tasklist:*)", - "Bash(findstr:*)", - "Bash(pushd:*)", - "Bash(popd)", - "Bash(curl:*)", - "WebSearch", - "WebFetch(domain:www.novelai.net)", - "WebFetch(domain:www.storywizard.ai)", - "WebFetch(domain:www.oscarstories.com)", - "WebFetch(domain:www.moshi.com)", - "WebFetch(domain:www.calm.com)", - "WebFetch(domain:www.epic.com)", - "WebFetch(domain:www.headspace.com)", - "WebFetch(domain:www.getepic.com)", - "WebFetch(domain:www.tonies.com)", - "Bash(del:*)", - "Bash(netstat:*)", - "Bash(taskkill:*)", - "Bash(codex-wrapper:*)", - "Bash(dir /b /s /a-d /o-d)", - "Bash(dir:*)", - "Bash(.venv/Scripts/python:*)", - "Bash(.venv/Scripts/ruff check:*)", - "Bash(npm run build:*)", - "Bash(test -f \"F:\\\\Code\\\\dreamweaver-python\\\\backend\\\\.env\")", - "Bash(pytest:*)", - "Bash(npm run type-check:*)", - "Bash(npx vue-tsc:*)", - "Bash(ls:*)", - "Bash(git init:*)", - "Bash(git add:*)", - "Bash(git commit:*)", - "Bash(git remote add:*)", - "Bash(git push:*)", - "Bash(git branch:*)" - ] - } -} diff --git a/.claude/specs/code-architecture/REFACTORING-PRD.md b/.claude/specs/code-architecture/REFACTORING-PRD.md deleted file mode 100644 index 34a5237..0000000 --- a/.claude/specs/code-architecture/REFACTORING-PRD.md +++ /dev/null @@ -1,416 +0,0 @@ -# 代码架构重构 PRD - -> 版本: 1.0 -> 日期: 2025-01-21 -> 状态: 待实施 - ---- - -## 1. 背景与目标 - -### 1.1 现状问题 - -经过代码审计,发现以下架构债务: - -| 问题 | 位置 | 影响 | -|------|------|------| -| 缺少 Schemas 层 | Backend API | 类型不安全,文档生成差 | -| Fat Controller | `stories.py` 562行 | 难测试,职责混乱 | -| 重复加载逻辑 | Frontend 5+ 组件 | 代码冗余,维护困难 | -| 无 Repository 层 | Backend | DB 查询散落,难复用 | - -### 1.2 重构目标 - -- **后端**:引入 Pydantic Schemas 层,分离请求/响应模型 -- **前端**:抽取 Vue Composables,消除重复逻辑 -- **原则**:渐进式重构,不破坏现有功能 - ---- - -## 2. 后端重构:Schemas 层 - -### 2.1 目标结构 - -``` -backend/app/ -├── api/ # 路由层(瘦身) -│ ├── stories.py -│ └── ... -├── schemas/ # 新增:Pydantic 模型 -│ ├── __init__.py -│ ├── auth.py -│ ├── story.py -│ ├── profile.py -│ ├── universe.py -│ ├── memory.py -│ ├── push_config.py -│ └── common.py # 分页、错误响应等通用结构 -├── models/ # SQLAlchemy ORM(原 db/) -└── services/ # 业务逻辑 -``` - -### 2.2 Schema 设计规范 - -#### 命名约定 - -| 类型 | 命名模式 | 示例 | -|------|----------|------| -| 创建请求 | `{Entity}Create` | `StoryCreate` | -| 更新请求 | `{Entity}Update` | `ProfileUpdate` | -| 响应模型 | `{Entity}Response` | `StoryResponse` | -| 列表响应 | `{Entity}ListResponse` | `StoryListResponse` | - -#### 示例:Story Schema - -```python -# schemas/story.py -from datetime import datetime -from typing import Literal -from pydantic import BaseModel, Field - -class StoryCreate(BaseModel): - """创建故事请求""" - type: Literal["keywords", "enhance", "storybook"] - data: str = Field(..., min_length=1, max_length=2000) - education_theme: str | None = None - child_profile_id: str | None = None - universe_id: str | None = None - -class StoryResponse(BaseModel): - """故事响应""" - id: int - title: str - story_text: str | None - pages: list[dict] | None - image_url: str | None - mode: str - created_at: datetime - - class Config: - from_attributes = True # 支持 ORM 模型转换 - -class StoryListResponse(BaseModel): - """故事列表响应""" - stories: list[StoryResponse] - total: int - page: int - page_size: int -``` - -### 2.3 迁移步骤 - -| 阶段 | 任务 | 文件 | -|------|------|------| -| P1 | 创建 `schemas/common.py` | 分页、错误响应 | -| P2 | 创建 `schemas/story.py` | 故事相关模型 | -| P3 | 创建 `schemas/profile.py` | 档案相关模型 | -| P4 | 创建 `schemas/universe.py` | 宇宙相关模型 | -| P5 | 创建 `schemas/auth.py` | 认证相关模型 | -| P6 | 创建 `schemas/memory.py` | 记忆相关模型 | -| P7 | 创建 `schemas/push_config.py` | 推送配置模型 | -| P8 | 重构 `api/stories.py` | 使用新 schemas | -| P9 | 重构其他 API 文件 | 逐个迁移 | - -### 2.4 验收标准 - -- [ ] 所有 API endpoint 使用 `response_model` 参数 -- [ ] 请求体使用 Pydantic 模型而非 `Body(...)` -- [ ] OpenAPI 文档自动生成完整类型 -- [ ] 现有测试全部通过 -- [ ] `ruff check` 无错误 - ---- - -## 3. 前端重构:Composables - -### 3.1 目标结构 - -``` -frontend/src/ -├── composables/ # 新增:可复用逻辑 -│ ├── index.ts -│ ├── useAsyncData.ts # 异步数据加载 -│ ├── useFormValidation.ts # 表单验证 -│ └── useDateFormat.ts # 日期格式化 -├── components/ -├── stores/ -└── views/ -``` - -### 3.2 Composable 设计 - -#### 3.2.1 useAsyncData - -**用途**:统一处理 API 数据加载的 loading/error 状态 - -```typescript -// composables/useAsyncData.ts -import { ref, type Ref } from 'vue' - -interface AsyncDataOptions { - immediate?: boolean // 立即执行,默认 true - initialData?: T // 初始数据 - onError?: (e: Error) => void -} - -interface AsyncDataReturn { - data: Ref - loading: Ref - error: Ref - execute: () => Promise - reset: () => void -} - -export function useAsyncData( - fetcher: () => Promise, - options: AsyncDataOptions = {} -): AsyncDataReturn { - const { immediate = true, initialData = null, onError } = options - - const data = ref(initialData) as Ref - const loading = ref(false) - const error = ref('') - - async function execute() { - loading.value = true - error.value = '' - try { - data.value = await fetcher() - } catch (e) { - const message = e instanceof Error ? e.message : '加载失败' - error.value = message - onError?.(e instanceof Error ? e : new Error(message)) - } finally { - loading.value = false - } - } - - function reset() { - data.value = initialData - loading.value = false - error.value = '' - } - - if (immediate) { - execute() - } - - return { data, loading, error, execute, reset } -} -``` - -**使用示例**: - -```typescript -// views/MyStories.vue(重构前 40 行 → 重构后 10 行) -import { useAsyncData } from '@/composables' -import { api } from '@/api/client' - -const { data: response, loading, error } = useAsyncData( - () => api.get('/api/stories') -) -const stories = computed(() => response.value?.stories ?? []) -``` - -#### 3.2.2 useFormValidation - -**用途**:统一表单验证逻辑 - -```typescript -// composables/useFormValidation.ts -import { ref, reactive } from 'vue' - -type ValidationRule = (value: unknown) => string | true - -interface FieldRules { - [field: string]: ValidationRule[] -} - -export function useFormValidation>( - initialData: T, - rules: FieldRules -) { - const form = reactive({ ...initialData }) - const errors = reactive>({}) - - function validate(): boolean { - let valid = true - for (const [field, fieldRules] of Object.entries(rules)) { - const value = form[field] - for (const rule of fieldRules) { - const result = rule(value) - if (result !== true) { - errors[field] = result - valid = false - break - } else { - errors[field] = '' - } - } - } - return valid - } - - function reset() { - Object.assign(form, initialData) - Object.keys(errors).forEach(k => errors[k] = '') - } - - return { form, errors, validate, reset } -} - -// 常用验证规则 -export const required = (msg = '此字段必填') => - (v: unknown) => (v !== null && v !== undefined && v !== '') || msg - -export const minLength = (min: number, msg?: string) => - (v: unknown) => (typeof v === 'string' && v.length >= min) || msg || `至少 ${min} 个字符` - -export const maxLength = (max: number, msg?: string) => - (v: unknown) => (typeof v === 'string' && v.length <= max) || msg || `最多 ${max} 个字符` -``` - -#### 3.2.3 useDateFormat - -**用途**:统一日期格式化 - -```typescript -// composables/useDateFormat.ts -export function useDateFormat() { - function formatDate(dateStr: string | Date, format: 'date' | 'datetime' | 'relative' = 'date'): string { - const date = typeof dateStr === 'string' ? new Date(dateStr) : dateStr - - if (format === 'relative') { - return formatRelative(date) - } - - const year = date.getFullYear() - const month = String(date.getMonth() + 1).padStart(2, '0') - const day = String(date.getDate()).padStart(2, '0') - - if (format === 'date') { - return `${year}-${month}-${day}` - } - - const hours = String(date.getHours()).padStart(2, '0') - const minutes = String(date.getMinutes()).padStart(2, '0') - return `${year}-${month}-${day} ${hours}:${minutes}` - } - - function formatRelative(date: Date): string { - const now = new Date() - const diffMs = now.getTime() - date.getTime() - const diffMins = Math.floor(diffMs / 60000) - const diffHours = Math.floor(diffMs / 3600000) - const diffDays = Math.floor(diffMs / 86400000) - - if (diffMins < 1) return '刚刚' - if (diffMins < 60) return `${diffMins} 分钟前` - if (diffHours < 24) return `${diffHours} 小时前` - if (diffDays < 7) return `${diffDays} 天前` - return formatDate(date, 'date') - } - - return { formatDate, formatRelative } -} -``` - -### 3.3 迁移步骤 - -| 阶段 | 任务 | 影响文件 | -|------|------|----------| -| P1 | 创建 `composables/useAsyncData.ts` | 新文件 | -| P2 | 创建 `composables/useDateFormat.ts` | 新文件 | -| P3 | 创建 `composables/useFormValidation.ts` | 新文件 | -| P4 | 创建 `composables/index.ts` | 统一导出 | -| P5 | 重构 `views/MyStories.vue` | 使用 useAsyncData | -| P6 | 重构 `views/ChildProfiles.vue` | 使用 useAsyncData | -| P7 | 重构 `views/Universes.vue` | 使用 useAsyncData | -| P8 | 重构 `components/CreateStoryModal.vue` | 使用 useFormValidation | -| P9 | 同步重构 `admin-frontend/` | 复制 composables | - -### 3.4 验收标准 - -- [ ] `composables/` 目录包含 3 个核心 composable -- [ ] 至少 3 个 view 组件使用 `useAsyncData` -- [ ] `CreateStoryModal` 使用 `useFormValidation` -- [ ] `npm run build` 无类型错误 -- [ ] 现有功能正常运行 - ---- - -## 4. 风险与缓解 - -| 风险 | 影响 | 缓解措施 | -|------|------|----------| -| 重构引入 bug | 高 | 每个阶段运行测试 | -| 类型不兼容 | 中 | 使用 `from_attributes = True` | -| 前端构建失败 | 中 | 逐文件迁移,及时 commit | - ---- - -## 5. 时间估算 - -| 模块 | 工作量 | 预计时间 | -|------|--------|----------| -| Backend Schemas | 10 个新文件 + 9 个 API 重构 | 3-4 小时 | -| Frontend Composables | 4 个新文件 + 6 个组件重构 | 2-3 小时 | -| 测试验证 | 全量回归 | 1 小时 | -| **总计** | | **6-8 小时** | - ---- - -## 6. 后续迭代 - -本次重构完成后,可继续: - -1. **Repository 层**:抽象 DB 查询逻辑 -2. **Service 层瘦身**:拆分 `provider_router.py` (433行) -3. **前端共享包**:`frontend` 和 `admin-frontend` 共用 UI 组件 -4. **API 版本化**:引入 `/api/v1/` 前缀 - ---- - -## 附录:文件清单 - -### 新增文件 - -``` -backend/app/schemas/ -├── __init__.py -├── common.py -├── auth.py -├── story.py -├── profile.py -├── universe.py -├── memory.py -└── push_config.py - -frontend/src/composables/ -├── index.ts -├── useAsyncData.ts -├── useFormValidation.ts -└── useDateFormat.ts -``` - -### 修改文件 - -``` -backend/app/api/ -├── stories.py -├── profiles.py -├── universes.py -├── memories.py -├── push_configs.py -├── reading_events.py -└── auth.py - -frontend/src/views/ -├── MyStories.vue -├── ChildProfiles.vue -├── Universes.vue -└── ... - -frontend/src/components/ -└── CreateStoryModal.vue -``` diff --git a/.claude/specs/design/BRAND-VISUAL-DIRECTIONS.md b/.claude/specs/design/BRAND-VISUAL-DIRECTIONS.md deleted file mode 100644 index b488f43..0000000 --- a/.claude/specs/design/BRAND-VISUAL-DIRECTIONS.md +++ /dev/null @@ -1,153 +0,0 @@ -# DreamWeaver 品牌视觉方向(Web 阶段) - -## 概述 - -提供三套高保真视觉方向,用于 Web MVP。三者的 UX 结构一致,仅在色彩、视觉重量与插画风格上不同。 - ---- - -## 方案 A:Soft Aurora(温暖高级) - -**理由** -- 家长信任感强,同时保留童趣与想象力。 -- 高级但不商业化。 - -**配色** -- 主色 600: #6C5CE7 -- 主色 500: #7C69FF -- 主色 100: #EAE7FF -- 强调粉: #FF8FB1 -- 强调蓝: #65C3FF -- 中性 900: #1F2430 -- 中性 700: #4B5563 -- 中性 500: #9AA3B2 -- 中性 200: #E5E7EB -- 中性 100: #F5F7FB -- 白色: #FFFFFF - -**渐变** -- Hero 背景:linear-gradient(135deg, #EAE7FF 0%, #FDF6FF 40%, #EAF6FF 100%) -- CTA 光晕:radial-gradient(circle at 30% 30%, #7C69FF 0%, #6C5CE7 50%, #4C3FCF 100%) - -**字体** -- 标题:Noto Sans SC / Inter -- 正文:Noto Sans SC / Inter -- 数字强调:Inter - -**插画风格** -- 柔和、低对比度、轻画笔质感。 -- 圆润形状、轻高光。 -- 角色简单轮廓与友好表情。 - -**图标风格** -- 1.5px 线宽,圆角端点。 -- 强调色点缀,避免过度饱和。 - -**组件建议** -- 按钮:主色实心 + 内阴影。 -- 卡片:大圆角 + 柔和阴影。 -- 输入:浅底色 + 主色焦点环。 - ---- - -## 方案 B:Storybook Minimal(极简编辑风) - -**理由** -- 强可读性,适合长文本阅读。 -- 简洁、专业、强调内容。 - -**配色** -- 主色 600: #3B82F6 -- 主色 500: #60A5FA -- 主色 100: #DBEAFE -- 强调金: #F5C542 -- 强调薄荷: #6EE7B7 -- 中性 900: #111827 -- 中性 700: #374151 -- 中性 500: #9CA3AF -- 中性 200: #E5E7EB -- 中性 100: #F9FAFB -- 白色: #FFFFFF - -**渐变** -- Hero 背景:linear-gradient(180deg, #F9FAFB 0%, #EEF2FF 100%) -- CTA 光晕:radial-gradient(circle at 40% 30%, #60A5FA 0%, #3B82F6 60%, #1D4ED8 100%) - -**字体** -- 标题:Inter / Noto Sans SC -- 正文:Inter / Noto Sans SC -- 阅读场景可提升行高和对比度。 - -**插画风格** -- 扁平化、线条干净、留白较多。 -- 色彩克制、视觉清爽。 - -**图标风格** -- 2px 线宽,极简。 - -**组件建议** -- 按钮:纯色、无明显渐变。 -- 卡片:细边框 + 极轻阴影。 -- 输入:白底 + 清晰边框。 - ---- - -## 方案 C:Playful Glow(活力明快) - -**理由** -- 视觉更鲜活,记忆点强。 -- 更偏童趣,但仍保持专业感。 - -**配色** -- 主色 600: #7C3AED -- 主色 500: #8B5CF6 -- 主色 100: #EDE9FE -- 强调珊瑚: #FB7185 -- 强调青蓝: #22D3EE -- 中性 900: #1F2937 -- 中性 700: #4B5563 -- 中性 500: #9CA3AF -- 中性 200: #E5E7EB -- 中性 100: #F5F5F7 -- 白色: #FFFFFF - -**渐变** -- Hero 背景:linear-gradient(135deg, #EDE9FE 0%, #FFE4F3 45%, #E0F7FF 100%) -- CTA 光晕:radial-gradient(circle at 30% 30%, #8B5CF6 0%, #7C3AED 60%, #5B21B6 100%) - -**字体** -- 标题:Noto Sans SC / Inter -- 正文:Noto Sans SC / Inter -- 强调色点到为止,避免花哨。 - -**插画风格** -- 更鲜艳、更活泼。 -- 大色块 + 轻纹理背景。 - -**图标风格** -- 1.5px 线宽 + 小实心点装饰。 - -**组件建议** -- 按钮:渐变或实心 + Hover 发光。 -- 卡片:更明显阴影 + 彩色边角。 -- 输入:轻微色彩底。 - ---- - -## 共享视觉资产 - -**封面比例** -- 列表卡片:21:9 -- 详情头图:16:9 - -**插画 vs 照片** -- 默认使用插画,避免真实儿童照片(隐私与合规)。 - -**空态插画** -- 统一 1 张主插画,做颜色变体复用。 - ---- - -## 推荐 - -建议 Web MVP 使用方案 A(Soft Aurora),兼顾温暖与信任。方案 B/C 可作为后续主题或 A/B 测试备选。 diff --git a/.claude/specs/design/LANDING-PAGE-REFACTOR-SPEC.md b/.claude/specs/design/LANDING-PAGE-REFACTOR-SPEC.md deleted file mode 100644 index 12c3f3e..0000000 --- a/.claude/specs/design/LANDING-PAGE-REFACTOR-SPEC.md +++ /dev/null @@ -1,639 +0,0 @@ -# DreamWeaver 落地页重构规范文档 - -## 1. 项目概述 - -### 1.1 目标 -将当前简单的 Home.vue 落地页重构为专业级 SaaS 产品落地页,提升品牌形象和用户转化率。 - -### 1.2 当前状态 -- 文件位置: `frontend/src/views/Home.vue` -- 问题: 页面结构单一,仅有一个故事生成表单,缺少产品介绍、功能展示、用户信任背书等专业落地页必备元素 - -### 1.3 技术栈 -- Vue 3 + Composition API + TypeScript -- Tailwind CSS -- vue-i18n 国际化 -- @heroicons/vue/24/outline 图标库 -- 现有 UI 组件: BaseButton, BaseCard, BaseInput, BaseSelect, BaseTextarea - ---- - -## 2. 页面结构规范 - -页面从上到下包含 8 个主要区块(Section),每个区块独立且可复用。 - -### 2.1 Hero Section(主视觉区) - -**布局**: 两栏布局,左 60% 右 40%,移动端堆叠 - -**左侧内容**: -``` -- 主标题: 使用 gradient-text 样式 - - 第一行: "为孩子编织" (普通渐变) - - 第二行: "专属的童话梦境" (加粗强调) -- 副标题: 灰色次要文字,说明产品价值 -- CTA 按钮组: - - 主按钮: "开始创作" (btn-magic 样式,点击打开创作模态框) - - 次按钮: "了解更多" (outline 样式,滚动到 Features 区块) -``` - -**右侧内容**: -``` -- 故事卡片预览 (模拟产品效果) - - 卡片使用 glass 样式 + 阴影 - - 顶部: 模拟封面图区域 (渐变色块 + 星星图标) - - 标题: "小兔子的勇气冒险" - - 内容预览: 故事开头文字 (截断显示) - - 底部: 模拟的播放按钮和图片生成按钮图标 - - 添加浮动动画 (animate-float) -``` - -**背景装饰**: -``` -- 左上角: 浮动星星 SVG (absolute, opacity-20) -- 右下角: 浮动云朵 SVG (absolute, opacity-15) -- 使用 CSS animation 实现缓慢浮动效果 -``` - -**i18n 键**: -- `home.heroTitle`, `home.heroTitleHighlight` -- `home.heroSubtitle` -- `home.heroCta`, `home.heroCtaSecondary` -- `home.heroPreviewTitle`, `home.heroPreviewText` - ---- - -### 2.2 Trust Bar(信任背书区) - -**布局**: 水平三等分,居中对齐 - -**内容**: -``` -| 10,000+ 故事已创作 | 5,000+ 家庭信赖 | 98% 满意度 | -``` - -**样式**: -``` -- 背景: 浅紫色渐变 (from-purple-50 to-pink-50) -- 数字: 大号加粗,渐变色 -- 文字: 灰色小号 -- 分隔: 使用竖线或间距分隔 -``` - -**交互**: -``` -- 数字使用计数动画 (从 0 递增到目标值) -- 使用 IntersectionObserver 触发动画 -- 动画时长: 2 秒,使用 easeOutQuart 缓动 -``` - -**实现要点**: -```typescript -// 计数动画函数 -function animateCount(target: number, duration: number, callback: (value: number) => void) { - const start = performance.now() - const step = (timestamp: number) => { - const progress = Math.min((timestamp - start) / duration, 1) - const eased = 1 - Math.pow(1 - progress, 4) // easeOutQuart - callback(Math.floor(eased * target)) - if (progress < 1) requestAnimationFrame(step) - } - requestAnimationFrame(step) -} -``` - -**i18n 键**: -- `home.trustStoriesCreated`, `home.trustFamilies`, `home.trustSatisfaction` - ---- - -### 2.3 Features Section(功能特性区) - -**布局**: 标题 + 副标题 + 6 卡片网格 (3x2,移动端 1 列) - -**标题区**: -``` -- 主标题: "为什么选择梦语织机" -- 副标题: "我们用 AI 技术和教育理念,为每个孩子打造独一无二的成长故事" -``` - -**6 个功能卡片**: - -| # | 图标 | 标题 | 描述 | -|---|------|------|------| -| 1 | SparklesIcon | AI 智能创作 | 输入几个关键词,AI 即刻为您的孩子创作一个充满想象力的原创故事 | -| 2 | UserIcon | 个性化记忆 | 系统记住孩子的喜好和成长轨迹,故事越来越懂 TA | -| 3 | PhotoIcon | 精美 AI 插画 | 为每个故事自动生成独特的精美封面插画,让故事更加生动 | -| 4 | SpeakerWaveIcon | 温暖语音朗读 | 专业级 AI 配音,温暖的声音陪伴孩子进入甜美梦乡 | -| 5 | AcademicCapIcon | 教育主题融入 | 勇气、友谊、分享、诚实...在故事中自然传递正向价值观 | -| 6 | GlobeAltIcon | 故事宇宙 | 创建专属世界观,让喜爱的角色在不同故事中持续冒险 | - -**卡片样式**: -``` -- 使用 BaseCard 组件,添加 hover 效果 -- 图标: 48x48,紫色渐变背景圆形容器 -- 标题: font-bold text-gray-800 -- 描述: text-gray-600 text-sm -- hover: 上移 + 阴影增强 -``` - -**滚动动画**: -``` -- 使用 IntersectionObserver -- 卡片依次渐入 (stagger 100ms) -- 动画: opacity 0->1, translateY 20px->0 -``` - -**i18n 键**: -- `home.featuresTitle`, `home.featuresSubtitle` -- `home.feature1Title` ~ `home.feature6Title` -- `home.feature1Desc` ~ `home.feature6Desc` - ---- - -### 2.4 How It Works Section(使用流程区) - -**布局**: 标题 + 4 步骤水平排列(移动端垂直) - -**步骤内容**: - -| 步骤 | 图标 | 标题 | 描述 | -|------|------|------|------| -| 1 | LightBulbIcon | 输入灵感 | 输入关键词、角色或简单想法 | -| 2 | CpuChipIcon | AI 创作 | AI 根据输入生成专属故事 | -| 3 | PaintBrushIcon | 丰富内容 | 自动生成精美插画和语音 | -| 4 | ShareIcon | 分享故事 | 保存收藏,随时为孩子讲述 | - -**样式**: -``` -- 步骤编号: 圆形渐变背景,白色数字 -- 步骤之间: 虚线连接 (桌面端水平,移动端垂直) -- 图标: 在编号下方,较大尺寸 -- 文字: 居中对齐 -``` - -**连接线实现**: -```css -/* 桌面端水平连接线 */ -.step-connector { - position: absolute; - top: 24px; - left: 100%; - width: 100%; - height: 2px; - background: linear-gradient(90deg, #c084fc, #f472b6); - opacity: 0.3; -} - -/* 移动端隐藏水平线,显示垂直线 */ -@media (max-width: 768px) { - .step-connector { display: none; } - .step-connector-vertical { display: block; } -} -``` - -**i18n 键**: -- `home.howItWorksTitle`, `home.howItWorksSubtitle` -- `home.step1Title` ~ `home.step4Title` -- `home.step1Desc` ~ `home.step4Desc` - ---- - -### 2.5 Product Showcase Section(产品展示区) - -**布局**: 两栏,左侧功能列表,右侧模拟界面 - -**左侧内容**: -``` -- 小标题: "专为家长设计" -- 大标题: "简单易用,功能强大" -- 功能列表 (带勾选图标): - ✓ 直观的创作界面,几秒即可生成故事 - ✓ 多孩子档案管理,每个孩子独立记忆 - ✓ 故事历史永久保存,随时回顾美好时光 - ✓ 支持中英双语,培养语言能力 -``` - -**右侧内容**: -``` -- 模拟的产品界面截图 (CSS 绘制) -- 包含: - - 模拟浏览器顶栏 (三个圆点) - - 模拟导航栏 - - 模拟故事卡片列表 -- 使用 glass 样式 + 阴影 -- 轻微倾斜 (transform: perspective + rotateY) -``` - -**模拟界面 CSS**: -```css -.mock-browser { - background: white; - border-radius: 12px; - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); - transform: perspective(1000px) rotateY(-5deg); -} - -.mock-browser-bar { - height: 32px; - background: #f1f5f9; - border-radius: 12px 12px 0 0; - display: flex; - align-items: center; - padding: 0 12px; - gap: 6px; -} - -.mock-dot { - width: 10px; - height: 10px; - border-radius: 50%; -} -.mock-dot-red { background: #ef4444; } -.mock-dot-yellow { background: #eab308; } -.mock-dot-green { background: #22c55e; } -``` - -**i18n 键**: -- `home.showcaseTitle`, `home.showcaseSubtitle` -- `home.showcaseFeature1` ~ `home.showcaseFeature4` - ---- - -### 2.6 Testimonials Section(用户评价区) - -**布局**: 标题 + 3 评价卡片水平排列 - -**评价内容**: - -| # | 评价 | 用户名 | 身份 | -|---|------|--------|------| -| 1 | "每晚睡前,女儿都要听一个新故事。梦语织机让我不再为编故事发愁,而且故事质量真的很高!" | 小雨妈妈 | 5岁女孩家长 | -| 2 | "最惊喜的是个性化功能,系统记住了儿子喜欢恐龙和太空,每个故事都能戳中他的兴趣点。" | 航航爸爸 | 6岁男孩家长 | -| 3 | "语音朗读功能太棒了!出差时也能远程给孩子讲故事,声音温暖自然,孩子很喜欢。" | 朵朵妈妈 | 4岁女孩家长 | - -**卡片样式**: -``` -- 背景: glass 样式 -- 顶部: 引号图标 (大号,低透明度) -- 评价文字: 斜体,灰色 -- 底部: 头像 + 用户名 + 身份 -- 头像: 渐变色圆形 + 首字母 -``` - -**头像生成**: -```typescript -// 根据名字生成渐变色头像 -function getAvatarStyle(name: string) { - const colors = [ - ['#667eea', '#764ba2'], - ['#f093fb', '#f5576c'], - ['#4facfe', '#00f2fe'], - ] - const index = name.charCodeAt(0) % colors.length - return { - background: `linear-gradient(135deg, ${colors[index][0]}, ${colors[index][1]})`, - } -} -``` - -**i18n 键**: -- `home.testimonialsTitle`, `home.testimonialsSubtitle` -- `home.testimonial1Text` ~ `home.testimonial3Text` -- `home.testimonial1Name` ~ `home.testimonial3Name` -- `home.testimonial1Role` ~ `home.testimonial3Role` - ---- - -### 2.7 FAQ Section(常见问题区) - -**布局**: 标题 + 手风琴问答列表 - -**问答内容**: - -| # | 问题 | 答案 | -|---|------|------| -| 1 | 梦语织机适合多大的孩子? | 我们专为 3-8 岁儿童设计,故事内容、语言难度和教育主题都针对这个年龄段优化。 | -| 2 | 生成的故事安全吗? | 绝对安全。所有故事都经过内容过滤,确保适合儿童阅读,传递积极正向的价值观。 | -| 3 | 可以自定义故事角色吗? | 可以!您可以在孩子档案中设置喜好,或在创作时指定角色名称、特点,AI 会将其融入故事。 | -| 4 | 故事会重复吗? | 不会。每个故事都是 AI 实时原创生成的,即使使用相同关键词,也会产生不同的故事。 | -| 5 | 支持哪些语言? | 目前支持中文和英文,您可以随时切换界面语言,故事也会相应调整。 | - -**手风琴实现**: -```typescript -const expandedFaq = ref(null) - -function toggleFaq(index: number) { - expandedFaq.value = expandedFaq.value === index ? null : index -} -``` - -**样式**: -``` -- 问题行: 可点击,右侧箭头图标 -- 展开时: 箭头旋转 180°,答案滑入显示 -- 使用 Transition 组件实现平滑动画 -``` - -**i18n 键**: -- `home.faqTitle` -- `home.faq1Question` ~ `home.faq5Question` -- `home.faq1Answer` ~ `home.faq5Answer` - ---- - -### 2.8 Final CTA Section(底部转化区) - -**布局**: 居中,渐变背景 - -**内容**: -``` -- 大标题: "准备好为孩子创造魔法了吗?" -- 副标题: "立即开始,让 AI 为您的孩子编织独一无二的成长故事" -- CTA 按钮: "免费开始创作" (大号,btn-magic) -- 小字: "无需信用卡,立即体验" -``` - -**背景**: -```css -.cta-section { - background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); - position: relative; - overflow: hidden; -} - -/* 装饰性圆形 */ -.cta-section::before { - content: ''; - position: absolute; - width: 400px; - height: 400px; - background: rgba(255, 255, 255, 0.1); - border-radius: 50%; - top: -200px; - right: -100px; -} -``` - -**i18n 键**: -- `home.ctaTitle`, `home.ctaSubtitle` -- `home.ctaButton`, `home.ctaNote` - ---- - -## 3. 创作模态框规范 - -### 3.1 触发方式 -- 点击 Hero 区 "开始创作" 按钮 -- 点击 Final CTA 区按钮 -- 已登录用户直接打开模态框 -- 未登录用户跳转登录流程 - -### 3.2 模态框结构 -``` -- 遮罩层: 半透明黑色背景 -- 模态框: 居中,最大宽度 600px -- 关闭按钮: 右上角 X 图标 -- 内容: 复用原有表单逻辑 -``` - -### 3.3 表单内容(保留原有逻辑) -``` -1. 输入类型切换: 关键词创作 / 故事润色 -2. 孩子档案选择 (可选) -3. 故事宇宙选择 (可选,依赖档案) -4. 输入区域 (关键词或故事文本) -5. 教育主题选择 (可选) -6. 提交按钮 -``` - -### 3.4 状态管理 -```typescript -const showCreateModal = ref(false) - -function openCreateModal() { - if (!userStore.user) { - // 跳转登录 - return - } - showCreateModal.value = true -} -``` - ---- - -## 4. 动画规范 - -### 4.1 滚动渐入动画 - -**实现方式**: 使用 IntersectionObserver + CSS Transition - -```typescript -// composables/useScrollAnimation.ts -export function useScrollAnimation() { - const observedElements = ref>(new Set()) - - onMounted(() => { - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - entry.target.classList.add('animate-in') - observer.unobserve(entry.target) - } - }) - }, - { threshold: 0.1 } - ) - - document.querySelectorAll('.scroll-animate').forEach((el) => { - observer.observe(el) - }) - }) -} -``` - -**CSS**: -```css -.scroll-animate { - opacity: 0; - transform: translateY(20px); - transition: opacity 0.6s ease, transform 0.6s ease; -} - -.scroll-animate.animate-in { - opacity: 1; - transform: translateY(0); -} - -/* 延迟类 */ -.delay-100 { transition-delay: 100ms; } -.delay-200 { transition-delay: 200ms; } -.delay-300 { transition-delay: 300ms; } -``` - -### 4.2 浮动动画 - -```css -@keyframes float { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-10px); } -} - -.animate-float { - animation: float 3s ease-in-out infinite; -} - -.animate-float-slow { - animation: float 5s ease-in-out infinite; -} -``` - -### 4.3 数字计数动画 - -见 2.2 节 Trust Bar 实现要点。 - ---- - -## 5. 响应式规范 - -### 5.1 断点定义 -``` -- sm: 640px -- md: 768px -- lg: 1024px -- xl: 1280px -``` - -### 5.2 各区块响应式行为 - -| 区块 | 桌面端 | 平板端 | 移动端 | -|------|--------|--------|--------| -| Hero | 两栏 60/40 | 两栏 50/50 | 单栏堆叠 | -| Trust Bar | 水平三等分 | 水平三等分 | 垂直堆叠 | -| Features | 3x2 网格 | 2x3 网格 | 单列 | -| How It Works | 水平 4 步 | 水平 4 步 | 垂直 4 步 | -| Showcase | 两栏 | 两栏 | 单栏堆叠 | -| Testimonials | 水平 3 卡 | 水平 3 卡 | 单列滚动 | -| FAQ | 单列 | 单列 | 单列 | -| Final CTA | 居中 | 居中 | 居中 | - ---- - -## 6. 暗色模式规范 - -### 6.1 颜色映射 - -| 元素 | 亮色模式 | 暗色模式 | -|------|----------|----------| -| 背景 | 渐变浅色 | 渐变深色 | -| 卡片背景 | rgba(255,255,255,0.7) | rgba(15,23,42,0.6) | -| 主文字 | gray-800 | gray-100 | -| 次文字 | gray-600 | gray-400 | -| 边框 | gray-200 | gray-700 | - -### 6.2 实现方式 -使用 Tailwind dark: 前缀,配合现有 .dark 类切换。 - ---- - -## 7. 验收标准 - -### 7.1 功能验收 -- [ ] Hero 区正确显示,CTA 按钮可点击 -- [ ] Trust Bar 数字动画正常触发 -- [ ] Features 6 个卡片正确显示 -- [ ] How It Works 4 步骤正确显示,连接线可见 -- [ ] Product Showcase 模拟界面正确渲染 -- [ ] Testimonials 3 个评价卡片正确显示 -- [ ] FAQ 手风琴展开/收起正常 -- [ ] Final CTA 按钮可点击 -- [ ] 创作模态框正常打开/关闭 -- [ ] 故事生成功能正常(保留原有逻辑) - -### 7.2 样式验收 -- [ ] 所有文案使用 i18n,中英文切换正常 -- [ ] 响应式布局在 320px ~ 1920px 宽度下正常 -- [ ] 暗色模式下所有元素可读 -- [ ] 滚动动画流畅,无卡顿 -- [ ] 所有图标正确显示 - -### 7.3 性能验收 -- [ ] 首屏加载时间 < 3s -- [ ] Lighthouse Performance 分数 > 80 -- [ ] 无控制台错误 - ---- - -## 8. 文件变更清单 - -| 文件 | 操作 | 说明 | -|------|------|------| -| `frontend/src/views/Home.vue` | 重写 | 完整重构落地页 | -| `frontend/src/locales/zh.json` | 已更新 | 新增落地页文案 | -| `frontend/src/locales/en.json` | 已更新 | 新增落地页文案 | -| `frontend/src/style.css` | 修改 | 新增动画和样式类 | -| `frontend/src/composables/useScrollAnimation.ts` | 新建 | 滚动动画 composable | - ---- - -## 9. 依赖说明 - -### 9.1 现有依赖(无需新增) -- Vue 3 -- vue-router -- vue-i18n -- Pinia -- Tailwind CSS -- @heroicons/vue - -### 9.2 需要使用的图标 -```typescript -import { - SparklesIcon, - UserIcon, - PhotoIcon, - SpeakerWaveIcon, - AcademicCapIcon, - GlobeAltIcon, - LightBulbIcon, - CpuChipIcon, - PaintBrushIcon, - ShareIcon, - CheckIcon, - ChevronDownIcon, - XMarkIcon, - ArrowRightIcon, -} from '@heroicons/vue/24/outline' -``` - ---- - -## 10. 实现顺序建议 - -1. **Phase 1**: 基础结构 - - 创建页面骨架(8 个 section) - - 实现 Hero 区(不含动画) - - 实现创作模态框 - -2. **Phase 2**: 内容区块 - - Trust Bar + 计数动画 - - Features 卡片 - - How It Works 步骤 - -3. **Phase 3**: 展示区块 - - Product Showcase - - Testimonials - - FAQ 手风琴 - -4. **Phase 4**: 收尾 - - Final CTA - - 滚动动画 - - 响应式调整 - - 暗色模式适配 - ---- - -*文档版本: 1.0* -*创建时间: 2025-12-30* -*作者: Claude Code* diff --git a/.claude/specs/design/PAGE-HIFI-LAYOUT-SPEC.md b/.claude/specs/design/PAGE-HIFI-LAYOUT-SPEC.md deleted file mode 100644 index de1bfd9..0000000 --- a/.claude/specs/design/PAGE-HIFI-LAYOUT-SPEC.md +++ /dev/null @@ -1,230 +0,0 @@ -# DreamWeaver 高保真页面布局与组件规格(Web) - -## 范围 - -本文将每个页面映射为高保真布局规范:结构、核心组件与状态,便于在 Figma 中快速搭建。 - ---- - -## 全局布局 - -- 画布:1440 x 900 -- 内容容器:1200px 居中 -- 栅格:12 列,24px 间距 -- 基础间距:8pt - ---- - -## 全局组件 - -**顶部导航** -- 左:Logo + 产品名 -- 中:主导航 -- 右:搜索、孩子切换器、头像菜单 - -**主 CTA** -- 主色实心按钮 - -**卡片** -- 21:9 封面 -- 标题、标签、元信息、操作 - -**表单控件** -- 文本输入、选择器、日期、滑块、标签 - -**状态** -- 空态、加载、错误、离线 - ---- - -## 1) 登录 / 授权 - -**结构** -- 渐变背景 -- 居中卡片(420px 宽) - -**组件** -- Logo 组合 -- 标题 + 副标题 -- OAuth 按钮(GitHub、Google) -- 隐私说明 - -**状态** -- Loading(按钮 spinner) -- Error(行内错误) - ---- - -## 2) 首页:生成故事 - -**结构** -- 双栏布局(左表单、右预览) -- 顶部步骤条 - -**左侧表单** -- 孩子选择器(下拉 + 头像) -- 宇宙选择器(延续 / 新建) -- 关键词标签输入 -- 成长主题选择 -- 长度选择(分段按钮) -- 生成按钮 - -**右侧预览** -- 封面占位 -- 标题占位 -- 摘要预览 -- 进度指示(文本 -> 封面 -> 语音) - -**状态** -- 空预览 -- 生成中(进度) -- 封面失败(重试) - ---- - -## 3) 我的故事(列表) - -**结构** -- 工具条 + 网格列表 - -**工具条** -- 搜索 -- 筛选(孩子、标签) -- 排序(最新、最早) -- 视图切换(网格/列表) - -**网格卡片** -- 桌面端 3 列 -- Hover 操作:阅读、重生成封面、删除 - -**状态** -- 空列表 + CTA -- 骨架屏 - ---- - -## 4) 故事详情 - -**头图** -- 16:9 封面 -- 标题 + 元信息(孩子、宇宙、标签) -- 操作按钮:重生成封面、生成语音、分享 - -**正文** -- 正文阅读区 -- 成就面板 - -**音频** -- 底部吸附迷你播放器 - -**状态** -- 封面失败 -- 语音未生成 -- 语音加载中 - ---- - -## 5) 孩子档案 - -**列表视图** -- 头像卡片网格 -- CTA:添加档案 - -**详情视图** -- 头像头部 + 编辑按钮 -- Tabs:基础信息 / 兴趣与成长 / 故事宇宙 / 阅读记录 - -**编辑弹窗** -- 姓名、生日、性别 -- 兴趣标签 -- 成长主题 - ---- - -## 6) 故事宇宙 - -**列表视图** -- 宇宙卡片 + 摘要 -- CTA:新建宇宙 - -**详情视图** -- 摘要区 -- 分区:主角、角色、世界观、成就 - -**创建/编辑** -- 结构化表单 + 示例提示 - ---- - -## 7) 推送设置 - -**结构** -- 卡片式设置 - -**组件** -- 主开关 -- 时间选择 + 周期 -- 触发开关 -- 免打扰时段 -- 文案预览 -- 测试推送按钮 - ---- - -## 8) 账户设置 - -**组件** -- 个人信息 -- OAuth 连接 -- 数据导出/删除 -- 语言(预留) - ---- - -## 9) 管理后台:Providers - -**结构** -- 表格布局 - -**表格列** -- Provider 名称、类型、状态、延迟、最近检查 - -**操作** -- 编辑、禁用、重载 -- JSON 配置编辑器(弹窗) - ---- - -## 10) 404 / 错误 / 空态 - -**布局** -- 居中插画 + CTA - ---- - -## 交互规范 - -- 按钮 Hover:轻微放大 1.02 -- 卡片 Hover:抬升阴影 -- Toast:右上角,自动消失 -- 列表使用 Skeleton - ---- - -## 响应式规则(移动端阶段) - -- 顶部导航 -> 底部 Tab -- 双栏 -> 单栏 -- 详情页操作 -> 底部吸附按钮 - ---- - -## Figma 搭建清单 - -- 新建 Page:Design System -- 新建 Page:Web Screens -- 建立颜色与字体样式 -- 组件做 Variant -- 全部使用 Auto Layout -- 1440/1200/1024/768 建立栅格 -- 状态页复制并标注 diff --git a/.claude/specs/design/WEB-HIFI-PROTOTYPE-SPEC.md b/.claude/specs/design/WEB-HIFI-PROTOTYPE-SPEC.md deleted file mode 100644 index c90c3ad..0000000 --- a/.claude/specs/design/WEB-HIFI-PROTOTYPE-SPEC.md +++ /dev/null @@ -1,299 +0,0 @@ -# DreamWeaver Web 高保真原型规范 (v1) - -## 范围与目标 - -- 目标:为 DreamWeaver 提供专业、Web 优先的高保真 UI/UX,既温暖有想象力,又让家长感到可信与高品质。 -- 受众:3-8 岁儿童的家长。 -- 阶段重点:Web 端(桌面/平板),同时制定响应式规则,方便后续移动端迁移。 -- 假设:界面语言为简体中文;管理端或系统字段可能含英文。 - ---- - -## 设计方向 - -- 氛围:温柔、治愈、有想象力,但保持简洁与高级感(避免过度幼儿化)。 -- 视觉风格:柔和渐变、圆润形状、插画风封面、轻量阴影、舒适中性色。 -- UX 原则:低阻力、流程清晰、反馈及时、错误可恢复、AI 失败时有明确兜底。 - ---- - -## 设计系统(Web) - -### 栅格与布局 - -- 基准:8pt 间距系统。 -- 容器:1200px 最大宽度,左右 24px 边距,12 列栅格。 -- 断点: - - 1440+(宽屏) - - 1200(标准桌面) - - 1024(横屏平板) - - 768(竖屏平板) - -### 色板 - -- 主色 600: #6C5CE7 -- 主色 500: #7C69FF -- 主色 100: #EAE7FF -- 强调粉: #FF8FB1 -- 强调蓝: #65C3FF -- 成功: #34C759 -- 警告: #F6A609 -- 错误: #FF5A5F - -- 中性 900: #1F2430 -- 中性 700: #4B5563 -- 中性 500: #9AA3B2 -- 中性 200: #E5E7EB -- 中性 100: #F5F7FB -- 白色: #FFFFFF - -### 字体 - -- 主体字体:PingFang SC, Noto Sans SC, Inter, system-ui -- H1:32/40,Semibold -- H2:24/32,Semibold -- H3:20/28,Semibold -- Body L:16/24,Regular -- Body M:14/22,Regular -- Caption:12/18,Regular - -### 圆角与阴影 - -- 圆角:12(卡片)、10(输入框)、8(按钮)、24(胶囊标签) -- 阴影 S:0 4 16 rgba(31,36,48,0.08) -- 阴影 M:0 10 30 rgba(31,36,48,0.12) - -### 核心组件 - -- 顶部导航:Logo、主 CTA、搜索、孩子切换器、头像菜单 -- 侧边导航(可用于设置/管理):图标 + 文案 -- 按钮:Primary / Secondary / Ghost / Destructive -- 输入:文本、文本域、数字、日期、选择、滑块 -- 标签:兴趣/成长主题(多选) -- 卡片:故事、孩子、宇宙、Provider -- 弹窗:创建/编辑表单 -- Toast:成功/错误/提示 -- Skeleton:列表与故事内容 -- 音频播放器:播放/暂停、进度、倍速、下载 -- 空态:插画 + CTA -- 错误态:行内错误 + 重试 - ---- - -## 信息架构(Web) - -顶级导航: - -- 生成故事(Home) -- 我的故事 -- 孩子档案 -- 故事宇宙 -- 推送设置 -- 账户设置 -- 管理后台(仅开启时显示) - ---- - -## 页面规格(高保真) - -### 1) 登录/授权 - -**布局** -- 渐变背景 + 居中卡片 -- Logo、Slogan、OAuth 按钮 - -**元素** -- 标题:“欢迎来到 DreamWeaver” -- 副标题:“为孩子生成独一无二的故事” -- 按钮:“使用 GitHub 登录”、“使用 Google 登录” -- 隐私说明:“我们仅使用公开信息创建账户” - -**状态** -- Loading:按钮 spinner -- Error:行内错误 + 重试 - ---- - -### 2) 生成故事(Home) - -**布局** -- 左表单 / 右预览双栏 -- 顶部步骤条 - -**主表单** -- 孩子选择器:头像 + 姓名 + 年龄,含“新建档案”入口 -- 宇宙选择器:默认“延续上一次”,可切“新建宇宙” -- 关键词输入(标签 + 手输) -- 成长主题选择(可选) -- 故事长度(短/中/长) -- 主要 CTA:“生成故事” - -**预览面板** -- 标题占位 -- 封面占位 -- 摘要预览 -- 错误态:封面失败提示 + 重试 - -**交互** -- Stepper:档案 → 宇宙 → 关键词 → 生成 -- 生成过程:阶段进度(文本/封面/语音) - ---- - -### 3) 我的故事(列表) - -**布局** -- 顶部工具条 + 网格列表 - -**卡片** -- 21:9 封面、标题、标签、所属孩子、更新时间 -- Hover 操作:继续阅读、重新生成封面、删除 - -**筛选** -- 孩子 -- 标签 -- 时间范围 - -**空态** -- 插画 + “开始生成第一个故事” - ---- - -### 4) 故事详情 - -**头图** -- 大封面 -- 标题 + 元信息(孩子、宇宙、标签、日期) -- 主操作:生成封面 / 生成语音 / 分享 - -**内容区** -- 正文排版(舒适行高) -- 成就模块(卡片式) - -**音频** -- 滚动时底部吸附播放器 -- 倍速切换(0.8/1.0/1.2) - -**状态** -- 封面失败:占位 + 重试 -- 语音未生成:显示 CTA - ---- - -### 5) 孩子档案 - -**列表** -- 头像卡片 + 基础信息 -- CTA:“添加档案” - -**详情** -- 档案头部 + 编辑 -- Tabs:基础信息 / 兴趣与成长 / 故事宇宙 / 阅读记录 - -**编辑弹窗** -- 姓名、生日、性别 -- 兴趣标签(多选) -- 成长主题(单选或多选) - ---- - -### 6) 故事宇宙 - -**列表** -- 宇宙卡片:主角、常驻角色、成就数量 -- CTA:“新建宇宙” - -**详情** -- 宇宙摘要 -- 可编辑区:主角 / 角色 / 世界观 -- 成就时间轴 - -**创建/编辑** -- 结构化表单 + 示例提示 - ---- - -### 7) 推送设置 - -**布局** -- 卡片式设置区 - -**设置项** -- 主开关:开启主动推送 -- 时间选择 + 周期 -- 触发类型(复选) -- 免打扰时段 - -**预览** -- 推送文案预览 -- 测试推送按钮 - ---- - -### 8) 账户设置 - -- 个人信息 -- OAuth 绑定 -- 数据隐私(导出/删除) -- 语言切换(预留) - ---- - -### 9) 管理后台(Provider) - -**表格** -- Provider 列表:状态、延迟、最近检查 -- 操作:编辑、禁用、重载 - -**详情** -- JSON 配置编辑器(等宽字体) -- 健康检查按钮 - ---- - -### 10) 404 / 错误 / 空态 - -- 友好插画 + 返回 CTA - ---- - -## 交互与动效 - -- 按钮:Hover 轻微放大(1.02) -- 卡片:Hover 提升阴影 -- Loading:列表 Skeleton、生成进度 -- Toast:右上角,3s 自动消失 - ---- - -## 可访问性 - -- 文本对比度 >= 4.5:1 -- 输入框/按钮焦点态清晰 -- 最小触控区域 44px - ---- - -## 响应式策略(移动端阶段) - -- 顶部导航改为底部 Tab -- 双栏变单栏 -- 详情页操作改为底部吸附操作条 -- 卡片 1 列展示,触控面积更大 - ---- - -## Figma 实现说明 - -- 全部使用 Auto Layout -- 统一命名:Page/Section/Component/State -- 按钮、输入、卡片做 Variant -- 颜色/字体/间距作为样式管理 - ---- - -## 交付物 - -- 设计系统库(色板、文字、组件) -- 全流程高保真页面 -- 原型链接(Figma 中生成) diff --git a/.claude/specs/design/figma-html/theme-a.zip b/.claude/specs/design/figma-html/theme-a.zip deleted file mode 100644 index 0d57508..0000000 Binary files a/.claude/specs/design/figma-html/theme-a.zip and /dev/null differ diff --git a/.claude/specs/design/figma-html/theme-a/account-settings.html b/.claude/specs/design/figma-html/theme-a/account-settings.html deleted file mode 100644 index 24dea4c..0000000 --- a/.claude/specs/design/figma-html/theme-a/account-settings.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - 账户设置 - - - -
- -
-
-
-

个人信息

-
- - -
- -
-
-

账号安全

-
已绑定 GitHub、Google
- -
-
-

数据隐私

- - -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/admin-providers.html b/.claude/specs/design/figma-html/theme-a/admin-providers.html deleted file mode 100644 index 481e987..0000000 --- a/.claude/specs/design/figma-html/theme-a/admin-providers.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 管理后台 - Providers - - - -
- -
-
-

Providers 管理

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
名称类型状态延迟最近检查操作
text_primaryText健康420ms2 分钟前编辑 · 禁用 · 重载
image_primaryImage健康860ms5 分钟前编辑 · 禁用 · 重载
- -
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/child-profile-detail.html b/.claude/specs/design/figma-html/theme-a/child-profile-detail.html deleted file mode 100644 index 1346b08..0000000 --- a/.claude/specs/design/figma-html/theme-a/child-profile-detail.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - 孩子档案详情 - - - -
- -
-
-
-
-
-
小明 · 5岁
-
男 · 生日 2020/05/12
-
-
- -
- -
-
基础信息
-
兴趣与成长
-
故事宇宙
-
阅读记录
-
- -
-
-

兴趣标签

-
- 太空 - 机器人 - 冒险 -
-
-
-

成长主题

-
- 勇气 - 分享 -
-
-
- -
-

故事宇宙

-
-
-
星际冒险
-
主角:小明船长 · 成就 3 个
-
-
-
梦幻森林
-
主角:森林守护者 · 成就 1 个
-
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/child-profiles.html b/.claude/specs/design/figma-html/theme-a/child-profiles.html deleted file mode 100644 index 1d8322f..0000000 --- a/.claude/specs/design/figma-html/theme-a/child-profiles.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - 孩子档案 - - - -
- -
-
-

我的宝贝

- -
-
-
-
-
小明 · 5岁
-
太空机器人
-
-
-
-
小红 · 3岁
-
公主动物
-
-
-
空态示例:添加一个孩子档案
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/home.html b/.claude/specs/design/figma-html/theme-a/home.html deleted file mode 100644 index 91a2ef1..0000000 --- a/.claude/specs/design/figma-html/theme-a/home.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - 生成故事 - - - -
- -
-
- 档案 - 宇宙 - 关键词 - 生成 -
-
-
-

为谁创作故事

-
-
- - -
-
- - -
-
-
- -
- 太空 - 勇气 - 机器人 - 探索 -
- -
-
-
- - -
-
- -
- - - -
-
-
-
- -
-
- -
-

生成预览

-
-
故事标题占位
-

故事摘要将显示在这里,支持 2-3 行预览。

-
-
生成中:文本 → 封面 → 语音
-
-
-
封面生成失败,稍后重试
- -
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/index.html b/.claude/specs/design/figma-html/theme-a/index.html deleted file mode 100644 index f8d7221..0000000 --- a/.claude/specs/design/figma-html/theme-a/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - DreamWeaver 原型入口 - - - - - - diff --git a/.claude/specs/design/figma-html/theme-a/login.html b/.claude/specs/design/figma-html/theme-a/login.html deleted file mode 100644 index 9688bc3..0000000 --- a/.claude/specs/design/figma-html/theme-a/login.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - 登录 / 授权 - - - -
-
-
- -

欢迎来到 DreamWeaver

-

为孩子生成独一无二的故事

-
- - -
- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/my-stories.html b/.claude/specs/design/figma-html/theme-a/my-stories.html deleted file mode 100644 index 4d06335..0000000 --- a/.claude/specs/design/figma-html/theme-a/my-stories.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - 我的故事 - - - -
- -
-
- - - - - -
- -
-
-
-
星际冒险 · 第三章
-
- 太空勇气 -
-
小明 · 更新于 2 天前
-
- - -
-
-
-
-
梦幻森林 · 朋友篇
-
- 友谊动物 -
-
小红 · 更新于 5 天前
-
- - -
-
-
-
空态示例:开始生成第一个故事
- -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/not-found.html b/.claude/specs/design/figma-html/theme-a/not-found.html deleted file mode 100644 index f90546f..0000000 --- a/.claude/specs/design/figma-html/theme-a/not-found.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - 404 - - - -
-
-
-

404

-

页面走丢了,回到生成故事开始吧。

- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/push-settings.html b/.claude/specs/design/figma-html/theme-a/push-settings.html deleted file mode 100644 index 2b439f7..0000000 --- a/.claude/specs/design/figma-html/theme-a/push-settings.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - 推送设置 - - - -
- -
-
-
-

主动推送

-
-
- - -
-
- - -
-
-
- -
- 时间触发 - 事件触发 - 行为触发 - 成长触发 -
-
-
- -
- - -
-
-
-
-

推送预览

-
“今晚给小明讲一个关于太空的故事,好吗?”
- -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/story-detail.html b/.claude/specs/design/figma-html/theme-a/story-detail.html deleted file mode 100644 index 0ab007d..0000000 --- a/.claude/specs/design/figma-html/theme-a/story-detail.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - 故事详情 - - - -
- -
-
-
-

星际冒险 · 勇气的种子

-
小明 · 星际冒险宇宙 · 2025/01/12
-
- - - -
-
-
-
-

故事正文

-

夜空像一条温柔的河流,小明驾驶着飞船穿过星光……

-

他握紧操纵杆,鼓起勇气,向未知的星球靠近。

-

最终,小明发现了新的朋友,也学会了如何面对黑暗。

-
-
-

成就

-
- 勇气 - 友谊 -
-
“克服了黑暗的恐惧”
-
“帮助了迷路的小伙伴”
-
-
- -
- -
- -
- -
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/style.css b/.claude/specs/design/figma-html/theme-a/style.css deleted file mode 100644 index 0ec99ce..0000000 --- a/.claude/specs/design/figma-html/theme-a/style.css +++ /dev/null @@ -1,217 +0,0 @@ -:root { - --primary-600: #6C5CE7; - --primary-500: #7C69FF; - --primary-100: #EAE7FF; - --accent-pink: #FF8FB1; - --accent-sky: #65C3FF; - --success: #34C759; - --warning: #F6A609; - --error: #FF5A5F; - --neutral-900: #1F2430; - --neutral-700: #4B5563; - --neutral-500: #9AA3B2; - --neutral-200: #E5E7EB; - --neutral-100: #F5F7FB; - --hero-gradient: linear-gradient(135deg, #EAE7FF 0%, #FDF6FF 40%, #EAF6FF 100%); -} -* { box-sizing: border-box; } -:root { - --container-width: 1200px; - --gutter: 24px; - --radius-card: 12px; - --radius-input: 10px; - --radius-button: 8px; - --radius-pill: 24px; - --shadow-s: 0 4px 16px rgba(31,36,48,0.08); - --shadow-m: 0 10px 30px rgba(31,36,48,0.12); -} - -body { - margin: 0; - font-family: "PingFang SC", "Noto Sans SC", Inter, system-ui, -apple-system, sans-serif; - color: var(--neutral-900); - background: var(--neutral-100); -} - -a { color: var(--primary-600); text-decoration: none; } - -.page { min-height: 100vh; } -.container { - width: min(var(--container-width), 100% - 48px); - margin: 0 auto; -} - -.nav { - background: #fff; - border-bottom: 1px solid var(--neutral-200); - position: sticky; - top: 0; - z-index: 10; -} -.nav__inner { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 0; - gap: 16px; -} -.nav__left, .nav__center, .nav__right { display: flex; align-items: center; gap: 16px; } -.nav__logo { - display: flex; - align-items: center; - gap: 10px; - font-weight: 700; - color: var(--neutral-900); -} -.nav__logo-badge { - width: 28px; - height: 28px; - border-radius: 8px; - background: linear-gradient(135deg, var(--primary-500), var(--accent-sky)); -} -.nav__item { color: var(--neutral-700); font-weight: 500; } -.nav__item.active { color: var(--primary-600); } - -.hero { - background: var(--hero-gradient); - border-radius: 16px; - padding: 32px; - box-shadow: var(--shadow-s); -} - -.section { margin: 28px 0; } -.section-title { font-size: 20px; font-weight: 600; margin-bottom: 12px; } - -.grid { display: grid; gap: 16px; } -.grid-2 { grid-template-columns: repeat(2, minmax(0,1fr)); } -.grid-3 { grid-template-columns: repeat(3, minmax(0,1fr)); } - -.card { - background: #fff; - border-radius: var(--radius-card); - padding: 16px; - box-shadow: var(--shadow-s); -} -.card--flat { box-shadow: none; border: 1px solid var(--neutral-200); } -.card-cover { - width: 100%; - aspect-ratio: 21 / 9; - border-radius: 10px; - background: linear-gradient(135deg, var(--primary-100), #fff 60%, var(--accent-sky)); - margin-bottom: 12px; -} -.card-title { font-weight: 600; margin: 6px 0; } -.card-meta { color: var(--neutral-500); font-size: 12px; } - -.badge { - display: inline-flex; - align-items: center; - padding: 4px 10px; - border-radius: var(--radius-pill); - background: var(--primary-100); - color: var(--primary-600); - font-size: 12px; -} - -.chips { display: flex; flex-wrap: wrap; gap: 8px; } -.chip { - padding: 6px 12px; - border-radius: var(--radius-pill); - border: 1px solid var(--neutral-200); - background: #fff; - font-size: 12px; -} -.chip.selected { background: var(--primary-100); border-color: var(--primary-500); color: var(--primary-600); } - -.btn { - height: 40px; - padding: 0 16px; - border-radius: var(--radius-button); - border: 1px solid transparent; - cursor: pointer; - font-weight: 600; -} -.btn--primary { background: var(--primary-600); color: #fff; } -.btn--secondary { background: #fff; border-color: var(--primary-600); color: var(--primary-600); } -.btn--ghost { background: transparent; color: var(--neutral-700); } -.btn--danger { background: #fff; border-color: var(--error); color: var(--error); } - -.input, select, textarea { - width: 100%; - padding: 10px 12px; - border-radius: var(--radius-input); - border: 1px solid var(--neutral-200); - background: #fff; - font-size: 14px; -} - -.row { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; } - -.stepper { display: flex; gap: 10px; align-items: center; } -.step { - padding: 6px 12px; - border-radius: var(--radius-pill); - background: var(--neutral-100); - color: var(--neutral-700); - font-size: 12px; -} -.step.active { background: var(--primary-100); color: var(--primary-600); } - -.toolbar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; } - -.table { width: 100%; border-collapse: collapse; } -.table th, .table td { border-bottom: 1px solid var(--neutral-200); padding: 12px 8px; text-align: left; font-size: 14px; } - -.avatar { - width: 40px; height: 40px; border-radius: 50%; - background: var(--primary-100); - display: inline-flex; align-items: center; justify-content: center; - font-weight: 700; color: var(--primary-600); -} - -.cover-hero { - aspect-ratio: 16 / 9; - border-radius: 14px; - background: linear-gradient(135deg, var(--primary-100), #fff 55%, var(--accent-pink)); -} - -.audio-player { - display: flex; align-items: center; gap: 12px; padding: 12px 16px; - border-radius: 12px; border: 1px solid var(--neutral-200); background: #fff; -} -.audio-bar { height: 6px; background: var(--neutral-200); border-radius: 999px; flex: 1; } -.audio-progress { width: 35%; height: 100%; background: var(--primary-600); border-radius: 999px; } - -.tabs { display: flex; gap: 8px; border-bottom: 1px solid var(--neutral-200); } -.tab { padding: 10px 12px; color: var(--neutral-700); } -.tab.active { color: var(--primary-600); border-bottom: 2px solid var(--primary-600); } - -.callout { - background: #fff; - border: 1px dashed var(--neutral-200); - border-radius: 12px; - padding: 12px; - color: var(--neutral-700); - font-size: 12px; -} - -.footer-note { color: var(--neutral-500); font-size: 12px; margin-top: 12px; } - -.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; } - -.split { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 24px; -} - -@media (max-width: 1024px) { - .grid-3 { grid-template-columns: repeat(2, minmax(0,1fr)); } - .split { grid-template-columns: 1fr; } -} - -@media (max-width: 768px) { - .grid-2 { grid-template-columns: 1fr; } - .grid-3 { grid-template-columns: 1fr; } - .row { grid-template-columns: 1fr; } -} diff --git a/.claude/specs/design/figma-html/theme-a/universe-detail.html b/.claude/specs/design/figma-html/theme-a/universe-detail.html deleted file mode 100644 index 1f075d9..0000000 --- a/.claude/specs/design/figma-html/theme-a/universe-detail.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - 宇宙详情 - - - -
- -
-
-

星际冒险

-
主角:小明船长 · 更新于 2025/01/12
-
-
-
-

主角设定

-
小明是来自地球的探险家,勇敢且好奇。
-
-
-

常驻角色

-
机器人小七、外星猫咪星星
-
-
-

世界观

-
星际学院、彩虹星云、飞船港湾
-
-
-

成就

-
克服恐惧 · 结交朋友 · 学会独立
-
-
-
- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-a/universes.html b/.claude/specs/design/figma-html/theme-a/universes.html deleted file mode 100644 index c5ceced..0000000 --- a/.claude/specs/design/figma-html/theme-a/universes.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - 故事宇宙 - - - -
- -
-
-

故事宇宙

- -
-
-
-
星际冒险
-
主角:小明船长
-
- 伙伴:机器人小七 - 成就:3 -
-
-
-
梦幻森林
-
主角:森林守护者
-
- 伙伴:魔法猫咪 - 成就:1 -
-
-
-
空态示例:创建第一个宇宙
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/account-settings.html b/.claude/specs/design/figma-html/theme-b/account-settings.html deleted file mode 100644 index 24dea4c..0000000 --- a/.claude/specs/design/figma-html/theme-b/account-settings.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - 账户设置 - - - -
- -
-
-
-

个人信息

-
- - -
- -
-
-

账号安全

-
已绑定 GitHub、Google
- -
-
-

数据隐私

- - -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/admin-providers.html b/.claude/specs/design/figma-html/theme-b/admin-providers.html deleted file mode 100644 index 481e987..0000000 --- a/.claude/specs/design/figma-html/theme-b/admin-providers.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 管理后台 - Providers - - - -
- -
-
-

Providers 管理

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
名称类型状态延迟最近检查操作
text_primaryText健康420ms2 分钟前编辑 · 禁用 · 重载
image_primaryImage健康860ms5 分钟前编辑 · 禁用 · 重载
- -
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/child-profile-detail.html b/.claude/specs/design/figma-html/theme-b/child-profile-detail.html deleted file mode 100644 index 1346b08..0000000 --- a/.claude/specs/design/figma-html/theme-b/child-profile-detail.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - 孩子档案详情 - - - -
- -
-
-
-
-
-
小明 · 5岁
-
男 · 生日 2020/05/12
-
-
- -
- -
-
基础信息
-
兴趣与成长
-
故事宇宙
-
阅读记录
-
- -
-
-

兴趣标签

-
- 太空 - 机器人 - 冒险 -
-
-
-

成长主题

-
- 勇气 - 分享 -
-
-
- -
-

故事宇宙

-
-
-
星际冒险
-
主角:小明船长 · 成就 3 个
-
-
-
梦幻森林
-
主角:森林守护者 · 成就 1 个
-
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/child-profiles.html b/.claude/specs/design/figma-html/theme-b/child-profiles.html deleted file mode 100644 index 1d8322f..0000000 --- a/.claude/specs/design/figma-html/theme-b/child-profiles.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - 孩子档案 - - - -
- -
-
-

我的宝贝

- -
-
-
-
-
小明 · 5岁
-
太空机器人
-
-
-
-
小红 · 3岁
-
公主动物
-
-
-
空态示例:添加一个孩子档案
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/home.html b/.claude/specs/design/figma-html/theme-b/home.html deleted file mode 100644 index 91a2ef1..0000000 --- a/.claude/specs/design/figma-html/theme-b/home.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - 生成故事 - - - -
- -
-
- 档案 - 宇宙 - 关键词 - 生成 -
-
-
-

为谁创作故事

-
-
- - -
-
- - -
-
-
- -
- 太空 - 勇气 - 机器人 - 探索 -
- -
-
-
- - -
-
- -
- - - -
-
-
-
- -
-
- -
-

生成预览

-
-
故事标题占位
-

故事摘要将显示在这里,支持 2-3 行预览。

-
-
生成中:文本 → 封面 → 语音
-
-
-
封面生成失败,稍后重试
- -
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/index.html b/.claude/specs/design/figma-html/theme-b/index.html deleted file mode 100644 index f8d7221..0000000 --- a/.claude/specs/design/figma-html/theme-b/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - DreamWeaver 原型入口 - - - - - - diff --git a/.claude/specs/design/figma-html/theme-b/login.html b/.claude/specs/design/figma-html/theme-b/login.html deleted file mode 100644 index 9688bc3..0000000 --- a/.claude/specs/design/figma-html/theme-b/login.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - 登录 / 授权 - - - -
-
-
- -

欢迎来到 DreamWeaver

-

为孩子生成独一无二的故事

-
- - -
- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/my-stories.html b/.claude/specs/design/figma-html/theme-b/my-stories.html deleted file mode 100644 index 4d06335..0000000 --- a/.claude/specs/design/figma-html/theme-b/my-stories.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - 我的故事 - - - -
- -
-
- - - - - -
- -
-
-
-
星际冒险 · 第三章
-
- 太空勇气 -
-
小明 · 更新于 2 天前
-
- - -
-
-
-
-
梦幻森林 · 朋友篇
-
- 友谊动物 -
-
小红 · 更新于 5 天前
-
- - -
-
-
-
空态示例:开始生成第一个故事
- -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/not-found.html b/.claude/specs/design/figma-html/theme-b/not-found.html deleted file mode 100644 index f90546f..0000000 --- a/.claude/specs/design/figma-html/theme-b/not-found.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - 404 - - - -
-
-
-

404

-

页面走丢了,回到生成故事开始吧。

- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/push-settings.html b/.claude/specs/design/figma-html/theme-b/push-settings.html deleted file mode 100644 index 2b439f7..0000000 --- a/.claude/specs/design/figma-html/theme-b/push-settings.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - 推送设置 - - - -
- -
-
-
-

主动推送

-
-
- - -
-
- - -
-
-
- -
- 时间触发 - 事件触发 - 行为触发 - 成长触发 -
-
-
- -
- - -
-
-
-
-

推送预览

-
“今晚给小明讲一个关于太空的故事,好吗?”
- -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/story-detail.html b/.claude/specs/design/figma-html/theme-b/story-detail.html deleted file mode 100644 index 0ab007d..0000000 --- a/.claude/specs/design/figma-html/theme-b/story-detail.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - 故事详情 - - - -
- -
-
-
-

星际冒险 · 勇气的种子

-
小明 · 星际冒险宇宙 · 2025/01/12
-
- - - -
-
-
-
-

故事正文

-

夜空像一条温柔的河流,小明驾驶着飞船穿过星光……

-

他握紧操纵杆,鼓起勇气,向未知的星球靠近。

-

最终,小明发现了新的朋友,也学会了如何面对黑暗。

-
-
-

成就

-
- 勇气 - 友谊 -
-
“克服了黑暗的恐惧”
-
“帮助了迷路的小伙伴”
-
-
- -
- -
- -
- -
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/style.css b/.claude/specs/design/figma-html/theme-b/style.css deleted file mode 100644 index 7f76735..0000000 --- a/.claude/specs/design/figma-html/theme-b/style.css +++ /dev/null @@ -1,217 +0,0 @@ -:root { - --primary-600: #3B82F6; - --primary-500: #60A5FA; - --primary-100: #DBEAFE; - --accent-pink: #F5C542; - --accent-sky: #6EE7B7; - --success: #34C759; - --warning: #F6A609; - --error: #FF5A5F; - --neutral-900: #111827; - --neutral-700: #374151; - --neutral-500: #9CA3AF; - --neutral-200: #E5E7EB; - --neutral-100: #F9FAFB; - --hero-gradient: linear-gradient(180deg, #F9FAFB 0%, #EEF2FF 100%); -} -* { box-sizing: border-box; } -:root { - --container-width: 1200px; - --gutter: 24px; - --radius-card: 12px; - --radius-input: 10px; - --radius-button: 8px; - --radius-pill: 24px; - --shadow-s: 0 4px 16px rgba(31,36,48,0.08); - --shadow-m: 0 10px 30px rgba(31,36,48,0.12); -} - -body { - margin: 0; - font-family: "PingFang SC", "Noto Sans SC", Inter, system-ui, -apple-system, sans-serif; - color: var(--neutral-900); - background: var(--neutral-100); -} - -a { color: var(--primary-600); text-decoration: none; } - -.page { min-height: 100vh; } -.container { - width: min(var(--container-width), 100% - 48px); - margin: 0 auto; -} - -.nav { - background: #fff; - border-bottom: 1px solid var(--neutral-200); - position: sticky; - top: 0; - z-index: 10; -} -.nav__inner { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 0; - gap: 16px; -} -.nav__left, .nav__center, .nav__right { display: flex; align-items: center; gap: 16px; } -.nav__logo { - display: flex; - align-items: center; - gap: 10px; - font-weight: 700; - color: var(--neutral-900); -} -.nav__logo-badge { - width: 28px; - height: 28px; - border-radius: 8px; - background: linear-gradient(135deg, var(--primary-500), var(--accent-sky)); -} -.nav__item { color: var(--neutral-700); font-weight: 500; } -.nav__item.active { color: var(--primary-600); } - -.hero { - background: var(--hero-gradient); - border-radius: 16px; - padding: 32px; - box-shadow: var(--shadow-s); -} - -.section { margin: 28px 0; } -.section-title { font-size: 20px; font-weight: 600; margin-bottom: 12px; } - -.grid { display: grid; gap: 16px; } -.grid-2 { grid-template-columns: repeat(2, minmax(0,1fr)); } -.grid-3 { grid-template-columns: repeat(3, minmax(0,1fr)); } - -.card { - background: #fff; - border-radius: var(--radius-card); - padding: 16px; - box-shadow: var(--shadow-s); -} -.card--flat { box-shadow: none; border: 1px solid var(--neutral-200); } -.card-cover { - width: 100%; - aspect-ratio: 21 / 9; - border-radius: 10px; - background: linear-gradient(135deg, var(--primary-100), #fff 60%, var(--accent-sky)); - margin-bottom: 12px; -} -.card-title { font-weight: 600; margin: 6px 0; } -.card-meta { color: var(--neutral-500); font-size: 12px; } - -.badge { - display: inline-flex; - align-items: center; - padding: 4px 10px; - border-radius: var(--radius-pill); - background: var(--primary-100); - color: var(--primary-600); - font-size: 12px; -} - -.chips { display: flex; flex-wrap: wrap; gap: 8px; } -.chip { - padding: 6px 12px; - border-radius: var(--radius-pill); - border: 1px solid var(--neutral-200); - background: #fff; - font-size: 12px; -} -.chip.selected { background: var(--primary-100); border-color: var(--primary-500); color: var(--primary-600); } - -.btn { - height: 40px; - padding: 0 16px; - border-radius: var(--radius-button); - border: 1px solid transparent; - cursor: pointer; - font-weight: 600; -} -.btn--primary { background: var(--primary-600); color: #fff; } -.btn--secondary { background: #fff; border-color: var(--primary-600); color: var(--primary-600); } -.btn--ghost { background: transparent; color: var(--neutral-700); } -.btn--danger { background: #fff; border-color: var(--error); color: var(--error); } - -.input, select, textarea { - width: 100%; - padding: 10px 12px; - border-radius: var(--radius-input); - border: 1px solid var(--neutral-200); - background: #fff; - font-size: 14px; -} - -.row { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; } - -.stepper { display: flex; gap: 10px; align-items: center; } -.step { - padding: 6px 12px; - border-radius: var(--radius-pill); - background: var(--neutral-100); - color: var(--neutral-700); - font-size: 12px; -} -.step.active { background: var(--primary-100); color: var(--primary-600); } - -.toolbar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; } - -.table { width: 100%; border-collapse: collapse; } -.table th, .table td { border-bottom: 1px solid var(--neutral-200); padding: 12px 8px; text-align: left; font-size: 14px; } - -.avatar { - width: 40px; height: 40px; border-radius: 50%; - background: var(--primary-100); - display: inline-flex; align-items: center; justify-content: center; - font-weight: 700; color: var(--primary-600); -} - -.cover-hero { - aspect-ratio: 16 / 9; - border-radius: 14px; - background: linear-gradient(135deg, var(--primary-100), #fff 55%, var(--accent-pink)); -} - -.audio-player { - display: flex; align-items: center; gap: 12px; padding: 12px 16px; - border-radius: 12px; border: 1px solid var(--neutral-200); background: #fff; -} -.audio-bar { height: 6px; background: var(--neutral-200); border-radius: 999px; flex: 1; } -.audio-progress { width: 35%; height: 100%; background: var(--primary-600); border-radius: 999px; } - -.tabs { display: flex; gap: 8px; border-bottom: 1px solid var(--neutral-200); } -.tab { padding: 10px 12px; color: var(--neutral-700); } -.tab.active { color: var(--primary-600); border-bottom: 2px solid var(--primary-600); } - -.callout { - background: #fff; - border: 1px dashed var(--neutral-200); - border-radius: 12px; - padding: 12px; - color: var(--neutral-700); - font-size: 12px; -} - -.footer-note { color: var(--neutral-500); font-size: 12px; margin-top: 12px; } - -.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; } - -.split { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 24px; -} - -@media (max-width: 1024px) { - .grid-3 { grid-template-columns: repeat(2, minmax(0,1fr)); } - .split { grid-template-columns: 1fr; } -} - -@media (max-width: 768px) { - .grid-2 { grid-template-columns: 1fr; } - .grid-3 { grid-template-columns: 1fr; } - .row { grid-template-columns: 1fr; } -} diff --git a/.claude/specs/design/figma-html/theme-b/universe-detail.html b/.claude/specs/design/figma-html/theme-b/universe-detail.html deleted file mode 100644 index 1f075d9..0000000 --- a/.claude/specs/design/figma-html/theme-b/universe-detail.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - 宇宙详情 - - - -
- -
-
-

星际冒险

-
主角:小明船长 · 更新于 2025/01/12
-
-
-
-

主角设定

-
小明是来自地球的探险家,勇敢且好奇。
-
-
-

常驻角色

-
机器人小七、外星猫咪星星
-
-
-

世界观

-
星际学院、彩虹星云、飞船港湾
-
-
-

成就

-
克服恐惧 · 结交朋友 · 学会独立
-
-
-
- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-b/universes.html b/.claude/specs/design/figma-html/theme-b/universes.html deleted file mode 100644 index c5ceced..0000000 --- a/.claude/specs/design/figma-html/theme-b/universes.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - 故事宇宙 - - - -
- -
-
-

故事宇宙

- -
-
-
-
星际冒险
-
主角:小明船长
-
- 伙伴:机器人小七 - 成就:3 -
-
-
-
梦幻森林
-
主角:森林守护者
-
- 伙伴:魔法猫咪 - 成就:1 -
-
-
-
空态示例:创建第一个宇宙
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/account-settings.html b/.claude/specs/design/figma-html/theme-c/account-settings.html deleted file mode 100644 index 24dea4c..0000000 --- a/.claude/specs/design/figma-html/theme-c/account-settings.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - 账户设置 - - - -
- -
-
-
-

个人信息

-
- - -
- -
-
-

账号安全

-
已绑定 GitHub、Google
- -
-
-

数据隐私

- - -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/admin-providers.html b/.claude/specs/design/figma-html/theme-c/admin-providers.html deleted file mode 100644 index 481e987..0000000 --- a/.claude/specs/design/figma-html/theme-c/admin-providers.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - 管理后台 - Providers - - - -
- -
-
-

Providers 管理

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
名称类型状态延迟最近检查操作
text_primaryText健康420ms2 分钟前编辑 · 禁用 · 重载
image_primaryImage健康860ms5 分钟前编辑 · 禁用 · 重载
- -
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/child-profile-detail.html b/.claude/specs/design/figma-html/theme-c/child-profile-detail.html deleted file mode 100644 index 1346b08..0000000 --- a/.claude/specs/design/figma-html/theme-c/child-profile-detail.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - 孩子档案详情 - - - -
- -
-
-
-
-
-
小明 · 5岁
-
男 · 生日 2020/05/12
-
-
- -
- -
-
基础信息
-
兴趣与成长
-
故事宇宙
-
阅读记录
-
- -
-
-

兴趣标签

-
- 太空 - 机器人 - 冒险 -
-
-
-

成长主题

-
- 勇气 - 分享 -
-
-
- -
-

故事宇宙

-
-
-
星际冒险
-
主角:小明船长 · 成就 3 个
-
-
-
梦幻森林
-
主角:森林守护者 · 成就 1 个
-
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/child-profiles.html b/.claude/specs/design/figma-html/theme-c/child-profiles.html deleted file mode 100644 index 1d8322f..0000000 --- a/.claude/specs/design/figma-html/theme-c/child-profiles.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - 孩子档案 - - - -
- -
-
-

我的宝贝

- -
-
-
-
-
小明 · 5岁
-
太空机器人
-
-
-
-
小红 · 3岁
-
公主动物
-
-
-
空态示例:添加一个孩子档案
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/home.html b/.claude/specs/design/figma-html/theme-c/home.html deleted file mode 100644 index 91a2ef1..0000000 --- a/.claude/specs/design/figma-html/theme-c/home.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - 生成故事 - - - -
- -
-
- 档案 - 宇宙 - 关键词 - 生成 -
-
-
-

为谁创作故事

-
-
- - -
-
- - -
-
-
- -
- 太空 - 勇气 - 机器人 - 探索 -
- -
-
-
- - -
-
- -
- - - -
-
-
-
- -
-
- -
-

生成预览

-
-
故事标题占位
-

故事摘要将显示在这里,支持 2-3 行预览。

-
-
生成中:文本 → 封面 → 语音
-
-
-
封面生成失败,稍后重试
- -
-
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/index.html b/.claude/specs/design/figma-html/theme-c/index.html deleted file mode 100644 index f8d7221..0000000 --- a/.claude/specs/design/figma-html/theme-c/index.html +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - DreamWeaver 原型入口 - - - - - - diff --git a/.claude/specs/design/figma-html/theme-c/login.html b/.claude/specs/design/figma-html/theme-c/login.html deleted file mode 100644 index 9688bc3..0000000 --- a/.claude/specs/design/figma-html/theme-c/login.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - 登录 / 授权 - - - -
-
-
- -

欢迎来到 DreamWeaver

-

为孩子生成独一无二的故事

-
- - -
- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/my-stories.html b/.claude/specs/design/figma-html/theme-c/my-stories.html deleted file mode 100644 index 4d06335..0000000 --- a/.claude/specs/design/figma-html/theme-c/my-stories.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - 我的故事 - - - -
- -
-
- - - - - -
- -
-
-
-
星际冒险 · 第三章
-
- 太空勇气 -
-
小明 · 更新于 2 天前
-
- - -
-
-
-
-
梦幻森林 · 朋友篇
-
- 友谊动物 -
-
小红 · 更新于 5 天前
-
- - -
-
-
-
空态示例:开始生成第一个故事
- -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/not-found.html b/.claude/specs/design/figma-html/theme-c/not-found.html deleted file mode 100644 index f90546f..0000000 --- a/.claude/specs/design/figma-html/theme-c/not-found.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - 404 - - - -
-
-
-

404

-

页面走丢了,回到生成故事开始吧。

- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/push-settings.html b/.claude/specs/design/figma-html/theme-c/push-settings.html deleted file mode 100644 index 2b439f7..0000000 --- a/.claude/specs/design/figma-html/theme-c/push-settings.html +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - 推送设置 - - - -
- -
-
-
-

主动推送

-
-
- - -
-
- - -
-
-
- -
- 时间触发 - 事件触发 - 行为触发 - 成长触发 -
-
-
- -
- - -
-
-
-
-

推送预览

-
“今晚给小明讲一个关于太空的故事,好吗?”
- -
-
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/story-detail.html b/.claude/specs/design/figma-html/theme-c/story-detail.html deleted file mode 100644 index 0ab007d..0000000 --- a/.claude/specs/design/figma-html/theme-c/story-detail.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - 故事详情 - - - -
- -
-
-
-

星际冒险 · 勇气的种子

-
小明 · 星际冒险宇宙 · 2025/01/12
-
- - - -
-
-
-
-

故事正文

-

夜空像一条温柔的河流,小明驾驶着飞船穿过星光……

-

他握紧操纵杆,鼓起勇气,向未知的星球靠近。

-

最终,小明发现了新的朋友,也学会了如何面对黑暗。

-
-
-

成就

-
- 勇气 - 友谊 -
-
“克服了黑暗的恐惧”
-
“帮助了迷路的小伙伴”
-
-
- -
- -
- -
- -
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/style.css b/.claude/specs/design/figma-html/theme-c/style.css deleted file mode 100644 index 956155a..0000000 --- a/.claude/specs/design/figma-html/theme-c/style.css +++ /dev/null @@ -1,217 +0,0 @@ -:root { - --primary-600: #7C3AED; - --primary-500: #8B5CF6; - --primary-100: #EDE9FE; - --accent-pink: #FB7185; - --accent-sky: #22D3EE; - --success: #34C759; - --warning: #F6A609; - --error: #FF5A5F; - --neutral-900: #1F2937; - --neutral-700: #4B5563; - --neutral-500: #9CA3AF; - --neutral-200: #E5E7EB; - --neutral-100: #F5F5F7; - --hero-gradient: linear-gradient(135deg, #EDE9FE 0%, #FFE4F3 45%, #E0F7FF 100%); -} -* { box-sizing: border-box; } -:root { - --container-width: 1200px; - --gutter: 24px; - --radius-card: 12px; - --radius-input: 10px; - --radius-button: 8px; - --radius-pill: 24px; - --shadow-s: 0 4px 16px rgba(31,36,48,0.08); - --shadow-m: 0 10px 30px rgba(31,36,48,0.12); -} - -body { - margin: 0; - font-family: "PingFang SC", "Noto Sans SC", Inter, system-ui, -apple-system, sans-serif; - color: var(--neutral-900); - background: var(--neutral-100); -} - -a { color: var(--primary-600); text-decoration: none; } - -.page { min-height: 100vh; } -.container { - width: min(var(--container-width), 100% - 48px); - margin: 0 auto; -} - -.nav { - background: #fff; - border-bottom: 1px solid var(--neutral-200); - position: sticky; - top: 0; - z-index: 10; -} -.nav__inner { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 0; - gap: 16px; -} -.nav__left, .nav__center, .nav__right { display: flex; align-items: center; gap: 16px; } -.nav__logo { - display: flex; - align-items: center; - gap: 10px; - font-weight: 700; - color: var(--neutral-900); -} -.nav__logo-badge { - width: 28px; - height: 28px; - border-radius: 8px; - background: linear-gradient(135deg, var(--primary-500), var(--accent-sky)); -} -.nav__item { color: var(--neutral-700); font-weight: 500; } -.nav__item.active { color: var(--primary-600); } - -.hero { - background: var(--hero-gradient); - border-radius: 16px; - padding: 32px; - box-shadow: var(--shadow-s); -} - -.section { margin: 28px 0; } -.section-title { font-size: 20px; font-weight: 600; margin-bottom: 12px; } - -.grid { display: grid; gap: 16px; } -.grid-2 { grid-template-columns: repeat(2, minmax(0,1fr)); } -.grid-3 { grid-template-columns: repeat(3, minmax(0,1fr)); } - -.card { - background: #fff; - border-radius: var(--radius-card); - padding: 16px; - box-shadow: var(--shadow-s); -} -.card--flat { box-shadow: none; border: 1px solid var(--neutral-200); } -.card-cover { - width: 100%; - aspect-ratio: 21 / 9; - border-radius: 10px; - background: linear-gradient(135deg, var(--primary-100), #fff 60%, var(--accent-sky)); - margin-bottom: 12px; -} -.card-title { font-weight: 600; margin: 6px 0; } -.card-meta { color: var(--neutral-500); font-size: 12px; } - -.badge { - display: inline-flex; - align-items: center; - padding: 4px 10px; - border-radius: var(--radius-pill); - background: var(--primary-100); - color: var(--primary-600); - font-size: 12px; -} - -.chips { display: flex; flex-wrap: wrap; gap: 8px; } -.chip { - padding: 6px 12px; - border-radius: var(--radius-pill); - border: 1px solid var(--neutral-200); - background: #fff; - font-size: 12px; -} -.chip.selected { background: var(--primary-100); border-color: var(--primary-500); color: var(--primary-600); } - -.btn { - height: 40px; - padding: 0 16px; - border-radius: var(--radius-button); - border: 1px solid transparent; - cursor: pointer; - font-weight: 600; -} -.btn--primary { background: var(--primary-600); color: #fff; } -.btn--secondary { background: #fff; border-color: var(--primary-600); color: var(--primary-600); } -.btn--ghost { background: transparent; color: var(--neutral-700); } -.btn--danger { background: #fff; border-color: var(--error); color: var(--error); } - -.input, select, textarea { - width: 100%; - padding: 10px 12px; - border-radius: var(--radius-input); - border: 1px solid var(--neutral-200); - background: #fff; - font-size: 14px; -} - -.row { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; } - -.stepper { display: flex; gap: 10px; align-items: center; } -.step { - padding: 6px 12px; - border-radius: var(--radius-pill); - background: var(--neutral-100); - color: var(--neutral-700); - font-size: 12px; -} -.step.active { background: var(--primary-100); color: var(--primary-600); } - -.toolbar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; } - -.table { width: 100%; border-collapse: collapse; } -.table th, .table td { border-bottom: 1px solid var(--neutral-200); padding: 12px 8px; text-align: left; font-size: 14px; } - -.avatar { - width: 40px; height: 40px; border-radius: 50%; - background: var(--primary-100); - display: inline-flex; align-items: center; justify-content: center; - font-weight: 700; color: var(--primary-600); -} - -.cover-hero { - aspect-ratio: 16 / 9; - border-radius: 14px; - background: linear-gradient(135deg, var(--primary-100), #fff 55%, var(--accent-pink)); -} - -.audio-player { - display: flex; align-items: center; gap: 12px; padding: 12px 16px; - border-radius: 12px; border: 1px solid var(--neutral-200); background: #fff; -} -.audio-bar { height: 6px; background: var(--neutral-200); border-radius: 999px; flex: 1; } -.audio-progress { width: 35%; height: 100%; background: var(--primary-600); border-radius: 999px; } - -.tabs { display: flex; gap: 8px; border-bottom: 1px solid var(--neutral-200); } -.tab { padding: 10px 12px; color: var(--neutral-700); } -.tab.active { color: var(--primary-600); border-bottom: 2px solid var(--primary-600); } - -.callout { - background: #fff; - border: 1px dashed var(--neutral-200); - border-radius: 12px; - padding: 12px; - color: var(--neutral-700); - font-size: 12px; -} - -.footer-note { color: var(--neutral-500); font-size: 12px; margin-top: 12px; } - -.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; } - -.split { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 24px; -} - -@media (max-width: 1024px) { - .grid-3 { grid-template-columns: repeat(2, minmax(0,1fr)); } - .split { grid-template-columns: 1fr; } -} - -@media (max-width: 768px) { - .grid-2 { grid-template-columns: 1fr; } - .grid-3 { grid-template-columns: 1fr; } - .row { grid-template-columns: 1fr; } -} diff --git a/.claude/specs/design/figma-html/theme-c/universe-detail.html b/.claude/specs/design/figma-html/theme-c/universe-detail.html deleted file mode 100644 index 1f075d9..0000000 --- a/.claude/specs/design/figma-html/theme-c/universe-detail.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - 宇宙详情 - - - -
- -
-
-

星际冒险

-
主角:小明船长 · 更新于 2025/01/12
-
-
-
-

主角设定

-
小明是来自地球的探险家,勇敢且好奇。
-
-
-

常驻角色

-
机器人小七、外星猫咪星星
-
-
-

世界观

-
星际学院、彩虹星云、飞船港湾
-
-
-

成就

-
克服恐惧 · 结交朋友 · 学会独立
-
-
-
- -
-
-
- - diff --git a/.claude/specs/design/figma-html/theme-c/universes.html b/.claude/specs/design/figma-html/theme-c/universes.html deleted file mode 100644 index c5ceced..0000000 --- a/.claude/specs/design/figma-html/theme-c/universes.html +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - 故事宇宙 - - - -
- -
-
-

故事宇宙

- -
-
-
-
星际冒险
-
主角:小明船长
-
- 伙伴:机器人小七 - 成就:3 -
-
-
-
梦幻森林
-
主角:森林守护者
-
- 伙伴:魔法猫咪 - 成就:1 -
-
-
-
空态示例:创建第一个宇宙
-
-
-
-
- - diff --git a/.claude/specs/memory-intelligence/CHILD-PROFILE-MODEL.md b/.claude/specs/memory-intelligence/CHILD-PROFILE-MODEL.md deleted file mode 100644 index 3bd8eef..0000000 --- a/.claude/specs/memory-intelligence/CHILD-PROFILE-MODEL.md +++ /dev/null @@ -1,429 +0,0 @@ -# 孩子档案数据模型 - -## 概述 - -孩子档案是记忆智能系统的核心,存储孩子的基础信息、兴趣偏好和阅读行为数据。 - ---- - -## 一、数据库模型 - -### 1.1 主表: child_profiles - -```sql -CREATE TABLE child_profiles ( - -- 主键 - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - - -- 外键: 所属用户 - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - - -- 基础信息 - name VARCHAR(50) NOT NULL, - avatar_url VARCHAR(500), - birth_date DATE, - gender VARCHAR(10) CHECK (gender IN ('male', 'female', 'other')), - - -- 显式偏好 (家长填写) - interests JSONB DEFAULT '[]', - -- 示例: ["恐龙", "太空", "公主", "动物"] - - growth_themes JSONB DEFAULT '[]', - -- 示例: ["勇气", "分享"] - - -- 隐式偏好 (系统学习) - reading_preferences JSONB DEFAULT '{}', - -- 示例: { - -- "preferred_length": "medium", -- short/medium/long - -- "preferred_style": "adventure", -- adventure/fairy/educational - -- "tag_weights": {"恐龙": 5, "公主": 2, "太空": 3} - -- } - - -- 统计数据 - stories_count INTEGER DEFAULT 0, - total_reading_time INTEGER DEFAULT 0, -- 秒 - - -- 时间戳 - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - -- 约束 - CONSTRAINT unique_child_per_user UNIQUE (user_id, name) -); - --- 索引 -CREATE INDEX idx_child_profiles_user_id ON child_profiles(user_id); -``` - -### 1.2 兴趣标签枚举 - -预定义的兴趣标签,前端展示用: - -```python -INTEREST_TAGS = { - "animals": { - "zh": "动物", - "icon": "🐾", - "subtags": ["恐龙", "猫咪", "狗狗", "兔子", "海洋动物"] - }, - "fantasy": { - "zh": "奇幻", - "icon": "✨", - "subtags": ["公主", "王子", "魔法", "精灵", "龙"] - }, - "adventure": { - "zh": "冒险", - "icon": "🗺️", - "subtags": ["太空", "海盗", "探险", "寻宝"] - }, - "vehicles": { - "zh": "交通工具", - "icon": "🚗", - "subtags": ["汽车", "火车", "飞机", "火箭"] - }, - "nature": { - "zh": "自然", - "icon": "🌳", - "subtags": ["森林", "海洋", "山川", "四季"] - } -} -``` - -### 1.3 成长主题枚举 - -```python -GROWTH_THEMES = [ - {"key": "courage", "zh": "勇气", "description": "克服恐惧,勇敢面对"}, - {"key": "sharing", "zh": "分享", "description": "学会与他人分享"}, - {"key": "friendship", "zh": "友谊", "description": "交朋友,珍惜友情"}, - {"key": "honesty", "zh": "诚实", "description": "说真话,不撒谎"}, - {"key": "independence", "zh": "独立", "description": "自己的事情自己做"}, - {"key": "kindness", "zh": "善良", "description": "帮助他人,关爱弱小"}, - {"key": "patience", "zh": "耐心", "description": "学会等待,不急躁"}, - {"key": "curiosity", "zh": "好奇", "description": "探索未知,爱问为什么"} -] -``` - ---- - -## 二、SQLAlchemy 模型 - -```python -# backend/app/db/models.py - -from sqlalchemy import Column, String, Date, Integer, ForeignKey, JSON -from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.orm import relationship -import uuid - -class ChildProfile(Base): - __tablename__ = "child_profiles" - - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False) - - # 基础信息 - name = Column(String(50), nullable=False) - avatar_url = Column(String(500)) - birth_date = Column(Date) - gender = Column(String(10)) - - # 偏好 - interests = Column(JSON, default=list) - growth_themes = Column(JSON, default=list) - reading_preferences = Column(JSON, default=dict) - - # 统计 - stories_count = Column(Integer, default=0) - total_reading_time = Column(Integer, default=0) - - # 时间戳 - created_at = Column(DateTime(timezone=True), server_default=func.now()) - updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) - - # 关系 - user = relationship("User", back_populates="child_profiles") - story_universes = relationship("StoryUniverse", back_populates="child_profile", cascade="all, delete-orphan") - - @property - def age(self) -> int | None: - """计算年龄""" - if not self.birth_date: - return None - today = date.today() - return today.year - self.birth_date.year - ( - (today.month, today.day) < (self.birth_date.month, self.birth_date.day) - ) -``` - ---- - -## 三、Pydantic Schema - -```python -# backend/app/schemas/child_profile.py - -from pydantic import BaseModel, Field -from datetime import date -from uuid import UUID - -class ChildProfileCreate(BaseModel): - name: str = Field(..., min_length=1, max_length=50) - birth_date: date | None = None - gender: str | None = Field(None, pattern="^(male|female|other)$") - interests: list[str] = Field(default_factory=list) - growth_themes: list[str] = Field(default_factory=list) - -class ChildProfileUpdate(BaseModel): - name: str | None = Field(None, min_length=1, max_length=50) - birth_date: date | None = None - gender: str | None = Field(None, pattern="^(male|female|other)$") - interests: list[str] | None = None - growth_themes: list[str] | None = None - avatar_url: str | None = None - -class ChildProfileResponse(BaseModel): - id: UUID - name: str - avatar_url: str | None - birth_date: date | None - gender: str | None - age: int | None - interests: list[str] - growth_themes: list[str] - stories_count: int - total_reading_time: int - - class Config: - from_attributes = True - -class ChildProfileListResponse(BaseModel): - profiles: list[ChildProfileResponse] - total: int -``` - ---- - -## 四、API 实现 - -```python -# backend/app/api/profiles.py - -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.ext.asyncio import AsyncSession -from uuid import UUID - -router = APIRouter(prefix="/api/profiles", tags=["profiles"]) - -@router.get("", response_model=ChildProfileListResponse) -async def list_profiles( - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """获取当前用户的所有孩子档案""" - profiles = await db.execute( - select(ChildProfile) - .where(ChildProfile.user_id == current_user.id) - .order_by(ChildProfile.created_at) - ) - profiles = profiles.scalars().all() - return ChildProfileListResponse(profiles=profiles, total=len(profiles)) - -@router.post("", response_model=ChildProfileResponse, status_code=201) -async def create_profile( - data: ChildProfileCreate, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """创建孩子档案""" - # 检查是否超过限制 (每用户最多5个孩子档案) - count = await db.scalar( - select(func.count(ChildProfile.id)) - .where(ChildProfile.user_id == current_user.id) - ) - if count >= 5: - raise HTTPException(400, "最多只能创建5个孩子档案") - - profile = ChildProfile(user_id=current_user.id, **data.model_dump()) - db.add(profile) - await db.commit() - await db.refresh(profile) - return profile - -@router.get("/{profile_id}", response_model=ChildProfileResponse) -async def get_profile( - profile_id: UUID, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """获取单个孩子档案""" - profile = await db.get(ChildProfile, profile_id) - if not profile or profile.user_id != current_user.id: - raise HTTPException(404, "档案不存在") - return profile - -@router.put("/{profile_id}", response_model=ChildProfileResponse) -async def update_profile( - profile_id: UUID, - data: ChildProfileUpdate, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """更新孩子档案""" - profile = await db.get(ChildProfile, profile_id) - if not profile or profile.user_id != current_user.id: - raise HTTPException(404, "档案不存在") - - for key, value in data.model_dump(exclude_unset=True).items(): - setattr(profile, key, value) - - await db.commit() - await db.refresh(profile) - return profile - -@router.delete("/{profile_id}", status_code=204) -async def delete_profile( - profile_id: UUID, - db: AsyncSession = Depends(get_db), - current_user: User = Depends(get_current_user) -): - """删除孩子档案""" - profile = await db.get(ChildProfile, profile_id) - if not profile or profile.user_id != current_user.id: - raise HTTPException(404, "档案不存在") - - await db.delete(profile) - await db.commit() -``` - ---- - -## 五、隐式偏好学习 - -### 5.1 行为事件 - -```python -class ReadingEvent(BaseModel): - """阅读行为事件""" - profile_id: UUID - story_id: UUID - event_type: Literal["started", "completed", "skipped", "replayed"] - reading_time: int # 秒 - timestamp: datetime -``` - -### 5.2 偏好更新算法 - -```python -async def update_reading_preferences( - db: AsyncSession, - profile_id: UUID, - story: Story, - event: ReadingEvent -): - """根据阅读行为更新隐式偏好""" - profile = await db.get(ChildProfile, profile_id) - prefs = profile.reading_preferences or {} - tag_weights = prefs.get("tag_weights", {}) - - # 权重调整 - weight_delta = { - "completed": 1.0, # 完整阅读,正向 - "replayed": 1.5, # 重复播放,强正向 - "skipped": -0.5, # 跳过,负向 - "started": 0.1 # 开始阅读,弱正向 - } - - delta = weight_delta.get(event.event_type, 0) - - for tag in story.tags: - current = tag_weights.get(tag, 0) - tag_weights[tag] = max(0, current + delta) # 不低于0 - - # 更新阅读长度偏好 - if event.event_type == "completed": - word_count = len(story.content) - if word_count < 300: - length_pref = "short" - elif word_count < 600: - length_pref = "medium" - else: - length_pref = "long" - - # 简单的移动平均 - prefs["preferred_length"] = length_pref - - prefs["tag_weights"] = tag_weights - profile.reading_preferences = prefs - await db.commit() -``` - ---- - -## 六、数据迁移 - -```python -# backend/alembic/versions/xxx_add_child_profiles.py - -def upgrade(): - op.create_table( - 'child_profiles', - sa.Column('id', sa.UUID(), nullable=False), - sa.Column('user_id', sa.UUID(), nullable=False), - sa.Column('name', sa.String(50), nullable=False), - sa.Column('avatar_url', sa.String(500)), - sa.Column('birth_date', sa.Date()), - sa.Column('gender', sa.String(10)), - sa.Column('interests', sa.JSON(), server_default='[]'), - sa.Column('growth_themes', sa.JSON(), server_default='[]'), - sa.Column('reading_preferences', sa.JSON(), server_default='{}'), - sa.Column('stories_count', sa.Integer(), server_default='0'), - sa.Column('total_reading_time', sa.Integer(), server_default='0'), - sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.func.now()), - sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.func.now()), - sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index('idx_child_profiles_user_id', 'child_profiles', ['user_id']) - -def downgrade(): - op.drop_index('idx_child_profiles_user_id') - op.drop_table('child_profiles') -``` - ---- - -## 七、隐私与安全 - -### 7.1 数据加密 - -敏感字段(姓名、出生日期)在存储时加密: - -```python -from cryptography.fernet import Fernet - -class EncryptedChildProfile: - """加密存储的孩子档案""" - - @staticmethod - def encrypt_name(name: str, key: bytes) -> str: - f = Fernet(key) - return f.encrypt(name.encode()).decode() - - @staticmethod - def decrypt_name(encrypted: str, key: bytes) -> str: - f = Fernet(key) - return f.decrypt(encrypted.encode()).decode() -``` - -### 7.2 访问控制 - -- 孩子档案只能被创建者访问 -- 删除用户时级联删除所有孩子档案 -- API 层强制校验 `user_id` 归属 - -### 7.3 数据保留 - -- 用户可随时删除孩子档案 -- 删除后 30 天内可恢复(软删除) -- 30 天后永久删除 diff --git a/.claude/specs/memory-intelligence/MEMORY-INTELLIGENCE-PRD.md b/.claude/specs/memory-intelligence/MEMORY-INTELLIGENCE-PRD.md deleted file mode 100644 index 7687c4d..0000000 --- a/.claude/specs/memory-intelligence/MEMORY-INTELLIGENCE-PRD.md +++ /dev/null @@ -1,541 +0,0 @@ -# 记忆智能系统 PRD - -## 概述 - -**功能名称**: 记忆智能 (Memory Intelligence) -**版本**: v1.1 -**优先级**: Phase 2.5 (体验增强后、社区化前) -**目标用户**: 家长 + 3-8 岁儿童 -**更新记录**: 2025-01-22 合并 `backend/docs/memory_system_prd.md` - -### 核心愿景 - -将当前的"数据存储"升级为有温度的**"情感连接系统"**。 -我们不只是在记住数据,而是在**维护孩子与故事世界的关系**。让每一个故事不再是孤立的碎片,而是构建孩子专属"故事宇宙"的砖瓦。 - -### 核心价值 - -让 DreamWeaver 从"故事生成工具"进化为"懂孩子的故事伙伴": -- **记住孩子**: 偏好、成长阶段、兴趣变化 -- **延续故事**: 角色、世界观跨故事延续 -- **主动关怀**: 适时推送个性化故事建议 - -### 产品痛点与解决方案 - -| 用户角色 | 核心痛点 | 解决方案 | 预期价值 | -|---------|---------|---------|---------| -| **孩子** | "上次的小兔子怎么不认识我了?" 故事之间缺乏连续性。 | **角色一致性与记忆注入** 故事开头主动提及往事,角色性格延续。 | 建立情感依恋,提升沉浸感。 | -| **家长** | "这App除了生成故事还能干嘛?" 无法感知产品的长期教育价值。 | **显性化成长轨迹** 词汇量统计、主题变化、成就徽章可视化。 | 提高付费意愿,提供社交货币。 | -| **平台** | 用户用完即走,缺乏留存壁垒。 | **沉没成本与情感资产** 积累的记忆越多,越舍不得离开。 | 提升长期留存率 (LTV)。 | - ---- - -## 一、功能模块 - -### 1.0 记忆分层模型 - -#### 层级 1: 核心档案 (Identity Layer) -*性质:永久、静态、显性* -- **数据**: 姓名、年龄、性别 -- **输入**: 家长在 Onboarding 阶段手动输入 -- **作用**: 决定故事的基础适龄性和称呼 - -#### 层级 2: 故事宇宙 (Universe Layer) -*性质:长期、动态积累、半显性* -- **主角设定**: 姓名、性格特征(勇敢/害羞)、外貌特征(戴眼镜/卷发) -- **常驻配角**: 从随机故事中涌现出的固定伙伴(如"爱吃胡萝卜的松鼠奇奇") -- **世界观**: 故事发生的背景(魔法森林、未来城市、海底世界) -- **成就系统**: 孩子获得的虚拟奖励(勇气勋章、小小探险家) - -#### 层级 3: 工作记忆 (Working Memory) -*性质:短期、自动衰减、隐性* -- **关键情节**: 最近 3 个故事的结局和核心冲突 -- **情感标记**: 孩子对特定内容的反应(根据"重播"、"跳过"推断) -- **新学词汇**: 故事中出现的高级词汇 - -### 1.1 孩子档案系统 (Child Profile) - -| 字段 | 类型 | 说明 | -|------|------|------| -| 基础信息 | 显式 | 姓名、年龄、性别 | -| 兴趣标签 | 显式+隐式 | 恐龙、公主、太空、动物等 | -| 成长主题 | 显式 | 当前关注:勇气/分享/独立等 | -| 阅读偏好 | 隐式 | 故事长度、风格、复杂度 | -| 互动历史 | 隐式 | 喜欢的故事、跳过的故事 | - -**数据来源**: -- 显式: 家长主动填写 -- 隐式: 系统从使用行为中学习 - -### 1.2 故事宇宙记忆 (Story Universe) - -跨故事保持连续性的元素: - -| 元素 | 说明 | 示例 | -|------|------|------| -| 主角设定 | 孩子的故事化身 | "小明是个爱冒险的男孩" | -| 常驻角色 | 反复出现的配角 | 魔法猫咪"星星"、智慧老树 | -| 世界观 | 故事发生的宇宙 | 梦幻森林、星际学院 | -| 成就系统 | 主角的成长轨迹 | "学会了勇敢"、"交到新朋友" | - -**记忆结构字段**: -- `protagonist` / `recurring_characters` / `world_settings` / `achievements`(JSON 结构) -- “延续上一个故事”默认选最近更新的宇宙(按 `updated_at` 倒序) - -### 1.3 主动推送系统 (Proactive Push) - -| 触发类型 | 条件 | 推送内容 | -|----------|------|----------| -| 时间触发 | 睡前时段 (19:00-21:00) | "今晚想听什么故事?" | -| 事件触发 | 节日/生日 | 主题故事推荐 | -| 行为触发 | 3天未使用 | 召回提醒 | -| 成长触发 | 年龄变化 | 难度升级建议 | - -**优先级与抑制**: -- 优先级:事件 > 成长 > 行为 > 时间 -- 抑制:当天已推送不再触发;静默时段(21:00-09:00)延迟;用户关闭推送则不触发 - ---- - -## 二、用户故事 - -### US-1: 创建孩子档案 -``` -作为家长 -我想要创建孩子的专属档案 -以便系统生成更适合孩子的故事 -``` - -**验收标准**: -- [ ] 可填写孩子基础信息(姓名、年龄、性别) -- [ ] 可选择兴趣标签(多选) -- [ ] 可设置当前成长主题 -- [ ] 支持多个孩子档案切换 - -### US-2: 故事角色延续 -``` -作为家长 -我想要故事中的角色能在新故事中再次出现 -以便孩子感受到故事的连续性 -``` - -**验收标准**: -- [ ] 生成故事时可选择"延续上一个故事" -- [ ] 系统自动带入主角设定和常驻角色 -- [ ] 新故事引用之前的"成就" - -### US-3: 睡前故事提醒 -``` -作为家长 -我想要在睡前时段收到故事推荐 -以便养成固定的亲子阅读习惯 -``` - -**验收标准**: -- [ ] 可设置提醒时间 -- [ ] 推送包含个性化故事建议 -- [ ] 可一键进入故事生成 - ---- - -## 三、数据模型 - -### 3.1 孩子档案表 (child_profiles) - -```sql -CREATE TABLE child_profiles ( - id UUID PRIMARY KEY, - user_id UUID REFERENCES users(id), - name VARCHAR(50) NOT NULL, - birth_date DATE, - gender VARCHAR(10), - interests JSONB DEFAULT '[]', - growth_themes JSONB DEFAULT '[]', - reading_preferences JSONB DEFAULT '{}', - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() -); -``` - -### 3.2 故事宇宙表 (story_universes) - -```sql -CREATE TABLE story_universes ( - id UUID PRIMARY KEY, - child_profile_id UUID REFERENCES child_profiles(id), - name VARCHAR(100) NOT NULL, - protagonist JSONB NOT NULL, - recurring_characters JSONB DEFAULT '[]', - world_settings JSONB DEFAULT '{}', - achievements JSONB DEFAULT '[]', - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() -); -``` - -### 3.3 推送配置表 (push_configs) - -```sql -CREATE TABLE push_configs ( - id UUID PRIMARY KEY, - user_id UUID REFERENCES users(id), - child_profile_id UUID REFERENCES child_profiles(id), - push_time TIME, - push_days INTEGER[], -- 0-6 表示周日到周六 - enabled BOOLEAN DEFAULT true, - created_at TIMESTAMP DEFAULT NOW() -); -``` - -### 3.4 推送事件表 (push_events) - -```sql -CREATE TABLE push_events ( - id UUID PRIMARY KEY, - user_id UUID NOT NULL, - child_profile_id UUID NOT NULL, - trigger_type VARCHAR(20) NOT NULL, -- time/event/behavior/growth - sent_at TIMESTAMP NOT NULL, - status VARCHAR(20) NOT NULL, -- sent/failed/suppressed - reason TEXT -); -``` - -### 3.5 记忆条目表 (memory_items) - -用于存储“可解释、可控”的记忆条目(兴趣偏好、成长主题、常驻角色、关键事件等),并支持时序衰减。 - -```sql -CREATE TABLE memory_items ( - id UUID PRIMARY KEY, - child_profile_id UUID NOT NULL, - universe_id UUID, - type VARCHAR(50) NOT NULL, -- interest/growth/character/event等 - value JSONB NOT NULL, -- 结构化内容 - base_weight FLOAT DEFAULT 1.0, -- 初始权重 - last_used_at TIMESTAMP, -- 最近使用时间 - created_at TIMESTAMP DEFAULT NOW(), - ttl_days INTEGER -- 可选:过期天数 -); -``` - ---- - -## 四、API 设计 - -### 4.1 孩子档案 API - -| 方法 | 路径 | 说明 | -|------|------|------| -| GET | `/api/profiles` | 获取当前用户的所有孩子档案 | -| POST | `/api/profiles` | 创建孩子档案 | -| GET | `/api/profiles/{id}` | 获取单个档案详情 | -| PUT | `/api/profiles/{id}` | 更新档案 | -| DELETE | `/api/profiles/{id}` | 删除档案 | - -### 4.2 故事宇宙 API - -| 方法 | 路径 | 说明 | -|------|------|------| -| GET | `/api/profiles/{id}/universes` | 获取孩子的故事宇宙列表 | -| POST | `/api/profiles/{id}/universes` | 创建新宇宙 | -| GET | `/api/universes/{id}` | 获取宇宙详情 | -| PUT | `/api/universes/{id}` | 更新宇宙设定 | -| POST | `/api/universes/{id}/achievements` | 添加成就 | - -### 4.3 推送配置 API - -| 方法 | 路径 | 说明 | -|------|------|------| -| GET | `/api/push-configs` | 获取推送配置 | -| PUT | `/api/push-configs` | 更新推送配置 | - ---- - -## 五、Prompt 工程 - -### 5.1 带记忆的故事生成 Prompt - -``` -你是一个专业的儿童故事作家。请为以下孩子创作一个故事: - -【孩子档案】 -- 姓名: {child_name} -- 年龄: {age}岁 -- 兴趣: {interests} -- 当前成长主题: {growth_theme} - -【故事宇宙】 -- 主角设定: {protagonist} -- 常驻角色: {recurring_characters} -- 世界观: {world_settings} -- 已获成就: {achievements} - -【本次创作要求】 -- 关键词: {keywords} -- 延续之前的故事世界观 -- 让主角在故事中有新的成长 - -请创作一个适合{age}岁儿童的故事,约{word_count}字。 -``` - -### 5.2 智能开场白 (Memory Injection) - -在生成新故事时,Prompt 必须包含一段"记忆唤醒"指令: -- **示例**: "小明,还记得上周我们帮小松鼠找回了松果吗?今天,小松鼠带来了一位新朋友..." -- **策略**: 提取权重最高的 Top 3 记忆注入 Prompt - -### 5.3 成就提取 Prompt - -``` -请分析以下故事,提取主角获得的成长/成就: - -【故事内容】 -{story_content} - -请以JSON格式返回: -{ - "achievements": [ - {"type": "勇气", "description": "克服了对黑暗的恐惧"}, - {"type": "友谊", "description": "帮助了迷路的小兔子"} - ] -} -``` - ---- - -## 六、前端设计 - -### 6.1 孩子档案页面 - -``` -┌─────────────────────────────────────┐ -│ 我的宝贝 [+添加] │ -├─────────────────────────────────────┤ -│ ┌─────┐ ┌─────┐ ┌─────┐ │ -│ │ 👦 │ │ 👧 │ │ + │ │ -│ │小明 │ │小红 │ │添加 │ │ -│ │5岁 │ │3岁 │ │ │ │ -│ └─────┘ └─────┘ └─────┘ │ -└─────────────────────────────────────┘ -``` - -### 6.2 档案详情页 - -``` -┌─────────────────────────────────────┐ -│ ← 小明的档案 [编辑] │ -├─────────────────────────────────────┤ -│ 基础信息 │ -│ 姓名: 小明 年龄: 5岁 性别: 男 │ -├─────────────────────────────────────┤ -│ 兴趣爱好 │ -│ [恐龙] [太空] [机器人] │ -├─────────────────────────────────────┤ -│ 成长主题 │ -│ ○ 勇气 ● 分享 ○ 独立 ○ 友谊 │ -├─────────────────────────────────────┤ -│ 故事宇宙 │ -│ ┌─────────────────────────────┐ │ -│ │ 🌟 星际冒险 │ │ -│ │ 主角: 小明船长 │ │ -│ │ 伙伴: 机器人小七、外星猫咪 │ │ -│ │ 成就: 3个 │ │ -│ └─────────────────────────────┘ │ -└─────────────────────────────────────┘ -``` - -### 6.3 故事生成时选择档案 - -``` -┌─────────────────────────────────────┐ -│ 为谁创作故事? │ -├─────────────────────────────────────┤ -│ ● 小明 (5岁) │ -│ ○ 小红 (3岁) │ -│ ○ 不使用档案 │ -├─────────────────────────────────────┤ -│ 选择故事宇宙 │ -│ ● 星际冒险 (延续上次) │ -│ ○ 创建新宇宙 │ -├─────────────────────────────────────┤ -│ [下一步: 输入关键词] │ -└─────────────────────────────────────┘ -``` - ---- - -## 七、技术实现要点 - -### 7.1 隐式偏好学习 - -```python -# 基于用户行为更新偏好 -async def update_implicit_preferences( - child_id: UUID, - story: Story, - interaction: Interaction # 完整阅读/跳过/重复播放 -): - profile = await get_child_profile(child_id) - - if interaction == "completed": - # 增加相关标签权重 - for tag in story.tags: - profile.reading_preferences[tag] = \ - profile.reading_preferences.get(tag, 0) + 1 - elif interaction == "skipped": - # 降低相关标签权重 - for tag in story.tags: - profile.reading_preferences[tag] = \ - profile.reading_preferences.get(tag, 0) - 0.5 -``` - -### 7.2 成就自动提取 - -故事生成完成后,异步调用 LLM 提取成就(以 `type + description` 去重): - -```python -@celery.task -async def extract_achievements(story_id: UUID, universe_id: UUID): - story = await get_story(story_id) - universe = await get_universe(universe_id) - - achievements = await llm.extract_achievements(story.content) - - universe.achievements.extend(achievements) - await save_universe(universe) -``` - -### 7.3 推送调度 - -使用 Celery Beat 定时检查推送: - -```python -@celery.task -def check_push_notifications(): - current_time = datetime.now().time() - current_day = datetime.now().weekday() - - configs = PushConfig.query.filter( - PushConfig.enabled == True, - PushConfig.push_time <= current_time, - current_day.in_(PushConfig.push_days) - ).all() - - for config in configs: - send_push_notification.delay(config.user_id, config.child_profile_id) -``` - -**执行约束**: -- 同一孩子每天最多 1 次推送 -- 推送前查询 `push_events` 去重,成功/抑制均需记录 - -### 7.4 时序衰减与记忆评分 - -**目标**:让“越新的记忆影响越大”,避免旧偏好长期干扰。 - -**默认实现(推荐)**:查询时动态计算分数,不直接修改数据库。 -- 记忆分数:`score = base_weight × decay(Δt)` -- 衰减示例(分段):0-7 天 1.0,8-30 天 0.7,31-90 天 0.4,90 天后 0.2 -- 读取时按 `score` 排序,选 Top N 进入 Prompt - -**可选实现**:定期批处理降权 -- 每日/每周批量更新 `base_weight` -- 适合数据量大、读多写少的场景 - -**RAG 场景的衰减用法**: -- 语义相似度分数 × 时间衰减 -- 可加时间窗口过滤(如仅取最近 90 天) - -**删除策略(默认不删)**: -- 默认只降权,不主动删除 -- 可选:对低权重且 180 天未使用的条目执行 TTL 清理 - ---- - -## 八、关键功能特性 - -### 8.1 成长时间轴 (Growth Timeline) - -一个可视化的 H5 页面或 App 模块,以时间轴形式展示里程碑: -- 🌟 **初次相遇**: 创建角色的第一天 -- 📖 **阅读打卡**: 累计阅读 10/50/100 本 -- 🏅 **获得成就**: 获得"诚实勋章" -- 🧠 **能力解锁**: 第一次阅读"科幻"题材 - -### 8.2 成就仪式感 (Achievement Ceremony) - -- **触发**: 故事生成并分析后,如果获得新成就 -- **表现**: 弹窗动画 + 音效 + "恭喜获得 [勇气] 徽章" -- **分享**: 允许生成带二维码的成就海报 - ---- - -## 九、记忆类型扩展 - -| 类型 Key | 描述 | 来源 | 过期策略 | -|---------|------|------|---------| -| `recent_story` | 最近读过的故事梗概 | 阅读事件 | 30天衰减 | -| `favorite_character` | 孩子喜欢的角色 | 重播/高评分 | 长期有效 | -| `scary_element` | 孩子害怕/不喜欢的元素 | 跳过/负反馈 | 长期有效 (避雷) | -| `vocabulary_growth` | 新掌握的词汇 | 故事分析 | 90天衰减 | -| `emotional_highlight` | 高光时刻 (如: 特别开心的情节) | 互动数据 | 60天衰减 | - ---- - -## 十、里程碑 - -### Phase 1: 基础建设 (v0.3.0) -- [x] 数据库 `MemoryItem` 表 (已存在) -- [ ] 扩展 `MemoryItem` 类型字段,支持更多维度 -- [ ] 优化 `_build_memory_context`,支持更自然的 Prompt 注入 -- [ ] 前端:简单的"近期回忆"展示列表 - -### M1: 孩子档案基础 -- [ ] 数据库模型 -- [ ] CRUD API -- [ ] 前端档案管理页面 -- [ ] 故事生成时选择档案 - -### M2: 故事宇宙 -- [ ] 宇宙数据模型 -- [ ] Prompt 集成 -- [ ] 成就自动提取 -- [ ] 前端宇宙管理 - -### M3: 主动推送 -- [ ] 推送配置 API -- [ ] Celery Beat 调度 -- [ ] 推送通知集成 (Web Push / 微信) - -### M4: 隐式学习 -- [ ] 行为埋点 -- [ ] 偏好学习算法 -- [ ] 推荐优化 - -### Phase 2: 可视化与成就 (v0.4.0) -- [ ] 实现"成就提取器" (Achievement Extractor) 的闭环通知 -- [ ] 前端:开发"我的成就"和"成长时间轴"页面 -- [ ] 增加故事开场白的动态生成逻辑 - -### Phase 3: 深度智能 (v0.5.0+) -- [ ] 引入向量数据库,实现基于语义的记忆检索 (不仅是时间最近) -- [ ] 情感分析模型:分析用户行为推断情感倾向 - ---- - -## 十一、风险与应对 - -| 风险 | 影响 | 应对 | -|------|------|------| -| 隐私合规 | 高 | 儿童数据加密存储,家长授权机制 | -| 推送骚扰 | 中 | 默认关闭,用户主动开启 | -| 记忆膨胀 | 低 | 定期清理旧数据,限制宇宙数量 | - ---- - -## 十二、相关文档 - -- [孩子档案数据模型](./CHILD-PROFILE-MODEL.md) -- [故事宇宙记忆结构](./STORY-UNIVERSE-MODEL.md) -- [主动推送触发规则](./PUSH-TRIGGER-RULES.md) diff --git a/.claude/specs/memory-intelligence/MEMORY-PERSONALIZATION-TECHNICAL-REPORT.md b/.claude/specs/memory-intelligence/MEMORY-PERSONALIZATION-TECHNICAL-REPORT.md deleted file mode 100644 index 9bcdc18..0000000 --- a/.claude/specs/memory-intelligence/MEMORY-PERSONALIZATION-TECHNICAL-REPORT.md +++ /dev/null @@ -1,177 +0,0 @@ -# 记忆与个性化技术方案建议(PRD 讨论稿) - -> 目标:给 DreamWeaver 的“记忆与个性化”提供可落地的技术路径与产品取舍依据,用于 PRD 细化。 - ---- - -## 1. 总体结论(推荐方案) - -**v1 推荐:混合方案(结构化 DB + 轻量语义检索)** - -- **DB** 作为权威事实与可解释记忆(孩子档案、宇宙设定、成就、偏好权重)。 -- **RAG** 用于非结构化内容(故事摘要、互动摘要、近期期望),辅助个性化提示词。 - -**原因** -- 纯 DB 可控但缺乏语义弹性;纯 RAG 难以稳定控制与审计。 -- 混合方案能在“可解释 + 个性化”之间取到最佳平衡。 - ---- - -## 2. DB vs RAG:技术与产品对比 - -### 2.1 DB(结构化记忆) - -**适用内容** -- 孩子档案(基础信息) -- 兴趣标签与成长主题 -- 故事宇宙设定(主角、世界观、常驻角色) -- 成就(可审核、可追溯) - -**优点** -- 高可解释性 -- 变更可追踪、可回滚 -- 便于用户管理(家长可编辑) - -**缺点** -- 灵活性不足 -- 难以覆盖“隐性偏好”(比如叙事风格喜好) - -### 2.2 RAG(语义记忆) - -**适用内容** -- 故事摘要 -- 互动摘要(“最近更喜欢冒险故事”) -- 非结构化日志 - -**优点** -- 具备语义召回能力 -- 适合挖掘“隐含偏好” - -**缺点** -- 可解释性弱 -- 成本与性能压力大 -- 隐私风险更高 - ---- - -## 3. 时序性与记忆衰减(建议必须有) - -**核心观点**:孩子兴趣会随时间变化,必须引入时间衰减。 - -**做法建议** -- 所有记忆项带 `created_at` / `last_used_at` -- 引入权重衰减模型: - - 近 7 天:高权重 - - 30 天:中权重 - - 90 天:低权重 - - 超过 90 天:降权或淘汰 - -**价值** -- 避免旧偏好过度影响新故事 -- 体现成长与兴趣演变 - ---- - -## 4. 分层记忆(建议引入) - -建议采用三层结构: - -### 4.1 短期记忆(Session) -- 当前生成上下文(关键词、选定档案/宇宙) -- 生命周期:仅本次请求有效 - -### 4.2 中期记忆(近期偏好) -- 最近 5-10 次故事生成/阅读偏好 -- 生命周期:30-60 天 - -### 4.3 长期记忆(稳定事实) -- 档案、宇宙、核心兴趣 -- 生命周期:长期可编辑 - -**价值** -- 既保留稳定设定,又能捕捉近期变化 - ---- - -## 5. Agent 动态判断是否写入记忆 - -**建议:规则优先 + 模型辅助** - -流程示例: -1. 命中规则(如完整阅读/重复播放)→ 进入候选 -2. LLM 抽取结构化信息 + 置信度 -3. 置信度不足 → 不写入 - -**优点** -- 避免模型“乱记忆” -- 降低噪声,提高记忆质量 - ---- - -## 6. 推荐的记忆数据结构 - -### 6.1 结构化表(DB) - -- `child_profiles`:基础信息、兴趣、成长主题 -- `story_universes`:主角、角色、世界观、成就 -- `reading_events`:阅读/跳过/重播行为日志 -- `memory_items`:抽象记忆表(type, value, confidence, ttl) - -### 6.2 语义检索(RAG) - -- 存储内容:故事摘要、成就摘要、行为总结 -- 向量库:**pgvector**(成本低、易部署) -- 检索过滤:`child_id` / `universe_id` / 时间窗口 - ---- - -## 7. 关键产品问题(需明确) - -1) **记忆是否可编辑** -- 家长是否能查看、修改、删除系统记忆? - -2) **跨孩子隔离** -- 同账号多孩子的记忆是否完全隔离(推荐隔离) - -3) **隐私与合规** -- 哪些数据进入记忆?是否脱敏?是否加密? - -4) **性能与成本** -- RAG 查询是否影响生成时延? -- 是否需要缓存与批量检索? - -5) **效果评估** -- 记忆是否提高故事满意度? -- 需要 A/B 或指标体系吗? - ---- - -## 8. 推荐实施路线 - -### v1(1-2 个月) -- DB 记忆为主,RAG 只做轻量补充 -- 引入时序衰减 -- 记忆来源:用户显式输入 + 行为日志 - -### v2(2-3 个月) -- 引入 Agent 记忆抽取与置信度 -- 记忆管理界面(家长可编辑) -- 更精细的个性化推荐 - ---- - -## 9. 需要确认的决定点 - -- 是否采用混合方案(DB + RAG) -- RAG 的检索范围(故事摘要 / 行为摘要 / 成就) -- 记忆分层与衰减规则 -- Agent 记忆写入规则与阈值 -- 家长可见/可控的记忆管理策略 - ---- - -如确认以上方向,我可以进一步输出: -- PRD 里的“记忆系统”完整章节 -- 数据模型(含字段 + 时序衰减) -- 交互与界面草案 -- 后端实现拆解(任务清单 + 里程碑) diff --git a/.claude/specs/memory-intelligence/PUSH-TRIGGER-RULES.md b/.claude/specs/memory-intelligence/PUSH-TRIGGER-RULES.md deleted file mode 100644 index cb4eb95..0000000 --- a/.claude/specs/memory-intelligence/PUSH-TRIGGER-RULES.md +++ /dev/null @@ -1,129 +0,0 @@ -# 主动推送触发规则 - -## 概述 - -主动推送用于在合适的时间为家长提供个性化故事建议,提升使用频次与亲子阅读习惯。推送默认关闭,需家长开启并配置时间。 - ---- - -## 一、数据输入 - -- **孩子档案**: `child_profiles`(年龄、兴趣、成长主题) -- **故事数据**: `stories`(最近生成/阅读时间、主题标签) -- **推送配置**: `push_configs`(时间、周期、开关) -- **节日与生日**: 预置日历 + `birth_date` -- **行为事件**: 阅读/播放/跳过等行为埋点 - ---- - -## 二、触发类型与规则 - -### 2.1 时间触发(睡前) -- 条件:当前时间落在用户设定 `push_time` 附近(建议 ±30 分钟)。 -- 频率:同一孩子每天最多 1 次。 -- 示例:19:00-21:00 之间推送“今晚想听什么故事?” - -### 2.2 事件触发(节日/生日) -- 条件: - - 生日:`birth_date` 月日与当天一致。 - - 节日:命中节日清单(如儿童节、中秋节等)。 -- 频率:当天仅推送 1 次,优先级高于时间触发。 - -### 2.3 行为触发(召回) -- 条件:最近 3 天无故事生成或阅读行为。 -- 频率:每 3 天最多 1 次,避免频繁打扰。 - -### 2.4 成长触发(年龄变化) -- 条件:年龄跨越关键节点(如 4→5 岁)。 -- 频率:每次年龄变化仅触发一次。 -- 目的:推荐难度升级或新的成长主题。 - ---- - -## 三、优先级与抑制规则 - -**优先级顺序**(从高到低): -1. 事件触发 -2. 成长触发 -3. 行为触发 -4. 时间触发 - -**抑制规则**: -- 当天已推送则不再触发其他类型。 -- 若在静默时间(21:00-09:00)触发,则延迟至下一个允许窗口。 -- 用户关闭推送或未配置推送时间时,不触发。 - ---- - -## 四、个性化内容策略 - -- **兴趣标签**: 引用孩子的兴趣标签生成主题。 -- **成长主题**: 优先匹配当前成长主题。 -- **历史偏好**: 参考最近故事的标签与完成度。 - -**示例模板**: -- “今晚给{child_name}讲一个关于{interest}的故事,好吗?” -- “{child_name}最近在学习{growth_theme},我准备了一个新故事。” - ---- - -## 五、调度实现建议 - -使用 Celery Beat 每 5-10 分钟执行一次规则检查: - -```python -@celery.task -def check_push_notifications(): - now = datetime.now(local_tz) - configs = get_enabled_configs(now) - - for config in configs: - if has_sent_today(config.child_profile_id): - continue - - trigger = select_trigger(config, now) - if trigger: - send_push_notification(config.user_id, config.child_profile_id, trigger) -``` - -**关键点**: -- 需要记录每日推送日志用于去重。 -- 优先级触发时应立即标记已发送。 - ---- - -## 六、日志与度量 - -建议增加 `push_events` 事件表用于统计与去重: - -```sql -CREATE TABLE push_events ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID NOT NULL, - child_profile_id UUID NOT NULL, - trigger_type VARCHAR(20) NOT NULL, -- time/event/behavior/growth - sent_at TIMESTAMP WITH TIME ZONE NOT NULL, - status VARCHAR(20) NOT NULL, -- sent/failed/suppressed - reason TEXT -); -``` - -核心指标: -- Push 发送成功率 -- 打开率(CTA 点击) -- 触发分布占比 - ---- - -## 七、安全与合规 - -- **默认关闭**,需家长显式开启。 -- 支持一键关闭或设定免打扰时段。 -- 遵循儿童隐私合规要求,最小化推送内容敏感信息。 - ---- - -## 八、相关文档 - -- [记忆智能系统 PRD](./MEMORY-INTELLIGENCE-PRD.md) -- [孩子档案数据模型](./CHILD-PROFILE-MODEL.md) diff --git a/.claude/specs/memory-intelligence/STORY-UNIVERSE-MODEL.md b/.claude/specs/memory-intelligence/STORY-UNIVERSE-MODEL.md deleted file mode 100644 index 514bcef..0000000 --- a/.claude/specs/memory-intelligence/STORY-UNIVERSE-MODEL.md +++ /dev/null @@ -1,231 +0,0 @@ -# 故事宇宙记忆结构 - -## 概述 - -故事宇宙用于在多次故事生成中保持角色、世界观与成长成就的连续性。每个孩子档案可以拥有多个宇宙,故事生成时可选择“延续上一个故事”,系统自动带入宇宙设定。 - ---- - -## 一、数据库模型 - -### 1.1 主表: story_universes - -```sql -CREATE TABLE story_universes ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - child_profile_id UUID NOT NULL REFERENCES child_profiles(id) ON DELETE CASCADE, - - -- 宇宙基础 - name VARCHAR(100) NOT NULL, - - -- 记忆结构 - protagonist JSONB NOT NULL, -- 主角设定 - recurring_characters JSONB DEFAULT '[]', - world_settings JSONB DEFAULT '{}', - achievements JSONB DEFAULT '[]', - - -- 时间戳 - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX idx_story_universes_child_id ON story_universes(child_profile_id); -CREATE INDEX idx_story_universes_updated_at ON story_universes(updated_at); -``` - -### 1.2 JSON 结构示例 - -**protagonist** -```json -{ - "name": "小明", - "role": "星际船长", - "traits": ["勇敢", "好奇"], - "goal": "寻找失落的星球", - "backstory": "来自地球的探险家" -} -``` - -**recurring_characters** -```json -[ - { - "name": "星星", - "role": "魔法猫咪", - "traits": ["聪明", "调皮"], - "relation": "伙伴", - "first_story_id": "story-uuid" - } -] -``` - -**world_settings** -```json -{ - "world_name": "梦幻森林", - "era": "童话时代", - "locations": ["彩虹河", "月光山"], - "rules": ["动物会说话", "星星会指路"], - "tone": "温暖治愈" -} -``` - -**achievements** -```json -[ - { - "type": "勇气", - "description": "克服了对黑暗的恐惧", - "story_id": "story-uuid", - "achieved_at": "2025-01-10T12:00:00Z" - } -] -``` - ---- - -## 二、SQLAlchemy 模型 - -```python -# backend/app/db/models.py - -from sqlalchemy import Column, String, ForeignKey, JSON -from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.orm import relationship -import uuid - -class StoryUniverse(Base): - __tablename__ = "story_universes" - - id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - child_profile_id = Column(UUID(as_uuid=True), ForeignKey("child_profiles.id", ondelete="CASCADE"), nullable=False) - - name = Column(String(100), nullable=False) - protagonist = Column(JSON, nullable=False) - recurring_characters = Column(JSON, default=list) - world_settings = Column(JSON, default=dict) - achievements = Column(JSON, default=list) - - created_at = Column(DateTime(timezone=True), server_default=func.now()) - updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) - - child_profile = relationship("ChildProfile", back_populates="story_universes") -``` - ---- - -## 三、Pydantic Schema - -```python -# backend/app/schemas/story_universe.py - -from pydantic import BaseModel, Field -from typing import Any -from uuid import UUID - -class StoryUniverseCreate(BaseModel): - name: str = Field(..., min_length=1, max_length=100) - protagonist: dict[str, Any] - recurring_characters: list[dict[str, Any]] = Field(default_factory=list) - world_settings: dict[str, Any] = Field(default_factory=dict) - -class StoryUniverseUpdate(BaseModel): - name: str | None = Field(None, min_length=1, max_length=100) - protagonist: dict[str, Any] | None = None - recurring_characters: list[dict[str, Any]] | None = None - world_settings: dict[str, Any] | None = None - -class StoryUniverseResponse(BaseModel): - id: UUID - child_profile_id: UUID - name: str - protagonist: dict[str, Any] - recurring_characters: list[dict[str, Any]] - world_settings: dict[str, Any] - achievements: list[dict[str, Any]] - - class Config: - from_attributes = True -``` - ---- - -## 四、API 约定 - -| 方法 | 路径 | 说明 | -|------|------|------| -| GET | `/api/profiles/{id}/universes` | 获取孩子的故事宇宙列表 | -| POST | `/api/profiles/{id}/universes` | 创建新宇宙 | -| GET | `/api/universes/{id}` | 获取宇宙详情 | -| PUT | `/api/universes/{id}` | 更新宇宙设定 | -| POST | `/api/universes/{id}/achievements` | 添加成就 | - ---- - -## 五、业务规则 - -- **延续故事**: “延续上一个故事”默认选最近更新的宇宙(按 `updated_at` 倒序)。 -- **成就追加**: 新成就追加到 `achievements`,以 `type + description` 去重。 -- **成长轨迹**: 成就保留顺序,优先展示最新项。 - ---- - -## 六、Prompt 集成 - -当选择宇宙时,生成 Prompt 需带入宇宙记忆: - -``` -【故事宇宙】 -- 主角设定: {protagonist} -- 常驻角色: {recurring_characters} -- 世界观: {world_settings} -- 已获成就: {achievements} -``` - -未选择宇宙时,提示词忽略该块,避免混淆。 - ---- - -## 七、数据迁移示例 - -```python -# backend/alembic/versions/xxx_add_story_universes.py - -def upgrade(): - op.create_table( - "story_universes", - sa.Column("id", sa.UUID(), nullable=False), - sa.Column("child_profile_id", sa.UUID(), nullable=False), - sa.Column("name", sa.String(100), nullable=False), - sa.Column("protagonist", sa.JSON(), nullable=False), - sa.Column("recurring_characters", sa.JSON(), server_default='[]'), - sa.Column("world_settings", sa.JSON(), server_default='{}'), - sa.Column("achievements", sa.JSON(), server_default='[]'), - sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()), - sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()), - sa.ForeignKeyConstraint(["child_profile_id"], ["child_profiles.id"], ondelete="CASCADE"), - sa.PrimaryKeyConstraint("id"), - ) - op.create_index("idx_story_universes_child_id", "story_universes", ["child_profile_id"]) - op.create_index("idx_story_universes_updated_at", "story_universes", ["updated_at"]) - - -def downgrade(): - op.drop_index("idx_story_universes_updated_at") - op.drop_index("idx_story_universes_child_id") - op.drop_table("story_universes") -``` - ---- - -## 八、权限与安全 - -- 宇宙数据必须通过 `child_profile_id` 归属校验,确保仅拥有者可访问。 -- 删除用户或档案时,级联删除所有宇宙数据。 - ---- - -## 九、相关文档 - -- [孩子档案数据模型](./CHILD-PROFILE-MODEL.md) -- [记忆智能系统 PRD](./MEMORY-INTELLIGENCE-PRD.md) diff --git a/.claude/specs/product-roadmap/PRODUCT-VISION.md b/.claude/specs/product-roadmap/PRODUCT-VISION.md deleted file mode 100644 index 0e94efd..0000000 --- a/.claude/specs/product-roadmap/PRODUCT-VISION.md +++ /dev/null @@ -1,130 +0,0 @@ -# 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. **商业模式** - 免费/付费边界在哪里? - -请确认以上产品愿景是否符合预期,我们再深入讨论供应商平台化的技术架构。 diff --git a/.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md b/.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md deleted file mode 100644 index 744c4e9..0000000 --- a/.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md +++ /dev/null @@ -1,677 +0,0 @@ -# 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 # 一个���败不影响另一个 - ) - - 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) - ---- - -## 确认后删除此区块 - -确认所有决策后,可以删除未选中的选项,保留最终方案作为实现依据。 diff --git a/.claude/specs/product-roadmap/ROADMAP.md b/.claude/specs/product-roadmap/ROADMAP.md deleted file mode 100644 index c7937d2..0000000 --- a/.claude/specs/product-roadmap/ROADMAP.md +++ /dev/null @@ -1,169 +0,0 @@ -# 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. **故事分享** - 自然增长引擎 - -是否需要我为这些功能生成详细的技术规格文档? diff --git a/.claude/specs/robustness-improvement/dev-plan.md b/.claude/specs/robustness-improvement/dev-plan.md deleted file mode 100644 index 7746b04..0000000 --- a/.claude/specs/robustness-improvement/dev-plan.md +++ /dev/null @@ -1,49 +0,0 @@ -# DreamWeaver 工程鲁棒性改进计划 - -## 概述 -本计划旨在提升 DreamWeaver 项目的工程质量,包括测试覆盖、稳定性、可观测性等方面。 - -## 任务列表 - -### P0 - 关键问题修复 - -#### Task-1: 修复 Rate Limit 内存泄漏 ✅ -- **文件**: `backend/app/api/stories.py` -- **方案**: 已迁移至 Redis 分布式限流,内存泄漏问题不再存在 - -#### Task-2: 添加核心 API 测试 ✅ -- **文件**: `backend/tests/` -- **范围**: test_auth, test_stories, test_profiles, test_universes, test_push_configs, test_reading_events, test_provider_router - -### P1 - 稳定性提升 - -#### Task-3: 添加 API 重试机制 ✅ -- **方案**: 所有适配器已使用 `tenacity` 指数退避重试 (gemini, openai, cqtai, antigravity, minimax, elevenlabs) - -#### Task-4: 添加结构化日志 ✅ -- **文件**: `backend/app/core/logging.py` -- **方案**: structlog JSON/Console 双模式,所有适配器和 provider_router 已集成 - -### P2 - 代码优化 - -#### Task-5: 重构 Provider Router ✅ -- **文件**: `backend/app/services/provider_router.py` -- **方案**: 已实现统一 `_route_with_failover` 函数 - -#### Task-6: 配置外部化 ✅ -- **文件**: `backend/app/core/config.py`, `backend/app/services/provider_router.py` -- **方案**: 所有模型名已移至 Settings,支持环境变量覆盖 - -#### Task-7: 修复脆弱的 URL 解析 ✅ -- **状态**: `drawing.py` 已被适配器系统取代,不再存在 - -## 新增依赖 (已添加) -```toml -# pyproject.toml [project.dependencies] -cachetools>=5.0.0 # Task-1: TTL cache -tenacity>=8.0.0 # Task-3: 重试机制 -structlog>=24.0.0 # Task-4: 结构化日志 - -# [project.optional-dependencies.dev] -pytest-cov>=4.0.0 # Task-2: 覆盖率报告 -``` diff --git a/.claude/ui-refactor-plan.md b/.claude/ui-refactor-plan.md deleted file mode 100644 index da679ee..0000000 --- a/.claude/ui-refactor-plan.md +++ /dev/null @@ -1,303 +0,0 @@ -DreamWeaver 前端 UI 重构任务列表 - -阶段一:基础设施(必须先完成) - -TASK-001 [x]: 安装图标库 - -文件: frontend/package.json -操作: 安装 @heroicons/vue 图标库 -命令: npm install @heroicons/vue -验收: 能在 Vue 组件中 import { SparklesIcon } from '@heroicons/vue/24/outline' - -TASK-002 [x]: 扩展 Tailwind 配置 - -文件: frontend/tailwind.config.js -操作: 添加完整的设计系统配置 -内容: - -- 扩展 fontFamily 添加 sans: ['Noto Sans SC', ...] -- 扩展 borderRadius 添加 '2xl': '1rem', '3xl': '1.5rem' -- 扩展 boxShadow 添加 'glass': '0 8px 32px rgba(0,0,0,0.08)' -- 扩展 animation 添加 'float': 'float 3s ease-in-out infinite' -- 扩展 keyframes 添加 float 动画定义 - 验收: Tailwind 类 font-sans, rounded-3xl, shadow-glass, animate-float 可用 - -TASK-003 [x]: 精简全局样式 - -文件: frontend/src/style.css -操作: - -1. 删除 .animate-float (移至 Tailwind) -2. 删除 .stars::before/after (移除 emoji 装饰) -3. 保留 .glass, .btn-magic, .input-magic, .card-hover, .gradient-text -4. 删除 --gradient-magic 变量(过于花哨) - 验收: 文件行数减少约 30%,无 emoji 相关 CSS - ---- - -阶段二:创建可复用组件 - -TASK-004 [x]: 创建 BaseButton 组件 - -文件: frontend/src/components/ui/BaseButton.vue -操作: 创建统一按钮组件 -Props: - -- variant: 'primary' | 'secondary' | 'danger' | 'ghost' -- size: 'sm' | 'md' | 'lg' -- loading: boolean -- disabled: boolean -- icon: Component (可选,Heroicon 组件) - 样式规范: -- primary: 使用 .btn-magic 渐变 -- secondary: bg-white border border-gray-200 -- danger: bg-red-500 text-white -- ghost: bg-transparent hover:bg-gray-100 - 验收: 导出组件,支持 slot 内容和所有 props - -TASK-005 [x]: 创建 BaseCard 组件 - -文件: frontend/src/components/ui/BaseCard.vue -操作: 创建统一卡片组件 -Props: - -- hover: boolean (是否启用悬浮效果) -- padding: 'none' | 'sm' | 'md' | 'lg' - 样式: 使用 .glass + rounded-2xl + 可选 .card-hover - 验收: 导出组件,支持默认 slot - -TASK-006 [x]: 创建 BaseInput 组件 - -文件: frontend/src/components/ui/BaseInput.vue -操作: 创建统一输入框组件 -Props: - -- modelValue: string -- type: 'text' | 'password' | 'email' | 'number' -- placeholder: string -- label: string (可选) -- error: string (可选) -- disabled: boolean - 样式: 使用 .input-magic + 错误状态红色边框 - 验收: 支持 v-model,显示 label 和 error - -TASK-007 [x]: 创建 BaseSelect 组件 - -文件: frontend/src/components/ui/BaseSelect.vue -操作: 创建统一下拉选择组件 -Props: - -- modelValue: string | number -- options: Array<{ value: string | number, label: string }> -- label: string (可选) -- placeholder: string -- disabled: boolean - 样式: 与 BaseInput 保持一致 - 验收: 支持 v-model,正确渲染 options - -TASK-008 [x]: 创建 BaseTextarea 组件 - -文件: frontend/src/components/ui/BaseTextarea.vue -操作: 创建统一文本域组件 -Props: - -- modelValue: string -- placeholder: string -- rows: number -- maxLength: number (可选,显示字数统计) -- label: string (可选) - 样式: 使用 .input-magic,右下角显示字数 - 验收: 支持 v-model,字数统计正确 - -TASK-009 [x]: 创建 LoadingSpinner 组件 - -文件: frontend/src/components/ui/LoadingSpinner.vue -操作: 创建统一加载动画组件 -Props: - -- size: 'sm' | 'md' | 'lg' -- text: string (可选,加载提示文字) - 样式: 紫色渐变圆环旋转动画,无 emoji - 验收: 三种尺寸正确渲染 - -TASK-010 [x]: 创建 EmptyState 组件 - -文件: frontend/src/components/ui/EmptyState.vue -操作: 创建统一空状态组件 -Props: - -- icon: Component (Heroicon) -- title: string -- description: string -- actionText: string (可选) -- actionTo: string (可选,路由路径) - 样式: 居中布局,图标使用 Heroicon 而非 emoji - 验收: 点击按钮正确跳转 - -TASK-011 [x]: 创建 ConfirmModal 组件 - -文件: frontend/src/components/ui/ConfirmModal.vue -操作: 创建统一确认弹窗组件 -Props: - -- show: boolean -- title: string -- message: string -- confirmText: string -- cancelText: string -- variant: 'danger' | 'warning' | 'info' - Emits: confirm, cancel - 样式: 使用 .glass 背景,Transition 动画 - 验收: 显示/隐藏动画流畅,事件正确触发 - -TASK-012 [x]: 创建组件导出索引 - -文件: frontend/src/components/ui/index.ts -操作: 统一导出所有 UI 组件 -内容: -export { default as BaseButton } from './BaseButton.vue' -export { default as BaseCard } from './BaseCard.vue' -// ... 其他组件 -验收: 可以 import { BaseButton, BaseCard } from '@/components/ui' - ---- - -阶段三:重构现有页面 - -TASK-013 [x]: 重构 NavBar 组件 - -文件: frontend/src/components/NavBar.vue -操作: - -1. 将 emoji ✨🌟📚🛠️🚪 替换为 Heroicons (SparklesIcon, StarIcon, BookOpenIcon, Cog6ToothIcon, ArrowRightOnRectangleIcon) -2. 将 ?? 占位符替换为正确图标 (UserGroupIcon, GlobeAltIcon) -3. 使用 BaseButton 替换登录按钮 -4. 移除 animate-float 和 animate-pulse 装饰动画 - 验收: 无 emoji,图标统一为 Heroicons,视觉更专业 - -TASK-014 [x]: 重构 Home.vue 页面 - -文件: frontend/src/views/Home.vue -操作: - -1. 删除 Hero 区域的浮动 emoji 装饰 (🌙⭐✨🌟) -2. 将模式切换按钮的 emoji (✨📝) 替换为 Heroicons -3. 将教育主题按钮的 emoji 替换为 Heroicons 或移除 -4. 使用 BaseButton 替换提交按钮 -5. 使用 BaseTextarea 替换文本输入区 -6. 使用 BaseSelect 替换档案/宇宙选择器 -7. 将 Features 区域的 emoji (🎨🔊📚) 替换为 Heroicons - 验收: 页面无 emoji,使用统一组件,视觉简洁专业 - -TASK-015 [x]: 重构 MyStories.vue 页面 - -文件: frontend/src/views/MyStories.vue -操作: - -1. 使用 BaseButton 替换"创作新故事"按钮 -2. 使用 LoadingSpinner 替换自定义加载动画 -3. 使用 EmptyState 替换空状态区域(移除 📚✨🪄 emoji) -4. 将错误状态的 😢 替换为 Heroicon ExclamationCircleIcon -5. 将统计区域的 📖 替换为 Heroicon -6. 使用 BaseCard 包装故事卡片 - 验收: 页面无 emoji,组件统一 - -TASK-016 [x]: 重构 StoryDetail.vue 页面 - -文件: frontend/src/views/StoryDetail.vue -操作: - -1. 使用 LoadingSpinner 替换加载动画 -2. 将 🎨 替换为 Heroicon PhotoIcon -3. 将 🔊 替换为 Heroicon SpeakerWaveIcon -4. 将 ✨ 替换为 Heroicon SparklesIcon -5. 将 🗑️ 替换为 Heroicon TrashIcon -6. 将 ⚠️ 替换为 Heroicon ExclamationTriangleIcon -7. 使用 BaseButton 替换所有按钮 -8. 使用 ConfirmModal 替换删除确认弹窗 - 验收: 页面无 emoji,弹窗使用统一组件 - -TASK-017 [x]: 重构 AdminProviders.vue 页面 - -文件: frontend/src/views/AdminProviders.vue -操作: - -1. 移除