像刷即时通讯软件一样和角色聊天,每个选择都会改变剧情,最终通向不同结局。
▶ 在线演示:https://chat.vibecoco.ai/
玩家在拟真的手机聊天界面里做选择,AI 会在原剧本台词的基础上重写出更自然的回复,但剧情走向、数值和结局始终由脚本控制。后端用 Next.js 接口路由把 DeepSeek 请求代理到服务端。
| 首页 | 取名页 |
|---|---|
![]() |
![]() |
| 聊天玩法 | 结局页 |
![]() |
![]() |
- 拟真的手机聊天体验:消息气泡、打字状态、系统时间提示,玩起来像真的在和人对话。
- 选择驱动的分支剧情:每个回复都会牵动好感与焦虑数值,导向不同结局。
- 多角色随机开局:首页从角色池里随机抽人,每局都是不同剧本。
- AI 让对话更自然:角色台词由 AI 结合人设和上下文重新组织,比照搬剧本更像真人;同时剧情走向和结局判定保持稳定,生成失败时无缝回退到原台词,不会卡住。
| 层 | 技术 |
|---|---|
| 前端 | Next.js 16(App Router)· React |
| 后端 | Next.js Route Handlers(app/api/*) |
| AI | DeepSeek(deepseek-v4-flash),经服务端代理调用 |
| 数据 | JSON 剧情 / 角色 / 章节 / 话术(data/) |
| 部署 | Vercel |
浏览器界面
└─ POST /api/ai/chat # 只传有限上下文
└─ 服务端 DeepSeek 代理 # 鉴权、白名单、限流、超时
└─ https://api.deepseek.com/chat/completions
密钥只作为服务端环境变量存在,不会下发到浏览器,也不要加 NEXT_PUBLIC_* 前缀。
- Node.js 20+(CI 在 Node 22 上验证)
- 一个 DeepSeek API Key
npm install
cp .env.example .env.local
# 在 .env.local 中填写 DEEPSEEK_API_KEY
npm run devnpm run build
npm run start -- --port 4180本地最少只需配置 DEEPSEEK_API_KEY,其余项都有默认值:
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
DEEPSEEK_API_KEY |
是 | 无 | 仅服务端使用,切勿加 NEXT_PUBLIC_ 前缀。 |
DEEPSEEK_MODEL |
否 | deepseek-v4-flash |
代理使用的模型。 |
DEEPSEEK_BASE_URL |
否 | https://api.deepseek.com |
必须为 HTTPS。 |
DEEPSEEK_TIMEOUT_MS |
否 | 8000 |
上游超时(毫秒)。 |
APP_PUBLIC_URL / AI_ALLOWED_ORIGINS |
推荐 | 生产环境仅放行生产域名 | 接口来源白名单。 |
限流与请求体限制等完整运维项见 docs/production.md。
提交前运行与 CI 一致的完整校验:
make verify # 等价于 npm run verify也可单项执行:
make verify-repo # python3 scripts/check_repository.py,结构与数据链接检查
make verify-ai # npm run verify:ai,关键路径集成测试
make build # npm run build
make verify-visual # python3 scripts/verify_visual_baseline.py
npm audit --omit=dev # 依赖变更时verify:ai 用模拟的 DeepSeek 请求覆盖生产关键路径:接口地址、模型名称、鉴权来源、关闭思考模式、来源白名单、请求体大小限制、玩家自定义姓名上下文和 /api/health 健康检查。
.
├── app/ # Next.js 页面与 API 路由
│ ├── api/ai/chat/ # DeepSeek 服务端代理
│ └── api/health/ # 健康检查接口
├── components/
│ ├── ChatSimulator.jsx # 顶层组件
│ └── chat/ # 界面、剧情引擎、状态与运行时
├── data/ # 剧情、角色、章节、话术数据
├── lib/ai/deepseek.js # DeepSeek 服务端客户端
├── docs/ # 部署、开源说明与 README 截图
├── public/ # 头像、图标与静态资源
└── scripts/ # 仓库 / AI / 视觉验证脚本
新增剧本通常涉及这些文件:
data/girls.json— 角色data/scenes.json— 剧情节点data/chapters.json— 仅章节结构变化时public/— 角色图片
首页会从 data/girls.json 中随机抽取角色,所以接好 firstScene 后新角色会自动进入随机池。
"yue": {
"id": "yue",
"name": "岳宁",
"avatar": "/yue.png",
"tags": ["段位:三星", "标签1", "标签2"],
"description": "一句简介",
"firstScene": "yue_scene_01"
}头像放在 public/yue.png。
在 data/scenes.json 中追加节点,建议用独立前缀(如 yue_):
{
"id": "yue_scene_01",
"chapter": 1,
"title": "初见",
"timeLabel": "周三 晚上 20:10",
"messages": [
{ "id": "s1", "sender": "system", "content": "[TIME] 周三 晚上 20:10", "delay": 400 },
{ "id": "m1", "sender": "her", "content": "你好呀" }
],
"choices": [
{
"id": "a",
"label": "A",
"text": "你好",
"replyText": "你好",
"affectionDelta": 2,
"anxietyDelta": 0,
"nextScene": "yue_scene_01_ans_a",
"badgeText": "礼貌"
}
]
}剧情推进有两种方式:choices[].nextScene(玩家选择后跳转)或 autoNext(节点结束后自动跳转)。
最稳妥的做法是在结局节点直接写 endingData,无需改 JavaScript:
{
"id": "yue_ending_good_01",
"chapter": 6,
"messages": [
{ "id": "s1", "sender": "system", "content": "你终于看清了。" }
],
"isEnding": true,
"endingType": "good",
"endingData": {
"title": "清醒结局:及时止损",
"desc": "结局描述..."
}
}如果省略 endingData 并想复用随机结局池,需要同时更新 components/chat/endingPools.js 和 components/chat/gameEngine.js,否则新前缀不会映射到预期结局池。
docs/production.md:生产部署、安全边界与完整环境变量docs/open-source.md:开源范围与发布说明docs/project-roadmap.md:项目路线图SECURITY.md:安全策略
欢迎提交 Issue 和 PR。提交前请运行 make verify,并阅读 CONTRIBUTING.md 了解改动约定。
本项目基于 MIT 许可证 开源。



