@cabbage0320/agent-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,375 @@
1
+ # AgentPlugin
2
+
3
+ 一个可嵌入任意网站的 AI 聊天助手 SDK。传入接口域名(`serverUrl`)和智能体 ID(`agentId`)即可在页面上挂载一个功能完整的浮动聊天组件。
4
+
5
+ ## 特性
6
+
7
+ - **零依赖接入** — React、ReactDOM、AI SDK、Markdown 渲染器等全部内联打包,宿主页面无需安装任何依赖
8
+ - **UMD / ESM 双格式** — 支持 `<script>` 标签引入和 npm 包 import 两种方式
9
+ - **样式完全隔离** — 通过 Shadow DOM 挂载,所有样式封装在 shadow root 内,宿主页面样式不会渗透进来,widget 样式也不会泄漏出去。CSS 已内联到 JS bundle 中,无需单独引入
10
+ - **纯手写 CSS + CSS 变量主题** — 不依赖 Tailwind CSS,所有样式用语义化类名(`ap-*` 前缀)手写。宿主可通过覆盖 CSS 变量轻松定制主题(主色、字体、圆角等),无需穿透 Shadow DOM
11
+ - **功能完整** — 与主项目一致的 UI/UX:
12
+ - 浮动 Launcher 按钮 + 滑出聊天面板
13
+ - Markdown 渲染(支持 GFM 表格、代码块等)
14
+ - 流式思考过程面板(可手动展开/折叠)
15
+ - `@` 提及选择 Skills(渐进式披露)
16
+ - 语音输入(Web Speech API)
17
+ - `ask_user` 交互式问答卡片
18
+ - 会话恢复(刷新页面后自动恢复历史)
19
+ - 消息编辑 / 重试 / 复制
20
+ - 关闭流式回复时的确认弹窗
21
+ - **SSR 安全** — ESM 构建将重量级依赖(react-markdown 等)拆分为独立 chunk,通过动态 `import()` 延迟加载。在 Next.js 等服务端渲染环境下静态 import 不会触发 `document is not defined` 错误
22
+
23
+ ## 安装
24
+
25
+ ### 方式一:CDN / 本地文件(UMD)
26
+
27
+ 只需引入一个 JS 文件,样式已内联并通过 Shadow DOM 隔离:
28
+
29
+ ```html
30
+ <script src="https://your-cdn.com/agent-plugin.umd.js"></script>
31
+ ```
32
+
33
+ ### 方式二:npm 安装
34
+
35
+ ```bash
36
+ npm install agent-plugin
37
+ # 或
38
+ pnpm add agent-plugin
39
+ # 或
40
+ yarn add agent-plugin
41
+ ```
42
+
43
+ ## 快速开始
44
+
45
+ ### UMD 方式
46
+
47
+ ```html
48
+ <!DOCTYPE html>
49
+ <html lang="zh-CN">
50
+ <head>
51
+ <meta charset="UTF-8" />
52
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
53
+ <title>我的网站</title>
54
+ </head>
55
+ <body>
56
+ <!-- 你的页面内容 -->
57
+ <h1>欢迎来到我的网站</h1>
58
+
59
+ <!-- 引入 SDK 脚本(样式已内联,无需单独引入 CSS) -->
60
+ <script src="./agent-plugin.umd.js"></script>
61
+ <script>
62
+ // 初始化
63
+ AgentPlugin.init({
64
+ serverUrl: "https://your-api-domain.com",
65
+ agentId: "your-agent-uuid"
66
+ });
67
+ </script>
68
+ </body>
69
+ </html>
70
+ ```
71
+
72
+ 全局变量 `AgentPlugin` 会在 `<script>` 加载后自动挂载到 `window` 上。
73
+
74
+ ### npm 方式
75
+
76
+ #### 在普通前端项目中
77
+
78
+ ```ts
79
+ import { init } from "agent-plugin";
80
+
81
+ init({
82
+ serverUrl: "https://your-api-domain.com",
83
+ agentId: "your-agent-uuid",
84
+ });
85
+ ```
86
+
87
+ #### 在 Vue 项目中
88
+
89
+ ```vue
90
+ <script setup>
91
+ import { onMounted, onUnmounted } from "vue";
92
+ import { init, destroy } from "agent-plugin";
93
+
94
+ onMounted(() => {
95
+ init({
96
+ serverUrl: "https://your-api-domain.com",
97
+ agentId: "your-agent-uuid",
98
+ });
99
+ });
100
+
101
+ onUnmounted(() => {
102
+ destroy();
103
+ });
104
+ </script>
105
+ ```
106
+
107
+ #### 在 React 项目中
108
+
109
+ ```tsx
110
+ import { useEffect } from "react";
111
+ import { init, destroy } from "agent-plugin";
112
+
113
+ export default function App() {
114
+ useEffect(() => {
115
+ init({
116
+ serverUrl: "https://your-api-domain.com",
117
+ agentId: "your-agent-uuid",
118
+ });
119
+ return () => destroy();
120
+ }, []);
121
+
122
+ return <div>你的应用内容</div>;
123
+ }
124
+ ```
125
+
126
+ > **注意**:如果你的项目本身也使用了 React 19,SDK 内部打包了自己的 React 实例,两者互不影响。
127
+
128
+ #### 在 Next.js / SSR 项目中
129
+
130
+ 确保只在客户端调用 `init`,避免服务端执行:
131
+
132
+ ```tsx
133
+ import { useEffect } from "react";
134
+ import { init, destroy } from "agent-plugin";
135
+
136
+ export default function Layout({ children }: { children: React.ReactNode }) {
137
+ useEffect(() => {
138
+ init({
139
+ serverUrl: process.env.NEXT_PUBLIC_API_URL!,
140
+ agentId: "your-agent-uuid",
141
+ });
142
+ return () => destroy();
143
+ }, []);
144
+
145
+ return <>{children}</>;
146
+ }
147
+ ```
148
+
149
+ > SDK 的 ESM 构建是 SSR 安全的——静态 import 只会加载轻量入口模块,重量级依赖(React DOM、react-markdown 等)通过动态 `import()` 在 `init()` 调用时才加载,不会在服务端触发 `document is not defined`。
150
+
151
+ ## API
152
+
153
+ ### `init(options)`
154
+
155
+ 挂载聊天组件到页面。重复调用会先卸载已有实例再重新挂载。返回一个 Promise,在组件挂载完成后 resolve。
156
+
157
+ **参数**
158
+
159
+ | 属性 | 类型 | 必填 | 说明 |
160
+ |---|---|---|---|
161
+ | `serverUrl` | `string` | ✅ | 接口域名,如 `"https://api.example.com"`(无需尾斜杠) |
162
+ | `agentId` | `string` | ✅ | 智能体 UUID,用于绑定对话上下文 |
163
+ | `container` | `string \| HTMLElement` | ❌ | 挂载点。传入 CSS 选择器或 DOM 元素;省略则自动在 `<body>` 末尾创建容器 |
164
+
165
+ **示例**
166
+
167
+ ```ts
168
+ // 基础用法 — 自动创建浮动容器
169
+ AgentPlugin.init({
170
+ serverUrl: "https://api.example.com",
171
+ agentId: "abc-123",
172
+ });
173
+
174
+ // 指定挂载点
175
+ AgentPlugin.init({
176
+ serverUrl: "https://api.example.com",
177
+ agentId: "abc-123",
178
+ container: "#chat-mount-point",
179
+ });
180
+ ```
181
+
182
+ ### `destroy()`
183
+
184
+ 卸载聊天组件并移除自动创建的容器。适用于 SPA 路由切换或组件销毁场景。
185
+
186
+ ```ts
187
+ AgentPlugin.destroy();
188
+ ```
189
+
190
+ ### `AiChatWidget`
191
+
192
+ React 组件,可在 React 项目中直接渲染(而非通过 `init` 命令式挂载)。
193
+
194
+ ```tsx
195
+ import { AiChatWidget } from "agent-plugin";
196
+
197
+ function MyPage() {
198
+ return (
199
+ <div>
200
+ <h1>我的页面</h1>
201
+ <AiChatWidget
202
+ serverUrl="https://api.example.com"
203
+ agentId="abc-123"
204
+ />
205
+ </div>
206
+ );
207
+ }
208
+ ```
209
+
210
+ ## 接口约定
211
+
212
+ SDK 会向 `serverUrl` 发起以下请求,服务端需确保对应路由可用并配置 **CORS 跨域**:
213
+
214
+ | 路由 | 方法 | 说明 |
215
+ |---|---|---|
216
+ | `/api/chat` | `POST` | 流式聊天接口(SSE),请求体含 `agentId`、`conversationId`、`guestId`、`activeSkillIds` 等 |
217
+ | `/api/agent/:agentId` | `GET` | 获取智能体信息(`welcome_message`、`skills` 列表) |
218
+ | `/api/chat/conversation` | `GET` | 恢复历史会话,查询参数 `conversationId` + `guestId` |
219
+
220
+ **CORS 示例配置(Express):**
221
+
222
+ ```js
223
+ app.use((req, res, next) => {
224
+ res.header("Access-Control-Allow-Origin", "*"); // 生产环境替换为具体域名
225
+ res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
226
+ res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
227
+ res.header("Access-Control-Expose-Headers", "X-Conversation-Id");
228
+ if (req.method === "OPTIONS") return res.sendStatus(204);
229
+ next();
230
+ });
231
+ ```
232
+
233
+ ## 鉴权(可选)
234
+
235
+ 如果后端接口需要登录态,SDK 会自动从 `localStorage` 读取 `authToken` 并附加 `Authorization: Bearer <token>` 请求头。在调用 `init` 之前写入即可:
236
+
237
+ ```ts
238
+ localStorage.setItem("authToken", "user-jwt-token");
239
+ AgentPlugin.init({ serverUrl: "...", agentId: "..." });
240
+ ```
241
+
242
+ ## 构建产物
243
+
244
+ | 文件 | 格式 | 说明 |
245
+ |---|---|---|
246
+ | `dist/agent-plugin.umd.js` | UMD | 适用于 `<script>` 标签引入,全局变量 `AgentPlugin`。单文件,样式已内联 |
247
+ | `dist/agent-plugin.esm.js` | ESM | 适用于 `import` 引入。轻量入口(re-export),样式已内联 |
248
+ | `dist/chunks/*.js` | ESM | 动态 import 的 chunk(React DOM、widget、react-markdown 等),在 `init()` 调用时按需加载 |
249
+
250
+ > 不再有 `agent-plugin.css` 文件——CSS 源码(`src/styles.css`)是纯手写的,通过 `?raw` 内联到 JS bundle 中。构建只需 `npm run build` 一步。
251
+
252
+ ## 主题定制
253
+
254
+ Widget 的所有视觉属性都通过 CSS 自定义属性(CSS Custom Properties)控制,定义在 Shadow DOM 的 `:host` 上。宿主页面可以在 **挂载容器** 上覆盖这些变量来定制主题。
255
+
256
+ ### 可用变量
257
+
258
+ | 变量 | 默认值 | 说明 |
259
+ |---|---|---|
260
+ | `--ap-primary` | `#059669` | 主色调(按钮、链接、用户消息气泡) |
261
+ | `--ap-primary-hover` | `#047857` | 主色 hover 态 |
262
+ | `--ap-primary-light` | `#ecfdf5` | 主色浅色背景(选中态、徽章) |
263
+ | `--ap-bg` | `#ffffff` | 面板背景色 |
264
+ | `--ap-bg-muted` | `#f9fafb` | 消息列表区背景色 |
265
+ | `--ap-border` | `#e5e7eb` | 边框色 |
266
+ | `--ap-text` | `#1f2937` | 主文字色 |
267
+ | `--ap-text-muted` | `#6b7280` | 次要文字色 |
268
+ | `--ap-danger` | `#dc2626` | 危险色(停止按钮、错误) |
269
+ | `--ap-warning` | `#d97706` | 警告色(继续提示) |
270
+ | `--ap-radius` | `10px` | 常规圆角 |
271
+ | `--ap-radius-lg` | `14px` | 大圆角 |
272
+ | `--ap-panel-width` | `400px` | 面板宽度 |
273
+ | `--ap-panel-height` | `620px` | 面板高度 |
274
+ | `--ap-font-family` | 系统字体栈 | 字体 |
275
+
276
+ 完整变量列表见 [`src/styles.css`](./src/styles.css) 开头的 `:host` 块。
277
+
278
+ ### 定制示例
279
+
280
+ **方式一:CSS**(在宿主页面的全局样式中)
281
+
282
+ ```css
283
+ /* SDK 默认在 <body> 末尾创建一个 div 容器并挂载 Shadow DOM。
284
+ 在该容器上设置 CSS 变量即可穿透到 Shadow DOM 内部。 */
285
+ #agent-plugin-chat-root {
286
+ --ap-primary: #3b82f6; /* 改成蓝色主题 */
287
+ --ap-primary-hover: #2563eb;
288
+ --ap-primary-light: #eff6ff;
289
+ --ap-panel-width: 440px;
290
+ --ap-font-family: "Inter", sans-serif;
291
+ }
292
+ ```
293
+
294
+ > 容器 ID 取决于 `init()` 的 `container` 参数。如果省略该参数,SDK 会自动创建一个 `<div id="agent-plugin-chat-root">`。
295
+
296
+ **方式二:JavaScript**(在 `init()` 之后设置)
297
+
298
+ ```ts
299
+ AgentPlugin.init({ serverUrl: "...", agentId: "..." });
300
+
301
+ // 等待容器创建后设置变量
302
+ requestAnimationFrame(() => {
303
+ const root = document.getElementById("agent-plugin-chat-root");
304
+ root?.style.setProperty("--ap-primary", "#3b82f6");
305
+ });
306
+ ```
307
+
308
+ ## 本地构建
309
+
310
+ ```bash
311
+ cd sdk
312
+ npm install
313
+ npm run build
314
+ ```
315
+
316
+ 产物输出到 `sdk/dist/` 目录。
317
+
318
+ ## 本地预览
319
+
320
+ ```bash
321
+ cd sdk
322
+ npm run build
323
+ # 用任意静态服务器打开 example.html
324
+ npx serve .
325
+ ```
326
+
327
+ 编辑 `example.html` 中的 `serverUrl` 和 `agentId` 为真实值后即可预览效果。
328
+
329
+ ## 浏览器兼容性
330
+
331
+ - Chrome / Edge 90+
332
+ - Firefox 90+
333
+ - Safari 14+
334
+ - 需支持 ES2020、Fetch API、CSS Custom Properties、Shadow DOM
335
+
336
+ 语音输入功能依赖 [Web Speech API](https://developer.mozilla.org/zh-CN/docs/Web/API/Web_Speech_API),在不支持的浏览器中会自动隐藏麦克风按钮,不影响其他功能。
337
+
338
+ ## 文件结构
339
+
340
+ ```
341
+ agent-plugin/
342
+ ├── src/
343
+ │ ├── index.tsx # SDK 入口(init / destroy / AiChatWidget)
344
+ │ ├── styles.css # 纯手写 CSS(CSS 变量主题 + 语义化类名)
345
+ │ ├── lib/
346
+ │ │ └── utils.ts # cn() 类名合并工具(零依赖)
347
+ │ ├── hooks/
348
+ │ │ └── useSpeechRecognition.ts # 语音识别 hook
349
+ │ └── widget/ # 聊天组件
350
+ │ ├── index.tsx # 主组件(接收 serverUrl + agentId)
351
+ │ ├── ChatInput.tsx # 输入框 + @ skill 选择器
352
+ │ ├── ChatHeader.tsx # 顶部标题栏
353
+ │ ├── MessageList.tsx # 消息列表
354
+ │ ├── MessageItem.tsx # 单条消息渲染
355
+ │ ├── ReasoningPanel.tsx # 思考过程面板
356
+ │ ├── AskUserCard.tsx # 交互式问答卡片
357
+ │ ├── ToolCallBlock.tsx # 工具调用状态
358
+ │ ├── Launcher.tsx # 浮动按钮
359
+ │ ├── ConfirmCloseOverlay.tsx # 关闭确认弹窗
360
+ │ ├── RetryOverlay.tsx # 重试确认弹窗
361
+ │ ├── TypingDots.tsx # 打字动画
362
+ │ ├── markdown.tsx # Markdown 渲染器
363
+ │ ├── constants.ts # 常量
364
+ │ ├── types.ts # 类型定义
365
+ │ └── utils.ts # 工具函数
366
+ ├── dist/ # 构建产物
367
+ ├── example.html # 使用示例
368
+ ├── rollup.config.mjs # Rollup 配置
369
+ ├── tsconfig.json
370
+ └── package.json
371
+ ```
372
+
373
+ ## License
374
+
375
+ MIT
@@ -0,0 +1 @@
1
+ export { d as destroy, i as init } from './chunks/index-CwibQfpn.js';