记账大师 AIUI购物记账与交互可靠性实践

0 阅读14分钟

从商品口述到可确认账单的原生 Ink 实践

作者 Dandy 实现版本 1.0.4

项目简介

在超市一次买很多东西时,我希望随时知道已记录商品的合计,也希望付款时能回看某件商品的价签。单纯把计算器放进眼镜并不能解决问题:有时没有价签,有时不想拍照,还有些商品需要在事后回查原始照片。因此,我设计了“记账大师”,让用户选择拍照或语音输入,核对后保存,再在结账时查看总额与小票差异。

记账大师已上架 Rokid 智能体商店,可在商店搜索“记账大师”体验。本文介绍 1.0.4 的实现,重点是两件事:如何把自然语言和照片变成可检查的账单,以及如何防止眼镜上的一次操作被重复解释为多个动作。

项目使用原生 AIUI / Ink、Rokid 相机和语音能力,以及必要时的 LanguageModel 结构化识别。开发过程借助 AI 编程工具辅助实现和排查,我参与了产品需求、交互选择及眼镜试用反馈。文中测试数字对应随版本保存的自动化报告,未将本地模拟结果当成真机验收结论。

场景与用户意图

用户表达“牛奶六块五,买两盒”时,真实意图不是得到一句口头回答,而是把名称、单价和数量放进当前购物记录。界面应先显示“牛奶、6.50 元、2 件、13.00 元”,让用户改正或取消,再由明确确认完成入账。

没有价签时直接口述,有价签时拍照并保留原图。用户说完或取消,不应意外触发下一步功能。现在所有录音都从手动选择的语音选项开始:进入“结账核对”只显示菜单,选择“说出收银金额”才录音。触控唤醒并不等同于这次明确选择。

方案设计与任务状态

整个任务按输入、处理、确认、保存划分。录音结果、模型结果和相机照片都先进入草稿,不直接写账。各阶段使用不同的状态说明和选项,用户能够看到当前是否仍在等待,以及取消会回到哪里。

状态界面与反馈下一步
home / record历史列表和记录摘要新建、继续、只读查看或删除
shop / camera购物合计;相机取景提示用户选择拍照或语音输入
listen / working正在听、正在结束或识别进度结束、等待结果或取消
draft / cashierDraft商品字段或收银金额预览修改、取消或确认保存
checkout / receiptResult金额差异与小票核对提示查看原图和商品,人工复核

拍照记录与语音记录共享同一套商品草稿:名称、计价方式、单价、数量、实际金额、备注和照片。商品缺价、名称不清或模型标记需要复核时,界面不提供“确认加入账单”,而是引导用户补充。重复的同名同价商品也会单独提示,避免一次识别被误认为两次购买。

图 1 商品与照片的确认信息 官方 Ink 本地示例数据

[配图文件:参赛文档配图/商品草稿示例.png]

AIUI实现与多模态输入

运行时入口由 app.json 和 app.js 定义,业务状态放在 pages/index/index.ink。utils/gestures.js 处理输入仲裁,utils/intelligence.js 处理解析与核对,utils/ledger.js 负责金额和持久化。项目以 AIX 交付,在 AIUI Studio 导入后按平台流程配置并提审。

能力调用与分工失败时的处理
相机wx.media.createCameraContext().takePhoto取消等待;识图失败时可保留照片并用语音补充
语音SpeechRecognition.onresult 收集正文无内容返回并提示;取消释放当前实例
模型LanguageModel.availability / create / prompt校验结构化结果;超时或取消不写账
存储wx.getStorageSync / setStorageSync写后读回;失败不显示保存成功
反馈Ink 页面状态与宿主语音播报区分处理中、待确认、成功与失败

常见商品和中文金额先用本地规则解析,复杂表述再交给模型。图像和语音提示词要求返回结构化字段,并将不确定金额保留为 null。后续还会验证商品名称、数量、计价方式和金额格式;模型返回 JSON 只意味着数据可以继续检查,不意味着可以直接入账。

照片转换为带真实 MIME 的 data URL,并随账单保存。必要时依据文件头判断 JPEG、PNG 或 WebP;不把短期有效的 blob 地址当成可长期回查的照片。缩略图用于快速定位,选择查看照片后进入全屏。

相机和识别依赖 Rokid 宿主,账单保存于本地,两者应分开说明。这个项目没有自建服务端,也没有第三方 API 密钥配置,但不能据此宣称所有语音与图片都只在设备端处理。

金额与历史记录的正确性

账本内部统一用整数分表示人民币金额。6.50 元先转换为 650 分,再乘以 2 件得到 1300 分,显示时格式化为 13.00 元。输入限制为明确的非负金额且最多两位小数,避免浮点累加和静默截断。

