ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

手把手编写儿童手机远程监控App:用Vue3与AI Agent打造智能守护

手把手编写儿童手机远程监控App:用Vue3与AI Agent打造智能守护 1. 从嘟妈首页到守护原型Vue3 AI Agent 的远程监控开发场景儿童手机远程监控 App 的核心诉求很直接家长端能随时看到孩子设备的状态、位置和音视频画面孩子端在后台稳定驻留、不掉线。我这次要做的「嘟妈」就是家长端用 Vue3 写一套能兼容 Web、App、小程序的界面「嘟宝」是孩子端用原生 Android 拿最大权限保活。两者之间通过 MQTT 做信令、WebRTC 做点对点音视频不需要中转服务器。为什么选 Vue3因为家长端要快速迭代Vant 组件库能直接出移动端 UIVite 启动快组合式 API 写状态逻辑清晰。为什么引入 AI Agent因为像「我的」页面、扫码页、设备列表页这类 CRUD 界面手写模板和样式很费时间用 GitHub Copilot Chat 一句话就能生成可用的骨架我再裁剪成业务需要的形态。实测下来一个中等复杂度的页面从描述到可运行大概能省掉一半的重复劳动。这篇文章面向的是想自己搭一套守护原型的开发者或者正在做 IoT 音视频方向、需要快速验证链路的同学。你会看到Vue3 项目怎么初始化、AI Agent 怎么接入、MQTT/WebRTC 怎么串起来、模型调用怎么用统一 Key 管理以及跑起来之后怎么验证「家长端真的能收到孩子端的数据」。全程可复制不跳步。需要提前说明的是本文不涉及任何网络访问工具所有依赖都通过公开的包管理器和官方 API 获取。模型调用部分我会用 TaoToken 做统一通道这样你不用在多个模型供应商之间来回切换 Key。2. TaoToken 前置统一 Key 与 API 通道管理模型调用在写监控 App 的过程中AI Agent 不只是写代码用运行时也可能需要调用模型做消息摘要、异常判断、语音转文字后的语义分析。如果每个能力都去接一家供应商Key 管理会非常乱。TaoToken 的作用就是把这些模型调用收敛到一个 Base URL 和一把 Key 上代码里只改 Model ID 就能切换模型。你可以先到官网了解整体能力https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注册后在控制台创建 API Key地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。API 的基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接用于代码里的 baseURL。如果你只是想在浏览器里先验证模型能不能通可以用模型对话页面https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。长期做编码和 Agent 开发的话Coding Plan 更合适https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 控制台入口是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。这里要强调一个原则TaoToken 是模型调用的统一通道不是用来替代 VS Code 或 Copilot 的编辑器插件。Copilot 负责在 IDE 里生成代码TaoToken 负责你 App 运行时或脚本里调用模型。两者分工不同不要混在一起。在项目里我建议把模型配置放在环境变量里不要硬编码。Vite 项目用.env.local存敏感信息.env.example存模板。这样你提交到 GitHub 时不会泄露 Key。下面是一个最小配置示例# .env.local VITE_TAOTOKEN_BASE_URLhttps://taotoken.net/api VITE_TAOTOKEN_API_KEYsk-你的Key VITE_TAOTOKEN_MODEL_IDgpt-4o-mini注意 Model ID 要和你账号里可用的模型一致不要编造。如果你不确定有哪些模型可以在控制台或文档里查。实测下来把 Base URL、Key、Model ID 三件套写全后面排障会轻松很多因为 401 和 404 能一眼区分是 Key 问题还是模型名问题。3. 可复制配置Vue3 项目初始化与 AI Agent 接入先初始化项目。我用 Vite Vue3 TypeScript包管理器用 pnpm。命令如下pnpm create vite duma-web --template vue-ts cd duma-web pnpm install pnpm add vant vue-router pinia mqtt pnpm add -D types/node然后配置 Vite 别名和代理。在vite.config.ts里加上指向src这样导入路径短一些import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, src) } }, server: { host: 0.0.0.0, port: 5173 } })接着在src/main.ts里注册 Vant 和 Piniaimport { createApp } from vue import { createPinia } from pinia import Vant from vant import vant/lib/index.css import App from ./App.vue import router from ./router const app createApp(App) app.use(createPinia()) app.use(router) app.use(Vant) app.mount(#app)AI Agent 接入分两部分。第一部分是 IDE 里的 GitHub Copilot 和 Copilot Chat在 VS Code 扩展商店搜索安装登录后右侧会出现 Chat 面板。第二部分是运行时的模型调用我封装一个src/api/ai.ts用 TaoToken 的 Base URLconst BASE_URL import.meta.env.VITE_TAOTOKEN_BASE_URL const API_KEY import.meta.env.VITE_TAOTOKEN_API_KEY const MODEL_ID import.meta.env.VITE_TAOTOKEN_MODEL_ID export async function chatCompletion(prompt: string) { const res await fetch(${BASE_URL}/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_KEY} }, body: JSON.stringify({ model: MODEL_ID, messages: [{ role: user, content: prompt }], temperature: 0.3 }) }) if (!res.ok) { const err await res.text() throw new Error(模型调用失败: ${res.status} ${err}) } const data await res.json() return data.choices?.[0]?.message?.content ?? }这段代码里Base URL、Key、Model ID 三件套都来自环境变量。如果你用 Cline MCP 或 Codex 的auth.json配置逻辑类似核心就是这三项。比如 Codex 的auth.json里会有base_url、api_key、model字段填的也是同一套值。MQTT 连接我放在 Pinia store 里用mqtt包连公共 broker 做原型验证。生产环境要换成自己的 broker 并加 TLS。store 里维护duma在线状态和最新消息import { defineStore } from pinia import mqtt from mqtt export const useMQTTStore defineStore(mqtt, { state: () ({ client: null as mqtt.MqttClient | null, duma: false, datamsg: }), actions: { connect(broker: string, topic: string) { this.client mqtt.connect(broker) this.client.on(connect, () { this.client?.subscribe(topic) }) this.client.on(message, (_t, payload) { this.datamsg payload.toString() this.duma true }) } } })这样家长端就能实时收到孩子端发来的状态消息。WebRTC 部分我单独放一个src/utils/rtc.ts负责创建 RTCPeerConnection、交换 SDP、处理 ICE candidate。原型阶段先用 MQTT 传 SDP后面再换成更安全的信令通道。4. 验证请求与成功结果监控功能跑通清单配置写完怎么确认真的通了我按顺序验证这几项。第一项模型调用。在浏览器控制台或写个临时脚本调chatCompletion(你好)如果返回文本说明 Base URL、Key、Model ID 都对。如果报 401检查 Key 是否复制完整如果报 404检查 Model ID 是否写错如果报local proxy failed说明你本地网络层有额外代理配置需要先排除。第二项MQTT 连接。启动项目后在onMounted里调用mqtt.connect(wss://broker.emqx.io:8084/mqtt, duma/status)然后用 MQTT 客户端工具往这个 topic 发一条消息。家长端页面上的「最新消息」应该立刻更新连接状态从「未连接」变成「已连接」。第三项WebRTC 点对点。孩子端 Android 用mAgentWeb封装 WebView解决全屏和摄像头权限问题。家长端点击「发起通话」通过 MQTT 发 offer孩子端回 answer双方交换 ICE 后建立通道。验证成功的标志是家长端能看到孩子端桌面共享的视频画面延迟在局域网内通常低于 300ms。第四项定位更新。孩子端用高德定位 SDK 定时上报经纬度到 MQTT topic家长端订阅后在地图上打点。验证时看地图标记是否随孩子端移动而更新。第五项后台驻留。孩子端开启前台服务任务栏显示常驻通知锁屏后 MQTT 长连接不断。验证方法是锁屏 10 分钟后再发消息家长端仍能收到。我把这些整理成一个验证清单你可以照着打勾验证项预期结果常见失败原因模型调用返回文本Key 错、Model ID 错、网络层代理MQTT 连接状态变已连接broker 地址错、topic 不匹配WebRTC 通话看到视频画面SDP 未交换、ICE 未连通定位更新地图标记移动权限未开、上报间隔太长后台驻留锁屏后仍在线前台服务未启、电池优化拦截实测下来最容易卡住的是 WebRTC 的 ICE 阶段因为涉及 STUN/TURN。原型阶段可以先用公共 STUN生产环境要自建 TURN。另外reading choices这类报错通常出现在解析模型返回时说明返回结构和你预期的不一致打印完整响应体就能定位。5. 本篇常见错排查401、local proxy failed 与 OAuth排障这块我按真实报错来说不绕弯子。401 Unauthorized。这是模型调用最常见的错。原因通常是 Key 没带、Key 过期、或者Authorization头格式不对。正确格式是Bearer sk-xxx注意 Bearer 后面有一个空格。如果你用的是 TaoToken 的 Key去控制台确认 Key 状态地址是 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。另外检查.env.local是否被 Vite 读取变量名必须以VITE_开头否则前端拿不到。local proxy failed。这个报错说明请求在到达 TaoToken 之前就被本地网络层拦截了。常见原因是系统或 IDE 里配置了额外的代理导致请求发不出去。解决方法是检查环境变量HTTP_PROXY、HTTPS_PROXY以及 VS Code 的http.proxy设置。如果你在容器里跑检查容器网络配置。这个错和 TaoToken 本身无关是本地链路问题。reading choices 报错。这通常发生在data.choices[0]为 undefined 时。原因是模型返回结构和你预期的不一样可能是模型名不对导致返回了错误对象也可能是流式返回没处理完。解决方法是在chatCompletion里先打印JSON.stringify(data)确认结构后再取字段。加个可选链data.choices?.[0]?.message?.content能避免直接崩溃。OAuth 登录失败。如果你在 VS Code 里登录 GitHub Copilot 时卡住先确认扩展版本是否最新然后检查账号是否有 Copilot 权限。Copilot Free 对一般开发者够用但需要 GitHub 账号完成授权。如果一直转圈试试退出账号重新登录或者换一个网络环境。注意这里说的是 GitHub 账号授权不是模型 API 的 Key。MQTT 连不上。检查 broker 地址协议是否正确WebSocket 用wss://或ws://端口要匹配。topic 要区分大小写。如果用的是公共 broker注意它有频率限制原型验证够用生产要换。WebRTC 黑屏。先看浏览器控制台有没有 ICE 失败日志。如果是局域网可以不加 STUN 直接连如果跨网络必须配 STUN/TURN。另外检查孩子端 WebView 是否授予了摄像头和麦克风权限mAgentWeb封装时要处理onPermissionRequest。这些错我都踩过核心思路是先确认请求有没有发出去再看返回结构最后看业务逻辑。分层排查比盲目改代码快得多。6. 语义一致 CTA把守护原型继续做下去到这里家长端的骨架已经能跑Vue3 项目初始化完成AI Agent 能生成页面MQTT 能收消息WebRTC 能通视频模型调用走统一通道。接下来你可以继续做的方向有几个把扫码绑定做完整、加设备列表多孩子管理、把消息记录存 SQLite 做历史回看、用模型对异常消息做语义告警。如果你在接入模型调用时遇到 Key 或 Base URL 的问题直接看接入文档最省时间https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。想先验证模型通不通用模型对话页面https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 。长期做编码和 Agent 开发Coding Plan 更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 。Key 管理在控制台https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 。最后分享一个实用技巧把.env.local加入.gitignore同时维护一份.env.example只写变量名不写值。这样团队协作时不会泄露 Key新人克隆后照着填就行。另外模型调用加一层重试和超时避免网络抖动导致页面卡死。守护类 App 对稳定性要求高这些细节比功能数量更重要。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进