无需后端!用 React + WebLLM 把大模型装进浏览器,手撸一个“有脾气”的 AI 机器人 🤖

前言 : 现在的 AI Chatbot 满大街都是,但大多是一个冰冷的对话框。我就在想,能不能做一个运行在浏览器本地有表情有性格的 3D AI 机器人?

于是,利用 WebGPU@mlc-ai/web-llm,我做了一个纯前端运行的"BotFace"。它不需要后端 API key,直接利用你的显卡推理,而且性格像"火箭浣熊"一样傲娇。

🚀 效果预览

在这个项目中,我们实现了:

  1. 0 后端依赖:模型下载并运行在浏览器(WebGPU)。
  2. 情感反馈:AI 输出不仅仅是文本,还包含"情绪标签",驱动面部表情变化。
  3. 动态 SVG 动画:无需复杂的 3D 引擎,用 React + SVG 实现流畅的机械脸动画。
  4. 人格化设定:闲置时会自言自语,吐槽你盯着它看。

🛠️ 技术栈

  • 框架: 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),初始化体验非常重要。

  1. 模型缓存: WebLLM 会自动利用浏览器的 Cache API 缓存模型权重,第二次加载几乎是秒开。
  2. Web Worker: 虽然这里直接用了主线程(为了简单),但 WebLLM 内部已经做了大量异步优化,不会卡死 UI 渲染。
  3. 小模型策略 : 默认推荐 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! 🚀

相关推荐
消防大队VUE支队6 小时前
🗓️ 2262年将有两个春节!作为前端的你,日历控件真的写对了吗?
前端·javascript
鸭蛋超人不会飞7 小时前
axios简易封装,适配H5开发
前端·javascript·vue.js
风止何安啊7 小时前
从 “翻页书” 到 “魔术盒”:React 路由凭啥如此丝滑?
前端·react.js·面试
徐小夕7 小时前
10k Star 的开源 AI 记忆引擎:6 行代码,用图谱+向量打造永不遗忘的 AI
前端·后端·github
前端不太难7 小时前
Vue 项目路由 + Layout 的最佳实践
前端·javascript·vue.js
Jolyne_7 小时前
个人积累的一些前端问题解决方案(理论或实践,持续更新....)
前端
程序员祥云7 小时前
港股证劵 社招 一面
前端·面试
qq_4783775157 小时前
python cut_merge video, convert video2gif, cut gif
java·前端·python
巴拉巴拉~~7 小时前
Flutter 通用列表刷新加载组件 CommonRefreshList:下拉刷新 + 上拉加载 + 状态适配
前端·javascript·flutter