代码 1 utils/ledger.js 中金额转换的计算段 输入校验由完整函数完成

const pieces = raw.split('.');
return cents(
  Number(pieces[0]) * 100 +
  Number(((pieces[1] || '') + '00').slice(0, 2))
);

称重商品若记录的是实际应付总金额,就只累计一次,不能再乘重量或件数。未知价格不会按零元保存。小票总额与手工商品合计不一致时显示待核对信息,商家简称、会员价和优惠也可能造成差异,不能自动把差额判断成收银错误。

持久化使用一个带版本的 JSON 快照,保存历史账单、当前账单标识和照片。打开应用先读取历史;每次确认新增、修改或删除后同步写入,并读回校验。读取失败或数据损坏时停止写入,不能把“读不到旧数据”解释成“没有旧数据”。

容量不足时如何保留记录

只有明确的配额不足错误才触发清理。重试先修改候选副本,按创建时间移除最旧的非当前账单,当前正在编辑的账单受到保护。普通写入异常不清历史;如果即使只保留当前记录仍写不下,就报告未保存,并保留原快照。

这种做法包含写后读回、状态一致性检查和尽力恢复,并不等同于数据库事务保证。跨设备同步和应用层加密尚未实现,卸载或清除应用数据也可能丢失记录。这些限制必须与“退出后能恢复已保存账单”区分开。

真机反馈暴露的跨页面误触

试用时,眼镜端先后出现三类现象:滑动附带确认,一次滑动跨过多个选项,以及语音结束或取消后仍进入录音。后续反馈更具体:商品语音退出后跳到了拍照页面,刚进入结账核对就开始收银录音。

根据这些反馈构造事件序列后,软件复现发现一条共同路径:同一次触控可能由 GlobalHook、Enter、控件 tap 与 clickAiAssist 多个入口上报。页面已经变化,迟到的事件却按新页面的焦点执行,于是触发了“拍照记账”或“说出收银金额”这样的首项。这里没有抓取真机底层事件日志,因此不把这条复现路径称为所有固件的唯一原因。

修复不能只增加一个延时

确认先进入 400ms 可取消队列,随后若出现滑动则撤销;一次完整接触只移动一步。缺少接触边界时,用 500ms 事件静默期合并回声。菜单使用取模计算实现首尾循环,跨边界仍然只走一步。

但是,如果语音自然结束时手指仍按着镜腿,固定保护期过后再松手,旧触摸仍可能被误当成新长按。因此页面切换会消费整个旧接触,直到真实松开;新的明确接触仍允许正常操作,不靠长期锁住界面来掩盖问题。

代码 2 queueInput 的核心逻辑节选 确认绑定创建时的页面版本

const revision = this._inputRevision;
this._gesture.request(() => {
  if (!this._alive || this._inputRevision !== revision) return;
  // 原实现还会核对 action 是否属于当前菜单并同步焦点。
  this.action(action);
});

菜单控件还携带稳定的 data-action 和 data-revision;旧控件事件不能只凭原序号落到新页面。Enter 必须在同一页面完成按下和松开;照片点击也校验页面版本。部分 Ink 运行时只提供 Entity.attributes,因此读取自定义字段时同时兼容 dataset 与 attributes。

录音结束与取消的实现

“说完了”和“取消”有不同语义。说完需要接收可能尚未到达的最终结果;取消则应立即放弃本次输入。两者都不能让旧识别实例的回调继续控制新页面。

代码 3 finishVoice 节选并调整排版 先核对实例身份再处理结果

finishVoice(rec, mode) {
  if (this._rec !== rec) return;
  const text = String(this._voiceText || '').trim();
  this.stopRecognition(true);
  if (text) this.handleText(text, mode);
  else {
    this.voiceBack(mode);
    // 原实现同时给出没有收到语音或收银金额的提示。
  }
}

停止识别使用 _voiceStopping 标识保证只执行一次,连续选择“说完了”不会重复 stop 或延长等待。先设置 1.8 秒兜底,再调用 stop(),同时处理同步返回 onend 和始终不返回 onend 的情况;等待最终结果时仍保留取消入口。

取消时先把当前实例置空,解绑 result、end 和 error 回调,清理计时器,再调用 abort。复杂语音已经交给模型时,通过递增任务编号让迟到结果失效,并保存返回上下文:收银语音取消回结账核对,商品语音取消回购物页。

明确选择才启动能力

onVoiceWakeup 只拦截默认行为,不启动录音也不改变页面;无会话来源的宿主 text 消息不生成草稿。长按与单击一样只确认高亮项。用户仍可使用自然语言输入,但前提是先明确选择对应语音功能。识别是否正确和操作是否经过授权,需要同时成立。

调试与验证

