ai-chat-ui-kit 0.1.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/.eslintrc.cjs +74 -0
- package/.github/actions/screenshot/action.yml +35 -0
- package/.github/workflows/pages.yml +46 -0
- package/README.md +285 -0
- package/docs/README.md +176 -0
- package/docs/api/components.md +344 -0
- package/docs/api/core.md +349 -0
- package/docs/chat-style-1-minimal.html +78 -0
- package/docs/chat-style-2-neon.html +74 -0
- package/docs/chat-style-3-glass.html +73 -0
- package/docs/chat-style-4-terminal.html +84 -0
- package/docs/chat-style-5-gradient.html +69 -0
- package/docs/chat-style-6-corporate.html +116 -0
- package/docs/examples/basic-chat.md +291 -0
- package/docs/examples/custom-plugins.md +431 -0
- package/docs/examples/multi-model.md +466 -0
- package/docs/guide/api-adapters.md +431 -0
- package/docs/guide/getting-started.md +244 -0
- package/docs/guide/headless-mode.md +508 -0
- package/docs/guide/plugins.md +416 -0
- package/docs/guide/themes.md +327 -0
- package/docs/index.html +256 -0
- package/docs/theme-preview-1-minimal.html +74 -0
- package/docs/theme-preview-2-neon.html +73 -0
- package/docs/theme-preview-3-glass.html +77 -0
- package/docs/theme-preview-4-terminal.html +86 -0
- package/docs/theme-preview-5-gradient.html +79 -0
- package/docs/theme-preview-6-corporate.html +71 -0
- package/examples/index.html +414 -0
- package/examples/react-app/App.tsx +131 -0
- package/examples/react-app/index.html +12 -0
- package/examples/react-app/main.tsx +15 -0
- package/examples/react-app/package.json +24 -0
- package/examples/vue-app/index.html +12 -0
- package/examples/vue-app/package.json +22 -0
- package/examples/vue-app/src/App.vue +145 -0
- package/examples/vue-app/src/main.ts +9 -0
- package/package.json +44 -0
- package/packages/components/package.json +25 -0
- package/packages/components/src/chat/chat.css +80 -0
- package/packages/components/src/chat/chat.ts +236 -0
- package/packages/components/src/index.ts +36 -0
- package/packages/components/src/input/input.css +52 -0
- package/packages/components/src/input/input.ts +116 -0
- package/packages/components/src/markdown/markdown.css +118 -0
- package/packages/components/src/markdown/markdown.ts +229 -0
- package/packages/components/src/message/message.css +56 -0
- package/packages/components/src/message/message.ts +72 -0
- package/packages/components/src/styles/global.css +43 -0
- package/packages/components/src/tool-call/tool-call.css +98 -0
- package/packages/components/src/tool-call/tool-call.ts +171 -0
- package/packages/components/src/types.ts +55 -0
- package/packages/components/src/utils/helpers.ts +128 -0
- package/packages/components/tsconfig.json +25 -0
- package/packages/components/tsup.config.ts +18 -0
- package/packages/core/package.json +47 -0
- package/packages/core/pnpm-lock.yaml +2032 -0
- package/packages/core/pnpm-workspace.yaml +2 -0
- package/packages/core/src/api/adapters.ts +717 -0
- package/packages/core/src/api/base.ts +210 -0
- package/packages/core/src/api/index.ts +54 -0
- package/packages/core/src/index.ts +93 -0
- package/packages/core/src/parser/latex.ts +274 -0
- package/packages/core/src/parser/markdown.test.ts +58 -0
- package/packages/core/src/parser/markdown.ts +206 -0
- package/packages/core/src/parser/mermaid.ts +276 -0
- package/packages/core/src/plugins/PluginManager.ts +232 -0
- package/packages/core/src/plugins/builtin.ts +406 -0
- package/packages/core/src/store/ChatStore.ts +163 -0
- package/packages/core/src/store/ModelConfigStore.ts +136 -0
- package/packages/core/src/store/ToolCallStore.ts +164 -0
- package/packages/core/src/store/base.ts +75 -0
- package/packages/core/src/types/index.ts +133 -0
- package/packages/core/tsup.config.ts +18 -0
- package/packages/themes/package.json +33 -0
- package/packages/themes/src/corporate/index.ts +52 -0
- package/packages/themes/src/corporate/theme.css +228 -0
- package/packages/themes/src/glass/index.ts +52 -0
- package/packages/themes/src/glass/theme.css +237 -0
- package/packages/themes/src/gradient/index.ts +53 -0
- package/packages/themes/src/gradient/theme.css +218 -0
- package/packages/themes/src/index.ts +13 -0
- package/packages/themes/src/minimal/index.ts +52 -0
- package/packages/themes/src/minimal/theme.css +198 -0
- package/packages/themes/src/neon/index.ts +52 -0
- package/packages/themes/src/neon/theme.css +233 -0
- package/packages/themes/src/terminal/index.ts +52 -0
- package/packages/themes/src/terminal/theme.css +235 -0
- package/packages/themes/src/types.ts +10 -0
- package/packages/themes/src/vite-env.d.ts +9 -0
- package/packages/themes/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +4 -0
- package/tsconfig.json +27 -0
- package/vite.config.ts +25 -0
- package/vitest.config.ts +28 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# 基本聊天示例
|
|
2
|
+
|
|
3
|
+
本示例将指导您创建一个最基本的聊天功能。
|
|
4
|
+
|
|
5
|
+
## 原生 HTML 示例
|
|
6
|
+
|
|
7
|
+
### 1. 创建 HTML 文件
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<!DOCTYPE html>
|
|
11
|
+
<html lang="zh-CN">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
15
|
+
<title>AI Chat - 基本示例</title>
|
|
16
|
+
<style>
|
|
17
|
+
* {
|
|
18
|
+
margin:0;
|
|
19
|
+
padding:0;
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
html, body {
|
|
24
|
+
height: 100%;
|
|
25
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#app {
|
|
29
|
+
height: 100vh;
|
|
30
|
+
}
|
|
31
|
+
</style>
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<div id="app">
|
|
35
|
+
<ai-chat id="chat"></ai-chat>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<script type="module">
|
|
39
|
+
// 导入组件和主题
|
|
40
|
+
import '@ai-chat/components';
|
|
41
|
+
import '@ai-chat/themes/default';
|
|
42
|
+
|
|
43
|
+
// 获取组件实例
|
|
44
|
+
const chat = document.getElementById('chat');
|
|
45
|
+
|
|
46
|
+
// 设置消息处理回调
|
|
47
|
+
chat.setMessageHandler(async (message) => {
|
|
48
|
+
// 模拟 AI 回复延迟
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
50
|
+
|
|
51
|
+
// 返回 AI 回复
|
|
52
|
+
return `您说的是:"${message}"。这是 AI 的回复。`;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 添加欢迎消息
|
|
56
|
+
chat.addMessage({
|
|
57
|
+
role: 'assistant',
|
|
58
|
+
content: '你好!我是 AI 助手,有什么可以帮助你的吗?'
|
|
59
|
+
});
|
|
60
|
+
</script>
|
|
61
|
+
</body>
|
|
62
|
+
</html>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. 运行
|
|
66
|
+
|
|
67
|
+
在浏览器中打开 HTML 文件即可使用。
|
|
68
|
+
|
|
69
|
+
## React 示例
|
|
70
|
+
|
|
71
|
+
### 1. 安装依赖
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pnpm add @ai-chat/core @ai-chat/components @ai-chat/themes
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. 创建聊天组件
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
// ChatApp.tsx
|
|
81
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
82
|
+
import '@ai-chat/components';
|
|
83
|
+
import '@ai-chat/themes/default';
|
|
84
|
+
|
|
85
|
+
const ChatApp: React.FC = () => {
|
|
86
|
+
const chatRef = useRef<HTMLElement>(null);
|
|
87
|
+
const [messages, setMessages] = useState<Message[]>([]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (chatRef.current) {
|
|
91
|
+
// 设置消息处理回调
|
|
92
|
+
(chatRef.current as any).setMessageHandler(async (message: string) => {
|
|
93
|
+
// 模拟 AI 回复延迟
|
|
94
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
95
|
+
|
|
96
|
+
// 返回 AI 回复
|
|
97
|
+
return `您说的是:"${message}"。这是 AI 的回复。`;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 添加欢迎消息
|
|
101
|
+
(chatRef.current as any).addMessage({
|
|
102
|
+
role: 'assistant',
|
|
103
|
+
content: '你好!我是 AI 助手,有什么可以帮助你的吗?'
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div style={{ height: '100vh' }}>
|
|
110
|
+
<ai-chat ref={chatRef}></ai-chat>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export default ChatApp;
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. 在 App 中使用
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// App.tsx
|
|
122
|
+
import React from 'react';
|
|
123
|
+
import ChatApp from './ChatApp';
|
|
124
|
+
|
|
125
|
+
function App() {
|
|
126
|
+
return <ChatApp />;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export default App;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. 运行
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm start
|
|
136
|
+
# 或
|
|
137
|
+
pnpm dev
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Vue 3 示例
|
|
141
|
+
|
|
142
|
+
### 1. 安装依赖
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
pnpm add @ai-chat/core @ai-chat/components @ai-chat/themes
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 2. 创建聊天组件
|
|
149
|
+
|
|
150
|
+
```vue
|
|
151
|
+
<!-- ChatApp.vue -->
|
|
152
|
+
<template>
|
|
153
|
+
<div style="height: 100vh">
|
|
154
|
+
<ai-chat ref="chatRef"></ai-chat>
|
|
155
|
+
</div>
|
|
156
|
+
</template>
|
|
157
|
+
|
|
158
|
+
<script setup lang="ts">
|
|
159
|
+
import { ref, onMounted } from 'vue';
|
|
160
|
+
import '@ai-chat/components';
|
|
161
|
+
import '@ai-chat/themes/default';
|
|
162
|
+
|
|
163
|
+
const chatRef = ref<HTMLElement>();
|
|
164
|
+
|
|
165
|
+
onMounted(() => {
|
|
166
|
+
if (chatRef.value) {
|
|
167
|
+
// 设置消息处理回调
|
|
168
|
+
(chatRef.value as any).setMessageHandler(async (message: string) => {
|
|
169
|
+
// 模拟 AI 回复延迟
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
171
|
+
|
|
172
|
+
// 返回 AI 回复
|
|
173
|
+
return `您说的是:"${message}"。这是 AI 的回复。`;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// 添加欢迎消息
|
|
177
|
+
(chatRef.value as any).addMessage({
|
|
178
|
+
role: 'assistant',
|
|
179
|
+
content: '你好!我是 AI 助手,有什么可以帮助你的吗?'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
</script>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 3. 在 App 中使用
|
|
187
|
+
|
|
188
|
+
```vue
|
|
189
|
+
<!-- App.vue -->
|
|
190
|
+
<template>
|
|
191
|
+
<ChatApp />
|
|
192
|
+
</template>
|
|
193
|
+
|
|
194
|
+
<script setup lang="ts">
|
|
195
|
+
import ChatApp from './components/ChatApp.vue';
|
|
196
|
+
</script>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 4. 运行
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm run dev
|
|
203
|
+
# 或
|
|
204
|
+
pnpm dev
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## 连接真实 API
|
|
208
|
+
|
|
209
|
+
### 使用 fetch
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
chat.setMessageHandler(async (message) => {
|
|
213
|
+
const response = await fetch('/api/chat', {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
headers: { 'Content-Type': 'application/json' },
|
|
216
|
+
body: JSON.stringify({ message })
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const data = await response.json();
|
|
224
|
+
return data.reply;
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### 使用 axios
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import axios from 'axios';
|
|
232
|
+
|
|
233
|
+
chat.setMessageHandler(async (message) => {
|
|
234
|
+
const response = await axios.post('/api/chat', { message });
|
|
235
|
+
return response.data.reply;
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 使用 OpenAI API
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
chat.setMessageHandler(async (message) => {
|
|
243
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/json',
|
|
247
|
+
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
model: 'gpt-3.5-turbo',
|
|
251
|
+
messages: [
|
|
252
|
+
{ role: 'user', content: message }
|
|
253
|
+
]
|
|
254
|
+
})
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const data = await response.json();
|
|
258
|
+
return data.choices[0].message.content;
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## 自定义样式
|
|
263
|
+
|
|
264
|
+
### 覆盖 CSS 变量
|
|
265
|
+
|
|
266
|
+
```css
|
|
267
|
+
:root {
|
|
268
|
+
--ai-primary: #your-primary-color;
|
|
269
|
+
--ai-message-user-bg: #your-user-msg-bg;
|
|
270
|
+
--ai-message-ai-bg: #your-ai-msg-bg;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 使用不同主题
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
// 默认主题
|
|
278
|
+
import '@ai-chat/themes/default';
|
|
279
|
+
|
|
280
|
+
// 气泡主题
|
|
281
|
+
import '@ai-chat/themes/bubble';
|
|
282
|
+
|
|
283
|
+
// 扁平主题
|
|
284
|
+
import '@ai-chat/themes/flat';
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## 下一步
|
|
288
|
+
|
|
289
|
+
- [自定义插件](../guide/plugins.md)
|
|
290
|
+
- [多模型支持](./multi-model.md)
|
|
291
|
+
- [主题定制](../guide/themes.md)
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# 自定义插件示例
|
|
2
|
+
|
|
3
|
+
本示例将指导您创建和使用自定义插件。
|
|
4
|
+
|
|
5
|
+
## 插件 1:消息前缀插件
|
|
6
|
+
|
|
7
|
+
为所有用户消息添加前缀(如时间戳、用户名等)。
|
|
8
|
+
|
|
9
|
+
### 创建插件
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// plugins/message-prefix.ts
|
|
13
|
+
import { ChatPlugin, PluginContext, Message } from '@ai-chat/core';
|
|
14
|
+
|
|
15
|
+
interface MessagePrefixPluginOptions {
|
|
16
|
+
prefix: string; // 前缀模板
|
|
17
|
+
position: 'before' | 'after'; // 前缀位置
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MessagePrefixPlugin implements ChatPlugin {
|
|
21
|
+
name = 'message-prefix';
|
|
22
|
+
|
|
23
|
+
private prefix: string;
|
|
24
|
+
private position: 'before' | 'after';
|
|
25
|
+
|
|
26
|
+
constructor(options: MessagePrefixPluginOptions) {
|
|
27
|
+
this.prefix = options.prefix;
|
|
28
|
+
this.position = options.position || 'before';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeSend(message: string, context: PluginContext): string {
|
|
32
|
+
const processedMessage =
|
|
33
|
+
this.position === 'before'
|
|
34
|
+
? `${this.prefix}${message}`
|
|
35
|
+
: `${message}${this.prefix}`;
|
|
36
|
+
|
|
37
|
+
return processedMessage;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 使用插件
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// main.ts
|
|
46
|
+
import { createChatStore } from '@ai-chat/core';
|
|
47
|
+
import { MessagePrefixPlugin } from './plugins/message-prefix';
|
|
48
|
+
|
|
49
|
+
// 创建插件实例
|
|
50
|
+
const prefixPlugin = new MessagePrefixPlugin({
|
|
51
|
+
prefix: '[User] ',
|
|
52
|
+
position: 'before',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 注册插件
|
|
56
|
+
const chatStore = createChatStore({
|
|
57
|
+
plugins: [prefixPlugin],
|
|
58
|
+
onMessage: async (message) => {
|
|
59
|
+
// message 已经包含前缀
|
|
60
|
+
console.log('Message with prefix:', message);
|
|
61
|
+
return `AI 回复:${message}`;
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 插件 2:消息翻译插件
|
|
67
|
+
|
|
68
|
+
在发送前将消息翻译成英文,或将 AI 回复翻译成中文。
|
|
69
|
+
|
|
70
|
+
### 创建插件
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// plugins/translator.ts
|
|
74
|
+
import { ChatPlugin } from '@ai-chat/core';
|
|
75
|
+
|
|
76
|
+
interface TranslatorPluginOptions {
|
|
77
|
+
translateInput?: boolean; // 是否翻译输入
|
|
78
|
+
translateOutput?: boolean; // 是否翻译输出
|
|
79
|
+
targetLang?: string; // 目标语言
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class TranslatorPlugin implements ChatPlugin {
|
|
83
|
+
name = 'translator';
|
|
84
|
+
|
|
85
|
+
private options: TranslatorPluginOptions;
|
|
86
|
+
|
|
87
|
+
constructor(options: TranslatorPluginOptions = {}) {
|
|
88
|
+
this.options = {
|
|
89
|
+
translateInput: true,
|
|
90
|
+
translateOutput: false,
|
|
91
|
+
targetLang: 'en',
|
|
92
|
+
...options,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async beforeSend(message: string): Promise<string> {
|
|
97
|
+
if (!this.options.translateInput) return message;
|
|
98
|
+
|
|
99
|
+
// 调用翻译 API
|
|
100
|
+
const translated = await this.translate(message, this.options.targetLang!);
|
|
101
|
+
return translated;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async afterReceive(message: string): Promise<string> {
|
|
105
|
+
if (!this.options.translateOutput) return message;
|
|
106
|
+
|
|
107
|
+
// 调用翻译 API
|
|
108
|
+
const translated = await this.translate(message, 'zh');
|
|
109
|
+
return translated;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async translate(text: string, targetLang: string): Promise<string> {
|
|
113
|
+
// 使用 Google Translate API 或其他翻译服务
|
|
114
|
+
const response = await fetch('https://translation.googleapis.com/language/translate/v2', {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: { 'Content-Type': 'application/json' },
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
q: text,
|
|
119
|
+
target: targetLang,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const data = await response.json();
|
|
124
|
+
return data.data.translations[0].translatedText;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 使用插件
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { TranslatorPlugin } from './plugins/translator';
|
|
133
|
+
|
|
134
|
+
const translatorPlugin = new TranslatorPlugin({
|
|
135
|
+
translateInput: true, // 将用户输入翻译成英文
|
|
136
|
+
translateOutput: true, // 将 AI 回复翻译成中文
|
|
137
|
+
targetLang: 'en',
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const chatStore = createChatStore({
|
|
141
|
+
plugins: [translatorPlugin],
|
|
142
|
+
onMessage: async (message) => {
|
|
143
|
+
// message 已经是英文
|
|
144
|
+
const response = await callOpenAI(message);
|
|
145
|
+
return response; // 将被插件翻译成中文
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## 插件 3:消息持久化插件
|
|
151
|
+
|
|
152
|
+
将聊天记录保存到 localStorage 或 IndexedDB。
|
|
153
|
+
|
|
154
|
+
### 创建插件
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// plugins/persistence.ts
|
|
158
|
+
import { ChatPlugin, PluginContext, Message } from '@ai-chat/core';
|
|
159
|
+
|
|
160
|
+
interface PersistencePluginOptions {
|
|
161
|
+
storage?: 'localStorage' | 'sessionStorage' | 'indexedDB';
|
|
162
|
+
key?: string;
|
|
163
|
+
maxMessages?: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class PersistencePlugin implements ChatPlugin {
|
|
167
|
+
name = 'persistence';
|
|
168
|
+
|
|
169
|
+
private storage: Storage;
|
|
170
|
+
private key: string;
|
|
171
|
+
private maxMessages: number;
|
|
172
|
+
|
|
173
|
+
constructor(options: PersistencePluginOptions = {}) {
|
|
174
|
+
this.storage =
|
|
175
|
+
options.storage === 'sessionStorage'
|
|
176
|
+
? sessionStorage
|
|
177
|
+
: localStorage;
|
|
178
|
+
this.key = options.key || 'chat-messages';
|
|
179
|
+
this.maxMessages = options.maxMessages || 100;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
init(context: PluginContext) {
|
|
183
|
+
// 加载已保存的消息
|
|
184
|
+
const savedMessages = this.loadMessages();
|
|
185
|
+
if (savedMessages.length > 0) {
|
|
186
|
+
savedMessages.forEach(msg => {
|
|
187
|
+
context.addMessage(msg);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// 监听消息变化,自动保存
|
|
192
|
+
context.on('message-updated', () => {
|
|
193
|
+
const messages = context.getMessages();
|
|
194
|
+
this.saveMessages(messages);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private saveMessages(messages: Message[]): void {
|
|
199
|
+
const toSave = messages.slice(-this.maxMessages); // 只保存最近 N 条
|
|
200
|
+
this.storage.setItem(this.key, JSON.stringify(toSave));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private loadMessages(): Message[] {
|
|
204
|
+
const saved = this.storage.getItem(this.key);
|
|
205
|
+
if (!saved) return [];
|
|
206
|
+
try {
|
|
207
|
+
return JSON.parse(saved);
|
|
208
|
+
} catch {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 导出聊天记录
|
|
214
|
+
exportMessages(): string {
|
|
215
|
+
const messages = this.loadMessages();
|
|
216
|
+
return JSON.stringify(messages, null, 2);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 清除聊天记录
|
|
220
|
+
clearMessages(): void {
|
|
221
|
+
this.storage.removeItem(this.key);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### 使用插件
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { PersistencePlugin } from './plugins/persistence';
|
|
230
|
+
|
|
231
|
+
const persistencePlugin = new PersistencePlugin({
|
|
232
|
+
storage: 'localStorage',
|
|
233
|
+
key: 'my-chat-history',
|
|
234
|
+
maxMessages: 50,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const chatStore = createChatStore({
|
|
238
|
+
plugins: [persistencePlugin],
|
|
239
|
+
onMessage: async (message) => {
|
|
240
|
+
return `AI 回复:${message}`;
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// 导出聊天记录
|
|
245
|
+
const exported = persistencePlugin.exportMessages();
|
|
246
|
+
console.log(exported);
|
|
247
|
+
|
|
248
|
+
// 清除聊天记录
|
|
249
|
+
persistencePlugin.clearMessages();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 插件 4:字数统计插件
|
|
253
|
+
|
|
254
|
+
统计消息字数、字符数等。
|
|
255
|
+
|
|
256
|
+
### 创建插件
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// plugins/word-counter.ts
|
|
260
|
+
import { ChatPlugin, PluginContext } from '@ai-chat/core';
|
|
261
|
+
|
|
262
|
+
interface WordCountStats {
|
|
263
|
+
totalMessages: number;
|
|
264
|
+
totalWords: number;
|
|
265
|
+
totalCharacters: number;
|
|
266
|
+
averageWordsPerMessage: number;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export class WordCounterPlugin implements ChatPlugin {
|
|
270
|
+
name = 'word-counter';
|
|
271
|
+
|
|
272
|
+
private context!: PluginContext;
|
|
273
|
+
private stats: WordCountStats = {
|
|
274
|
+
totalMessages: 0,
|
|
275
|
+
totalWords: 0,
|
|
276
|
+
totalCharacters: 0,
|
|
277
|
+
averageWordsPerMessage: 0,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
init(context: PluginContext) {
|
|
281
|
+
this.context = context;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
afterReceive(message: string): string {
|
|
285
|
+
// 更新统计
|
|
286
|
+
this.stats.totalMessages++;
|
|
287
|
+
this.stats.totalCharacters += message.length;
|
|
288
|
+
this.stats.totalWords += message.trim().split(/\s+/).length;
|
|
289
|
+
this.stats.averageWordsPerMessage =
|
|
290
|
+
this.stats.totalWords / this.stats.totalMessages;
|
|
291
|
+
|
|
292
|
+
// 触发统计更新事件
|
|
293
|
+
this.context.emit('stats-updated', { ...this.stats });
|
|
294
|
+
|
|
295
|
+
return message;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// 获取统计信息
|
|
299
|
+
getStats(): WordStats {
|
|
300
|
+
return { ...this.stats };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// 重置统计
|
|
304
|
+
resetStats(): void {
|
|
305
|
+
this.stats = {
|
|
306
|
+
totalMessages: 0,
|
|
307
|
+
totalWords: 0,
|
|
308
|
+
totalCharacters: 0,
|
|
309
|
+
averageWordsPerMessage: 0,
|
|
310
|
+
};
|
|
311
|
+
this.context.emit('stats-updated', { ...this.stats });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 使用插件
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { WordCounterPlugin } from './plugins/word-counter';
|
|
320
|
+
|
|
321
|
+
const wordCounterPlugin = new WordCounterPlugin();
|
|
322
|
+
|
|
323
|
+
const chatStore = createChatStore({
|
|
324
|
+
plugins: [wordCounterPlugin],
|
|
325
|
+
onMessage: async (message) => {
|
|
326
|
+
return `AI 回复:${message}`;
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// 监听统计更新
|
|
331
|
+
wordCounterPlugin.context.on('stats-updated', (stats) => {
|
|
332
|
+
console.log('Stats updated:', stats);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// 获取统计信息
|
|
336
|
+
const stats = wordCounterPlugin.getStats();
|
|
337
|
+
console.log(stats);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 插件 5:敏感信息脱敏插件
|
|
341
|
+
|
|
342
|
+
检测并脱敏敏感信息(如手机号、邮箱、身份证号等)。
|
|
343
|
+
|
|
344
|
+
### 创建插件
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
// plugins/sensitive-info-mask.ts
|
|
348
|
+
import { ChatPlugin } from '@ai-chat/core';
|
|
349
|
+
|
|
350
|
+
interface SensitiveInfoMaskOptions {
|
|
351
|
+
maskPhone?: boolean;
|
|
352
|
+
maskEmail?: boolean;
|
|
353
|
+
maskIDCard?: boolean;
|
|
354
|
+
maskBankCard?: boolean;
|
|
355
|
+
maskCustom?: Array<{ regex: RegExp; replacement: string }>;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export class SensitiveInfoMaskPlugin implements ChatPlugin {
|
|
359
|
+
name = 'sensitive-info-mask';
|
|
360
|
+
|
|
361
|
+
private rules: Array<{ regex: RegExp; replacement: string }> = [];
|
|
362
|
+
|
|
363
|
+
constructor(options: SensitiveInfoMaskOptions = {}) {
|
|
364
|
+
if (options.maskPhone) {
|
|
365
|
+
this.rules.push({
|
|
366
|
+
regex: /(\+?86\s?)?1[3-9]\d{9}/g,
|
|
367
|
+
replacement: '*** *** ****',
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (options.maskEmail) {
|
|
372
|
+
this.rules.push({
|
|
373
|
+
regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
374
|
+
replacement: '***@***.***',
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (options.maskIDCard) {
|
|
379
|
+
this.rules.push({
|
|
380
|
+
regex: /(\d{6})(\d{8})(\d{4})/g,
|
|
381
|
+
replacement: '$1********$3',
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (options.maskBankCard) {
|
|
386
|
+
this.rules.push({
|
|
387
|
+
regex: /\d{16,19}/g,
|
|
388
|
+
replacement: '**** **** **** ****',
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (options.maskCustom) {
|
|
393
|
+
this.rules.push(...options.maskCustom);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
beforeSend(message: string): string {
|
|
398
|
+
let masked = message;
|
|
399
|
+
for (const rule of this.rules) {
|
|
400
|
+
masked = masked.replace(rule.regex, rule.replacement);
|
|
401
|
+
}
|
|
402
|
+
return masked;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### 使用插件
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { SensitiveInfoMaskPlugin } from './plugins/sensitive-info-mask';
|
|
411
|
+
|
|
412
|
+
const maskPlugin = new SensitiveInfoMaskPlugin({
|
|
413
|
+
maskPhone: true,
|
|
414
|
+
maskEmail: true,
|
|
415
|
+
maskIDCard: true,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const chatStore = createChatStore({
|
|
419
|
+
plugins: [maskPlugin],
|
|
420
|
+
onMessage: async (message) => {
|
|
421
|
+
// message 已经脱敏
|
|
422
|
+
return `AI 回复:${message}`;
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## 下一步
|
|
428
|
+
|
|
429
|
+
- [多模型支持](./multi-model.md)
|
|
430
|
+
- [基本聊天](./basic-chat.md)
|
|
431
|
+
- [插件开发指南](../guide/plugins.md)
|