从 85.4% 到满分:SpacetimeDB 聊天应用 LLM 基准评测解析(Gemini 3 Pro × Level 5 Edit History)
【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB
导读
本文深入解析 SpacetimeDB 官方llm-oneshot评测体系中一份真实生成的评分报告——由 Gemini 3 Pro 依据 Level 5(消息编辑与历史)提示词、以 TypeScript + SpacetimeDB 技术栈"一次性"生成的实时聊天应用,最终获得 20.5/24(85.4%)的功能得分。读者将从中看到:8 个聊天功能逐项评分背后的实现方式与扣分点、5 类导致功能失效的典型缺陷(含 3 个关键 bug)、与 Claude Opus 4.5 同题对比的结果,以及从这份报告中提炼出的 SpacetimeDB TypeScript SDK 实战经验(useTable钩子用法、public: true表可见性、定时 reducer、token 持久化等),可直接用于指导后续 LLM 代码生成与人工 Code Review。
一、评测背景:llm-oneshot 是什么
该报告产出自仓库 tools/llm-oneshot 下的 AI 一次性应用生成基准(One-Shot App Generation):在 Cursor 中把 语言规则文件 与某个 功能分级提示词 一起喂给被测模型,要求其在一次交互内生成可部署的完整应用,随后按 评分标准 逐项打分并写入GRADING_RESULTS.md。
报告头部信息表明本次评测的关键上下文:
| 元数据 | 值 |
|---|---|
| 被测模型 | Gemini(Gemini 3 Pro) |
| 生成时间 | 2026-01-07 12:00 |
| 使用提示词 | 05_spacetime_edit_history.md(Level 5,编辑历史) |
| 评分者 | Claude Opus 4.5 |
| 技术栈 | TypeScript 后端模块 + React/Vite 前端 |
1.1 提示词分级与功能映射
根据 评分框架 中的 Prompt-to-Feature 映射表,提示词是逐级累积的:每个高级别都包含其下的全部功能。
| 提示词级别 | 包含功能 | 满分 |
|---|---|---|
01_*_basic | 1-4(基础聊天、输入指示、已读回执、未读数) | 12 |
02_*_scheduled | 1-5(+ 定时消息) | 15 |
03_*_realtime | 1-6(+ 阅后即焚消息) | 18 |
04_*_reactions | 1-7(+ 消息表情) | 21 |
05_*_edit_history | 1-8(+ 消息编辑历史) | 24 |
06-12 | 1-9 至 1-15(权限、在线状态、线程等) | 27-45 |
本次使用的 05_edit_history.md 对应功能 1-8,满分 24 分。重要原则:只对提示词中包含的功能打分,因此功能 9-15 不计入总分(报告中以 N/A 列出)。
1.2 评分体系:0-3 分制
每个功能按 0-3 分评定,可精确到 0.5:
| 得分 | 含义 |
|---|---|
| 0 | 未实现或完全不可用 |
| 1 | 部分实现,存在重大缺陷或缺少核心功能 |
| 2 | 基本可用,有轻微 bug 或缺失边界情况 |
| 3 | 完全符合规格 |
同时记录编译是否通过、是否崩溃、是否一次成功、前后端代码行数、文件数、外部依赖等整体指标。
二、整体指标:85.4% 总分与代码规模
报告记录的整体指标如下:
| 指标 | 值 |
|---|---|
| 使用的提示词级别 | 5(带历史的消息编辑) |
| 评估的功能 | 1-8(该级别最大为 8) |
| 功能总得分 | 20.5 / 24(85.4%) |
| 编译无错误 | ✅ |
| 运行不崩溃 | ✅ |
| 一次成功 | ✅ |
| 后端代码行数 | 约 550(schema.ts约 200 +index.ts约 350) |
| 前端代码行数 | 约 600(App.tsx约 430 +main.tsx约 30 +styles.css约 200) |
| 创建文件数 | 约 10(不含生成的绑定代码) |
| 外部依赖 | 后端spacetimedb;前端react、react-dom、spacetimedb、vite |
可以看出:这份应用规模相当克制——约 1150 行手写代码覆盖了 8 项功能、11 张表与 17 个 reducer(后文详述),验证了 SpacetimeDB"一个模块即后端"的低代码量优势:状态存储、实时同步全部由数据库承载,前端只需通过订阅(subscriptions)获取增量更新。
三、功能逐项评分详解(1-8)
Feature 1:基础聊天功能(2.5 / 3)
| 评分项 | 得分 |
|---|---|
| 用户可以设置显示名称 | 0.5 ✅ |
| 用户可以创建聊天室 | 0.5 ✅ |
| 用户可以加入/离开聊天室 | 0 ❌ |
| 用户可以给已加入的聊天室发消息 | 0.5 ✅ |
| 显示在线用户 | 0.5 ✅ |
| 存在基础校验 | 0.5 ✅ |
实现要点:set_namereducer 限制 50 字符;create_roomreducer 支持可选描述;send_messagereducer 限制 2000 字符;在线面板用绿点状态指示用户在线;实现了空名称、重复成员关系、房间存在性等校验。
缺陷:handleJoinRoom与handleLeaveRoom回调在代码中定义了但从未在 UI 中调用。房间列表点击只是选中,没有"加入"按钮,用户只能是自己创建房间的成员——多用户聊天功能实际不可用。这是本次评测中影响最重的缺陷之一,也让"基础聊天"从满分 3 分掉到 2.5 分。
Feature 2:输入指示器(2.5 / 3)
| 评分项 | 得分 |
|---|---|
| 输入状态广播给同房间成员 | 1 ✅ |
| 无操作后自动过期 | 0.5 ⚠️ |
| UI 显示"XX 正在输入..."或"多人正在输入..." | 1 ✅ |
实现要点:TypingIndicator表按房间/用户跟踪状态;start_typing、stop_typing两个 reducer 管理开关;断线与发送消息时清除指示;UI 对单人显示单数文案、多人显示复数文案;输入框onBlur时清除。
部分实现:没有基于时间的自动过期机制——指示器只在显式动作(失焦、发送、断线)时清除,没有定时 reducer 在数秒无操作后自动清理。用户输入后停下但保持焦点,指示器会永久残留(提示词要求 6 秒内自动消失,参考 05_edit_history.md 的 UI 契约)。Discord 风格的指示器约 5 秒自动过期。
Feature 3:已读回执(3 / 3,满分)
| 评分项 | 得分 |
|---|---|
| 系统跟踪哪些用户看过哪些消息 | 1 ✅ |
| 消息下方显示"已被 X、Y、Z 查看" | 1 ✅ |
| 已读状态实时更新 | 1 ✅ |
实现要点:ReadReceipt表记录消息/用户/时间戳;mark_message_readreducer 标记单条消息;mark_room_readreducer 将房间内所有消息标记为已读;UI 用getUserName辅助函数拼接"已被 X、Y、Z 查看";通过 SpacetimeDB 订阅实现实时同步。
Feature 4:未读消息计数(3 / 3,满分)
| 评分项 | 得分 |
|---|---|
| 房间列表显示未读计数徽标 | 1 ✅ |
| 计数跟踪每个用户在每个房间的最后读取位置 | 1 ✅ |
| 计数实时更新 | 1 ✅ |
实现要点:RoomReadPosition表含lastReadMessageId字段;getUnreadCount回调通过对比消息 ID 与最后读取位置计算未读数;徽标使用unread-badge类显示数量;聊天头部"标记已读"按钮更新读取位置。3、4 两项功能的共同点是:状态模型清晰(读位置与消息一一对应),并通过订阅实时驱动 UI,因此拿到了满分。
Feature 5:定时消息(2 / 3)
| 评分项 | 得分 |
|---|---|
| 用户可编写并预约未来投递的消息 | 1 ✅ |
| 作者可查看待发送消息并取消 | 0 ❌ |
| 消息在约定时间出现在房间 | 1 ✅ |
实现要点:ScheduledMessage作为定时表,配send_scheduled_messagereducer;schedule_messagereducer 带延迟校验(10 秒 - 24 小时);cancel_scheduled_messagereducer 已实现;UI 展示待发送消息的代码也存在。
关键 Bug:ScheduledMessage表缺少public: true声明。在 SpacetimeDB 中,表默认是私有的,客户端必须能订阅(subscribe)该表才能收到数据;没有public: true,客户端永远收不到待发送消息,取消功能随之失效——UI 代码写了但拿不到数据。这是本评测中最典型的一个"改一行属性即修复"的 bug,也是评分框架中 Feature Broken 类的代表案例。
Feature 6:阅后即焚消息(2 / 3)
| 评分项 | 得分 |
|---|---|
| 用户可发送带自动删除计时器的消息 | 1 ✅ |
| UI 显示倒计时或消失指示 | 0 ❌ |
| 计时器到期后消息被永久删除 | 1 ✅ |
实现要点:EphemeralMessage定时表配cleanup_ephemeral_messagereducer;send_ephemeral_messagereducer 带时长校验(10 秒 - 1 小时);清理 reducer 会连带删除关联数据(表情、回执、编辑记录);前端提供"标记为阅后即焚"复选框与时长输入。
关键 Bug:后端构造ephemeralExpiresAt时用了{ microsSinceUnixEpoch: expiresAt }这种普通对象而非合法的Timestamp对象,导致序列化失败,客户端收到的字段是 undefined/畸形数据,倒计时指示无从渲染。注意"删除"本身是正常工作的(由定时 reducer 驱动),扣分全在 UI 指示缺失上。
Feature 7:消息表情(2.5 / 3)
| 评分项 | 得分 |
|---|---|
| 用户可给消息添加 emoji 表情 | 0.75 ✅ |
| 表情计数显示并实时更新 | 0.75 ✅ |
| 用户可开关自己的表情 | 0.75 ✅ |
| 悬停/点击显示谁点了表情 | 0 ❌ |
实现要点:Reaction表跟踪用户/消息/emoji;toggle_reactionreducer 负责添加或移除;内置 5 个 emoji 选项(👍 ❤️ 😂 😮 😢);自己的表情用active类高亮;分组展示为"emoji + 计数"。
缺失功能:分组数据里其实已经跟踪了data.users,但 UI 从未展示——用户只能看到计数,看不到谁点过。对照提示词 UI 契约中的"title属性展示点赞者姓名",这属于"数据有了、展示没做"的典型半成品。
Feature 8:带历史的消息编辑(3 / 3,满分)
| 评分项 | 得分 |
|---|---|
| 用户可编辑自己的消息 | 1 ✅ |
| 被编辑消息显示"(已编辑)"标记 | 0.5 ✅ |
| 其他用户可查看编辑历史 | 1 ✅ |
| 编辑实时同步给所有查看者 | 0.5 ✅ |
实现要点:edit_messagereducer 带所有权校验(只能编辑自己的消息);MessageEdit表存储旧内容、新内容、时间戳与编辑者;"(已编辑)"徽标通过message-edited类渲染;"显示历史/隐藏历史"按钮切换编辑历史面板;历史视图用删除线展示旧内容、高亮展示新内容;所有变更经 SpacetimeDB 订阅实时同步。
未评估功能(Level 5 提示词未包含)
| 功能 | 满分 | 得分 | 说明 |
|---|---|---|---|
| 9. 实时权限 | 3 | N/A | 提示词未要求(虽存在部分角色逻辑) |
| 10. 在线状态 | 3 | N/A | 未要求 |
| 11. 消息线程 | 3 | N/A | 未要求 |
| 12. 私密房间与私信 | 3 | N/A | 未要求 |
| 13. 活跃度指示 | 3 | N/A | 未要求 |
| 14. 草稿同步 | 3 | N/A | 未要求 |
| 15. 匿名迁移 | 3 | N/A | 未要求 |
总分汇总表
| 功能 | 满分 | 得分 | 扣分原因 |
|---|---|---|---|
| 1. 基础聊天 | 3 | 2.5 | 无加入/离开房间 UI |
| 2. 输入指示器 | 3 | 2.5 | 无基于时间的自动过期 |
| 3. 已读回执 | 3 | 3 | 满分 |
| 4. 未读计数 | 3 | 3 | 满分 |
| 5. 定时消息 | 3 | 2 | 表缺少public: true |
| 6. 阅后即焚 | 3 | 2 | 无可见指示(时间戳构造错误) |
| 7. 消息表情 | 3 | 2.5 | 无悬停显示点赞者 |
| 8. 消息编辑 | 3 | 3 | 满分 |
| 总计 | 24 | 20.5 | 85.4% |
四、技术笔记:8 个缺陷逐一剖析
报告的 Technical Notes 部分列出了 8 个问题,其中前 5 个直接造成功能扣分,后 3 个属于质量隐患。对照 TypeScript SDK 源码与 提示词 UI 契约,逐一剖析如下。
1. 无加入/离开房间 UI(功能级缺陷)
handleJoinRoom回调定义了却从未接线到 UI。后果是用户只能在自己创建的房间内聊天,多用户场景直接失效。教训:回调定义与事件绑定必须成对存在,Code Review 时应搜索"定义了但未被引用的 handler"。
2.ScheduledMessage表未声明public: true(功能级缺陷)
SpacetimeDB 中表默认对客户端不可见,客户端要订阅实时变化,表必须声明为public。缺少该属性意味着前端订阅永远拿不到数据——即便 UI 逻辑完整。教训:任何需要客户端可见/可订阅的表都要显式加public: true。
3. 阅后即焚时间戳构造错误(数据契约缺陷)
后端写出{ microsSinceUnixEpoch: expiresAt }普通对象冒充Timestamp,客户端反序列化失败。这说明跨端数据必须严格遵循 SDK 定义的类型构造,不能用手写对象字面量替代(尤其涉及时间、十进制等特殊类型)。
4. 表情缺少悬停显示点赞者(展示层缺陷)
数据(data.users)已经跟踪,但 UI 没渲染。对照提示词契约"悬停显示点赞者姓名",这是"最后一公里"没走完。
5. 输入指示无自动过期(状态机缺陷)
没有定时 reducer 清理陈旧输入状态,指示器直到用户显式操作才消失。正确做法是复用 SpacetimeDB 的定时 reducer(scheduled reducer)机制:在start_typing时登记一个若干秒后执行的清理任务(本报告 Feature 5/6 中send_scheduled_message、cleanup_ephemeral_message即同类用法),保证状态自愈。
6. token 持久化缺失(会话缺陷)
代码中没有.withToken()调用,onConnect中也没有localStorage.setItem('auth_token', token)。后果:每次刷新页面用户身份都丢失。在 SpacetimeDB 中,auth_token是客户端连接的身份凭证,正确流程是首次连接时保存 token,后续连接通过.withToken(token)恢复同一身份。
7.useTable用法错误(SDK API 缺陷)
代码用了const rows = useTable(table),而该钩子实际返回元组[rows, isLoading]。这一点在 SDK 源码中有明确佐证:React 版useTable的文档示例即为const [rows, isReady] = useTable(tables.user)(见 crates/bindings-typescript/src/react/useTable.ts),其返回值是"行数据 + 加载状态"的元组而非单一数组。用错模式会直接导致解构异常或状态缺失。
8. 多列索引上的.filter()(潜在崩溃隐患)
后端在多列索引(如room_identity、room_user)上使用.filter()遍历。评测规则明确指出该用法已损坏,可能引发 PANIC 或静默返回空结果。教训:对多列索引/复合键应使用按索引查询的 API,而非在内存中 filter。
五、架构决策:11 张表与 17 个 reducer 的后端全景
报告还原了 Gemini 生成的完整数据模型与逻辑层:
5.1 表设计(11 张,全部定义于schema.ts)
| 表 | 用途 |
|---|---|
User | 用户档案(名称等) |
Room | 聊天室(名称、可选描述) |
RoomMember | 房间成员关系 |
Message | 消息主表 |
MessageEdit | 编辑历史记录 |
Reaction | 表情反应 |
ReadReceipt | 已读回执 |
RoomReadPosition | 每用户每房间的读取位置 |
TypingIndicator | 输入状态 |
ScheduledMessage | 定时消息(定时表) |
EphemeralMessage | 阅后即焚消息(定时表) |
5.2 Reducer 全集
- 普通 reducer(15 个):
set_name、create_room、join_room、leave_room、send_message、edit_message、delete_message、toggle_reaction、mark_message_read、mark_room_read、start_typing、stop_typing、schedule_message、cancel_scheduled_message、send_ephemeral_message - 定时 reducer(2 个):
send_scheduled_message(到点投递)、cleanup_ephemeral_message(到点删除) - 生命周期钩子(2 个):
clientConnected、clientDisconnected
一个值得注意的设计:定时消息与阅后即焚共用 SpacetimeDB 的定时任务机制,区别仅在于到期动作——前者插入消息,后者删除消息及全部关联数据。这种"同一机制、两种到期行为"的设计简洁且正确。
5.3 文件结构
backend/spacetimedb/ ├── package.json ├── tsconfig.json ├── dist/bundle.js └── src/ ├── schema.ts (约 200 行:11 张表) ├── reducers.ts (约 350 行:reducer 全集) └── index.ts (入口导入) client/ ├── package.json ├── tsconfig.json ├── vite.config.ts ├── index.html └── src/ ├── config.ts ├── main.tsx (约 30 行:入口) ├── App.tsx (约 430 行:单文件 React 组件,Discord 风格深色主题) ├── styles.css (约 200 行) └── module_bindings/ (由 SDK 生成的类型绑定)前端的单文件组件 + 深色 Discord 风格主题,符合提示词"保持极简可读"与品牌色规范(主色#4cf490SpacetimeDB 绿、辅色#a880ff紫,参见 typescript-spacetime.md)。角色权限(owner/admin/member)有部分实现,但该提示词级别不要求,故未评估。
六、同题对比:Gemini 3 Pro vs Claude Opus 4.5
| 功能 | Gemini | Opus 4.5 |
|---|---|---|
| 1. 基础聊天 | 2.5 | 3 |
| 2. 输入指示器 | 2.5 | 3 |
| 3. 已读回执 | 3 | 3 |
| 4. 未读计数 | 3 | 3 |
| 5. 定时消息 | 2 | 3 |
| 6. 阅后即焚 | 2 | 2.5 |
| 7. 消息表情 | 2.5 | 2.5 |
| 8. 消息编辑 | 3 | 3 |
| 总计 | 20.5 | 23 |
关键差异(报告原文结论):
- Gemini 定义了
handleJoinRoom但未接线到 UI,用户无法加入房间 - Gemini 没有输入指示器的定时自动过期
- Gemini 遗漏
ScheduledMessage表的public: true(关键 bug) - Gemini 错误构造阅后即焚时间戳(指示完全不显示)
- Gemini 缺少 token 持久化(刷新即丢失身份)
- Gemini 用错
useTable模式(元组 vs 直接取行) - 两个模型都漏掉了表情悬停显示(共性问题)
- Opus 的阅后即焚指示有部分问题,但比 Gemini 的完全失效要好
七、从评分报告提炼的实战清单
这份报告对开发者(无论是否使用 LLM 生成代码)都是一份高质量的 SpacetimeDB 检查单:
- 表可见性:凡客户端需订阅的表,逐一确认带
public: true(Feature 5 的教训)。 - 回调闭环:每个 UI 交互回调都要确认被真实绑定;可 grep 未被引用的 handler(Feature 1 的教训)。
- 类型契约:时间等特殊值必须用 SDK 类型构造,禁止手写对象字面量冒充(Feature 6 的教训)。
- 状态自愈:需要过期的状态(输入指示)务必配定时 reducer,别依赖用户操作(Feature 2 的教训)。
- 身份持久化:连接成功后保存
auth_token,重连时用.withToken()恢复同一身份(技术笔记第 6 条)。 - Hook 用法:
useTable返回[rows, isReady]元组,解构取用;多列索引查询勿用.filter()(技术笔记第 7、8 条,源码佐证)。 - 数据可用 ≠ 展示完成:表情悬停、历史面板这类"最后一公里"功能最容易丢分,生成代码后应逐条对照提示词的 UI 契约验收。
这些经验同时适用于人工评审 LLM 生成代码与自主编写 SpacetimeDB 模块。完整的评分框架、分级提示词与基准运行方式见 tools/llm-oneshot/apps/chat-app/prompts/grading_rubric.md 与 tools/llm-oneshot/README.md,如需复现本评测,可将 语言规则 与 Level 5 提示词 喂给被测模型,并按报告格式生成自己的GRADING_RESULTS.md。
【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考