一个戴在眼前的饮水记录助手 —— 用一句话记录饮水、查看个人目标进度,并随时核对与撤销。
1. 项目简介
「喝水节奏教练」是一款面向 Rokid AI 眼镜 的本地饮水记录智能体。它解决的核心问题是:日常工作、学习或通勤中,用户常常不方便拿出手机记录刚喝的每一口水;而只靠语音对话复述总量,又难以核对“是不是重复记了一次”。
这个项目把「记录 — 查看 — 修正」压缩成一张始终出现在眼前的界面:一句话记一笔,页面同步显示今天的累计量、个人目标进度和最近记录;记错了能直接撤销,目标可以自己调整,数据只存在设备本地。
目标用户 是那些习惯用语音交互、希望在不打断手头事情的前提下,轻量、可信地管理自己饮水节奏的人。
项目的定位有三个明确边界,也是贯穿全程的设计原则:
- 不是健康 App:目标只是用户自设的记录参考,不是医学建议,不推荐目标量、不催促达标、不鼓励过量饮水。
- 不猜需求:不依据身高、体重、活动量、天气或疾病推算摄水需求,也不采集图像、位置或健康传感器数据。
- 数据在本地:当天记录与个人目标只保存在设备本地,不上传网络,日期按设备本地日期切换。
2. 场景与用户意图
2.1 用户在什么情况下唤醒
典型的触发场景非常短促:
-
刚喝了一杯水、不想打断手头的节奏 → 说一句“记一杯”。
-
下午想检查一下今天喝了多少 → “今天喝了多少?”
-
发现刚才多记了一次 → “撤销上一笔。”
-
第一次用,想设定自己的参考值 → “把我的目标设成 1600 毫升。”
-
想重新开始 → “清空今天的记录。”
2.2 智能体如何理解与承接
用户的表达通常很短且口语化:“记一杯”“刚喝了 300 毫升”“喝了一大杯”。AIUI 将这些说法映射成少量、清晰的页面意图和结构化参数,页面再根据动作呈现结果。
具体承接分两层:
1. 意图映射:把自然语言收敛到 5 类动作 —— `status`(查看)、`log`(记录)、`undo`(撤销)、`set_goal`(设目标)、`reset`(清空),并提取 `amountMl`(饮水量)与 `goalMl`(目标量)两个参数。
2. 结果可视化:页面根据动作即时更新总量、进度、最近记录和反馈,让“执行结果可见并可修正”。用户不需要学习固定口令。
同时,系统对“数量可能缺失”做了明确的默认处理:用户只说“喝了一杯”而未给数量时,按 250 ml 记一笔,并在页面上明确说明默认值。
3. 方案设计
3.1 AIUI 智能体定义
智能体以单页面对话式应用实现,核心概念是“意图驱动的界面”(Intent-driven UI),而非连续聊天。眼镜上的主要诉求是快速记一笔、检查和撤销,而不是阅读长解释,因此采用单页面结构。
两条输入路径写入同一份状态:
- AIUI 宿主语音意图路由:由系统把语音转成结构化 `query` 参数,分发给页面的 `onLoad(query)`。
- 页面内语音入口:页面自建的 `SpeechRecognition`,用于“点按语音按钮 / 唤醒词”后识别并本地解析饮水量。
这两条路径是相互独立的,但最终都调用同一套记录处理逻辑,避免累计量不一致。
3.2 页面 / 卡片设计
页面自上而下组织为四个区域:
| 区域 | 内容 | 渲染方式 |
|---|---|---|
| 顶部信息区 | 当天日期、今日已记录总量 | 动态(A2UI) |
| 中部状态区 | 个人目标、进度百分比、分段进度条、最近两笔记录、空状态 | 动态(A2UI) |
| 中部提示区 | 语音状态 / 不支持提示 | 普通组件 |
| 底部操作区 | 记一杯(250 ml)、撤销、目标 ±100、清空今日 | 普通组件 |
为什么这样分工:固定且高频的操作(记、撤销、调目标)用普通 AIUI `button` 组件,交互稳定、无需动态协议;会随 AI 意图和记录数据改变内容的结果区(总量、进度、记录行、反馈)用 A2UI 动态生成。
3.3 状态流转
初始状态(无记录)
│ 用户说“记 300 毫升” / 点“记一杯”
▼
记录追加(appendRecord)─────────────────┐
│ │
├─ 用户说“撤销” → 移除最后一笔(removeLatestRecord)
├─ 用户说“设目标 1600”→ 更新 goalMl(goalIsDemo=false)
├─ 用户说“清空”→ records=[](目标保留)
│
▼
重新计算 viewState → 生成 A2UI 更新指令 → context.write() 刷新界面
每个动作都会走同一个 commit(records, goalMl, goalIsDemo, feedback) 流程:先本地存储,再重算视图状态,最后生成 A2UI 指令并刷新。这保证了“语音”和“点按”两条入口的状态始终一致。
3.4 能力调用关系
-
LanguageModel(端侧语言模型):当页面内语音识别到文本但无法靠规则确定数量时,用来估算饮水量;不可用时回退到parseAmountFromText正则解析。 -
SpeechRecognition(页面语音):监听、转写、错误与超时处理。 -
wx.setStorageSync / getStorageSync:本地持久化目标与当天记录。 -
a2ui:动态渲染与更新结果区。
4. AIUI 实现
4.1 项目结构
喝水节奏教练/
├── AGENTS.md # 智能体能力与边界约定
├── app.js # 应用入口(onLaunch)
├── app.json # 页面与权限声明
├── package.json # 工程元信息
├── README.md # 使用与实现说明
└── pages/
└── index/
└── index.ink # AIUI 单文件 Page(唯一的页面)
4.2 app.json:一个页面 + 最小权限
app.json 只注册一个全屏页面,并声明页面语音识别所需的 `RECORD_AUDIO` 权限;不申请摄像头或定位权限。
{
"name": "喝水节奏教练",
"description": "用自然语言记录饮水、查看个人目标进度,并在眼前即时核对和撤销。",
"pages": ["pages/index/index"],
"permissions": ["RECORD_AUDIO"],
"window": { "navigationBarTitleText": "喝水节奏教练" }
}
4.3 页面 Schema:自然语言描述 + 结构化意图
index.ink 的 <script def> 声明了页面的自然语言描述和 JSON Schema。Schema 里定义了 5 类动作和两个数值参数:
{
"action": { "enum": ["status", "log", "undo", "set_goal", "reset"] },
"amountMl": { "type": "integer", "minimum": 1, "maximum": 5000 },
"goalMl": { "type": "integer", "minimum": 100, "maximum": 10000 }
}
amountMl 用于“本次饮水量”,未给出时留空、页面默认 250;goalMl 只在 set_goal 动作中使用。
4.4 状态核心:纯函数状态机
视图状态由 makeViewState(records, goalMl, goalIsDemo, feedback, storageAvailable) 统一计算,产出总量、目标、进度百分比、最近两笔记录、记录数、反馈文案和存储状态。记录的增加 / 撤销是纯函数:
function appendRecord(records, amountMl, source) {
return records.concat([{ amountMl: Math.round(Number(amountMl)), source }]);
}
function removeLatestRecord(records) {
if (!records.length) return { records, removed: null };
return { records: records.slice(0, -1), removed: records[records.length - 1] };
}
4.5 A2UI:动态渲染结果区
首次渲染时,页面把 createSurface 与初始 updateComponents 指令序列化成字符串传给 <a2ui commands="{{initialCommands}}">;之后每次状态变化,通过 a2ui.createA2UIContext('hydration-dashboard') 获取组件上下文,再用 context.write() 推送更新指令。
function initialA2UICommands(view) {
return JSON.stringify([
{ version: 'v0.9', createSurface: { surfaceId: 'main' } },
{ version: 'v0.9', updateComponents: { surfaceId: 'main', components: buildA2UIComponents(view) } }
]);
}
flushA2UI() {
if (!this.a2uiReady || !this.pendingA2UI) return;
const context = a2ui.createA2UIContext('hydration-dashboard');
context.write(this.pendingA2UI);
this.pendingA2UI = null;
}
组件树会依记录状态动态生成空状态或多条记录行;新数据只更新 A2UI 页面内容,不执行动态 JavaScript(安全边界)。
4.6 本地存储与日期隔离
用日期键隔离每天记录,个人目标在跨天时保留。存储不可用时页面仍能在当前会话内操作,并在反馈中提示“仅本次打开有效”。
function localDayKey() {
return new Date().toDateString();
}
function normalizeSavedRecords(value, today) {
if (!value || typeof value !== 'object' || value.dayKey !== today || !Array.isArray(value.records)) return [];
return value.records
.filter((r) => r && Number.isFinite(Number(r.amountMl)) && Number(r.amountMl) > 0)
.map((r) => ({ amountMl: Number(r.amountMl), source: String(r.source || '记录') }));
}
onShow 里检测日期变化,若已跨天则重新开始当天记录、保留目标。
4.7 数量智能解析:LLM + 规则双保险
页面内语音识别到文本后,先尝试用端侧 LanguageModel 估算饮水量,失败或结果无效时回退到正则规则(支持“300 毫升”“两杯”“一大杯”“半瓶”等口语表达)。
async handleTranscript(transcript) {
this.setData({ voiceState: 'processing', feedback: '解析中…' });
let amount = await this.parseAmountWithLLM(transcript);
if (amount === null) amount = parseAmountFromText(transcript);
if (amount !== null && amount > 0 && amount <= 5000) {
this.addWater(amount, '语音记录');
this.endVoiceSession(`已识别"${transcript}",记录 ${Math.round(amount)} 毫升。`);
} else {
this.endVoiceSession(`听到"${transcript}",未识别出饮水量。可说"300 毫升"或"一杯"。`);
}
}
5. 交互与体验
5.1 重要界面
- 有记录时:显示“已记录 X ml”、个人目标(标注“我的目标”或“演示目标 · 可调整”)、进度百分比、10 段进度条,以及最近两笔记录。
- 无记录时:显示独立的空状态提示“今天还没有记录。说出饮水量后,这里会生成记录。”
- 语音状态:聆听中(脉冲圆点)/ 解析中 / 不支持语音,均有明确文字反馈。
5.2 操作路径
| 操作 | 语音 | 点按 / 按键 |
|---|---|---|
| 记录一杯 | “记一杯” | 「记一杯」按钮(250 ml)/ Enter |
| 记录定量 | “记 300 毫升” | 语音入口 + 文本解析 |
| 撤销 | “撤销上一笔” | 「撤销」按钮 |
| 调目标 | “设成 1600 毫升” | 「−100 / +100」按钮 |
| 清空 | “清空今天的记录” | 「清空今日」按钮 |
页面还支持方向键移动选中、Enter 激活,方便在不方便触控/语音时完成操作。
5.3 反馈机制
-
每次动作后立即反馈结果,例如“已记录 300 毫升;可随时撤销”。
-
默认值透明化:未给数量时反馈“已按默认一杯 250 毫升记录”。
-
目标边界透明化:反馈里明确“它只是你的记录参考,不是健康建议”。
-
存储状态透明化:显示“保存在此设备”或“仅本次打开有效”。
5.4 可读性设计(面向 AR 眼镜窄视野)
- 采用单色(绿色 `#40ff5e`)+ 细分隔线 + 短状态文字,不依赖颜色语义。
- 同步使用文字、数字、分段线和符号表达状态,色弱用户也能辨识。
- 明亮操作边界、紧凑字号,适配眼镜窄视野与透明显示。
6. 调试与验证
6.1 AIUI Studio 预览
在 AIUI Studio 中国站 用“本地导入”加载项目文件夹,在设备模拟预览中逐条试说:
-
“今天喝了多少?”
-
“记录 300 毫升水。”
-
“我刚刚喝了一杯。”(应提示按 250 ml 默认记录)
-
“撤销上一笔。”
-
“把我的目标设成 1600 毫升。”
-
“清空今天的记录。”
同时点按页面上的「记一杯」「撤销」和目标调整按钮,验证按钮与语音是否写入同一份状态、反馈是否同步。
6.2 关键验证点
- 结构化参数:确认 `action` / `amountMl` / `goalMl` 是否正确落到 `onLoad(query)`。
- A2UI 刷新:记录/撤销/调目标后,总量、进度、记录行、反馈是否即时更新。
- 跨天行为:修改设备日期,确认当天记录重新开始、个人目标保留。
- 存储异常:模拟 `wx` 存储失败,确认页面仍能用、且提示“仅本次打开有效”。
- 数量解析边界:口语“两杯”“一大杯”“半瓶”与纯数字“300”的解析结果是否符合预期。
7. 成果与复盘
7.1 可运行成果
-
一个可导入 AIUI Studio、可模拟预览的完整 AIUI 单页面智能体工程。
-
支持 5 类语音意图 + 5 类页面操作,语音与点按共用同一份状态逻辑。
-
A2UI 动态渲染总量、进度、最近记录与反馈;本地持久化目标与当天记录;跨天自动重置记录、保留目标。
7.2 遇到的问题与解决方案
| 问题 | 解决方案 |
|---|---|
| 用户说“喝了一杯”没给数量,无法记账 | 明确 250 ml 默认值,并在反馈与说明中透明化 |
| 误记后难以恢复 | 保存每笔数量与来源,撤销只删除最后一笔,无需重置整天 |
| 目标易被误解成健康建议 | 目标标记为“用户自设 / 演示值”,且不自动估算、不催促达标 |
| 真机本地存储可能受限 | 对 wx 读写加异常处理;失败时明确提示“仅本次打开有效” |
| 语音与点按各自维护状态导致累计量不一致 | 两条入口共用同一份记录状态与新增/撤销辅助逻辑 |
| 页面内语音对口语数量解析不准 | LLM 估算 + 正则规则双保险,LLM 不可用时优雅回退 |
| AR 眼镜窄视野下可读性差 | 单色 + 分段线 + 短文字 + 明亮边界,不依赖颜色语义 |