验证分为实际页面逻辑、官方 Ink 运行时和设备使用三层。页面测试运行真实 Ink 脚本与工具模块,替换硬件、时间和模型返回,便于稳定重现迟到事件;最终还从 AIX 解包后再执行这些测试,确认交付内容与验证对象一致。

测试组数量重点
页面流程25 项确认后保存、编辑、只读、删除和取消
手势序列34 项滑动一步、首尾循环、回声不确认
语音退出42 项停止竞态、取消、无内容、旧回调
手动导航60 项跨页旧事件无效,新操作仍然可用
账单与存储16 项配额、写入失败、回读、恢复与一致性
金额与解析14 项中文金额、缺失字段及小票核对

上述 191 项全部通过。官方 Ink 0.19.1 运行时另完成 15 类原生语音与导航、10 类手势检查,浏览器错误为 0。运行时语音链路使用 Chrome 假麦克风与宿主能力桩;它能验证事件与回调流程,不能证明真实语音识别准确率。

同一组 60 项手动导航序列在旧版仅 4 项通过,在新版全部通过。这个对比用于说明已知错误序列得到修复,不是用户满意度、设备覆盖量或真实任务成功率。1.0.4 的完整眼镜复测、AI APP 兼容和卖场环境测试仍是后续验收工作。

图 2 历史记录与恢复入口 官方 Ink 本地示例数据

[配图文件:参赛文档配图/01-历史账单-官方Ink示例.png]

复现方式与成果材料

读者可以按项目目录逐层检查实现。复现前需准备 Node、Python 3、官方 AIX CLI 和 Ink 本地运行时;浏览器检查另需 Playwright 与 Chrome。工程工具含开发机绝对路径,需改成本机位置,不能把下面命令当作零配置安装器。

命令示例 在项目根目录运行 打包可用 --node 和 --cli 指定实际工具路径

node tests/page-regression.cjs
node tests/gesture-regression.cjs
node tests/voice-exit-regression.cjs
node tests/manual-navigation-regression.cjs
node tests/ledger.test.cjs
node tests/intelligence.test.mjs
python3 tools/build_aix.py --output 记账大师-v1.0.4.aix
python3 tools/verify_aix.py 记账大师-v1.0.4.aix

预览服务使用 tools/preview_server.py 的 --aix 参数加载交付包;浏览器测试通过 PREVIEW_URL 指向该服务。预览消息桥只存在于开发工具的内存注入中,不进入交付 AIX,避免把演示入口误带到用户环境。

可验证材料包括文中官方 Ink 界面图、提审用示例操作视频、AIX 完整性报告和版本回归报告。图片与视频均使用明确标注的示例数据,不是实地购物或真实识别效果的证明。文章发布时配图随正文展示,项目源码和仓库地址只在具备真实可访问地址后提供,不虚构仓库链接。

成果与后续计划

当前成果是可以导入的原生 AIX,商品输入、确认、存储、照片回查和结账核对已经连接为完整流程。这次实践最直接的经验是:小屏设备中的“用户想做什么”不仅体现在自然语言里,也体现在一次输入究竟属于哪个页面、哪个识别会话。

后续先完成最新版真机和 AI APP 任务验收,再评估卖场噪声、价签模糊、会员价和长小票等实际输入。图片压缩、优惠解释与小票分段处理属于后续候选方向;每项优化都应先量化问题,再用真实任务验证收益。

来源与项目依据

应用业务与本文围绕记账大师的设计、代码和真实反馈整理。代码片段是项目实现节选,部分为便于排版省略了无关分支,不能独立替代完整模块。配图取自官方 Ink 本地运行;示例价签和小票是项目演示素材。

开发验证使用 @yodaos-pkg/ink 0.19.1,本地 package.json 标明 Apache-2.0,官方运行时只用于开发预览,不随本应用 AIX 打包。设备、语音和模型接口由 Rokid 平台提供;使用时遵守相应平台条款。后续公开仓库或再分发工具时,应保留第三方版权、许可证及所需声明。

AIUI 官方文档 框架与设备能力接口 js.rokid.com/AIUI

AIUI 官方项目 示例与工程结构参考 github.com/yodaos-proj…

Ink 官方项目 原生界面与浏览器运行时 github.com/jsar-projec…

AIUI 十月征文计划 主题与作品要求 forum.rokid.com/post/detail…

可核对的本地材料

实现文件:pages/index/index.ink、utils/gestures.js、utils/intelligence.js、utils/ledger.js。验证依据:验证报告/1.0.4/AIX内实际源码回归.json、官方Ink语音退出回归.json、官方Ink手势回归.json 和 AIX构建校验.json。

版本包:记账大师-v1.0.4.aix;784,192 字节。SHA-256:

交付包校验值

ceabb716a51454d4a12173d2572d7d2aba897289dd596a5d9be35ea120b5dd0e

署名 Dandy