1. 从嘟妈首页到守护原型:Vue3 + AI Agent 的远程监控开发场景
儿童手机远程监控 App 的核心诉求很直接:家长端能随时看到孩子设备的状态、位置和音视频画面,孩子端在后台稳定驻留、不掉线。我这次要做的「嘟妈」就是家长端,用 Vue3 写一套能兼容 Web、App、小程序的界面;「嘟宝」是孩子端,用原生 Android 拿最大权限保活。两者之间通过 MQTT 做信令、WebRTC 做点对点音视频,不需要中转服务器。
为什么选 Vue3?因为家长端要快速迭代,Vant 组件库能直接出移动端 UI,Vite 启动快,组合式 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_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。注册后在控制台创建 API Key,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。API 的基础地址是 https://taotoken.net/api ,注意这个地址不带 UTM 参数,直接用于代码里的 baseURL。
如果你只是想在浏览器里先验证模型能不能通,可以用模型对话页面:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。长期做编码和 Agent 开发的话,Coding Plan 更合适:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,控制台入口是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。
这里要强调一个原则:TaoToken 是模型调用的统一通道,不是用来替代 VS Code 或 Copilot 的编辑器插件。Copilot 负责在 IDE 里生成代码,TaoToken 负责你 App 运行时或脚本里调用模型。两者分工不同,不要混在一起。
在项目里,我建议把模型配置放在环境变量里,不要硬编码。Vite 项目用.env.local存敏感信息,.env.example存模板。这样你提交到 GitHub 时不会泄露 Key。下面是一个最小配置示例:
# .env.local VITE_TAOTOKEN_BASE_URL=https://taotoken.net/api VITE_TAOTOKEN_API_KEY=sk-你的Key VITE_TAOTOKEN_MODEL_ID=gpt-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 和 Pinia:
import { 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 URL:
const 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_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。另外检查.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_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。想先验证模型通不通,用模型对话页面:https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。长期做编码和 Agent 开发,Coding Plan 更划算:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。Key 管理在控制台:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。
最后分享一个实用技巧:把.env.local加入.gitignore,同时维护一份.env.example只写变量名不写值。这样团队协作时不会泄露 Key,新人克隆后照着填就行。另外,模型调用加一层重试和超时,避免网络抖动导致页面卡死。守护类 App 对稳定性要求高,这些细节比功能数量更重要。