Initial commit: clean project structure
- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+) - Frontend: Vue 3 + TypeScript + Pinia + Tailwind - Admin Frontend: separate Vue 3 app for management - Docker Compose: 9 services orchestration - Specs: design prototypes, memory system PRD, product roadmap Cleanup performed: - Removed temporary debug scripts from backend root - Removed deprecated admin_app.py (embedded UI) - Removed duplicate docs from admin-frontend - Updated .gitignore for Vite cache and egg-info
This commit is contained in:
44
.claude/settings.local.json
Normal file
44
.claude/settings.local.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"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:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
153
.claude/specs/design/BRAND-VISUAL-DIRECTIONS.md
Normal file
153
.claude/specs/design/BRAND-VISUAL-DIRECTIONS.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# 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 测试备选。
|
||||
230
.claude/specs/design/PAGE-HIFI-LAYOUT-SPEC.md
Normal file
230
.claude/specs/design/PAGE-HIFI-LAYOUT-SPEC.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 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 建立栅格
|
||||
- 状态页复制并标注
|
||||
299
.claude/specs/design/WEB-HIFI-PROTOTYPE-SPEC.md
Normal file
299
.claude/specs/design/WEB-HIFI-PROTOTYPE-SPEC.md
Normal file
@@ -0,0 +1,299 @@
|
||||
# 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 中生成)
|
||||
BIN
.claude/specs/design/figma-html/theme-a.zip
Normal file
BIN
.claude/specs/design/figma-html/theme-a.zip
Normal file
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>账户设置</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>个人信息</h3>
|
||||
<div class="row section">
|
||||
<input class="input" placeholder="昵称" value="Dream Parent" />
|
||||
<input class="input" placeholder="邮箱" value="parent@example.com" />
|
||||
</div>
|
||||
<button class="btn btn--primary">保存</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>账号安全</h3>
|
||||
<div class="callout">已绑定 GitHub、Google</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 12px;">管理绑定</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>数据隐私</h3>
|
||||
<button class="btn btn--secondary">导出数据</button>
|
||||
<button class="btn btn--danger" style="margin-top: 12px;">删除账户</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
74
.claude/specs/design/figma-html/theme-a/admin-providers.html
Normal file
74
.claude/specs/design/figma-html/theme-a/admin-providers.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>管理后台 - Providers</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>Providers 管理</h2>
|
||||
<button class="btn btn--primary">新增 Provider</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>状态</th>
|
||||
<th>延迟</th>
|
||||
<th>最近检查</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>text_primary</td>
|
||||
<td>Text</td>
|
||||
<td><span class="badge">健康</span></td>
|
||||
<td>420ms</td>
|
||||
<td>2 分钟前</td>
|
||||
<td><a href="#">编辑</a> · <a href="#">禁用</a> · <a href="#">重载</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>image_primary</td>
|
||||
<td>Image</td>
|
||||
<td><span class="badge">健康</span></td>
|
||||
<td>860ms</td>
|
||||
<td>5 分钟前</td>
|
||||
<td><a href="#">编辑</a> · <a href="#">禁用</a> · <a href="#">重载</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="footer-note">点击编辑后弹出 JSON 配置编辑器。</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>孩子档案详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="card section" style="display:flex; align-items:center; justify-content: space-between;">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div class="avatar">明</div>
|
||||
<div>
|
||||
<div class="card-title">小明 · 5岁</div>
|
||||
<div class="card-meta">男 · 生日 2020/05/12</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn--secondary">编辑档案</button>
|
||||
</div>
|
||||
|
||||
<div class="tabs section">
|
||||
<div class="tab active">基础信息</div>
|
||||
<div class="tab">兴趣与成长</div>
|
||||
<div class="tab">故事宇宙</div>
|
||||
<div class="tab">阅读记录</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>兴趣标签</h3>
|
||||
<div class="chips">
|
||||
<span class="chip selected">太空</span>
|
||||
<span class="chip selected">机器人</span>
|
||||
<span class="chip">冒险</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成长主题</h3>
|
||||
<div class="chips">
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip">分享</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>故事宇宙</h3>
|
||||
<div class="grid grid-2">
|
||||
<div class="card">
|
||||
<div class="card-title">星际冒险</div>
|
||||
<div class="card-meta">主角:小明船长 · 成就 3 个</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">梦幻森林</div>
|
||||
<div class="card-meta">主角:森林守护者 · 成就 1 个</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
58
.claude/specs/design/figma-html/theme-a/child-profiles.html
Normal file
58
.claude/specs/design/figma-html/theme-a/child-profiles.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>孩子档案</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>我的宝贝</h2>
|
||||
<button class="btn btn--primary">添加档案</button>
|
||||
</div>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="avatar">明</div>
|
||||
<div class="card-title">小明 · 5岁</div>
|
||||
<div class="chips"><span class="chip">太空</span><span class="chip">机器人</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="avatar">红</div>
|
||||
<div class="card-title">小红 · 3岁</div>
|
||||
<div class="chips"><span class="chip">公主</span><span class="chip">动物</span></div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:添加一个孩子档案</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
111
.claude/specs/design/figma-html/theme-a/home.html
Normal file
111
.claude/specs/design/figma-html/theme-a/home.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>生成故事</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="stepper section">
|
||||
<span class="step active">档案</span>
|
||||
<span class="step">宇宙</span>
|
||||
<span class="step">关键词</span>
|
||||
<span class="step">生成</span>
|
||||
</div>
|
||||
<div class="split section">
|
||||
<div class="card">
|
||||
<h3>为谁创作故事</h3>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>孩子档案</label>
|
||||
<select>
|
||||
<option>小明 · 5岁</option>
|
||||
<option>小红 · 3岁</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>故事宇宙</label>
|
||||
<select>
|
||||
<option>延续上一次(星际冒险)</option>
|
||||
<option>新建宇宙</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>关键词</label>
|
||||
<div class="chips section">
|
||||
<span class="chip selected">太空</span>
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip">机器人</span>
|
||||
<span class="chip">探索</span>
|
||||
</div>
|
||||
<input class="input" placeholder="输入更多关键词" />
|
||||
</div>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>成长主题</label>
|
||||
<select>
|
||||
<option>勇气</option>
|
||||
<option>分享</option>
|
||||
<option>独立</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>故事长度</label>
|
||||
<div class="chips">
|
||||
<span class="chip selected">短</span>
|
||||
<span class="chip">中</span>
|
||||
<span class="chip">长</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<button class="btn btn--primary" style="width: 100%;">生成故事</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>生成预览</h3>
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">故事标题占位</div>
|
||||
<p class="card-meta">故事摘要将显示在这里,支持 2-3 行预览。</p>
|
||||
<div class="section">
|
||||
<div class="callout">生成中:文本 → 封面 → 语音</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="callout" style="border-color: var(--error); color: var(--error);">封面生成失败,稍后重试</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 8px;">重新生成封面</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
33
.claude/specs/design/figma-html/theme-a/index.html
Normal file
33
.claude/specs/design/figma-html/theme-a/index.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DreamWeaver 原型入口</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 48px 0;">
|
||||
<div class="hero">
|
||||
<h1>DreamWeaver HTML 原型入口</h1>
|
||||
<p>请选择页面进行导入或预览(HTML to Figma)。</p>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card"><a href="login.html">登录 / 授权</a></div>
|
||||
<div class="card"><a href="home.html">生成故事(Home)</a></div>
|
||||
<div class="card"><a href="my-stories.html">我的故事(列表)</a></div>
|
||||
<div class="card"><a href="story-detail.html">故事详情</a></div>
|
||||
<div class="card"><a href="child-profiles.html">孩子档案(列表)</a></div>
|
||||
<div class="card"><a href="child-profile-detail.html">孩子档案(详情)</a></div>
|
||||
<div class="card"><a href="universes.html">故事宇宙(列表)</a></div>
|
||||
<div class="card"><a href="universe-detail.html">故事宇宙(详情)</a></div>
|
||||
<div class="card"><a href="push-settings.html">推送设置</a></div>
|
||||
<div class="card"><a href="account-settings.html">账户设置</a></div>
|
||||
<div class="card"><a href="admin-providers.html">管理后台(Providers)</a></div>
|
||||
<div class="card"><a href="not-found.html">404 / 错误</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
.claude/specs/design/figma-html/theme-a/login.html
Normal file
28
.claude/specs/design/figma-html/theme-a/login.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>登录 / 授权</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 80px 0;">
|
||||
<div class="hero" style="max-width: 420px; margin: 0 auto; text-align: center;">
|
||||
<div class="nav__logo" style="justify-content: center;">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<h2 style="margin-top: 16px;">欢迎来到 DreamWeaver</h2>
|
||||
<p>为孩子生成独一无二的故事</p>
|
||||
<div class="section" style="display: grid; gap: 12px;">
|
||||
<button class="btn btn--primary">使用 GitHub 登录</button>
|
||||
<button class="btn btn--secondary">使用 Google 登录</button>
|
||||
</div>
|
||||
<div class="footer-note">我们仅使用公开信息创建账户</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
84
.claude/specs/design/figma-html/theme-a/my-stories.html
Normal file
84
.claude/specs/design/figma-html/theme-a/my-stories.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>我的故事</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<input class="input" style="width: 260px;" placeholder="搜索标题或关键词" />
|
||||
<select style="width: 160px;">
|
||||
<option>孩子:全部</option>
|
||||
<option>小明</option>
|
||||
<option>小红</option>
|
||||
</select>
|
||||
<select style="width: 160px;">
|
||||
<option>排序:最新</option>
|
||||
<option>最早</option>
|
||||
</select>
|
||||
<button class="btn btn--ghost">网格</button>
|
||||
<button class="btn btn--ghost">列表</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">星际冒险 · 第三章</div>
|
||||
<div class="chips">
|
||||
<span class="chip">太空</span><span class="chip">勇气</span>
|
||||
</div>
|
||||
<div class="card-meta">小明 · 更新于 2 天前</div>
|
||||
<div class="section hero-actions">
|
||||
<button class="btn btn--primary">继续阅读</button>
|
||||
<button class="btn btn--secondary">重生成封面</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">梦幻森林 · 朋友篇</div>
|
||||
<div class="chips">
|
||||
<span class="chip">友谊</span><span class="chip">动物</span>
|
||||
</div>
|
||||
<div class="card-meta">小红 · 更新于 5 天前</div>
|
||||
<div class="section hero-actions">
|
||||
<button class="btn btn--primary">继续阅读</button>
|
||||
<button class="btn btn--danger">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:开始生成第一个故事</div>
|
||||
<button class="btn btn--primary" style="margin-top: 12px;">生成故事</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
20
.claude/specs/design/figma-html/theme-a/not-found.html
Normal file
20
.claude/specs/design/figma-html/theme-a/not-found.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>404</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 80px 0; text-align:center;">
|
||||
<div class="hero">
|
||||
<h1>404</h1>
|
||||
<p>页面走丢了,回到生成故事开始吧。</p>
|
||||
<button class="btn btn--primary">返回首页</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
78
.claude/specs/design/figma-html/theme-a/push-settings.html
Normal file
78
.claude/specs/design/figma-html/theme-a/push-settings.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>推送设置</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>主动推送</h3>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>主开关</label>
|
||||
<select>
|
||||
<option>开启</option>
|
||||
<option>关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>推送时间</label>
|
||||
<input class="input" placeholder="20:00" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>触发类型</label>
|
||||
<div class="chips">
|
||||
<span class="chip selected">时间触发</span>
|
||||
<span class="chip selected">事件触发</span>
|
||||
<span class="chip">行为触发</span>
|
||||
<span class="chip">成长触发</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>免打扰</label>
|
||||
<div class="row">
|
||||
<input class="input" placeholder="21:00" />
|
||||
<input class="input" placeholder="09:00" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>推送预览</h3>
|
||||
<div class="callout">“今晚给小明讲一个关于太空的故事,好吗?”</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 12px;">发送测试推送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
73
.claude/specs/design/figma-html/theme-a/story-detail.html
Normal file
73
.claude/specs/design/figma-html/theme-a/story-detail.html
Normal file
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>故事详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="section">
|
||||
<div class="cover-hero"></div>
|
||||
<h2 style="margin-top: 16px;">星际冒险 · 勇气的种子</h2>
|
||||
<div class="card-meta">小明 · 星际冒险宇宙 · 2025/01/12</div>
|
||||
<div class="hero-actions section">
|
||||
<button class="btn btn--secondary">重新生成封面</button>
|
||||
<button class="btn btn--primary">生成语音</button>
|
||||
<button class="btn btn--ghost">分享</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split section">
|
||||
<div class="card">
|
||||
<h3>故事正文</h3>
|
||||
<p>夜空像一条温柔的河流,小明驾驶着飞船穿过星光……</p>
|
||||
<p>他握紧操纵杆,鼓起勇气,向未知的星球靠近。</p>
|
||||
<p>最终,小明发现了新的朋友,也学会了如何面对黑暗。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成就</h3>
|
||||
<div class="chips section">
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip selected">友谊</span>
|
||||
</div>
|
||||
<div class="callout section">“克服了黑暗的恐惧”</div>
|
||||
<div class="callout">“帮助了迷路的小伙伴”</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section audio-player">
|
||||
<button class="btn btn--ghost">播放</button>
|
||||
<div class="audio-bar"><div class="audio-progress"></div></div>
|
||||
<button class="btn btn--ghost">1.0x</button>
|
||||
</div>
|
||||
<div class="footer-note">语音未生成时,显示“生成语音”按钮作为主操作。</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
217
.claude/specs/design/figma-html/theme-a/style.css
Normal file
217
.claude/specs/design/figma-html/theme-a/style.css
Normal file
@@ -0,0 +1,217 @@
|
||||
: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; }
|
||||
}
|
||||
64
.claude/specs/design/figma-html/theme-a/universe-detail.html
Normal file
64
.claude/specs/design/figma-html/theme-a/universe-detail.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>宇宙详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="card section">
|
||||
<h2>星际冒险</h2>
|
||||
<div class="card-meta">主角:小明船长 · 更新于 2025/01/12</div>
|
||||
</div>
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>主角设定</h3>
|
||||
<div class="callout">小明是来自地球的探险家,勇敢且好奇。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>常驻角色</h3>
|
||||
<div class="callout">机器人小七、外星猫咪星星</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>世界观</h3>
|
||||
<div class="callout">星际学院、彩虹星云、飞船港湾</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成就</h3>
|
||||
<div class="callout">克服恐惧 · 结交朋友 · 学会独立</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<button class="btn btn--secondary">编辑宇宙</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
64
.claude/specs/design/figma-html/theme-a/universes.html
Normal file
64
.claude/specs/design/figma-html/theme-a/universes.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>故事宇宙</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>故事宇宙</h2>
|
||||
<button class="btn btn--primary">新建宇宙</button>
|
||||
</div>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="card-title">星际冒险</div>
|
||||
<div class="card-meta">主角:小明船长</div>
|
||||
<div class="chips section">
|
||||
<span class="chip">伙伴:机器人小七</span>
|
||||
<span class="chip">成就:3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">梦幻森林</div>
|
||||
<div class="card-meta">主角:森林守护者</div>
|
||||
<div class="chips section">
|
||||
<span class="chip">伙伴:魔法猫咪</span>
|
||||
<span class="chip">成就:1</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:创建第一个宇宙</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>账户设置</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>个人信息</h3>
|
||||
<div class="row section">
|
||||
<input class="input" placeholder="昵称" value="Dream Parent" />
|
||||
<input class="input" placeholder="邮箱" value="parent@example.com" />
|
||||
</div>
|
||||
<button class="btn btn--primary">保存</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>账号安全</h3>
|
||||
<div class="callout">已绑定 GitHub、Google</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 12px;">管理绑定</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>数据隐私</h3>
|
||||
<button class="btn btn--secondary">导出数据</button>
|
||||
<button class="btn btn--danger" style="margin-top: 12px;">删除账户</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
74
.claude/specs/design/figma-html/theme-b/admin-providers.html
Normal file
74
.claude/specs/design/figma-html/theme-b/admin-providers.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>管理后台 - Providers</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>Providers 管理</h2>
|
||||
<button class="btn btn--primary">新增 Provider</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>状态</th>
|
||||
<th>延迟</th>
|
||||
<th>最近检查</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>text_primary</td>
|
||||
<td>Text</td>
|
||||
<td><span class="badge">健康</span></td>
|
||||
<td>420ms</td>
|
||||
<td>2 分钟前</td>
|
||||
<td><a href="#">编辑</a> · <a href="#">禁用</a> · <a href="#">重载</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>image_primary</td>
|
||||
<td>Image</td>
|
||||
<td><span class="badge">健康</span></td>
|
||||
<td>860ms</td>
|
||||
<td>5 分钟前</td>
|
||||
<td><a href="#">编辑</a> · <a href="#">禁用</a> · <a href="#">重载</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="footer-note">点击编辑后弹出 JSON 配置编辑器。</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>孩子档案详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="card section" style="display:flex; align-items:center; justify-content: space-between;">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div class="avatar">明</div>
|
||||
<div>
|
||||
<div class="card-title">小明 · 5岁</div>
|
||||
<div class="card-meta">男 · 生日 2020/05/12</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn--secondary">编辑档案</button>
|
||||
</div>
|
||||
|
||||
<div class="tabs section">
|
||||
<div class="tab active">基础信息</div>
|
||||
<div class="tab">兴趣与成长</div>
|
||||
<div class="tab">故事宇宙</div>
|
||||
<div class="tab">阅读记录</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>兴趣标签</h3>
|
||||
<div class="chips">
|
||||
<span class="chip selected">太空</span>
|
||||
<span class="chip selected">机器人</span>
|
||||
<span class="chip">冒险</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成长主题</h3>
|
||||
<div class="chips">
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip">分享</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>故事宇宙</h3>
|
||||
<div class="grid grid-2">
|
||||
<div class="card">
|
||||
<div class="card-title">星际冒险</div>
|
||||
<div class="card-meta">主角:小明船长 · 成就 3 个</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">梦幻森林</div>
|
||||
<div class="card-meta">主角:森林守护者 · 成就 1 个</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
58
.claude/specs/design/figma-html/theme-b/child-profiles.html
Normal file
58
.claude/specs/design/figma-html/theme-b/child-profiles.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>孩子档案</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>我的宝贝</h2>
|
||||
<button class="btn btn--primary">添加档案</button>
|
||||
</div>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="avatar">明</div>
|
||||
<div class="card-title">小明 · 5岁</div>
|
||||
<div class="chips"><span class="chip">太空</span><span class="chip">机器人</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="avatar">红</div>
|
||||
<div class="card-title">小红 · 3岁</div>
|
||||
<div class="chips"><span class="chip">公主</span><span class="chip">动物</span></div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:添加一个孩子档案</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
111
.claude/specs/design/figma-html/theme-b/home.html
Normal file
111
.claude/specs/design/figma-html/theme-b/home.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>生成故事</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="stepper section">
|
||||
<span class="step active">档案</span>
|
||||
<span class="step">宇宙</span>
|
||||
<span class="step">关键词</span>
|
||||
<span class="step">生成</span>
|
||||
</div>
|
||||
<div class="split section">
|
||||
<div class="card">
|
||||
<h3>为谁创作故事</h3>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>孩子档案</label>
|
||||
<select>
|
||||
<option>小明 · 5岁</option>
|
||||
<option>小红 · 3岁</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>故事宇宙</label>
|
||||
<select>
|
||||
<option>延续上一次(星际冒险)</option>
|
||||
<option>新建宇宙</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>关键词</label>
|
||||
<div class="chips section">
|
||||
<span class="chip selected">太空</span>
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip">机器人</span>
|
||||
<span class="chip">探索</span>
|
||||
</div>
|
||||
<input class="input" placeholder="输入更多关键词" />
|
||||
</div>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>成长主题</label>
|
||||
<select>
|
||||
<option>勇气</option>
|
||||
<option>分享</option>
|
||||
<option>独立</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>故事长度</label>
|
||||
<div class="chips">
|
||||
<span class="chip selected">短</span>
|
||||
<span class="chip">中</span>
|
||||
<span class="chip">长</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<button class="btn btn--primary" style="width: 100%;">生成故事</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>生成预览</h3>
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">故事标题占位</div>
|
||||
<p class="card-meta">故事摘要将显示在这里,支持 2-3 行预览。</p>
|
||||
<div class="section">
|
||||
<div class="callout">生成中:文本 → 封面 → 语音</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="callout" style="border-color: var(--error); color: var(--error);">封面生成失败,稍后重试</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 8px;">重新生成封面</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
33
.claude/specs/design/figma-html/theme-b/index.html
Normal file
33
.claude/specs/design/figma-html/theme-b/index.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DreamWeaver 原型入口</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 48px 0;">
|
||||
<div class="hero">
|
||||
<h1>DreamWeaver HTML 原型入口</h1>
|
||||
<p>请选择页面进行导入或预览(HTML to Figma)。</p>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card"><a href="login.html">登录 / 授权</a></div>
|
||||
<div class="card"><a href="home.html">生成故事(Home)</a></div>
|
||||
<div class="card"><a href="my-stories.html">我的故事(列表)</a></div>
|
||||
<div class="card"><a href="story-detail.html">故事详情</a></div>
|
||||
<div class="card"><a href="child-profiles.html">孩子档案(列表)</a></div>
|
||||
<div class="card"><a href="child-profile-detail.html">孩子档案(详情)</a></div>
|
||||
<div class="card"><a href="universes.html">故事宇宙(列表)</a></div>
|
||||
<div class="card"><a href="universe-detail.html">故事宇宙(详情)</a></div>
|
||||
<div class="card"><a href="push-settings.html">推送设置</a></div>
|
||||
<div class="card"><a href="account-settings.html">账户设置</a></div>
|
||||
<div class="card"><a href="admin-providers.html">管理后台(Providers)</a></div>
|
||||
<div class="card"><a href="not-found.html">404 / 错误</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
.claude/specs/design/figma-html/theme-b/login.html
Normal file
28
.claude/specs/design/figma-html/theme-b/login.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>登录 / 授权</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 80px 0;">
|
||||
<div class="hero" style="max-width: 420px; margin: 0 auto; text-align: center;">
|
||||
<div class="nav__logo" style="justify-content: center;">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<h2 style="margin-top: 16px;">欢迎来到 DreamWeaver</h2>
|
||||
<p>为孩子生成独一无二的故事</p>
|
||||
<div class="section" style="display: grid; gap: 12px;">
|
||||
<button class="btn btn--primary">使用 GitHub 登录</button>
|
||||
<button class="btn btn--secondary">使用 Google 登录</button>
|
||||
</div>
|
||||
<div class="footer-note">我们仅使用公开信息创建账户</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
84
.claude/specs/design/figma-html/theme-b/my-stories.html
Normal file
84
.claude/specs/design/figma-html/theme-b/my-stories.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>我的故事</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<input class="input" style="width: 260px;" placeholder="搜索标题或关键词" />
|
||||
<select style="width: 160px;">
|
||||
<option>孩子:全部</option>
|
||||
<option>小明</option>
|
||||
<option>小红</option>
|
||||
</select>
|
||||
<select style="width: 160px;">
|
||||
<option>排序:最新</option>
|
||||
<option>最早</option>
|
||||
</select>
|
||||
<button class="btn btn--ghost">网格</button>
|
||||
<button class="btn btn--ghost">列表</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">星际冒险 · 第三章</div>
|
||||
<div class="chips">
|
||||
<span class="chip">太空</span><span class="chip">勇气</span>
|
||||
</div>
|
||||
<div class="card-meta">小明 · 更新于 2 天前</div>
|
||||
<div class="section hero-actions">
|
||||
<button class="btn btn--primary">继续阅读</button>
|
||||
<button class="btn btn--secondary">重生成封面</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">梦幻森林 · 朋友篇</div>
|
||||
<div class="chips">
|
||||
<span class="chip">友谊</span><span class="chip">动物</span>
|
||||
</div>
|
||||
<div class="card-meta">小红 · 更新于 5 天前</div>
|
||||
<div class="section hero-actions">
|
||||
<button class="btn btn--primary">继续阅读</button>
|
||||
<button class="btn btn--danger">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:开始生成第一个故事</div>
|
||||
<button class="btn btn--primary" style="margin-top: 12px;">生成故事</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
20
.claude/specs/design/figma-html/theme-b/not-found.html
Normal file
20
.claude/specs/design/figma-html/theme-b/not-found.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>404</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 80px 0; text-align:center;">
|
||||
<div class="hero">
|
||||
<h1>404</h1>
|
||||
<p>页面走丢了,回到生成故事开始吧。</p>
|
||||
<button class="btn btn--primary">返回首页</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
78
.claude/specs/design/figma-html/theme-b/push-settings.html
Normal file
78
.claude/specs/design/figma-html/theme-b/push-settings.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>推送设置</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>主动推送</h3>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>主开关</label>
|
||||
<select>
|
||||
<option>开启</option>
|
||||
<option>关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>推送时间</label>
|
||||
<input class="input" placeholder="20:00" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>触发类型</label>
|
||||
<div class="chips">
|
||||
<span class="chip selected">时间触发</span>
|
||||
<span class="chip selected">事件触发</span>
|
||||
<span class="chip">行为触发</span>
|
||||
<span class="chip">成长触发</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>免打扰</label>
|
||||
<div class="row">
|
||||
<input class="input" placeholder="21:00" />
|
||||
<input class="input" placeholder="09:00" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>推送预览</h3>
|
||||
<div class="callout">“今晚给小明讲一个关于太空的故事,好吗?”</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 12px;">发送测试推送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
73
.claude/specs/design/figma-html/theme-b/story-detail.html
Normal file
73
.claude/specs/design/figma-html/theme-b/story-detail.html
Normal file
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>故事详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="section">
|
||||
<div class="cover-hero"></div>
|
||||
<h2 style="margin-top: 16px;">星际冒险 · 勇气的种子</h2>
|
||||
<div class="card-meta">小明 · 星际冒险宇宙 · 2025/01/12</div>
|
||||
<div class="hero-actions section">
|
||||
<button class="btn btn--secondary">重新生成封面</button>
|
||||
<button class="btn btn--primary">生成语音</button>
|
||||
<button class="btn btn--ghost">分享</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split section">
|
||||
<div class="card">
|
||||
<h3>故事正文</h3>
|
||||
<p>夜空像一条温柔的河流,小明驾驶着飞船穿过星光……</p>
|
||||
<p>他握紧操纵杆,鼓起勇气,向未知的星球靠近。</p>
|
||||
<p>最终,小明发现了新的朋友,也学会了如何面对黑暗。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成就</h3>
|
||||
<div class="chips section">
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip selected">友谊</span>
|
||||
</div>
|
||||
<div class="callout section">“克服了黑暗的恐惧”</div>
|
||||
<div class="callout">“帮助了迷路的小伙伴”</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section audio-player">
|
||||
<button class="btn btn--ghost">播放</button>
|
||||
<div class="audio-bar"><div class="audio-progress"></div></div>
|
||||
<button class="btn btn--ghost">1.0x</button>
|
||||
</div>
|
||||
<div class="footer-note">语音未生成时,显示“生成语音”按钮作为主操作。</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
217
.claude/specs/design/figma-html/theme-b/style.css
Normal file
217
.claude/specs/design/figma-html/theme-b/style.css
Normal file
@@ -0,0 +1,217 @@
|
||||
: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; }
|
||||
}
|
||||
64
.claude/specs/design/figma-html/theme-b/universe-detail.html
Normal file
64
.claude/specs/design/figma-html/theme-b/universe-detail.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>宇宙详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="card section">
|
||||
<h2>星际冒险</h2>
|
||||
<div class="card-meta">主角:小明船长 · 更新于 2025/01/12</div>
|
||||
</div>
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>主角设定</h3>
|
||||
<div class="callout">小明是来自地球的探险家,勇敢且好奇。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>常驻角色</h3>
|
||||
<div class="callout">机器人小七、外星猫咪星星</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>世界观</h3>
|
||||
<div class="callout">星际学院、彩虹星云、飞船港湾</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成就</h3>
|
||||
<div class="callout">克服恐惧 · 结交朋友 · 学会独立</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<button class="btn btn--secondary">编辑宇宙</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
64
.claude/specs/design/figma-html/theme-b/universes.html
Normal file
64
.claude/specs/design/figma-html/theme-b/universes.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>故事宇宙</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>故事宇宙</h2>
|
||||
<button class="btn btn--primary">新建宇宙</button>
|
||||
</div>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="card-title">星际冒险</div>
|
||||
<div class="card-meta">主角:小明船长</div>
|
||||
<div class="chips section">
|
||||
<span class="chip">伙伴:机器人小七</span>
|
||||
<span class="chip">成就:3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">梦幻森林</div>
|
||||
<div class="card-meta">主角:森林守护者</div>
|
||||
<div class="chips section">
|
||||
<span class="chip">伙伴:魔法猫咪</span>
|
||||
<span class="chip">成就:1</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:创建第一个宇宙</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>账户设置</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>个人信息</h3>
|
||||
<div class="row section">
|
||||
<input class="input" placeholder="昵称" value="Dream Parent" />
|
||||
<input class="input" placeholder="邮箱" value="parent@example.com" />
|
||||
</div>
|
||||
<button class="btn btn--primary">保存</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>账号安全</h3>
|
||||
<div class="callout">已绑定 GitHub、Google</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 12px;">管理绑定</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>数据隐私</h3>
|
||||
<button class="btn btn--secondary">导出数据</button>
|
||||
<button class="btn btn--danger" style="margin-top: 12px;">删除账户</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
74
.claude/specs/design/figma-html/theme-c/admin-providers.html
Normal file
74
.claude/specs/design/figma-html/theme-c/admin-providers.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>管理后台 - Providers</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>Providers 管理</h2>
|
||||
<button class="btn btn--primary">新增 Provider</button>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>类型</th>
|
||||
<th>状态</th>
|
||||
<th>延迟</th>
|
||||
<th>最近检查</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>text_primary</td>
|
||||
<td>Text</td>
|
||||
<td><span class="badge">健康</span></td>
|
||||
<td>420ms</td>
|
||||
<td>2 分钟前</td>
|
||||
<td><a href="#">编辑</a> · <a href="#">禁用</a> · <a href="#">重载</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>image_primary</td>
|
||||
<td>Image</td>
|
||||
<td><span class="badge">健康</span></td>
|
||||
<td>860ms</td>
|
||||
<td>5 分钟前</td>
|
||||
<td><a href="#">编辑</a> · <a href="#">禁用</a> · <a href="#">重载</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="footer-note">点击编辑后弹出 JSON 配置编辑器。</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>孩子档案详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="card section" style="display:flex; align-items:center; justify-content: space-between;">
|
||||
<div style="display:flex; gap:12px; align-items:center;">
|
||||
<div class="avatar">明</div>
|
||||
<div>
|
||||
<div class="card-title">小明 · 5岁</div>
|
||||
<div class="card-meta">男 · 生日 2020/05/12</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn--secondary">编辑档案</button>
|
||||
</div>
|
||||
|
||||
<div class="tabs section">
|
||||
<div class="tab active">基础信息</div>
|
||||
<div class="tab">兴趣与成长</div>
|
||||
<div class="tab">故事宇宙</div>
|
||||
<div class="tab">阅读记录</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>兴趣标签</h3>
|
||||
<div class="chips">
|
||||
<span class="chip selected">太空</span>
|
||||
<span class="chip selected">机器人</span>
|
||||
<span class="chip">冒险</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成长主题</h3>
|
||||
<div class="chips">
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip">分享</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>故事宇宙</h3>
|
||||
<div class="grid grid-2">
|
||||
<div class="card">
|
||||
<div class="card-title">星际冒险</div>
|
||||
<div class="card-meta">主角:小明船长 · 成就 3 个</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">梦幻森林</div>
|
||||
<div class="card-meta">主角:森林守护者 · 成就 1 个</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
58
.claude/specs/design/figma-html/theme-c/child-profiles.html
Normal file
58
.claude/specs/design/figma-html/theme-c/child-profiles.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>孩子档案</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>我的宝贝</h2>
|
||||
<button class="btn btn--primary">添加档案</button>
|
||||
</div>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="avatar">明</div>
|
||||
<div class="card-title">小明 · 5岁</div>
|
||||
<div class="chips"><span class="chip">太空</span><span class="chip">机器人</span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="avatar">红</div>
|
||||
<div class="card-title">小红 · 3岁</div>
|
||||
<div class="chips"><span class="chip">公主</span><span class="chip">动物</span></div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:添加一个孩子档案</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
111
.claude/specs/design/figma-html/theme-c/home.html
Normal file
111
.claude/specs/design/figma-html/theme-c/home.html
Normal file
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>生成故事</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="stepper section">
|
||||
<span class="step active">档案</span>
|
||||
<span class="step">宇宙</span>
|
||||
<span class="step">关键词</span>
|
||||
<span class="step">生成</span>
|
||||
</div>
|
||||
<div class="split section">
|
||||
<div class="card">
|
||||
<h3>为谁创作故事</h3>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>孩子档案</label>
|
||||
<select>
|
||||
<option>小明 · 5岁</option>
|
||||
<option>小红 · 3岁</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>故事宇宙</label>
|
||||
<select>
|
||||
<option>延续上一次(星际冒险)</option>
|
||||
<option>新建宇宙</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>关键词</label>
|
||||
<div class="chips section">
|
||||
<span class="chip selected">太空</span>
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip">机器人</span>
|
||||
<span class="chip">探索</span>
|
||||
</div>
|
||||
<input class="input" placeholder="输入更多关键词" />
|
||||
</div>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>成长主题</label>
|
||||
<select>
|
||||
<option>勇气</option>
|
||||
<option>分享</option>
|
||||
<option>独立</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>故事长度</label>
|
||||
<div class="chips">
|
||||
<span class="chip selected">短</span>
|
||||
<span class="chip">中</span>
|
||||
<span class="chip">长</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<button class="btn btn--primary" style="width: 100%;">生成故事</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>生成预览</h3>
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">故事标题占位</div>
|
||||
<p class="card-meta">故事摘要将显示在这里,支持 2-3 行预览。</p>
|
||||
<div class="section">
|
||||
<div class="callout">生成中:文本 → 封面 → 语音</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<div class="callout" style="border-color: var(--error); color: var(--error);">封面生成失败,稍后重试</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 8px;">重新生成封面</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
33
.claude/specs/design/figma-html/theme-c/index.html
Normal file
33
.claude/specs/design/figma-html/theme-c/index.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DreamWeaver 原型入口</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 48px 0;">
|
||||
<div class="hero">
|
||||
<h1>DreamWeaver HTML 原型入口</h1>
|
||||
<p>请选择页面进行导入或预览(HTML to Figma)。</p>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card"><a href="login.html">登录 / 授权</a></div>
|
||||
<div class="card"><a href="home.html">生成故事(Home)</a></div>
|
||||
<div class="card"><a href="my-stories.html">我的故事(列表)</a></div>
|
||||
<div class="card"><a href="story-detail.html">故事详情</a></div>
|
||||
<div class="card"><a href="child-profiles.html">孩子档案(列表)</a></div>
|
||||
<div class="card"><a href="child-profile-detail.html">孩子档案(详情)</a></div>
|
||||
<div class="card"><a href="universes.html">故事宇宙(列表)</a></div>
|
||||
<div class="card"><a href="universe-detail.html">故事宇宙(详情)</a></div>
|
||||
<div class="card"><a href="push-settings.html">推送设置</a></div>
|
||||
<div class="card"><a href="account-settings.html">账户设置</a></div>
|
||||
<div class="card"><a href="admin-providers.html">管理后台(Providers)</a></div>
|
||||
<div class="card"><a href="not-found.html">404 / 错误</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
28
.claude/specs/design/figma-html/theme-c/login.html
Normal file
28
.claude/specs/design/figma-html/theme-c/login.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>登录 / 授权</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 80px 0;">
|
||||
<div class="hero" style="max-width: 420px; margin: 0 auto; text-align: center;">
|
||||
<div class="nav__logo" style="justify-content: center;">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<h2 style="margin-top: 16px;">欢迎来到 DreamWeaver</h2>
|
||||
<p>为孩子生成独一无二的故事</p>
|
||||
<div class="section" style="display: grid; gap: 12px;">
|
||||
<button class="btn btn--primary">使用 GitHub 登录</button>
|
||||
<button class="btn btn--secondary">使用 Google 登录</button>
|
||||
</div>
|
||||
<div class="footer-note">我们仅使用公开信息创建账户</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
84
.claude/specs/design/figma-html/theme-c/my-stories.html
Normal file
84
.claude/specs/design/figma-html/theme-c/my-stories.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>我的故事</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<input class="input" style="width: 260px;" placeholder="搜索标题或关键词" />
|
||||
<select style="width: 160px;">
|
||||
<option>孩子:全部</option>
|
||||
<option>小明</option>
|
||||
<option>小红</option>
|
||||
</select>
|
||||
<select style="width: 160px;">
|
||||
<option>排序:最新</option>
|
||||
<option>最早</option>
|
||||
</select>
|
||||
<button class="btn btn--ghost">网格</button>
|
||||
<button class="btn btn--ghost">列表</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">星际冒险 · 第三章</div>
|
||||
<div class="chips">
|
||||
<span class="chip">太空</span><span class="chip">勇气</span>
|
||||
</div>
|
||||
<div class="card-meta">小明 · 更新于 2 天前</div>
|
||||
<div class="section hero-actions">
|
||||
<button class="btn btn--primary">继续阅读</button>
|
||||
<button class="btn btn--secondary">重生成封面</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-cover"></div>
|
||||
<div class="card-title">梦幻森林 · 朋友篇</div>
|
||||
<div class="chips">
|
||||
<span class="chip">友谊</span><span class="chip">动物</span>
|
||||
</div>
|
||||
<div class="card-meta">小红 · 更新于 5 天前</div>
|
||||
<div class="section hero-actions">
|
||||
<button class="btn btn--primary">继续阅读</button>
|
||||
<button class="btn btn--danger">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:开始生成第一个故事</div>
|
||||
<button class="btn btn--primary" style="margin-top: 12px;">生成故事</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
20
.claude/specs/design/figma-html/theme-c/not-found.html
Normal file
20
.claude/specs/design/figma-html/theme-c/not-found.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>404</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="container" style="padding: 80px 0; text-align:center;">
|
||||
<div class="hero">
|
||||
<h1>404</h1>
|
||||
<p>页面走丢了,回到生成故事开始吧。</p>
|
||||
<button class="btn btn--primary">返回首页</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
78
.claude/specs/design/figma-html/theme-c/push-settings.html
Normal file
78
.claude/specs/design/figma-html/theme-c/push-settings.html
Normal file
@@ -0,0 +1,78 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>推送设置</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>主动推送</h3>
|
||||
<div class="row section">
|
||||
<div>
|
||||
<label>主开关</label>
|
||||
<select>
|
||||
<option>开启</option>
|
||||
<option>关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>推送时间</label>
|
||||
<input class="input" placeholder="20:00" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>触发类型</label>
|
||||
<div class="chips">
|
||||
<span class="chip selected">时间触发</span>
|
||||
<span class="chip selected">事件触发</span>
|
||||
<span class="chip">行为触发</span>
|
||||
<span class="chip">成长触发</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<label>免打扰</label>
|
||||
<div class="row">
|
||||
<input class="input" placeholder="21:00" />
|
||||
<input class="input" placeholder="09:00" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>推送预览</h3>
|
||||
<div class="callout">“今晚给小明讲一个关于太空的故事,好吗?”</div>
|
||||
<button class="btn btn--secondary" style="margin-top: 12px;">发送测试推送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
73
.claude/specs/design/figma-html/theme-c/story-detail.html
Normal file
73
.claude/specs/design/figma-html/theme-c/story-detail.html
Normal file
@@ -0,0 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>故事详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="section">
|
||||
<div class="cover-hero"></div>
|
||||
<h2 style="margin-top: 16px;">星际冒险 · 勇气的种子</h2>
|
||||
<div class="card-meta">小明 · 星际冒险宇宙 · 2025/01/12</div>
|
||||
<div class="hero-actions section">
|
||||
<button class="btn btn--secondary">重新生成封面</button>
|
||||
<button class="btn btn--primary">生成语音</button>
|
||||
<button class="btn btn--ghost">分享</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split section">
|
||||
<div class="card">
|
||||
<h3>故事正文</h3>
|
||||
<p>夜空像一条温柔的河流,小明驾驶着飞船穿过星光……</p>
|
||||
<p>他握紧操纵杆,鼓起勇气,向未知的星球靠近。</p>
|
||||
<p>最终,小明发现了新的朋友,也学会了如何面对黑暗。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成就</h3>
|
||||
<div class="chips section">
|
||||
<span class="chip selected">勇气</span>
|
||||
<span class="chip selected">友谊</span>
|
||||
</div>
|
||||
<div class="callout section">“克服了黑暗的恐惧”</div>
|
||||
<div class="callout">“帮助了迷路的小伙伴”</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section audio-player">
|
||||
<button class="btn btn--ghost">播放</button>
|
||||
<div class="audio-bar"><div class="audio-progress"></div></div>
|
||||
<button class="btn btn--ghost">1.0x</button>
|
||||
</div>
|
||||
<div class="footer-note">语音未生成时,显示“生成语音”按钮作为主操作。</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
217
.claude/specs/design/figma-html/theme-c/style.css
Normal file
217
.claude/specs/design/figma-html/theme-c/style.css
Normal file
@@ -0,0 +1,217 @@
|
||||
: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; }
|
||||
}
|
||||
64
.claude/specs/design/figma-html/theme-c/universe-detail.html
Normal file
64
.claude/specs/design/figma-html/theme-c/universe-detail.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>宇宙详情</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="card section">
|
||||
<h2>星际冒险</h2>
|
||||
<div class="card-meta">主角:小明船长 · 更新于 2025/01/12</div>
|
||||
</div>
|
||||
<div class="grid grid-2 section">
|
||||
<div class="card">
|
||||
<h3>主角设定</h3>
|
||||
<div class="callout">小明是来自地球的探险家,勇敢且好奇。</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>常驻角色</h3>
|
||||
<div class="callout">机器人小七、外星猫咪星星</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>世界观</h3>
|
||||
<div class="callout">星际学院、彩虹星云、飞船港湾</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>成就</h3>
|
||||
<div class="callout">克服恐惧 · 结交朋友 · 学会独立</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section">
|
||||
<button class="btn btn--secondary">编辑宇宙</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
64
.claude/specs/design/figma-html/theme-c/universes.html
Normal file
64
.claude/specs/design/figma-html/theme-c/universes.html
Normal file
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>故事宇宙</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<header class="nav">
|
||||
<div class="container nav__inner">
|
||||
<div class="nav__left">
|
||||
<div class="nav__logo">
|
||||
<span class="nav__logo-badge"></span>
|
||||
DreamWeaver
|
||||
</div>
|
||||
<span class="badge">Web 原型</span>
|
||||
</div>
|
||||
<div class="nav__center">
|
||||
<a class="nav__item active" href="home.html">生成故事</a>
|
||||
<a class="nav__item" href="my-stories.html">我的故事</a>
|
||||
<a class="nav__item" href="child-profiles.html">孩子档案</a>
|
||||
<a class="nav__item" href="universes.html">故事宇宙</a>
|
||||
<a class="nav__item" href="push-settings.html">推送设置</a>
|
||||
<a class="nav__item" href="account-settings.html">账户设置</a>
|
||||
<a class="nav__item" href="admin-providers.html">管理后台</a>
|
||||
</div>
|
||||
<div class="nav__right">
|
||||
<input class="input" style="width: 200px;" placeholder="搜索故事" />
|
||||
<div class="avatar">家</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 28px 0 60px;">
|
||||
<div class="toolbar section">
|
||||
<h2>故事宇宙</h2>
|
||||
<button class="btn btn--primary">新建宇宙</button>
|
||||
</div>
|
||||
<div class="grid grid-3 section">
|
||||
<div class="card">
|
||||
<div class="card-title">星际冒险</div>
|
||||
<div class="card-meta">主角:小明船长</div>
|
||||
<div class="chips section">
|
||||
<span class="chip">伙伴:机器人小七</span>
|
||||
<span class="chip">成就:3</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title">梦幻森林</div>
|
||||
<div class="card-meta">主角:森林守护者</div>
|
||||
<div class="chips section">
|
||||
<span class="chip">伙伴:魔法猫咪</span>
|
||||
<span class="chip">成就:1</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card--flat">
|
||||
<div class="callout">空态示例:创建第一个宇宙</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
429
.claude/specs/memory-intelligence/CHILD-PROFILE-MODEL.md
Normal file
429
.claude/specs/memory-intelligence/CHILD-PROFILE-MODEL.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# 孩子档案数据模型
|
||||
|
||||
## 概述
|
||||
|
||||
孩子档案是记忆智能系统的核心,存储孩子的基础信息、兴趣偏好和阅读行为数据。
|
||||
|
||||
---
|
||||
|
||||
## 一、数据库模型
|
||||
|
||||
### 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 天后永久删除
|
||||
455
.claude/specs/memory-intelligence/MEMORY-INTELLIGENCE-PRD.md
Normal file
455
.claude/specs/memory-intelligence/MEMORY-INTELLIGENCE-PRD.md
Normal file
@@ -0,0 +1,455 @@
|
||||
# 记忆智能系统 PRD
|
||||
|
||||
## 概述
|
||||
|
||||
**功能名称**: 记忆智能 (Memory Intelligence)
|
||||
**版本**: v1.0
|
||||
**优先级**: Phase 2.5 (体验增强后、社区化前)
|
||||
**目标用户**: 家长 + 3-8 岁儿童
|
||||
|
||||
### 核心价值
|
||||
|
||||
让 DreamWeaver 从"故事生成工具"进化为"懂孩子的故事伙伴":
|
||||
- **记住孩子**: 偏好、成长阶段、兴趣变化
|
||||
- **延续故事**: 角色、世界观跨故事延续
|
||||
- **主动关怀**: 适时推送个性化故事建议
|
||||
|
||||
---
|
||||
|
||||
## 一、功能模块
|
||||
|
||||
### 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 成就提取 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 清理
|
||||
|
||||
---
|
||||
|
||||
## 八、里程碑
|
||||
|
||||
### M1: 孩子档案基础
|
||||
- [ ] 数据库模型
|
||||
- [ ] CRUD API
|
||||
- [ ] 前端档案管理页面
|
||||
- [ ] 故事生成时选择档案
|
||||
|
||||
### M2: 故事宇宙
|
||||
- [ ] 宇宙数据模型
|
||||
- [ ] Prompt 集成
|
||||
- [ ] 成就自动提取
|
||||
- [ ] 前端宇宙管理
|
||||
|
||||
### M3: 主动推送
|
||||
- [ ] 推送配置 API
|
||||
- [ ] Celery Beat 调度
|
||||
- [ ] 推送通知集成 (Web Push / 微信)
|
||||
|
||||
### M4: 隐式学习
|
||||
- [ ] 行为埋点
|
||||
- [ ] 偏好学习算法
|
||||
- [ ] 推荐优化
|
||||
|
||||
---
|
||||
|
||||
## 九、风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对 |
|
||||
|------|------|------|
|
||||
| 隐私合规 | 高 | 儿童数据加密存储,家长授权机制 |
|
||||
| 推送骚扰 | 中 | 默认关闭,用户主动开启 |
|
||||
| 记忆膨胀 | 低 | 定期清理旧数据,限制宇宙数量 |
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- [孩子档案数据模型](./CHILD-PROFILE-MODEL.md)
|
||||
- [故事宇宙记忆结构](./STORY-UNIVERSE-MODEL.md)
|
||||
- [主动推送触发规则](./PUSH-TRIGGER-RULES.md)
|
||||
@@ -0,0 +1,177 @@
|
||||
# 记忆与个性化技术方案建议(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 里的“记忆系统”完整章节
|
||||
- 数据模型(含字段 + 时序衰减)
|
||||
- 交互与界面草案
|
||||
- 后端实现拆解(任务清单 + 里程碑)
|
||||
129
.claude/specs/memory-intelligence/PUSH-TRIGGER-RULES.md
Normal file
129
.claude/specs/memory-intelligence/PUSH-TRIGGER-RULES.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 主动推送触发规则
|
||||
|
||||
## 概述
|
||||
|
||||
主动推送用于在合适的时间为家长提供个性化故事建议,提升使用频次与亲子阅读习惯。推送默认关闭,需家长开启并配置时间。
|
||||
|
||||
---
|
||||
|
||||
## 一、数据输入
|
||||
|
||||
- **孩子档案**: `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)
|
||||
231
.claude/specs/memory-intelligence/STORY-UNIVERSE-MODEL.md
Normal file
231
.claude/specs/memory-intelligence/STORY-UNIVERSE-MODEL.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# 故事宇宙记忆结构
|
||||
|
||||
## 概述
|
||||
|
||||
故事宇宙用于在多次故事生成中保持角色、世界观与成长成就的连续性。每个孩子档案可以拥有多个宇宙,故事生成时可选择“延续上一个故事”,系统自动带入宇宙设定。
|
||||
|
||||
---
|
||||
|
||||
## 一、数据库模型
|
||||
|
||||
### 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)
|
||||
130
.claude/specs/product-roadmap/PRODUCT-VISION.md
Normal file
130
.claude/specs/product-roadmap/PRODUCT-VISION.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# DreamWeaver 产品愿景与全流程规划
|
||||
|
||||
## 一、产品定位
|
||||
|
||||
### 1.1 愿景
|
||||
**梦语织机** - 为 3-8 岁儿童打造的 AI 故事创作平台,让每个孩子都能拥有专属的成长故事。
|
||||
|
||||
### 1.2 核心价值
|
||||
| 维度 | 价值主张 |
|
||||
|------|----------|
|
||||
| 个性化 | 基于关键词/主角定制,每个故事独一无二 |
|
||||
| 教育性 | 融入成长主题(勇气、友谊、诚实等) |
|
||||
| 沉浸感 | AI 封面 + 语音朗读,多感官体验 |
|
||||
| 亲子互动 | 家长参与创作,增进亲子关系 |
|
||||
|
||||
### 1.3 目标用户
|
||||
**主要用户:家长(25-40岁)**
|
||||
- 需求:为孩子找到有教育意义的睡前故事
|
||||
- 痛点:市面故事千篇一律,缺乏个性化
|
||||
- 场景:睡前、旅途、周末亲子时光
|
||||
|
||||
**次要用户:幼儿园/早教机构**
|
||||
- 需求:批量生成教学故事素材
|
||||
- 痛点:内容制作成本高
|
||||
|
||||
---
|
||||
|
||||
## 二、竞品分析
|
||||
|
||||
| 产品 | 优势 | 劣势 | 我们的差异化 |
|
||||
|------|------|------|--------------|
|
||||
| 凯叔讲故事 | 内容丰富、品牌知名 | 无个性化、订阅贵 | AI 个性化生成 |
|
||||
| 喜马拉雅儿童 | 海量音频、多平台 | 内容同质化 | 用户参与创作 |
|
||||
| ChatGPT | AI 能力强 | 非儿童专属、无配套 | 垂直场景优化 |
|
||||
| Midjourney | 图像质量高 | 无故事整合 | 故事+图像+音频一体 |
|
||||
|
||||
---
|
||||
|
||||
## 三、产品路线图
|
||||
|
||||
### Phase 1: MVP 完善 ✅ 已完成
|
||||
- [x] 关键词生成故事
|
||||
- [x] 故事润色增强
|
||||
- [x] AI 封面生成
|
||||
- [x] 语音朗读
|
||||
- [x] 故事收藏管理
|
||||
- [x] OAuth 登录
|
||||
- [x] 工程鲁棒性改进
|
||||
|
||||
### Phase 2: 体验增强
|
||||
| 功能 | 优先级 | 用户价值 |
|
||||
|------|--------|----------|
|
||||
| 故事编辑 | P0 | 用户可修改 AI 生成内容 |
|
||||
| 角色定制 | P0 | 孩子成为故事主角 |
|
||||
| 故事续写 | P1 | 形成系列故事 |
|
||||
| 多语言支持 | P1 | 英文故事学习 |
|
||||
| 故事分享 | P1 | 社交传播 |
|
||||
|
||||
### Phase 3: 供应商平台化
|
||||
| 功能 | 优先级 | 技术价值 |
|
||||
|------|--------|----------|
|
||||
| 供应商管理后台 | P0 | 可视化配置 AI 供应商 |
|
||||
| 适配器插件化 | P0 | 新供应商零代码接入 |
|
||||
| 供应商健康监控 | P1 | 自动故障转移 |
|
||||
| A/B 测试框架 | P1 | 供应商效果对比 |
|
||||
| 成本分析面板 | P2 | API 调用成本追踪 |
|
||||
|
||||
### Phase 4: 社区与增长
|
||||
| 功能 | 优先级 | 增长价值 |
|
||||
|------|--------|----------|
|
||||
| 故事广场 | P0 | 内容发现 |
|
||||
| 点赞/收藏 | P0 | 社区互动 |
|
||||
| 创作者主页 | P1 | 用户留存 |
|
||||
| 故事模板 | P1 | 降低创作门槛 |
|
||||
|
||||
### Phase 5: 商业化
|
||||
| 功能 | 优先级 | 商业价值 |
|
||||
|------|--------|----------|
|
||||
| 会员订阅 | P0 | 核心收入 |
|
||||
| 故事导出 | P0 | 增值服务 |
|
||||
| 实体书打印 | P1 | 高客单价 |
|
||||
| API 开放 | P2 | B 端收入 |
|
||||
|
||||
---
|
||||
|
||||
## 四、核心指标 (KPIs)
|
||||
|
||||
### 4.1 用户指标
|
||||
| 指标 | 定义 | 目标 |
|
||||
|------|------|------|
|
||||
| DAU | 日活跃用户 | Phase 2: 1000+ |
|
||||
| 留存率 | 次日/7日/30日 | 40%/25%/15% |
|
||||
| 创作转化率 | 访问→创作 | 30%+ |
|
||||
|
||||
### 4.2 业务指标
|
||||
| 指标 | 定义 | 目标 |
|
||||
|------|------|------|
|
||||
| 故事生成量 | 日均生成数 | 5000+ |
|
||||
| 分享率 | 故事被分享比例 | 10%+ |
|
||||
| 付费转化率 | 免费→付费 | 5%+ |
|
||||
|
||||
### 4.3 技术指标
|
||||
| 指标 | 定义 | 目标 |
|
||||
|------|------|------|
|
||||
| API 成功率 | 供应商调用成功率 | 99%+ |
|
||||
| 响应时间 | 故事生成 P95 | <30s |
|
||||
| 成本/故事 | 单个故事 API 成本 | <$0.05 |
|
||||
|
||||
---
|
||||
|
||||
## 五、风险与应对
|
||||
|
||||
| 风险 | 影响 | 概率 | 应对策略 |
|
||||
|------|------|------|----------|
|
||||
| AI 生成内容不当 | 高 | 中 | 内容审核 + 家长控制 + 敏感词过滤 |
|
||||
| API 成本过高 | 高 | 中 | 多供应商比价 + 缓存优化 + 分级限流 |
|
||||
| 供应商服务中断 | 高 | 低 | 多供应商冗余 + 自动故障转移 |
|
||||
| 用户增长缓慢 | 中 | 中 | 社区运营 + 分享裂变 + SEO |
|
||||
| 竞品模仿 | 低 | 高 | 快速迭代 + 深耕垂直 + 数据壁垒 |
|
||||
|
||||
---
|
||||
|
||||
## 六、下一步讨论议题
|
||||
|
||||
1. **供应商平台化架构** - 如何设计插件化的适配器系统?
|
||||
2. **Phase 2 功能优先级** - 先做哪个功能?
|
||||
3. **技术选型** - nanobanana vs flux vs 其他图像供应商?
|
||||
4. **商业模式** - 免费/付费边界在哪里?
|
||||
|
||||
请确认以上产品愿景是否符合预期,我们再深入讨论供应商平台化的技术架构。
|
||||
677
.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md
Normal file
677
.claude/specs/product-roadmap/PROVIDER-PLATFORM-RFC.md
Normal file
@@ -0,0 +1,677 @@
|
||||
# RFC: 供应商平台化架构设计
|
||||
|
||||
## 背景
|
||||
|
||||
### 当前问题
|
||||
1. **硬编码适配器**: `gemini`, `flux`, `minimax` 写死在代码中
|
||||
2. **新供应商需改代码**: 接入 nanobanana 等新供应商需要修改 `provider_router.py`
|
||||
3. **无法动态切换**: 供应商故障时需要重启服务
|
||||
4. **缺乏监控**: 不知道哪个供应商更快、更便宜、更稳定
|
||||
|
||||
### 目标
|
||||
- **零代码接入**: 通过后台配置即可接入新供应商
|
||||
- **动态切换**: 运行时切换供应商,无需重启
|
||||
- **智能路由**: 基于成本、延迟、成功率自动选择最优供应商
|
||||
- **可观测性**: 供应商健康状态、成本、性能一目了然
|
||||
|
||||
---
|
||||
|
||||
## 架构设计
|
||||
|
||||
### 1. 整体架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Admin Dashboard │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
|
||||
│ │ 供应商管理 │ │ 健康监控 │ │ 成本分析 │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Provider Router │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 路由策略: Priority → Weight → Health → Cost │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────┬───────────┬───────────┬───────────────────┐ │
|
||||
│ │ Adapter │ Adapter │ Adapter │ Adapter │ │
|
||||
│ │ Registry │ Factory │ Health │ Metrics │ │
|
||||
│ └───────────┴───────────┴───────────┴───────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
||||
│ Text Adapters │ │ Image Adapters│ │ TTS Adapters │
|
||||
├───────────────┤ ├───────────────┤ ├───────────────┤
|
||||
│ • Gemini │ │ • Flux │ │ • Minimax │
|
||||
│ • OpenAI │ │ • Nanobanana │ │ • ElevenLabs │
|
||||
│ • Claude │ │ • DALL-E │ │ • Azure TTS │
|
||||
│ • Qwen │ │ • Midjourney │ │ • Google TTS │
|
||||
└───────────────┘ └───────────────┘ └───────────────┘
|
||||
```
|
||||
|
||||
### 2. 核心组件
|
||||
|
||||
#### 2.1 Adapter 接口定义
|
||||
|
||||
```python
|
||||
# 统一适配器接口
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TypeVar, Generic
|
||||
from pydantic import BaseModel
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
class AdapterConfig(BaseModel):
|
||||
"""适配器配置基类"""
|
||||
api_key: str
|
||||
api_base: str | None = None
|
||||
model: str | None = None
|
||||
timeout_ms: int = 60000
|
||||
max_retries: int = 3
|
||||
|
||||
class BaseAdapter(ABC, Generic[T]):
|
||||
"""适配器基类"""
|
||||
|
||||
# 适配器元信息
|
||||
adapter_type: str # text / image / tts
|
||||
adapter_name: str # gemini / flux / minimax
|
||||
|
||||
def __init__(self, config: AdapterConfig):
|
||||
self.config = config
|
||||
|
||||
@abstractmethod
|
||||
async def execute(self, **kwargs) -> T:
|
||||
"""执行适配器逻辑"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def health_check(self) -> bool:
|
||||
"""健康检查"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def estimated_cost(self) -> float:
|
||||
"""预估单次调用成本 (USD)"""
|
||||
pass
|
||||
```
|
||||
|
||||
#### 2.2 适配器注册表
|
||||
|
||||
```python
|
||||
# 适配器注册表 - 支持动态注册
|
||||
class AdapterRegistry:
|
||||
"""适配器注册表"""
|
||||
|
||||
_adapters: dict[str, type[BaseAdapter]] = {}
|
||||
|
||||
@classmethod
|
||||
def register(cls, adapter_type: str, adapter_name: str):
|
||||
"""装饰器: 注册适配器"""
|
||||
def decorator(adapter_class: type[BaseAdapter]):
|
||||
key = f"{adapter_type}:{adapter_name}"
|
||||
cls._adapters[key] = adapter_class
|
||||
return adapter_class
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def get(cls, adapter_type: str, adapter_name: str) -> type[BaseAdapter] | None:
|
||||
key = f"{adapter_type}:{adapter_name}"
|
||||
return cls._adapters.get(key)
|
||||
|
||||
@classmethod
|
||||
def list_adapters(cls, adapter_type: str | None = None) -> list[str]:
|
||||
"""列出所有已注册的适配器"""
|
||||
if adapter_type:
|
||||
return [k for k in cls._adapters if k.startswith(f"{adapter_type}:")]
|
||||
return list(cls._adapters.keys())
|
||||
```
|
||||
|
||||
#### 2.3 适配器实现示例
|
||||
|
||||
```python
|
||||
# 图像适配器示例: Nanobanana
|
||||
@AdapterRegistry.register("image", "nanobanana")
|
||||
class NanobananapAdapter(BaseAdapter[str]):
|
||||
adapter_type = "image"
|
||||
adapter_name = "nanobanana"
|
||||
|
||||
async def execute(self, prompt: str, **kwargs) -> str:
|
||||
"""生成图片,返回 URL"""
|
||||
async with httpx.AsyncClient(timeout=self.config.timeout_ms / 1000) as client:
|
||||
response = await client.post(
|
||||
f"{self.config.api_base}/generate",
|
||||
json={"prompt": prompt, "model": self.config.model},
|
||||
headers={"Authorization": f"Bearer {self.config.api_key}"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()["image_url"]
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
# 简单的健康检查
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5) as client:
|
||||
response = await client.get(f"{self.config.api_base}/health")
|
||||
return response.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@property
|
||||
def estimated_cost(self) -> float:
|
||||
return 0.02 # $0.02 per image
|
||||
```
|
||||
|
||||
#### 2.4 智能路由器
|
||||
|
||||
```python
|
||||
class ProviderRouter:
|
||||
"""智能供应商路由器"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
self._health_cache: dict[str, tuple[bool, float]] = {} # adapter_key -> (healthy, last_check)
|
||||
|
||||
async def route(
|
||||
self,
|
||||
provider_type: str,
|
||||
strategy: str = "priority", # priority / cost / latency / round_robin
|
||||
**kwargs
|
||||
):
|
||||
"""路由到最优供应商"""
|
||||
providers = await self._get_enabled_providers(provider_type)
|
||||
|
||||
if not providers:
|
||||
raise ValueError(f"No {provider_type} providers configured")
|
||||
|
||||
# 按策略排序
|
||||
sorted_providers = self._sort_by_strategy(providers, strategy)
|
||||
|
||||
errors = []
|
||||
for provider in sorted_providers:
|
||||
# 检查健康状态
|
||||
if not await self._is_healthy(provider):
|
||||
continue
|
||||
|
||||
try:
|
||||
adapter = self._create_adapter(provider)
|
||||
result = await adapter.execute(**kwargs)
|
||||
|
||||
# 记录成功指标
|
||||
await self._record_metrics(provider, success=True)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"{provider.name}: {e}")
|
||||
await self._record_metrics(provider, success=False, error=str(e))
|
||||
continue
|
||||
|
||||
raise ValueError(f"All providers failed: {' | '.join(errors)}")
|
||||
|
||||
def _sort_by_strategy(self, providers: list[Provider], strategy: str) -> list[Provider]:
|
||||
if strategy == "priority":
|
||||
return sorted(providers, key=lambda p: (-p.priority, -p.weight))
|
||||
elif strategy == "cost":
|
||||
return sorted(providers, key=lambda p: self._get_estimated_cost(p))
|
||||
elif strategy == "latency":
|
||||
return sorted(providers, key=lambda p: self._get_avg_latency(p))
|
||||
else:
|
||||
return providers
|
||||
```
|
||||
|
||||
### 3. 数据模型扩展
|
||||
|
||||
```sql
|
||||
-- 供应商表 (已有,需扩展)
|
||||
ALTER TABLE providers ADD COLUMN api_key_ref VARCHAR(100); -- 密钥引用 (从 secrets 表获取)
|
||||
ALTER TABLE providers ADD COLUMN request_schema JSONB; -- 请求参数 schema
|
||||
ALTER TABLE providers ADD COLUMN response_parser VARCHAR(200); -- 响应解析规则
|
||||
|
||||
-- 供应商指标表 (新增)
|
||||
CREATE TABLE provider_metrics (
|
||||
id SERIAL PRIMARY KEY,
|
||||
provider_id VARCHAR(36) REFERENCES providers(id),
|
||||
timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
success BOOLEAN,
|
||||
latency_ms INTEGER,
|
||||
cost_usd DECIMAL(10, 6),
|
||||
error_message TEXT,
|
||||
request_id VARCHAR(100)
|
||||
);
|
||||
|
||||
-- 供应商健康状态表 (新增)
|
||||
CREATE TABLE provider_health (
|
||||
provider_id VARCHAR(36) PRIMARY KEY REFERENCES providers(id),
|
||||
is_healthy BOOLEAN DEFAULT TRUE,
|
||||
last_check TIMESTAMP WITH TIME ZONE,
|
||||
consecutive_failures INTEGER DEFAULT 0,
|
||||
last_error TEXT
|
||||
);
|
||||
|
||||
-- 密钥管理表 (新增)
|
||||
CREATE TABLE provider_secrets (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
name VARCHAR(100) UNIQUE NOT NULL,
|
||||
encrypted_value TEXT NOT NULL, -- 加密存储
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
### 4. Admin Dashboard 功能
|
||||
|
||||
#### 4.1 供应商管理
|
||||
- 供应商列表 (启用/禁用/删除)
|
||||
- 新增供应商 (选择适配器类型 + 配置参数)
|
||||
- 编辑供应商 (修改优先级/权重/超时等)
|
||||
- 测试连接 (验证 API Key 有效性)
|
||||
|
||||
#### 4.2 健康监控
|
||||
- 实时健康状态 (绿/黄/红)
|
||||
- 成功率趋势图
|
||||
- 延迟分布图
|
||||
- 故障告警配置
|
||||
|
||||
#### 4.3 成本分析
|
||||
- 按供应商统计调用量
|
||||
- 按供应商统计成本
|
||||
- 成本趋势图
|
||||
- 预算告警
|
||||
|
||||
#### 4.4 A/B 测试
|
||||
- 创建实验 (供应商 A vs B)
|
||||
- 流量分配 (50/50 或自定义)
|
||||
- 效果对比 (成功率/延迟/成本)
|
||||
|
||||
---
|
||||
|
||||
## 实现路径
|
||||
|
||||
### 阶段 1: 适配器抽象 (基础) - ✅ 已完成
|
||||
|
||||
| 任务 | 状态 | 文件 |
|
||||
|------|------|------|
|
||||
| 定义 `BaseAdapter` 接口 | ✅ | `services/adapters/base.py` |
|
||||
| 实现 `AdapterRegistry` 注册表 | ✅ | `services/adapters/registry.py` |
|
||||
| 重构 GeminiAdapter | ✅ | `services/adapters/text/gemini.py` |
|
||||
| 重构 FluxAdapter | ✅ | `services/adapters/image/flux.py` |
|
||||
| 重构 MinimaxAdapter | ✅ | `services/adapters/tts/minimax.py` |
|
||||
| 重构 `ProviderRouter` 使用新接口 | ✅ | `services/provider_router.py` |
|
||||
|
||||
### 阶段 2: 新供应商接入 (扩展) - 待开始
|
||||
1. 实现 Nanobanana 适配器
|
||||
2. 实现 OpenAI/Claude 文本适配器
|
||||
3. 实现 ElevenLabs TTS 适配器
|
||||
4. 验证零代码接入流程
|
||||
|
||||
### 阶段 3: 监控与分析 (可观测) - 待开始
|
||||
1. 实现指标收集
|
||||
2. 实现健康检查
|
||||
3. 实现成本追踪
|
||||
4. Admin Dashboard 开发
|
||||
|
||||
### 阶段 4: 智能路由 (优化) - 待开始
|
||||
1. 实现多种路由策略
|
||||
2. 实现自动故障转移
|
||||
3. 实现 A/B 测试框架
|
||||
|
||||
---
|
||||
|
||||
## 并行执行与容错设计
|
||||
|
||||
### 问题
|
||||
|
||||
当前串行流程存在两个问题:
|
||||
1. **等待时间长**: 故事(3-5s) → 封面(5-10s) → 音频(3-5s) = 总计 11-20s
|
||||
2. **单点失败**: 某一步502/超时导致整个流程失败
|
||||
|
||||
### 方案 1: 并行执行
|
||||
|
||||
```python
|
||||
async def generate_story_full(keywords: list[str]) -> StoryResult:
|
||||
# Step 1: 故事生成(必须先完成,后续依赖它)
|
||||
story = await generate_story_content(keywords)
|
||||
|
||||
# Step 2: 图片和音频并行执行
|
||||
image_task = asyncio.create_task(generate_image(story.summary))
|
||||
audio_task = asyncio.create_task(text_to_speech(story.content))
|
||||
|
||||
# 等待两者完成,互不阻塞
|
||||
image_result, audio_result = await asyncio.gather(
|
||||
image_task, audio_task,
|
||||
return_exceptions=True # 一个<E4B880><E4B8AA><EFBFBD>败不影响另一个
|
||||
)
|
||||
|
||||
return StoryResult(
|
||||
story=story,
|
||||
image_url=image_result if not isinstance(image_result, Exception) else None,
|
||||
audio_url=audio_result if not isinstance(audio_result, Exception) else None,
|
||||
errors={
|
||||
"image": str(image_result) if isinstance(image_result, Exception) else None,
|
||||
"audio": str(audio_result) if isinstance(audio_result, Exception) else None,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**时间对比:**
|
||||
```
|
||||
串行: 3s + 8s + 4s = 15s
|
||||
并行: 3s + max(8s, 4s) = 11s (节省 27%)
|
||||
```
|
||||
|
||||
### 方案 2: 部分成功处理
|
||||
|
||||
**核心原则: 部分成功 > 全部失败**
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class StoryResult:
|
||||
story: Story # 核心,必须成功
|
||||
image_url: str | None = None # 增强,可降级
|
||||
audio_url: str | None = None # 增强,可降级
|
||||
errors: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def is_complete(self) -> bool:
|
||||
return self.image_url is not None and self.audio_url is not None
|
||||
|
||||
@property
|
||||
def failed_components(self) -> list[str]:
|
||||
return [k for k, v in self.errors.items() if v is not None]
|
||||
```
|
||||
|
||||
**降级策略:**
|
||||
|
||||
| 组件 | 失败时降级方案 | 用户体验 |
|
||||
|------|---------------|---------|
|
||||
| 故事 | 无降级,整体失败 | 显示错误,提示重试 |
|
||||
| 封面 | 使用默认封面图 | 显示占位图 + "重新生成"按钮 |
|
||||
| 音频 | 不生成音频 | 隐藏播放按钮 + "生成语音"按钮 |
|
||||
|
||||
### 方案 3: 流式返回 (SSE)
|
||||
|
||||
**为什么用 SSE:**
|
||||
- 用户无需等待全部完成
|
||||
- 每完成一步立即展示
|
||||
- 比 WebSocket 简单,HTTP 兼容性好
|
||||
|
||||
**后端实现:**
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/api/generate/stream")
|
||||
async def generate_story_stream(
|
||||
request: GenerateRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
async def event_generator():
|
||||
# 1. 立即返回任务ID
|
||||
story_id = str(uuid.uuid4())
|
||||
yield {"event": "started", "data": json.dumps({"story_id": story_id})}
|
||||
|
||||
# 2. 生成故事
|
||||
try:
|
||||
story = await generate_story_content(request.keywords)
|
||||
yield {"event": "story_ready", "data": json.dumps({
|
||||
"title": story.title,
|
||||
"content": story.content,
|
||||
})}
|
||||
except Exception as e:
|
||||
yield {"event": "story_failed", "data": json.dumps({"error": str(e)})}
|
||||
return
|
||||
|
||||
# 3. 并行生成图片和音频
|
||||
async def gen_image():
|
||||
try:
|
||||
url = await generate_image(story.summary)
|
||||
yield {"event": "image_ready", "data": json.dumps({"image_url": url})}
|
||||
except Exception as e:
|
||||
yield {"event": "image_failed", "data": json.dumps({"error": str(e)})}
|
||||
|
||||
async def gen_audio():
|
||||
try:
|
||||
url = await text_to_speech(story.content)
|
||||
yield {"event": "audio_ready", "data": json.dumps({"audio_url": url})}
|
||||
except Exception as e:
|
||||
yield {"event": "audio_failed", "data": json.dumps({"error": str(e)})}
|
||||
|
||||
# 并行执行,逐个yield结果
|
||||
tasks = [gen_image(), gen_audio()]
|
||||
for coro in asyncio.as_completed([t.__anext__() for t in tasks]):
|
||||
result = await coro
|
||||
yield result
|
||||
|
||||
yield {"event": "complete", "data": json.dumps({"story_id": story_id})}
|
||||
|
||||
return EventSourceResponse(event_generator())
|
||||
```
|
||||
|
||||
**前端实现:**
|
||||
|
||||
```typescript
|
||||
const eventSource = new EventSource('/api/generate/stream', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ keywords }),
|
||||
});
|
||||
|
||||
eventSource.addEventListener('started', (e) => {
|
||||
const { story_id } = JSON.parse(e.data);
|
||||
showLoading('正在创作故事...');
|
||||
});
|
||||
|
||||
eventSource.addEventListener('story_ready', (e) => {
|
||||
const { title, content } = JSON.parse(e.data);
|
||||
renderStory(title, content);
|
||||
showLoading('正在生成封面和语音...');
|
||||
});
|
||||
|
||||
eventSource.addEventListener('image_ready', (e) => {
|
||||
const { image_url } = JSON.parse(e.data);
|
||||
renderCover(image_url);
|
||||
});
|
||||
|
||||
eventSource.addEventListener('image_failed', (e) => {
|
||||
showRetryButton('image');
|
||||
});
|
||||
|
||||
eventSource.addEventListener('audio_ready', (e) => {
|
||||
const { audio_url } = JSON.parse(e.data);
|
||||
enablePlayButton(audio_url);
|
||||
});
|
||||
|
||||
eventSource.addEventListener('complete', () => {
|
||||
eventSource.close();
|
||||
hideLoading();
|
||||
});
|
||||
```
|
||||
|
||||
**用户体验时间线:**
|
||||
```
|
||||
0s → 显示"正在创作..."
|
||||
3s → 故事文本渲染,显示"正在生成封面和语音..."
|
||||
3-7s → 音频就绪,播放按钮可用
|
||||
3-11s → 封面就绪,图片显示
|
||||
11s → 完成
|
||||
```
|
||||
|
||||
### 方案 4: 断点续传 (可选)
|
||||
|
||||
适用于网络不稳定场景,支持刷新页面后继续:
|
||||
|
||||
```python
|
||||
class StoryWorkflowState(Base):
|
||||
__tablename__ = "story_workflow_states"
|
||||
|
||||
story_id: Mapped[str] = mapped_column(String(36), primary_key=True)
|
||||
status: Mapped[str] = mapped_column(String(20)) # pending/story_done/image_done/audio_done/complete
|
||||
story_content: Mapped[str | None] = mapped_column(Text)
|
||||
image_url: Mapped[str | None] = mapped_column(String(500))
|
||||
audio_url: Mapped[str | None] = mapped_column(String(500))
|
||||
last_error: Mapped[str | None] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, onupdate=datetime.utcnow)
|
||||
|
||||
async def resume_workflow(story_id: str) -> StoryResult:
|
||||
state = await get_workflow_state(story_id)
|
||||
|
||||
if state.status == "story_done":
|
||||
# 从图片+音频生成继续
|
||||
return await generate_image_and_audio(state)
|
||||
elif state.status == "image_done":
|
||||
# 只需要生成音频
|
||||
return await generate_audio_only(state)
|
||||
elif state.status == "audio_done":
|
||||
# 只需要生成图片
|
||||
return await generate_image_only(state)
|
||||
else:
|
||||
return StoryResult.from_state(state)
|
||||
```
|
||||
|
||||
### 推荐实现顺序
|
||||
|
||||
| 优先级 | 方案 | 收益 | 复杂度 | 状态 |
|
||||
|--------|------|------|--------|------|
|
||||
| P0 | 并行执行 | 节省 27% 时间 | 低 | ✅ 已完成 |
|
||||
| P0 | 部分成功 | 提升容错性 | 低 | ✅ 已完成 |
|
||||
| P1 | SSE 流式返回 | 体验大幅提升 | 中 | 待开始 |
|
||||
| P2 | 断点续传 | 极端场景保障 | 高 | 待开始 |
|
||||
|
||||
**P0 实现详情:**
|
||||
- 新增 API: `POST /api/generate/full`
|
||||
- 文件: `api/stories.py:113-189`
|
||||
- 响应模型: `FullStoryResponse` (含 `errors` 字段标识失败组件)
|
||||
|
||||
---
|
||||
|
||||
## 待决策清单
|
||||
|
||||
> **使用说明**: 在每个决策的 `[ ]` 中填入你的选择(如 `[x]` 或 `[B]`),确认后删除未选中的选项。
|
||||
|
||||
---
|
||||
|
||||
### 决策 1: 适配器配置存储
|
||||
|
||||
**问题**: 适配器的配置信息(API地址、模型名、超时等)存在哪里?
|
||||
|
||||
| 选项 | 方案 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| [ ] A | 全部存数据库 | 完全动态,运行时可改 | 需要管理界面,初始化复杂 |
|
||||
| [ ] B | 代码定义 + DB配置 | 平衡,核心逻辑在代码,参数可调 | 新适配器仍需改代码 |
|
||||
| [ ] C | 配置文件 (YAML/JSON) | 简单,版本控制友好 | 改配置需重启 |
|
||||
|
||||
**推荐**: B(代码定义适配器类,DB存储启用状态/优先级/API Key引用)
|
||||
|
||||
---
|
||||
|
||||
### 决策 2: 密钥管理
|
||||
|
||||
**问题**: API Key 等敏感信息如何存储?
|
||||
|
||||
| 选项 | 方案 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| [ ] A | 环境变量 | 简单,当前方式 | 多供应商时env膨胀,改key需重启 |
|
||||
| [ ] B | 数据库加密存储 | 动态管理,支持多key | 需要加密方案,安全风险 |
|
||||
| [ ] C | 外部密钥服务 (Vault/AWS Secrets) | 企业级安全 | 复杂,增加依赖 |
|
||||
|
||||
**推荐**: A(当前阶段),后期可迁移到B
|
||||
|
||||
---
|
||||
|
||||
### 决策 3: 图像供应商优先级
|
||||
|
||||
**问题**: 接入多个图像供应商后,默认使用哪个?
|
||||
|
||||
| 选项 | 供应商 | 特点 | 预估成本 |
|
||||
|------|--------|------|----------|
|
||||
| [ ] 1 | Nanobanana | 新兴,据说效果好 | 待调研 |
|
||||
| [ ] 2 | Flux (当前) | 稳定,已接入 | ~$0.03/张 |
|
||||
| [ ] 3 | DALL-E 3 | OpenAI出品,质量高 | ~$0.04/张 |
|
||||
| [ ] 4 | Midjourney | 艺术风格强 | API受限 |
|
||||
|
||||
**推荐**: 先调研Nanobanana,效果好则替换Flux
|
||||
|
||||
---
|
||||
|
||||
### 决策 4: 文本供应商优先级
|
||||
|
||||
**问题**: 故事生成使用哪个LLM?
|
||||
|
||||
| 选项 | 供应商 | 特点 | 预估成本 |
|
||||
|------|--------|------|----------|
|
||||
| [ ] 1 | Gemini (当前) | 免费额度大,中文好 | 免费/低成本 |
|
||||
| [ ] 2 | OpenAI GPT-4o | 质量稳定 | ~$0.01/1K tokens |
|
||||
| [ ] 3 | Claude | 创意写作强 | ~$0.015/1K tokens |
|
||||
| [ ] 4 | Qwen (通义千问) | 国内,中文优化 | 待调研 |
|
||||
|
||||
**推荐**: Gemini为主,OpenAI备用
|
||||
|
||||
---
|
||||
|
||||
### 决策 5: TTS供应商优先级
|
||||
|
||||
**问题**: 语音合成使用哪个服务?
|
||||
|
||||
| 选项 | 供应商 | 特点 | 预估成本 |
|
||||
|------|--------|------|----------|
|
||||
| [ ] 1 | Minimax (当前) | 中文效果好,已接入 | ~$0.01/1K字符 |
|
||||
| [ ] 2 | ElevenLabs | 英文最佳,多语言 | ~$0.03/1K字符 |
|
||||
| [ ] 3 | Azure TTS | 稳定,多语言 | ~$0.016/1K字符 |
|
||||
| [ ] 4 | Google TTS | 便宜 | ~$0.004/1K字符 |
|
||||
|
||||
**推荐**: Minimax为主(中文场景)
|
||||
|
||||
---
|
||||
|
||||
### 决策 6: Admin Dashboard 技术栈
|
||||
|
||||
**问题**: 供应商管理后台用什么技术?
|
||||
|
||||
| 选项 | 方案 | 优点 | 缺点 |
|
||||
|------|------|------|------|
|
||||
| [ ] A | 复用 Vue 前端 | 技术栈统一,复用组件 | 需要自己写UI |
|
||||
| [ ] B | React Admin | 成熟的Admin框架 | 引入新技术栈 |
|
||||
| [ ] C | 现成方案 (AdminJS/Retool) | 开发快 | 定制性差,可能收费 |
|
||||
|
||||
**推荐**: A(在现有Vue项目中加 `/admin` 路由)
|
||||
|
||||
---
|
||||
|
||||
### 决策 7: Phase 2 功能优先级
|
||||
|
||||
**问题**: 体验增强阶段先做哪个功能?
|
||||
|
||||
| 选项 | 功能 | 用户价值 | 开发复杂度 |
|
||||
|------|------|----------|------------|
|
||||
| [ ] 1 | 故事编辑 | 高(用户可修改AI内容) | 中 |
|
||||
| [ ] 2 | 角色定制 | 高(孩子成为主角) | 低 |
|
||||
| [ ] 3 | 故事分享 | 高(增长引擎) | 中 |
|
||||
| [ ] 4 | 故事续写 | 中(延长使用时长) | 中 |
|
||||
|
||||
**推荐**: 2 → 1 → 3 → 4(角色定制最快出效果)
|
||||
|
||||
---
|
||||
|
||||
### 决策 8: 并行与容错实现顺序
|
||||
|
||||
**问题**: 并行执行、部分成功、SSE、断点续传,先做哪些?
|
||||
|
||||
| 选项 | 方案 | 说明 |
|
||||
|------|------|------|
|
||||
| [ ] A | P0先做 | 先实现并行+部分成功,快速见效 |
|
||||
| [ ] B | P0+P1一起 | 并行+部分成功+SSE,体验完整 |
|
||||
| [ ] C | 只做SSE | 跳过简单方案,直接上流式 |
|
||||
|
||||
**推荐**: A(先P0,验证后再做SSE)
|
||||
|
||||
---
|
||||
|
||||
## 确认后删除此区块
|
||||
|
||||
确认所有决策后,可以删除未选中的选项,保留最终方案作为实现依据。
|
||||
169
.claude/specs/product-roadmap/ROADMAP.md
Normal file
169
.claude/specs/product-roadmap/ROADMAP.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# DreamWeaver 产品路线图
|
||||
|
||||
## 产品愿景
|
||||
|
||||
**梦语织机** - 为 3-8 岁儿童打造的 AI 故事创作平台,让每个孩子都能拥有专属的成长故事。
|
||||
|
||||
### 核心价值主张
|
||||
- **个性化**: 基于关键词生成独一无二的故事
|
||||
- **教育性**: 融入成长主题(勇气、友谊、诚实等)
|
||||
- **沉浸感**: AI 封面 + 语音朗读,多感官体验
|
||||
- **亲子互动**: 家长参与创作,增进亲子关系
|
||||
|
||||
---
|
||||
|
||||
## 用户画像
|
||||
|
||||
### 主要用户:家长(25-40岁)
|
||||
- **需求**: 为孩子找到有教育意义的睡前故事
|
||||
- **痛点**: 市面故事千篇一律,缺乏个性化
|
||||
- **场景**: 睡前、旅途、周末亲子时光
|
||||
|
||||
### 次要用户:幼儿园/早教机构
|
||||
- **需求**: 批量生成教学故事素材
|
||||
- **痛点**: 内容制作成本高
|
||||
- **场景**: 课堂教学、活动策划
|
||||
|
||||
---
|
||||
|
||||
## 功能规划
|
||||
|
||||
### Phase 1: MVP 完善(当前)
|
||||
> 目标:核心体验闭环,用户可完整使用
|
||||
|
||||
| 功能 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 关键词生成故事 | ✅ 已完成 | 输入关键词,AI 生成故事 |
|
||||
| 故事润色增强 | ✅ 已完成 | 用户提供草稿,AI 润色 |
|
||||
| AI 封面生成 | ✅ 已完成 | 根据故事生成插画 |
|
||||
| 语音朗读 | ✅ 已完成 | TTS 朗读故事 |
|
||||
| 故事收藏管理 | ✅ 已完成 | 保存、查看、删除 |
|
||||
| OAuth 登录 | ✅ 已完成 | GitHub/Google 登录 |
|
||||
|
||||
### Phase 2: 体验增强
|
||||
> 目标:提升用户粘性,增加互动性
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| **故事编辑** | P0 | 用户可修改 AI 生成的故事内容 |
|
||||
| **角色定制** | P0 | 输入孩子姓名/性别,成为故事主角 |
|
||||
| **故事续写** | P1 | 基于已有故事继续创作下一章 |
|
||||
| **多语言支持** | P1 | 英文故事生成(已有 i18n 基础) |
|
||||
| **故事分享** | P1 | 生成分享图片/链接 |
|
||||
| **收藏夹/标签** | P2 | 故事分类管理 |
|
||||
|
||||
### Phase 3: 社区与增长
|
||||
> 目标:构建用户社区,实现自然增长
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| **故事广场** | P0 | 公开优质故事,用户可浏览 |
|
||||
| **点赞/收藏** | P0 | 社区互动基础 |
|
||||
| **故事模板** | P1 | 预设故事框架(冒险/友谊/成长) |
|
||||
| **创作者主页** | P1 | 展示用户创作的故事集 |
|
||||
| **评论系统** | P2 | 用户交流反馈 |
|
||||
|
||||
### Phase 4: 商业化
|
||||
> 目标:建立可持续商业模式
|
||||
|
||||
| 功能 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| **会员订阅** | P0 | 免费/基础/高级三档 |
|
||||
| **故事导出** | P0 | PDF/电子书格式导出 |
|
||||
| **实体书打印** | P1 | 对接印刷服务,生成实体绘本 |
|
||||
| **API 开放** | P2 | 为 B 端客户提供 API |
|
||||
| **企业版** | P2 | 幼儿园/早教机构定制 |
|
||||
|
||||
---
|
||||
|
||||
## 技术架构演进
|
||||
|
||||
### 当前架构 (Phase 1)
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Vue 3 │────▶│ FastAPI │────▶│ PostgreSQL │
|
||||
│ Frontend │ │ Backend │ │ (Neon) │
|
||||
└─────────────┘ └──────┬──────┘ └─────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
▼ ▼ ▼
|
||||
┌────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Gemini │ │ Minimax │ │ Flux │
|
||||
│ (Text) │ │ (TTS) │ │ (Image) │
|
||||
└────────┘ └─────────┘ └─────────┘
|
||||
```
|
||||
|
||||
### Phase 2 架构演进
|
||||
```
|
||||
新增组件:
|
||||
- Redis: 缓存 + 会话 + Rate Limit
|
||||
- Celery: 异步任务队列(图片/音频生成)
|
||||
- S3/OSS: 静态资源存储
|
||||
```
|
||||
|
||||
### Phase 3 架构演进
|
||||
```
|
||||
新增组件:
|
||||
- Elasticsearch: 故事全文搜索
|
||||
- CDN: 静态资源加速
|
||||
- 消息队列: 社区通知推送
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 里程碑规划
|
||||
|
||||
### M1: MVP 完善 ✅
|
||||
- [x] 核心功能闭环
|
||||
- [x] 工程鲁棒性改进
|
||||
- [x] 测试覆盖
|
||||
|
||||
### M2: 体验增强
|
||||
- [ ] 故事编辑功能
|
||||
- [ ] 角色定制(孩子成为主角)
|
||||
- [ ] 故事续写
|
||||
- [ ] 多语言支持
|
||||
- [ ] 分享功能
|
||||
|
||||
### M3: 社区上线
|
||||
- [ ] 故事广场
|
||||
- [ ] 用户互动(点赞/收藏)
|
||||
- [ ] 创作者主页
|
||||
|
||||
### M4: 商业化
|
||||
- [ ] 会员体系
|
||||
- [ ] 故事导出
|
||||
- [ ] 实体书打印
|
||||
|
||||
---
|
||||
|
||||
## 竞品分析
|
||||
|
||||
| 产品 | 优势 | 劣势 | 我们的差异化 |
|
||||
|------|------|------|--------------|
|
||||
| 凯叔讲故事 | 内容丰富、品牌知名 | 无个性化、订阅贵 | AI 个性化生成 |
|
||||
| 喜马拉雅儿童 | 海量音频、多平台 | 内容同质化 | 用户参与创作 |
|
||||
| ChatGPT | AI 能力强 | 非儿童专属、无配套 | 垂直场景优化 |
|
||||
|
||||
---
|
||||
|
||||
## 风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对策略 |
|
||||
|------|------|----------|
|
||||
| AI 生成内容不当 | 高 | 内容审核 + 家长控制 |
|
||||
| API 成本过高 | 中 | 缓存优化 + 分级限流 |
|
||||
| 用户增长缓慢 | 中 | 社区运营 + 分享裂变 |
|
||||
| 竞品模仿 | 低 | 快速迭代 + 深耕垂直 |
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
**Phase 2 优先实现功能:**
|
||||
|
||||
1. **故事编辑** - 用户体验核心痛点
|
||||
2. **角色定制** - 差异化竞争力
|
||||
3. **故事分享** - 自然增长引擎
|
||||
|
||||
是否需要我为这些功能生成详细的技术规格文档?
|
||||
72
.claude/specs/robustness-improvement/dev-plan.md
Normal file
72
.claude/specs/robustness-improvement/dev-plan.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# DreamWeaver 工程鲁棒性改进计划
|
||||
|
||||
## 概述
|
||||
本计划旨在提升 DreamWeaver 项目的工程质量,包括测试覆盖、稳定性、可观测性等方面。
|
||||
|
||||
## 任务列表
|
||||
|
||||
### P0 - 关键问题修复
|
||||
|
||||
#### Task-1: 修复 Rate Limit 内存泄漏
|
||||
- **文件**: `backend/app/api/stories.py`
|
||||
- **问题**: `_request_log` 全局字典无清理机制,长期运行内存无限增长
|
||||
- **方案**: 添加 TTL 自动清理机制,使用 `cachetools.TTLCache`
|
||||
- **测试**: 验证过期条目自动清理
|
||||
|
||||
#### Task-2: 添加核心 API 测试
|
||||
- **文件**: `backend/tests/` (新建)
|
||||
- **范围**:
|
||||
- `test_auth.py`: OAuth 流程、session 验证
|
||||
- `test_stories.py`: 故事 CRUD、rate limit
|
||||
- **目标**: 核心路径覆盖率 ≥80%
|
||||
|
||||
### P1 - 稳定性提升
|
||||
|
||||
#### Task-3: 添加 API 重试机制
|
||||
- **文件**: `backend/app/services/gemini.py`, `minimax.py`, `drawing.py`
|
||||
- **方案**: 使用 `tenacity` 库,指数退避重试
|
||||
- **配置**: 最多 3 次重试,初始间隔 1s
|
||||
|
||||
#### Task-4: 添加结构化日志
|
||||
- **文件**: `backend/app/core/logging.py` (新建), 各 service 文件
|
||||
- **方案**: 使用 `structlog`,JSON 格式输出
|
||||
- **埋点**: API 调用、错误、性能指标
|
||||
|
||||
### P2 - 代码优化
|
||||
|
||||
#### Task-5: 重构 Provider Router
|
||||
- **文件**: `backend/app/services/provider_router.py`
|
||||
- **问题**: 三个函数重复代码
|
||||
- **方案**: 抽象通用 failover 函数
|
||||
|
||||
#### Task-6: 配置外部化
|
||||
- **文件**: `backend/app/core/config.py`, `backend/app/services/gemini.py`
|
||||
- **问题**: 模型名硬编码
|
||||
- **方案**: 移至环境变量配置
|
||||
|
||||
#### Task-7: 修复脆弱的 URL 解析
|
||||
- **文件**: `backend/app/services/drawing.py`
|
||||
- **问题**: 字符串切片解析 URL 不可靠
|
||||
- **方案**: 使用正则表达式
|
||||
|
||||
## 依赖关系
|
||||
```
|
||||
Task-1 (独立)
|
||||
Task-2 (独立,但需要 Task-1 完成后验证)
|
||||
Task-3 (独立)
|
||||
Task-4 (独立)
|
||||
Task-5 (独立)
|
||||
Task-6 (独立)
|
||||
Task-7 (独立)
|
||||
```
|
||||
|
||||
## 新增依赖
|
||||
```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: 覆盖率报告
|
||||
httpx[http2] # Task-2: 测试 mock
|
||||
303
.claude/ui-refactor-plan.md
Normal file
303
.claude/ui-refactor-plan.md
Normal file
@@ -0,0 +1,303 @@
|
||||
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. 移除 <style scoped> 中的所有自定义样式
|
||||
2. 登录表单使用 BaseCard + BaseInput + BaseButton
|
||||
3. Provider 表单使用 BaseCard + BaseInput + BaseSelect + BaseButton
|
||||
4. 表格使用 Tailwind 样式:divide-y divide-gray-200,hover:bg-gray-50
|
||||
5. 操作按钮使用 BaseButton variant="ghost"
|
||||
6. 整体布局使用 .glass 背景
|
||||
7. 添加页面标题使用 .gradient-text
|
||||
验收: 与主应用风格一致,无原生 HTML 样式
|
||||
|
||||
TASK-018 [x]: 重构 ChildProfiles.vue 页面
|
||||
|
||||
文件: frontend/src/views/ChildProfiles.vue
|
||||
操作:
|
||||
|
||||
1. 检查并替换所有 emoji 为 Heroicons
|
||||
2. 使用 BaseButton, BaseCard, BaseInput 等统一组件
|
||||
3. 使用 EmptyState 处理空状态
|
||||
4. 使用 LoadingSpinner 处理加载状态
|
||||
验收: 页面无 emoji,组件统一
|
||||
|
||||
TASK-019 [x]: 重构 ChildProfileDetail.vue 页面
|
||||
|
||||
文件: frontend/src/views/ChildProfileDetail.vue
|
||||
操作: 同 TASK-018
|
||||
验收: 页面无 emoji,组件统一
|
||||
|
||||
TASK-020 [x]: 重构 Universes.vue 页面
|
||||
|
||||
文件: frontend/src/views/Universes.vue
|
||||
操作: 同 TASK-018
|
||||
验收: 页面无 emoji,组件统一
|
||||
|
||||
TASK-021 [x]: 重构 UniverseDetail.vue 页面
|
||||
|
||||
文件: frontend/src/views/UniverseDetail.vue
|
||||
操作: 同 TASK-018
|
||||
验收: 页面无 emoji,组件统一
|
||||
|
||||
---
|
||||
|
||||
阶段四:优化与收尾
|
||||
|
||||
TASK-022 [x]: 添加深色模式支持(可选)
|
||||
|
||||
文件: frontend/tailwind.config.js, frontend/src/style.css
|
||||
操作:
|
||||
|
||||
1. 在 tailwind.config.js 添加 darkMode: 'class'
|
||||
2. 为 .glass, .btn-magic 等添加 dark: 变体
|
||||
3. 在 NavBar 添加主题切换按钮
|
||||
验收: 点击切换按钮,整体配色切换
|
||||
|
||||
TASK-023 [x]: 添加 prefers-reduced-motion 支持
|
||||
|
||||
文件: frontend/src/style.css
|
||||
操作: 为所有动画添加媒体查询
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.animate-float, .card-hover, .btn-magic { animation: none; transition: none; }
|
||||
}
|
||||
验收: 系统设置"减少动态效果"时,动画停止
|
||||
|
||||
TASK-024 [x]: 性能优化 - 减少 backdrop-filter
|
||||
|
||||
文件: frontend/src/style.css
|
||||
操作:
|
||||
|
||||
1. 将 .glass 的 backdrop-filter: blur(20px) 改为 blur(10px)
|
||||
2. 移除嵌套 .glass 元素的 backdrop-filter
|
||||
验收: 页面滚动更流畅,尤其在移动端
|
||||
|
||||
---
|
||||
|
||||
执行顺序建议
|
||||
|
||||
阶段一 (TASK-001 ~ 003) → 阶段二 (TASK-004 ~ 012) → 阶段三 (TASK-013 ~ 021) → 阶段四 (TASK-022 ~ 024)
|
||||
|
||||
每个任务完成后运行 npm run build 确保无类型错误。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user