从零打造一个跨框架通用的 AI Agent 插件
📝 前言
在 AI 大模型时代,越来越多的 Web 应用需要集成智能对话功能。然而,前端技术栈的多样性(Vue、React、jQuery 等)给开发者带来了一个难题:如何开发一个一次编写、到处运行的 AI 对话插件?
本文记录了 AI Agent Plugin 项目从诞生到完成的全过程,分享技术选型、架构设计、核心实现以及遇到的挑战与解决方案。
🎯 项目背景与目标
痛点分析
在实际项目中,我们遇到以下问题:
- 技术栈碎片化:公司不同项目使用 Vue 2、Vue 3、React、jQuery 等不同技术栈
- 重复开发成本高:每个技术栈都要单独开发一套 AI 对话组件
- 维护困难:功能更新需要同步修改多个版本
- 样式冲突:插件样式容易被宿主项目覆盖
设计目标
基于以上痛点,我们确定了以下核心目标:
- ✅ 跨框架兼容:一套代码支持 Vue、React、jQuery 等任意前端框架
- ✅ 零依赖:不依赖任何第三方框架,纯原生实现
- ✅ 样式隔离:避免与宿主项目样式冲突
- ✅ TypeScript 支持:提供完整的类型定义
- ✅ 主题定制:支持浅色/深色主题和自定义配色
- ✅ 灵活配置:支持流式/普通响应、四角定位等
🏗️ 技术选型与架构设计
1. 模块化规范:UMD
为了实现跨框架兼容,我们选择了 UMD (Universal Module Definition) 规范:
1 | // UMD 模式可以同时支持多种加载方式 |
通过 Webpack 的 output.library 配置,我们可以自动生成 UMD 格式的代码:
1 | // webpack.config.js |
2. 开发语言:TypeScript
使用 TypeScript 开发带来的优势:
- 类型安全:编译时捕获错误,减少运行时 bug
- 智能提示:IDE 提供完整的代码提示和自动补全
- 可维护性:接口定义即文档,代码意图清晰
核心类型定义:
1 | export interface AIAgentOptions { |
3. 样式隔离:命名空间 + CSS 变量
为了避免样式冲突,我们采用了两种策略:
策略一:命名空间隔离
所有 CSS 类名添加 ai-agent- 前缀:
1 | .ai-agent-panel { /* 面板 */ } |
策略二:CSS 变量动态主题
使用 CSS 变量实现主题定制:
1 | :root { |
通过 JavaScript 动态注入自定义颜色:
1 | private injectStyles(): void { |
🔧 核心功能实现
1. 插件生命周期管理
1 | class AIAgent { |
2. 双模式 API 调用
插件支持普通模式和流式模式两种 API 调用方式:
普通模式(一次性返回)
1 | private async sendNormalMessage(text: string): Promise<void> { |
流式模式(SSE 实时推送)
1 | private async sendStreamMessage(text: string): Promise<void> { |
3. 智能端点路由
根据配置自动选择合适的 API 端点:
1 | private calculateEndpoints() { |
🎨 主题定制系统
渐进式主题方案
我们设计了三层主题定制方案:
第一层:预设主题
1 | new AIAgent({ |
第二层:自定义颜色
1 | new AIAgent({ |
第三层:完整配色方案
1 | new AIAgent({ |
CSS 变量的优势
使用 CSS 变量而非直接修改样式的好处:
- 性能更好:浏览器原生支持,无需 JavaScript 逐个修改元素
- 优先级明确:CSS 变量继承机制清晰
- 响应式支持:可以配合媒体查询实现响应式主题
- 易于维护:修改一处,全局生效
🚀 打包与发布
Webpack 多产物输出
为了满足不同场景的需求,我们配置了三种输出格式:
1 | // webpack.config.js |
TypeScript 类型声明
使用 ts-loader 自动生成类型声明文件:
1 | module: { |
生成的类型文件结构:
dist/
├── ai-agent.js # UMD 格式(未压缩)
├── ai-agent.min.js # UMD 格式(压缩)
├── ai-agent.esm.js # ESM 格式
└── types/
├── ai-agent.d.ts # 主入口类型声明
└── types/
└── index.d.ts # 类型定义
NPM 包配置
1 | { |
🎯 跨框架使用实践
1. jQuery 项目
1 | <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> |
2. React 项目
1 | import React, { useEffect } from 'react'; |
3. Vue 3 项目
1 | <template> |
🐛 踩坑与解决方案
问题 1:TypeScript 类型不兼容
现象:使用 Required<AIAgentOptions> 导致可选属性变必选
1 | // ❌ 错误写法 |
解决方案:直接使用原接口类型
1 | // ✅ 正确写法 |
问题 2:端口占用导致开发服务器无法启动
现象:Error: listen EADDRINUSE: address already in use :::9000
解决方案:PowerShell 脚本清理占用端口
1 | # 查找并停止占用 9000 端口的进程 |
问题 3:CSS 变量在 IE 中不生效
现象:IE 11 不支持 CSS 变量
解决方案:提供回退方案
1 | .ai-agent-btn { |
或者使用 PostCSS 插件自动生成回退:
1 | // postcss.config.js |
问题 4:流式响应解析错误
现象:SSE 数据格式不规范导致解析失败
解决方案:增强容错处理
1 | for (const line of lines) { |
📊 性能优化
1. 代码分割与按需加载
虽然插件本身较小(~20KB),但仍可优化:
1 | // 懒加载 Markdown 渲染库 |
2. 防抖与节流
输入框添加防抖,避免频繁触发:
1 | private handleInput = debounce((text: string) => { |
3. 虚拟滚动(未来规划)
对话消息过多时,使用虚拟滚动:
1 | // 只渲染可视区域的消息 |
🔮 未来规划
短期目标(v0.1.0)
- [ ] 支持 Markdown 渲染
- [ ] 代码高亮显示
- [ ] 消息历史持久化(LocalStorage)
- [ ] 消息重发/编辑功能
- [ ] 多语言支持(i18n)
中期目标(v0.2.0)
- [ ] 语音输入/输出
- [ ] 文件上传(图片、文档)
- [ ] 多会话管理
- [ ] 自定义快捷指令
- [ ] WebSocket 支持
长期目标(v1.0.0)
- [ ] 插件市场(第三方扩展)
- [ ] AI 能力扩展(绘画、搜索、代码执行)
- [ ] 团队协作功能
- [ ] 移动端适配(React Native)
- [ ] Electron 桌面应用
💡 经验总结
技术层面
- 选择合适的模块化规范:UMD 确实能实现跨框架兼容
- TypeScript 提升开发体验:类型安全减少 80% 的运行时错误
- CSS 变量是主题定制的最佳实践:灵活、高效、易维护
- 流式响应提升用户体验:实时反馈比加载动画更友好
工程层面
- 文档优先:README 写得好,issue 减少一半
- 示例丰富:提供 jQuery/React/Vue 示例,降低使用门槛
- 语义化版本:严格遵循 SemVer,避免破坏性更新
- 自动化测试(规划中):保证多框架环境下的稳定性
产品层面
- 保持简单:不要过度设计,先满足核心需求
- 渐进增强:功能分层,让用户自由选择
- 用户反馈驱动:根据 issue 和 PR 优化功能
- 社区运营:开源项目需要持续的社区互动
🙏 致谢
感谢以下开源项目的启发:
- marked - Markdown 解析器
- highlight.js - 代码高亮
- webpack - 现代化构建工具
感谢所有给项目提 issue 和 PR 的开发者!
📚 参考资料
- UMD 模块规范
- TypeScript Handbook
- CSS Variables (Custom Properties)
- Server-Sent Events
- Webpack Output Configuration
结语
从零打造一个跨框架通用插件是一次有趣的技术实践,它让我们深入理解了:
- 模块化规范的本质
- TypeScript 的工程化价值
- CSS 架构的设计思想
- 前端构建工具的工作原理
希望这篇文章能给正在开发跨框架组件的开发者一些启发。如果你有任何问题或建议,欢迎在 GitHub 上提 issue 讨论!
项目地址:https://github.com/hujinbin/ai-agent
NPM 包:npm install ai-agent-plugin
开源协议:MIT License
觉得有帮助?给个 ⭐ Star 吧!