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.
Files changed (95) hide show
  1. package/.eslintrc.cjs +74 -0
  2. package/.github/actions/screenshot/action.yml +35 -0
  3. package/.github/workflows/pages.yml +46 -0
  4. package/README.md +285 -0
  5. package/docs/README.md +176 -0
  6. package/docs/api/components.md +344 -0
  7. package/docs/api/core.md +349 -0
  8. package/docs/chat-style-1-minimal.html +78 -0
  9. package/docs/chat-style-2-neon.html +74 -0
  10. package/docs/chat-style-3-glass.html +73 -0
  11. package/docs/chat-style-4-terminal.html +84 -0
  12. package/docs/chat-style-5-gradient.html +69 -0
  13. package/docs/chat-style-6-corporate.html +116 -0
  14. package/docs/examples/basic-chat.md +291 -0
  15. package/docs/examples/custom-plugins.md +431 -0
  16. package/docs/examples/multi-model.md +466 -0
  17. package/docs/guide/api-adapters.md +431 -0
  18. package/docs/guide/getting-started.md +244 -0
  19. package/docs/guide/headless-mode.md +508 -0
  20. package/docs/guide/plugins.md +416 -0
  21. package/docs/guide/themes.md +327 -0
  22. package/docs/index.html +256 -0
  23. package/docs/theme-preview-1-minimal.html +74 -0
  24. package/docs/theme-preview-2-neon.html +73 -0
  25. package/docs/theme-preview-3-glass.html +77 -0
  26. package/docs/theme-preview-4-terminal.html +86 -0
  27. package/docs/theme-preview-5-gradient.html +79 -0
  28. package/docs/theme-preview-6-corporate.html +71 -0
  29. package/examples/index.html +414 -0
  30. package/examples/react-app/App.tsx +131 -0
  31. package/examples/react-app/index.html +12 -0
  32. package/examples/react-app/main.tsx +15 -0
  33. package/examples/react-app/package.json +24 -0
  34. package/examples/vue-app/index.html +12 -0
  35. package/examples/vue-app/package.json +22 -0
  36. package/examples/vue-app/src/App.vue +145 -0
  37. package/examples/vue-app/src/main.ts +9 -0
  38. package/package.json +44 -0
  39. package/packages/components/package.json +25 -0
  40. package/packages/components/src/chat/chat.css +80 -0
  41. package/packages/components/src/chat/chat.ts +236 -0
  42. package/packages/components/src/index.ts +36 -0
  43. package/packages/components/src/input/input.css +52 -0
  44. package/packages/components/src/input/input.ts +116 -0
  45. package/packages/components/src/markdown/markdown.css +118 -0
  46. package/packages/components/src/markdown/markdown.ts +229 -0
  47. package/packages/components/src/message/message.css +56 -0
  48. package/packages/components/src/message/message.ts +72 -0
  49. package/packages/components/src/styles/global.css +43 -0
  50. package/packages/components/src/tool-call/tool-call.css +98 -0
  51. package/packages/components/src/tool-call/tool-call.ts +171 -0
  52. package/packages/components/src/types.ts +55 -0
  53. package/packages/components/src/utils/helpers.ts +128 -0
  54. package/packages/components/tsconfig.json +25 -0
  55. package/packages/components/tsup.config.ts +18 -0
  56. package/packages/core/package.json +47 -0
  57. package/packages/core/pnpm-lock.yaml +2032 -0
  58. package/packages/core/pnpm-workspace.yaml +2 -0
  59. package/packages/core/src/api/adapters.ts +717 -0
  60. package/packages/core/src/api/base.ts +210 -0
  61. package/packages/core/src/api/index.ts +54 -0
  62. package/packages/core/src/index.ts +93 -0
  63. package/packages/core/src/parser/latex.ts +274 -0
  64. package/packages/core/src/parser/markdown.test.ts +58 -0
  65. package/packages/core/src/parser/markdown.ts +206 -0
  66. package/packages/core/src/parser/mermaid.ts +276 -0
  67. package/packages/core/src/plugins/PluginManager.ts +232 -0
  68. package/packages/core/src/plugins/builtin.ts +406 -0
  69. package/packages/core/src/store/ChatStore.ts +163 -0
  70. package/packages/core/src/store/ModelConfigStore.ts +136 -0
  71. package/packages/core/src/store/ToolCallStore.ts +164 -0
  72. package/packages/core/src/store/base.ts +75 -0
  73. package/packages/core/src/types/index.ts +133 -0
  74. package/packages/core/tsup.config.ts +18 -0
  75. package/packages/themes/package.json +33 -0
  76. package/packages/themes/src/corporate/index.ts +52 -0
  77. package/packages/themes/src/corporate/theme.css +228 -0
  78. package/packages/themes/src/glass/index.ts +52 -0
  79. package/packages/themes/src/glass/theme.css +237 -0
  80. package/packages/themes/src/gradient/index.ts +53 -0
  81. package/packages/themes/src/gradient/theme.css +218 -0
  82. package/packages/themes/src/index.ts +13 -0
  83. package/packages/themes/src/minimal/index.ts +52 -0
  84. package/packages/themes/src/minimal/theme.css +198 -0
  85. package/packages/themes/src/neon/index.ts +52 -0
  86. package/packages/themes/src/neon/theme.css +233 -0
  87. package/packages/themes/src/terminal/index.ts +52 -0
  88. package/packages/themes/src/terminal/theme.css +235 -0
  89. package/packages/themes/src/types.ts +10 -0
  90. package/packages/themes/src/vite-env.d.ts +9 -0
  91. package/packages/themes/tsup.config.ts +21 -0
  92. package/pnpm-workspace.yaml +4 -0
  93. package/tsconfig.json +27 -0
  94. package/vite.config.ts +25 -0
  95. package/vitest.config.ts +28 -0
