本文档用于从产品与工程两条线理解本项目。已有 doc/项目说明.md 偏实现说明,本文偏「为什么做、做成什么、模块如何协作、学习时应该先看哪里」。
AI TodoList 是一个基于 Next.js 的待办与 AI 能力实验台。它把任务管理、提示词优化、流式对话、多模态生成、Vercel AI SDK 示例集中到一个页面的五个 Tab 中,方便学习「前端交互 + API 路由 + 数据库 + 大模型调用」的完整链路。
提供一个可运行的 TodoList 应用,支持任务增删改查、父子任务层级和 AI 任务拆解。
演示多个 AI 应用形态:普通调用、SSE 流式返回、AI SDK 流式协议、工具调用、多模态工具、记忆管理。
通过 Pages Router 与 App Router 混用,让学习者对比 Next.js 两套路由/API 写法。
将 Supabase、OpenAI 兼容网关、硅基流动、语音转写、TTS、视频生成等外部服务接入方式集中展示。
不是生产级多用户 Todo 系统,目前没有登录、租户隔离、权限模型。
不是统一 AI 网关平台,多个模块各自使用不同模型来源,主要用于教学对照。
不是持久化记忆系统,多模态长期记忆当前使用进程内 Map,服务重启后清空。
不是完整移动端产品,页面以桌面学习和本地调试为主。
用户
诉求
项目价值
前端学习者
学习 React/Next.js 项目组织
通过五个 Tab 理解组件、状态、样式和 API 调用
AI 应用开发者
学习大模型接入模式
对比普通 HTTP、SSE、AI SDK、Tools、图片/视频工具
全栈开发者
学习数据库与 API 契约
通过 Supabase tasks 表和 Pages API 理解 CRUD
提示词学习者
学习提示词优化流程
查看 prompts/元提示词.md 如何被后端读取并调用模型
pages/index.js:理解首页入口和五个 Tab 如何挂载。
components/TasksTab.js + pages/api/tasks/*:先掌握最常规的前后端 CRUD。
lib/supabase.ts + supabase-schema.sql:理解数据表、RLS 和 Supabase 客户端。
components/PromptsTab.tsx + pages/api/prompts/optimize.ts:学习一次性模型调用。
components/StreamChatTab.tsx + pages/api/chat/stream.ts:学习自定义 SSE 流式协议。
components/AISDKDemo.tsx + app/api/ai-sdk/*:学习 Vercel AI SDK 的标准写法。
components/MultimodalChat.tsx + app/api/ai-sdk/multimodal/route.ts + lib/memory-manager.ts:最后看业务复杂度最高的多模态与记忆模块。
Tab
前端组件
用户可做什么
后端能力
待办事项
components/TasksTab.js
创建、完成、删除任务,AI 拆解子任务
Supabase CRUD,DeepSeek 任务拆解
提示词生成
components/PromptsTab.tsx
输入原始提示词,得到优化版本并复制
读取元提示词模板,调用 DeepSeek
流式AI
components/StreamChatTab.tsx
多轮对话,查看流式输出和 token 成本
硅基流动 SSE,自定义 start/chunk/done 协议
多模态
components/MultimodalChat.tsx
文本对话,生成图片/视频,语音输入,TTS 播放
AI SDK Tools,图片/视频生成,短期摘要,长期画像
AI SDK
components/AISDKDemo.tsx
体验 useChat、useCompletion、Tools 示例
App Router + Vercel AI SDK
作为用户,我可以新增一个待办任务,避免任务散落。
作为用户,我可以勾选任务完成状态,并让父任务完成时直接子任务同步完成。
作为用户,我可以让 AI 把一个大任务拆解成 3-5 个可执行子任务。
作为用户,我可以删除任务;删除父任务时,数据库通过外键级联删除子任务。
title 必填,最长 200 字符。
status 只能是 pending 或 completed。
priority 只能是 low、medium、high,默认 medium。
parent_id 关联同表任务,禁止任务成为自己的子任务,接口层也防止直接循环引用。
AI 拆解必须返回至少 3 个子任务,最多保留 5 个。
用户输入一个原始想法或粗糙提示词。
系统读取 prompts/元提示词.md,将用户输入替换到模板占位符中。
后端调用 DeepSeek 兼容接口,返回优化后的结构化提示词。
前端展示结果,并支持复制到剪贴板。
userPrompt 必填且不能为空字符串。
后端清理模型返回中可能出现的 markdown 代码块标记。
没有 DEEPSEEK_API_KEY 时直接返回配置错误。
用户输入消息后,前端把历史消息一起提交给 /api/chat/stream。
后端通过硅基流动 OpenAI 兼容接口开启 stream: true。
后端以 SSE 返回 start、chunk、done、error。
前端逐字消费缓冲区,展示接近打字机效果的流式回复。
结束时展示输入、输出、总 token 和估算费用。
SSE 响应头需要 text/event-stream、no-cache、keep-alive。
Next 配置中 compress: false,用于降低流式场景被压缩缓冲影响的概率。
接口通过 p-retry 处理 429 限流重试。
token 统计使用 js-tiktoken,价格配置在 lib/config.ts。
子功能
前端 Hook
API
返回协议
Chat
useChat
/api/ai-sdk/chat
toUIMessageStreamResponse()
Completion
useCompletion
/api/ai-sdk/completion
toTextStreamResponse()
Tools
useChat
/api/ai-sdk/tools
toUIMessageStreamResponse()
Chat 保留多轮消息,后端过滤出 user/assistant 消息。
Completion 是单次 prompt,不依赖历史。
Tools 演示包含当前时间、数学计算、模拟天气。
Tools 中数学计算使用 eval,只适合教学演示,不建议直接用于生产。
用户可以进行普通文本聊天。
用户明确要求生成图片时,模型调用 generateImage 工具。
用户明确要求生成视频时,模型调用 generateVideo 工具。
用户可以录音,前端上传 WebM 到 /api/speech-to-text 转写。
助手回复完成后,前端可调用 /api/text-to-speech 播放语音。
后端在对话结束后旁路提取用户画像,用于之后个性化回答。
短期记忆:compressHistory 使用滑动窗口,超过 MAX_MESSAGES = 10 后压缩较早消息。
长期记忆:extractUserProfile 从对话中抽取职业、技术栈、偏好、兴趣、沟通风格和目标。
画像存储:当前是内存 Map,适合本地教学,不适合生产。
Prompt 注入:buildFullSystemPrompt 将基础系统提示、历史摘要、用户画像合并后传给模型。
flowchart TB
User["浏览器用户"] --> Home["pages/index.js 五个 Tab"]
Home --> Tasks["TasksTab"]
Home --> Prompt["PromptsTab"]
Home --> Stream["StreamChatTab"]
Home --> Multi["MultimodalChat"]
Home --> SDK["AISDKDemo"]
Tasks --> TasksAPI["pages/api/tasks/*"]
Prompt --> PromptAPI["pages/api/prompts/optimize"]
Stream --> StreamAPI["pages/api/chat/stream"]
Multi --> MultiAPI["app/api/ai-sdk/multimodal"]
Multi --> SpeechAPI["pages/api/speech-to-text"]
Multi --> TTSAPI["pages/api/text-to-speech"]
SDK --> SDKAPI["app/api/ai-sdk/chat|completion|tools"]
TasksAPI --> Supabase["Supabase tasks 表"]
TasksAPI --> DeepSeek["DeepSeek/OpenAI 兼容接口"]
PromptAPI --> DeepSeek
StreamAPI --> SiliconFlow["硅基流动"]
MultiAPI --> DeepSeek
MultiAPI --> Memory["memory-manager 进程内记忆"]
SpeechAPI --> OpenAIWhisper["OpenAI Whisper"]
TTSAPI --> Minimax["Minimax TTS"]
Loading
路由类型
目录
主要用途
Pages Router 页面
pages/index.js
首页和 Tab 容器
Pages API
pages/api/*
传统 REST、SSE、multipart、音频流接口
App Router API
app/api/*/route.ts
Vercel AI SDK 流式接口
App Layout
app/layout.tsx
满足 App Router 结构要求,页面仍由 pages 提供
文件
职责
lib/config.ts
环境变量、模型网关、价格、视频策略配置
lib/supabase.ts
Supabase 客户端初始化
lib/ai/models.ts
AI SDK 模型实例,如 deepseek、nanobanana、veo3
lib/memory-manager.ts
短期摘要、长期画像、System Prompt 拼装
types/task.ts
Task 类型、请求类型、通用 API 响应类型
supabase-schema.sql
tasks 表结构、索引、RLS 策略
字段
类型
说明
id
BIGSERIAL
主键
title
TEXT
任务标题,必填
description
TEXT
任务描述,可为空
status
TEXT
pending 或 completed
priority
TEXT
low、medium、high
parent_id
BIGINT
父任务 ID,自关联,删除父任务时级联删除
created_at
TIMESTAMPTZ
创建时间
索引:status、priority、parent_id、created_at DESC。
RLS:已启用行级安全。
当前策略:开发阶段允许所有操作。
生产建议:加入登录后按 user_id 隔离数据,并收紧 RLS 策略。
多数 Pages API 使用统一格式:
{
success: boolean
data: T | null
error: string | null
}
AI SDK 路由通常返回流式 Response,失败时返回 JSON 错误。
方法
路径
入参
出参
依赖
GET
/api/tasks
status?、priority?、parent_id?
Task[]
Supabase
POST
/api/tasks
CreateTaskRequest
Task
Supabase
GET
/api/tasks/[id]
path id
Task + subtasks
Supabase
PATCH
/api/tasks/[id]
UpdateTaskRequest
Task
Supabase
DELETE
/api/tasks/[id]
path id
删除结果
Supabase
POST
/api/tasks/breakdown
taskId 或 taskTitle
子任务列表
Supabase、DeepSeek
POST
/api/prompts/optimize
userPrompt
optimizedPrompt
DeepSeek、元提示词文件
POST
/api/chat/stream
messages[]
SSE
SiliconFlow
POST
/api/speech-to-text
multipart audio
{ text }
OpenAI
POST
/api/text-to-speech
{ text }
audio/mpeg
Minimax
方法
路径
入参
出参
依赖
POST
/api/ai-sdk/chat
UIMessage[]
UIMessage stream
AI SDK、DeepSeek
POST
/api/ai-sdk/completion
prompt
text stream
AI SDK、DeepSeek
POST
/api/ai-sdk/tools
UIMessage[]
UIMessage stream
AI SDK、DeepSeek、Zod
POST
/api/ai-sdk/multimodal
UIMessage[]、userId?
UIMessage stream
AI SDK、DeepSeek、图片/视频工具、记忆模块
变量
用途
不配置的影响
SUPABASE_URL
Supabase 项目地址
待办 API 初始化失败
SUPABASE_KEY
Supabase anon key
待办 API 初始化失败
DEEPSEEK_API_KEY
任务拆解、提示词优化、AI SDK、多模态
对应 AI 功能 500
变量
用途
DEEPSEEK_API_URL
DeepSeek/OpenAI 兼容网关地址,默认 https://sg.uiuiapi.com/v1
SILICONFLOW_API_KEY
流式 AI Tab
OPENAI_API_KEY
多模态语音转文字
MINIMAX_API_KEY
多模态文字转语音
DOUBAO_API_BASE_URL
豆包视频接口地址
DOUBAO_API_TOKEN
豆包视频鉴权
DOUBAO_VIDEO_MODEL
豆包视频模型
VIDEO_GENERATION_METHOD
doubao 或 veo3
cp env.example .env.local
npm install
npm run dev
默认访问:http://localhost:3000。
类别
选型
框架
Next.js 14
UI
React 18、Tailwind CSS、styled-jsx
语言
TypeScript + JavaScript
数据库
Supabase/PostgreSQL
AI
Vercel AI SDK、OpenAI SDK、OpenAI 兼容模型网关
校验
Zod
命令
说明
npm run dev
本地开发
npm run build
生产构建
npm run start
启动生产构建
npm run test:api
执行 API 脚本测试
pages/api/chat/stream.ts 当前引用了 p-retry 与 js-tiktoken,但 package.json 中未声明这两个依赖。如果运行流式 AI Tab 时出现模块找不到,需要补充安装并写入依赖:
npm install p-retry js-tiktoken
待办事项:能新增、拉取、勾选、删除任务。
数据库:执行 supabase-schema.sql 后,任务能持久化到 Supabase。
AI 拆解:配置 DEEPSEEK_API_KEY 后,能把父任务拆成 3-5 个子任务。
提示词生成:输入文本后能返回优化提示词。
流式 AI:配置 SILICONFLOW_API_KEY 后,能连续收到 chunk,并展示费用统计。
AI SDK:Chat、Completion、Tools 三个子 Tab 能正常流式返回。
多模态:配置对应密钥后,文本对话、图片/视频工具、语音/TTS 能按模块工作。
缺少密钥时,接口返回明确错误,不应静默失败。
模型调用失败时,前端要展示可理解提示。
Supabase 查询失败时,应返回统一 ApiResponse。
SSE 流式失败时,应尽量返回 type: "error" 消息给前端。
不要提交 .env.local。
Supabase RLS 生产环境必须按用户隔离。
Tools 演示中的 eval 不应直接进入生产。
服务端日志当前较多,生产环境应降低敏感请求体日志。
API Key 只在服务端使用,前端不要暴露非 NEXT_PUBLIC_* 密钥。
风险
当前表现
建议
无登录体系
所有用户共用任务表策略
增加 Supabase Auth 与 user_id
记忆不持久
重启 Node 后用户画像丢失
改为数据库或 Redis
依赖未声明
p-retry、js-tiktoken 未在 package.json 出现
补依赖并重新锁定
模型配置分散
lib/config.ts 和 lib/ai/models.ts 都有模型/网关信息
收敛到统一配置层
mixed JS/TS
TasksTab.js 与 TS 文件混用
学习期可接受,长期建议统一 TS
多外部服务
Supabase、DeepSeek、SiliconFlow、OpenAI、Minimax、Doubao
按 Tab 分阶段配置,不要一次性排查全部
生产日志过细
多模态路由会记录请求体和 prompt
生产前加日志开关与脱敏
补齐缺失依赖:p-retry、js-tiktoken。
在 README 或文档首页明确最小可运行配置和完整功能配置。
增加 .env.local 配置自检接口或启动前检查脚本。
引入 Supabase Auth。
tasks 表增加 user_id。
RLS 改为用户只能访问自己的任务。
增加编辑任务标题、优先级筛选、层级折叠。
统一模型配置入口,减少硬编码 baseURL/model id。
将多模态记忆迁移到数据库。
将流式 AI 改为 AI SDK 协议或抽象统一的流式客户端。
给工具调用增加审计与超时控制。
读完项目后,建议你能回答这些问题:
为什么首页在 pages/index.js,但部分 AI API 在 app/api?
TasksTab 新增任务时,请求体和 Supabase 插入数据有哪些差异?
AI 任务拆解为什么要解析模型返回的 JSON,并做兜底解析?
自定义 SSE 的 start/chunk/done/error 和 AI SDK 的流式响应有什么区别?
useChat 和 useCompletion 适合哪些不同场景?
多模态工具是由前端直接调用,还是由模型在服务端选择调用?
短期记忆摘要与长期用户画像分别解决什么问题?
如果要上线生产,你最先会改哪三处?
pages/index.js 首页与五个 Tab 容器
components/TasksTab.js 待办事项 UI 与交互
components/PromptsTab.tsx 提示词优化 UI
components/StreamChatTab.tsx 自定义 SSE 流式聊天 UI
components/MultimodalChat.tsx 多模态聊天 UI
components/AISDKDemo.tsx Vercel AI SDK 教学 UI
pages/api/tasks/index.ts 任务列表与创建
pages/api/tasks/[id].ts 单任务查询、更新、删除
pages/api/tasks/breakdown.ts AI 任务拆解
pages/api/prompts/optimize.ts 提示词优化
pages/api/chat/stream.ts 自定义 SSE 流式聊天
pages/api/speech-to-text.ts 语音转文字
pages/api/text-to-speech.ts 文字转语音
app/api/ai-sdk/chat/route.ts AI SDK useChat API
app/api/ai-sdk/completion/route.ts AI SDK useCompletion API
app/api/ai-sdk/tools/route.ts AI SDK Tools API
app/api/ai-sdk/multimodal/route.ts 多模态、工具、记忆主 API
lib/config.ts 环境变量与配置
lib/ai/models.ts AI SDK 模型实例
lib/memory-manager.ts 记忆与画像
lib/supabase.ts Supabase 客户端
types/task.ts 任务类型
supabase-schema.sql 数据库结构
env.example 环境变量模板