德语老师项目计划 - 升级版
✅ 建议加一个 Phase 0:项目骨架 & 设计规范
在你动手写双栏 UI 之前,先把“地基”打好,会让后面所有阶段舒服很多。
Phase 0 目标
- 搭好基础项目结构和设计系统(组件 + 配色 + 字体)。
- 配好环境变量、API 封装、代码质量工具。
具体补充
- 项目初始化
npx create-next-app@latest german-tutor --ts --tailwind --eslint- App Router 开启,src 目录结构大致可以一开始就定好:
src/
app/
page.tsx
api/
chat/route.ts # 统一 LLM 网关
components/
layout/
chat/
correction/
lib/
llm.ts # 封装调用 Gemini/DeepSeek
types.ts # 公共类型
utils.ts
- 设计系统 (Design System Lite)
- 定一个简单但统一的视觉语言:
- 主色:如
#2563eb(蓝) - 错误高亮:
#ef4444(红) - 正确高亮:
#22c55e(绿)
- 主色:如
- Tailwind 里自定义:
// tailwind.config.ts 里 extend theme
colors: {
primary: '#2563eb',
error: '#ef4444',
success: '#22c55e',
}
- 封装几个通用组件:
<Button /><TextArea /><Card />(右侧纠错卡复用)
- 类型定义(从第一天就 strongly-typed)
// src/lib/types.ts
export type Role = 'user' | 'assistant';
export interface Message {
id: string;
role: Role;
content: string;
createdAt: string;
}
export interface Correction {
id: string;
messageId: string;
originalText: string;
correctedText: string;
explanation: string;
}
export interface TutorResponse {
correction: string;
revised_sentence: string;
reply: string;
}
这些类型从 Phase 1 用到 Phase 5,都能复用。
🏁 Phase 1 再细化:前端 & 模型接入
你写得已经很清楚,我补三个点:交互细节、错误处理、JSON 校验。
1. UI 细节补完
- 响应式设计:
- Desktop:左右两栏。
- Mobile:上下两栏(Tab 切换 “对话 / 纠错”)。
- 输入体验:
Shift + Enter换行,Enter发送。- 发送中按钮 Loading 状态;右上角可以加一个 “Model: Gemini Pro / DeepSeek” 标签。
- 气泡的语义区分:
- 用户气泡:右对齐,浅背景。
- AI 气泡:左对齐,带小头像(🇩🇪)。
2. API Route & Vercel AI SDK 封装
目标:不让前端直接碰第三方 API,统一走 /api/chat:
// src/app/api/chat/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { callTutorModel } from '@/lib/llm';
const BodySchema = z.object({
message: z.string().min(1),
});
export async function POST(req: NextRequest) {
const json = await req.json();
const parsed = BodySchema.safeParse(json);
if (!parsed.success) {
return NextResponse.json({ error: 'Invalid request' }, { status: 400 });
}
try {
const result = await callTutorModel(parsed.data.message);
return NextResponse.json(result);
} catch (e) {
console.error(e);
return NextResponse.json(
{ error: 'Model error, please try again later.' },
{ status: 500 }
);
}
}
callTutorModel 里根据环境变量选择 Gemini 或 DeepSeek。
3. 严格的 JSON 约束(防止模型乱返回)
配合你的 system prompt,把“只能返回 JSON”写死,并在服务器端再校验一遍:
// src/lib/llm.ts (示例伪代码)
import { z } from 'zod';
const TutorResponseSchema = z.object({
correction: z.string(),
revised_sentence: z.string(),
reply: z.string(),
});
export async function callTutorModel(userText: string) {
const prompt = `
你是一个德语私教。用户发德语句子,你只返回 JSON,不要多余文字:
{
"correction": "中文解释错误,如果无错则留空",
"revised_sentence": "修正后的德语句子,如果无需修改就等于原句",
"reply": "你的德语回复,引导对话继续"
}
用户输入:${userText}
`;
const raw = await callGeminiOrDeepSeek(prompt); // 具体实现略
const parsedJson = safeParseJson(raw); // 自己写一个 try/catch
return TutorResponseSchema.parse(parsedJson); // 若schema不匹配直接抛错
}
前端就可以放心地按 TutorResponse 类型渲染两栏。
🚧 Phase 2:后端与持久化再补几笔
你已经列出了四张表,我补充:
1. 推荐表结构(更贴合“多会话 + 匿名体验”)
-- profiles
id uuid primary key
auth_user_id uuid unique -- Supabase auth.user.id
display_name text
created_at timestamptz
-- sessions (一个会话=一个“学习场景”)
id uuid primary key
user_id uuid references profiles(id)
title text
created_at timestamptz
updated_at timestamptz
-- messages
id uuid primary key
session_id uuid references sessions(id)
role text check (role in ('user', 'assistant'))
content text
created_at timestamptz
language text default 'de' -- 将来可扩展其他语言
-- corrections
id uuid primary key
message_id uuid references messages(id)
original_text text
corrected_text text
explanation text
error_type text -- 如 'grammar', 'word_order', 'vocab'
created_at timestamptz
2. 匿名用户 → 注册用户的过渡
- Phase 2 还可以加一个小目标:
未登录也可以用,但一旦登录,就把当前 session“绑定”到用户。 - 逻辑示意:
- 客户端生成一个
local_session_id(UUID)存 localStorage。 - 未登录状态下:messages/corrections 依然可以存 Supabase,以匿名 user_id 或 null 标记(RLS 控制可见性)。
- 用户登录后:调用一次后端迁移 API,把
local_session_id下的所有 messages/corrections 绑定到该用户的 profile。
- 客户端生成一个
这样第一天你就可以有“无感 onboarding”:用得爽了再注册保留记录。
3. Supabase RLS / 安全
messages、corrections必须开 RLS:- 规则:只能看到
user_id = auth.uid()或user_id IS NULL AND session_id IN (客户端提供、且验证所有权)
- 规则:只能看到
- 后端改造:
- 原来的
/api/chat在 Phase 2 变成:- 解析请求中的
sessionId。 - 插入 user 消息。
- 调 LLM。
- 插入 assistant 消息 & corrections。
- 返回最新状态给前端。
- 解析请求中的
- 原来的
🧠 Phase 3:Dify / 知识库层的“架构升级”
你写的“混合架构”方向很好,我帮你明确一下**“切换条件”和接口设计**。
1. 什么时候走 Dify?
- 用户选择了某个“场景模式”:
- 点餐 Deutsch im Restaurant
- 电话咨询 Termin beim Arzt
- 学术写作 E-Mail an Professor
- 或者某一类练习:
- “基于某本教材第 5 章做练习”。
这些场景需要知识库检索 / 多 step 的 Agent 逻辑,你就走 Dify:
// 伪逻辑
if (mode === 'free_chat') {
return await callGemini(userText);
} else {
return await callDifyFlow({ userText, mode });
}
2. Dify 接入时的“防缠绕”原则
- 保持现在的
/api/chat接口不变,只在内部决定走哪条路。 - 保持
TutorResponse类型不变:
无论是 Dify 还是 Gemini,最后统一适配成相同的 JSON 输出格式。
-> 这样前端完全不用改,Phase 1 的 UI 可以一路用到 Phase 5。
💰 Phase 4:商业化时再多考虑 3 件事
你提到了配额 + VIP + 语音,非常好。我帮你再加:
1. 计费 & 配额设计
- 免费层:
- 无登录试用:每天 5 条。
- 注册免费:每天 20 条。
- 付费层:
- 月订阅:不限对话数 + 高级纠错分析 + 专项训练 + 语音。
- 技术上:
- 在 Supabase 加个
plans表和usage表。 - 每次请求
/api/chat时:- 查当天使用次数。
- 超限则返回 “配额用完” 的统一错误 JSON。
- 在 Supabase 加个
2. 语音细节
- 录音 → 文字:
- 先用浏览器 Web Speech API(轻量版),之后可以接 OpenAI Whisper API 做高质量版。
- 文字 → 语音:
- 封装一个
/api/tts,内部调用 ElevenLabs 或 OpenAI TTS。 - 聊天气泡下方加一个小喇叭按钮,点击就播放语音版回复。
- 封装一个
🚀 Phase 5:数据飞轮的“产品化”
你提到错题生成 & 周报推送,我帮你把它拆成可开发任务。
1. “错题本” → “练习生成器”
- 新增一个 API:
/api/generate-exercises- 入参:
correction_idorcorrected_text + explanation - 出参:一个数组,比如:
- 入参:
[
{
"type": "fill_in_blank",
"question": "Ich ___ müde.",
"options": ["bin", "bist", "seid"],
"answer": "bin",
"explanation": "动词 sein 的第一人称单数变位是 bin。"
},
...
]
- 前端在纠错卡片上加一个按钮:“基于此生成练习”。
2. 周报(或月报)自动化
- Supabase + 定时任务(cron):
- 每周一跑一个 Edge Function:
- 统计每个用户上一周的错误类型分布(
error_type)。 - 生成一个摘要 prompt 给 LLM,总结本周重点 + 推荐练习。
- 通过邮件(如 Resend / Supabase 内置 email)发送。
- 统计每个用户上一周的错误类型分布(
- 每周一跑一个 Edge Function:
📅 本周执行计划:升级版
你原来的 3 天计划很好,我帮你再精细化一下(仍然只做 Phase 1):
Day 1 – 只做 UI,不碰 API
- 搭 Next.js + Tailwind 项目结构。
- 写出:
<ChatLayout />:左右分栏 + 响应式布局。<ChatMessageList />+<ChatInput />。<CorrectionPanel />+<CorrectionCard />。
- 先用假数据填充:
- 左侧几条机器 / 用户对话。
- 右侧几条纠错卡片(红/绿高亮)。
完成标准:不用任何后端,页面一打开就能看到“像产品”的 UI。
Day 2 – API Key + 后端接口壳子
- 申请 Gemini / DeepSeek API Key。
- 在
.env.local配好:GEMINI_API_KEY、DEEPSEEK_API_KEY。 - 写
/api/chatroute(先用 mock 返回固定 JSON)。 - 写
callTutorModel函数(先不真正调第三方,只返回 hard-coded JSON)。
完成标准:前端输入一句话,能收到“看起来合理的”假响应,并完成左 / 右栏联动渲染。
Day 3 – 真正接入 LLM & JSON 校验
- 把
callTutorModel里改成真正调用 Gemini/DeepSeek。 - 加上:
- System prompt。
- JSON 解析 + zod 校验。
- 改善一点 UX:
- “发送中...” 状态。
- 模型错误时,在右侧显示一张红色错误卡片(而不是无声失败)。
完成标准:
你可以自然地用德语和它聊几句,左侧对话 + 右侧纠错都正常、无崩溃。
如果你愿意,下一步我可以直接帮你:
- 出一个 Phase 1 的完整文件结构 + 关键组件代码草稿,
- 或者出一版 Supabase 数据库建表 SQL + RLS 策略模板,你只要复制粘贴就能用。