@@ -0,0 +1,508 @@
1
+ # Headless 模式
2
+
3
+ Headless 模式允许您只使用 AI Chat UI Kit 的逻辑层,完全自定义 UI 层。
4
+
5
+ ## 什么是 Headless 模式?
6
+
7
+ Headless 模式是指只使用 `@ai-chat/core` 包提供的状态和逻辑,不使用 `@ai-chat/components` 提供的默认 UI 组件。这样您可以:
8
+
9
+ - 完全控制 UI 的外观和交互
10
+ - 使用任何 UI 框架(React、Vue、Angular 等)
11
+ - 自定义消息气泡、输入框、工具调用等所有 UI 元素
12
+ - 与现有设计系统无缝集成
13
+
14
+ ## 基本用法
15
+
16
+ ### 1. 安装核心包
17
+
18
+ ```bash
19
+ pnpm add @ai-chat/core
20
+ ```
21
+
22
+ ### 2. 创建 Chat Store
23
+
24
+ ```typescript
25
+ // store.ts
26
+ import { createChatStore } from '@ai-chat/core';
27
+
28
+ export const chatStore = createChatStore({
29
+ initialState: {
30
+ messages: [],
31
+ isLoading: false,
32
+ error: null,
33
+ },
34
+
35
+ // 消息处理回调
36
+ onMessage: async (message) => {
37
+ // 调用您的 AI API
38
+ const response = await fetch('/api/chat', {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify({ message })
42
+ });
43
+ const data = await response.json();
44
+ return data.reply;
45
+ },
46
+ });
47
+ ```
48
+
49
+ ### 3. 在 React 中使用
50
+
51
+ ```tsx
52
+ // ChatApp.tsx
53
+ import React, { useState, useEffect, useRef } from 'react';
54
+ import { chatStore } from './store';
55
+
56
+ const ChatApp: React.FC = () => {
57
+ const [messages, setMessages] = useState(chatStore.getMessages());
58
+ const [inputValue, setInputValue] = useState('');
59
+ const [isLoading, setIsLoading] = useState(false);
60
+ const messagesEndRef = useRef<HTMLDivElement>(null);
61
+
62
+ // 订阅状态变化
63
+ useEffect(() => {
64
+ const unsubscribe = chatStore.subscribe((state) => {
65
+ setMessages([...state.messages]);
66
+ setIsLoading(state.isLoading);
67
+ });
68
+
69
+ return unsubscribe;
70
+ }, []);
71
+
72
+ // 滚动到最新消息
73
+ useEffect(() => {
74
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
75
+ }, [messages]);
76
+
77
+ // 发送消息
78
+ const handleSend = async () => {
79
+ if (!inputValue.trim() || isLoading) return;
80
+
81
+ const message = inputValue.trim();
82
+ setInputValue('');
83
+
84
+ await chatStore.sendMessage(message);
85
+ };
86
+
87
+ return (
88
+ <div style={styles.container}>
89
+ <div style={styles.messagesContainer}>
90
+ {messages.map((msg, index) => (
91
+ <div
92
+ key={index}
93
+ style={{
94
+ ...styles.message,
95
+ ...(msg.role === 'user' ? styles.messageUser : styles.messageAI),
96
+ }}
97
+ >
98
+ <div style={styles.avatar}>
99
+ {msg.role === 'user' ? '我' : 'AI'}
100
+ </div>
101
+ <div style={styles.messageContent}>
102
+ {msg.content}
103
+ </div>
104
+ </div>
105
+ ))}
106
+ {isLoading && (
107
+ <div style={styles.loading}>AI 正在思考...</div>
108
+ )}
109
+ <div ref={messagesEndRef} />
110
+ </div>
111
+
112
+ <div style={styles.inputContainer}>
113
+ <input
114
+ style={styles.input}
115
+ type="text"
116
+ value={inputValue}
117
+ onChange={(e) => setInputValue(e.target.value)}
118
+ onKeyPress={(e) => e.key === 'Enter' && handleSend()}
119
+ placeholder="输入消息..."
120
+ disabled={isLoading}
121
+ />
122
+ <button
123
+ style={styles.sendButton}
124
+ onClick={handleSend}
125
+ disabled={isLoading || !inputValue.trim()}
126
+ >
127
+ 发送
128
+ </button>
129
+ </div>
130
+ </div>
131
+ );
132
+ };
133
+
134
+ const styles = {
135
+ container: {
136
+ display: 'flex',
137
+ flexDirection: 'column',
138
+ height: '100vh',
139
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
140
+ },
141
+ messagesContainer: {
142
+ flex: 1,
143
+ overflowY: 'auto',
144
+ padding: '16px',
145
+ display: 'flex',
146
+ flexDirection: 'column',
147
+ gap: '12px',
148
+ },
149
+ message: {
150
+ display: 'flex',
151
+ gap: '8px',
152
+ maxWidth: '80%',
153
+ },
154
+ messageUser: {
155
+ flexDirection: 'row-reverse',
156
+ marginLeft: 'auto',
157
+ },
158
+ messageAI: {
159
+ marginRight: 'auto',
160
+ },
161
+ avatar: {
162
+ width: '36px',
163
+ height: '36px',
164
+ borderRadius: '50%',
165
+ backgroundColor: '#1677ff',
166
+ color: 'white',
167
+ display: 'flex',
168
+ alignItems: 'center',
169
+ justifyContent: 'center',
170
+ flexShrink: 0,
171
+ },
172
+ messageContent: {
173
+ padding: '12px 16px',
174
+ borderRadius: '12px',
175
+ lineHeight: 1.6,
176
+ wordBreak: 'break-word',
177
+ },
178
+ loading: {
179
+ textAlign: 'center',
180
+ color: '#999',
181
+ padding: '8px',
182
+ },
183
+ inputContainer: {
184
+ padding: '16px',
185
+ borderTop: '1px solid #e8e8e8',
186
+ display: 'flex',
187
+ gap: '8px',
188
+ },
189
+ input: {
190
+ flex: 1,
191
+ padding: '8px 12px',
192
+ border: '1px solid #d9d9d9',
193
+ borderRadius: '8px',
194
+ fontSize: '14px',
195
+ },
196
+ sendButton: {
197
+ padding: '8px 20px',
198
+ backgroundColor: '#1677ff',
199
+ color: 'white',
200
+ border: 'none',
201
+ borderRadius: '8px',
202
+ cursor: 'pointer',
203
+ fontSize: '14px',
204
+ },
205
+ };
206
+
207
+ export default ChatApp;
208
+ ```
209
+
210
+ ### 4. 在 Vue 中使用
211
+
212
+ ```vue
213
+ <!-- ChatApp.vue -->
214
+ <template>
215
+ <div class="container">
216
+ <div class="messages-container">
217
+ <div
218
+ v-for="(msg, index) in messages"
219
+ :key="index"
220
+ :class="['message', msg.role === 'user' ? 'message-user' : 'message-ai']"
221
+ >
222
+ <div class="avatar">
223
+ {{ msg.role === 'user' ? '我' : 'AI' }}
224
+ </div>
225
+ <div class="message-content">
226
+ {{ msg.content }}
227
+ </div>
228
+ </div>
229
+ <div v-if="isLoading" class="loading">AI 正在思考...</div>
230
+ <div ref="messagesEnd"></div>
231
+ </div>
232
+
233
+ <div class="input-container">
234
+ <input
235
+ v-model="inputValue"
236
+ class="input"
237
+ type="text"
238
+ @keypress="handleKeyPress"
239
+ placeholder="输入消息..."
240
+ :disabled="isLoading"
241
+ />
242
+ <button
243
+ class="send-button"
244
+ @click="handleSend"
245
+ :disabled="isLoading || !inputValue.trim()"
246
+ >
247
+ 发送
248
+ </button>
249
+ </div>
250
+ </div>
251
+ </template>
252
+
253
+ <script setup lang="ts">
254
+ import { ref, onMounted, onUnmounted, nextTick } from 'vue';
255
+ import { chatStore } from './store';
256
+
257
+ const messages = ref(chatStore.getMessages());
258
+ const inputValue = ref('');
259
+ const isLoading = ref(false);
260
+ const messagesEnd = ref<HTMLDivElement>();
261
+
262
+ let unsubscribe: () => void;
263
+
264
+ // 订阅状态变化
265
+ onMounted(() => {
266
+ unsubscribe = chatStore.subscribe((state) => {
267
+ messages.value = [...state.messages];
268
+ isLoading.value = state.isLoading;
269
+ nextTick(() => {
270
+ messagesEnd.value?.scrollIntoView({ behavior: 'smooth' });
271
+ });
272
+ });
273
+ });
274
+
275
+ onUnmounted(() => {
276
+ unsubscribe?.();
277
+ });
278
+
279
+ // 发送消息
280
+ const handleSend = async () => {
281
+ if (!inputValue.value.trim() || isLoading.value) return;
282
+
283
+ const message = inputValue.value.trim();
284
+ inputValue.value = '';
285
+
286
+ await chatStore.sendMessage(message);
287
+ };
288
+
289
+ // 处理回车键
290
+ const handleKeyPress = (e: KeyboardEvent) => {
291
+ if (e.key === 'Enter') {
292
+ handleSend();
293
+ }
294
+ };
295
+ </script>
296
+
297
+ <style scoped>
298
+ .container {
299
+ display: flex;
300
+ flex-direction: column;
301
+ height: 100vh;
302
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
303
+ }
304
+
305
+ .messages-container {
306
+ flex: 1;
307
+ overflow-y: auto;
308
+ padding: 16px;
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 12px;
312
+ }
313
+
314
+ .message {
315
+ display: flex;
316
+ gap: 8px;
317
+ max-width: 80%;
318
+ }
319
+
320
+ .message-user {
321
+ flex-direction: row-reverse;
322
+ margin-left: auto;
323
+ }
324
+
325
+ .message-ai {
326
+ margin-right: auto;
327
+ }
328
+
329
+ .avatar {
330
+ width: 36px;
331
+ height: 36px;
332
+ border-radius: 50%;
333
+ background-color: #1677ff;
334
+ color: white;
335
+ display: flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ flex-shrink: 0;
339
+ }
340
+
341
+ .message-content {
342
+ padding: 12px 16px;
343
+ border-radius: 12px;
344
+ line-height: 1.6;
345
+ word-break: break-word;
346
+ background-color: #f5f5f5;
347
+ }
348
+
349
+ .message-user .message-content {
350
+ background-color: #1677ff;
351
+ color: white;
352
+ }
353
+
354
+ .loading {
355
+ text-align: center;
356
+ color: #999;
357
+ padding: 8px;
358
+ }
359
+
360
+ .input-container {
361
+ padding: 16px;
362
+ border-top: 1px solid #e8e8e8;
363
+ display: flex;
364
+ gap: 8px;
365
+ }
366
+
367
+ .input {
368
+ flex: 1;
369
+ padding: 8px 12px;
370
+ border: 1px solid #d9d9d9;
371
+ border-radius: 8px;
372
+ font-size: 14px;
373
+ }
374
+
375
+ .send-button {
376
+ padding: 8px 20px;
377
+ background-color: #1677ff;
378
+ color: white;
379
+ border: none;
380
+ border-radius: 8px;
381
+ cursor: pointer;
382
+ font-size: 14px;
383
+ }
384
+
385
+ .send-button:disabled {
386
+ background-color: #d9d9d9;
387
+ cursor: not-allowed;
388
+ }
389
+ </style>
390
+ ```
391
+
392
+ ## Chat Store API
393
+
394
+ ### `createChatStore(config)`
395
+
396
+ 创建一个聊天状态管理实例。
397
+
398
+ **参数:**
399
+
400
+ ```typescript
401
+ interface ChatStoreConfig {
402
+ initialState?: {
403
+ messages?: Message[];
404
+ isLoading?: boolean;
405
+ error?: string | null;
406
+ };
407
+ onMessage: (message: string) => Promise<string>;
408
+ }
409
+ ```
410
+
411
+ **返回:**
412
+
413
+ ```typescript
414
+ interface ChatStore {
415
+ // 获取当前消息列表
416
+ getMessages(): Message[];
417
+
418
+ // 发送消息
419
+ sendMessage(message: string): Promise<void>;
420
+
421
+ // 添加消息
422
+ addMessage(message: Message): void;
423
+
424
+ // 清除消息
425
+ clearMessages(): void;
426
+
427
+ // 订阅状态变化
428
+ subscribe(listener: (state: ChatState) => void): () => void;
429
+
430
+ // 获取当前状态
431
+ getState(): ChatState;
432
+ }
433
+ ```
434
+
435
+ ## 进阶用法
436
+
437
+ ### 自定义消息类型
438
+
439
+ ```typescript
440
+ interface Message {
441
+ role: 'user' | 'assistant' | 'system';
442
+ content: string;
443
+ timestamp?: number;
444
+ metadata?: Record<string, any>; // 自定义元数据
445
+ }
446
+ ```
447
+
448
+ ### 错误处理
449
+
450
+ ```typescript
451
+ const chatStore = createChatStore({
452
+ onMessage: async (message) => {
453
+ try {
454
+ const response = await fetch('/api/chat', {
455
+ method: 'POST',
456
+ headers: { 'Content-Type': 'application/json' },
457
+ body: JSON.stringify({ message })
458
+ });
459
+
460
+ if (!response.ok) {
461
+ throw new Error(`HTTP error! status: ${response.status}`);
462
+ }
463
+
464
+ const data = await response.json();
465
+ return data.reply;
466
+ } catch (error) {
467
+ console.error('Failed to send message:', error);
468
+ throw error; // 让 store 处理错误状态
469
+ }
470
+ },
471
+ });
472
+ ```
473
+
474
+ ### 流式响应(Streaming)
475
+
476
+ ```typescript
477
+ const chatStore = createChatStore({
478
+ onMessage: async (message, { onChunk }) => {
479
+ const response = await fetch('/api/chat/stream', {
480
+ method: 'POST',
481
+ headers: { 'Content-Type': 'application/json' },
482
+ body: JSON.stringify({ message })
483
+ });
484
+
485
+ const reader = response.body.getReader();
486
+ const decoder = new TextDecoder();
487
+
488
+ let fullText = '';
489
+
490
+ while (true) {
491
+ const { done, value } = await reader.read();
492
+ if (done) break;
493
+
494
+ const chunk = decoder.decode(value);
495
+ fullText += chunk;
496
+ onChunk(fullText); // 实时更新 UI
497
+ }
498
+
499
+ return fullText;
500
+ },
501
+ });
502
+ ```
503
+
504
+ ## 下一步
505
+
506
+ - [主题定制](./themes.md) - 学习如何自定义主题
507
+ - [插件开发](./plugins.md) - 学习如何扩展功能
508
+ - [API 参考](../../api/) - 查看完整 API 文档