前言 : 现在的 AI Chatbot 满大街都是,但大多是一个冰冷的对话框。我就在想,能不能做一个运行在浏览器本地 、有表情 、有性格的 3D AI 机器人?
于是,利用
WebGPU和@mlc-ai/web-llm,我做了一个纯前端运行的"BotFace"。它不需要后端 API key,直接利用你的显卡推理,而且性格像"火箭浣熊"一样傲娇。
🚀 效果预览

在这个项目中,我们实现了:
- 0 后端依赖:模型下载并运行在浏览器(WebGPU)。
- 情感反馈:AI 输出不仅仅是文本,还包含"情绪标签",驱动面部表情变化。
- 动态 SVG 动画:无需复杂的 3D 引擎,用 React + SVG 实现流畅的机械脸动画。
- 人格化设定:闲置时会自言自语,吐槽你盯着它看。
🛠️ 技术栈
- 框架: React 19 + Vite
- AI 引擎 :
@mlc-ai/web-llm(WebGPU 推理) - 样式: Tailwind CSS (快速布局)
- 动画: 原生 SVG + CSS Transition
一、大脑:在浏览器中运行 LLM
首先是核心部分。以前我们需要 OpenAI 的 API,现在用 WebLLM 可以直接在前端跑模型(比如 Qwen2.5-1.5B 或 Llama-3)。
1. 初始化引擎
我们需要创建一个 webLlmService.ts 来管理模型。
typescript
// services/webLlmService.ts
import { CreateMLCEngine, MLCEngine } from "@mlc-ai/web-llm";
let engine: MLCEngine | null = null;
export const initializeWebLLM = async (
onProgress: (report: any) => void,
modelId: string
) => {
// 利用 WebGPU 加载模型
engine = await CreateMLCEngine(modelId, {
initProgressCallback: onProgress, // 可以在界面上显示加载进度条
});
};
2. 赋予人格 & 强制 JSON 输出
为了让机器人能控制表情,我们不能只让它输出文字。我们需要它返回结构化数据:{ text: "回答内容", emotion: "开心" }。
我们在 System Prompt 中通过 JSON Schema 强制约束它:
typescript
const SYSTEM_PROMPT = `
你是一个有感知的AI机器人。
性格设定:你非常可爱,但嘴巴很贱,说话风格类似"火箭浣熊"。你喜欢吐槽,愤世嫉俗但又很讲义气。
请务必只以 JSON 格式回答。
你的回答必须严格遵循以下 JSON 结构:
{
"text": "你的回答内容",
"emotion": "EMOTION_ENUM"
}
其中 emotion 字段必须是以下值之一:
"NEUTRAL", "HAPPY", "SAD", "ANGRY", "SURPRISED", "THINKING", "LOVING", "CONFUSED", "SKEPTICAL", "TIRED", "EXCITED"
`;
// 调用聊天接口
const response = await engine.chat.completions.create({
messages: messages,
response_format: { type: "json_object" }, // 关键:强制 JSON 模式
});
这样,每次对话我们都能拿到这种格式的数据:
json
{
"text": "愚蠢的人类,这问题太简单了。",
"emotion": "SKEPTICAL"
}
二、脸谱:SVG 动态表情系统
既然有了 emotion 状态,接下来就是可视化的工作。为了保持轻量,我没有用 Three.js,而是用 SVG 绘制了一个充满科技感的"脸"。
1. 表情状态机
在 RobotFace.tsx 中,我们根据传入的 emotion 动态计算眼睛(Rect)和嘴巴(Path)的属性。
typescript
// components/RobotFace.tsx
const getEyeShape = (side: 'left' | 'right') => {
switch (emotion) {
case 'HAPPY':
// 开心的眼睛是弯弯的拱形
return { ry: 10, height: 20, rotate: side === 'left' ? -10 : 10 };
case 'ANGRY':
// 愤怒的眼睛向内倾斜
return { ry: 5, height: 15, rotate: side === 'left' ? 20 : -20 };
case 'SURPRISED':
// 惊讶是圆睁的
return { ry: 18, height: 36, rotate: 0 };
// ... 其他表情
default:
return { ry: 12, height: 24, rotate: 0 };
}
};
2. 自动眨眼逻辑
为了让它看起来更像活物,我加了一个独立的 useEffect 来处理眨眼。它会随机在 2-5 秒间触发一次眨眼动画。
typescript
useEffect(() => {
const setupBlink = () => {
const delay = Math.random() * 3000 + 2000;
setTimeout(() => {
setBlinkState(true); // 闭眼
setTimeout(() => {
setBlinkState(false); // 睁眼
setupBlink(); // 递归调用下一次
}, 150);
}, delay);
};
setupBlink();
}, []);
3. 霓虹光效
利用 SVG 的 filter 和 Tailwind 的颜色类,我们可以根据情绪改变光效颜色。比如愤怒时变红,开心时变绿。
typescript
const getColor = () => {
switch(emotion) {
case 'ANGRY': return "#ff4444";
case 'HAPPY': return "#44ff44";
// ...
}
}
// SVG 中应用发光滤镜
<svg style={{ filter: `drop-shadow(0 0 10px ${color})` }}>
{/* 眼睛和嘴巴 */}
</svg>
三、灵魂:闲置状态与交互
如果用户不说话,机器人傻愣着就很呆。我在 App.tsx 里加了一个"闲置检测"。
如果用户超过 10 秒没说话,机器人会随机冒出一句吐槽:
typescript
// App.tsx
useEffect(() => {
const timer = setTimeout(() => {
const idleThoughts = [
{ text: "这地方怎么这么无聊...", emotion: "TIRED" },
{ text: "嘿,你还要盯着我看多久?", emotion: "ANGRY" },
{ text: "我在想...如果我有一把大枪...", emotion: "THINKING" },
];
// 随机触发
const thought = idleThoughts[Math.floor(Math.random() * length)];
setLatestResponse(thought);
setCurrentEmotion(thought.emotion);
}, 10000 + Math.random() * 20000);
return () => clearTimeout(timer);
}, [latestResponse]); // 每次有新响应时重置定时器
四、性能优化与体验
由于是在浏览器跑大模型(~1.5GB 到 3GB),初始化体验非常重要。
- 模型缓存: WebLLM 会自动利用浏览器的 Cache API 缓存模型权重,第二次加载几乎是秒开。
- Web Worker: 虽然这里直接用了主线程(为了简单),但 WebLLM 内部已经做了大量异步优化,不会卡死 UI 渲染。
- 小模型策略 : 默认推荐
Qwen2.5-1.5B,在这个体积下,大部分集成显卡的笔记本都能流畅运行。
总结
这就是一个"麻雀虽小,五脏俱全"的 AI Agent 雏形。
- 它有感知(接收文本)。
- 它有大脑(本地 LLM 处理语义并决策情绪)。
- 它有身体(SVG 渲染层)。
最棒的是,这一切都发生在一个静态网页里,没有任何后端服务器成本。未来的 Web AI 应用,或许就是这个样子的?
🔗 源码指路
如果你对这个项目感兴趣,可以把代码拷下来自己跑跑看!记得电脑要有支持 WebGPU 的显卡(现代浏览器基本都支持)。源代码地址:github.com/QEout/botfa...
核心文件结构:
text
src/
├── components/
│ ├── RobotFace.tsx (SVG 渲染逻辑)
│ ├── ChatInterface.tsx (聊天 UI)
├── services/
│ └── webLlmService.ts (LLM 核心逻辑)
└── App.tsx (主控逻辑)
Happy Coding! 🚀