@huyooo/ai-chat-frontend-react 0.1.6 → 0.2.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 (91) hide show
  1. package/README.md +368 -0
  2. package/dist/index.css +2575 -0
  3. package/dist/index.css.map +1 -0
  4. package/dist/index.d.ts +378 -135
  5. package/dist/index.js +3956 -1042
  6. package/dist/index.js.map +1 -1
  7. package/dist/style.css +48 -987
  8. package/package.json +7 -4
  9. package/src/adapter.ts +10 -70
  10. package/src/components/ChatPanel.tsx +373 -117
  11. package/src/components/common/ConfirmDialog.css +136 -0
  12. package/src/components/common/ConfirmDialog.tsx +91 -0
  13. package/src/components/common/CopyButton.css +22 -0
  14. package/src/components/common/CopyButton.tsx +46 -0
  15. package/src/components/common/IndexingSettings.css +207 -0
  16. package/src/components/common/IndexingSettings.tsx +398 -0
  17. package/src/components/common/SettingsPanel.css +256 -0
  18. package/src/components/common/SettingsPanel.tsx +120 -0
  19. package/src/components/common/Toast.css +50 -0
  20. package/src/components/common/Toast.tsx +38 -0
  21. package/src/components/common/ToggleSwitch.css +52 -0
  22. package/src/components/common/ToggleSwitch.tsx +20 -0
  23. package/src/components/header/ChatHeader.css +285 -0
  24. package/src/components/header/ChatHeader.tsx +376 -0
  25. package/src/components/input/AtFilePicker.css +147 -0
  26. package/src/components/input/AtFilePicker.tsx +519 -0
  27. package/src/components/input/ChatInput.css +204 -0
  28. package/src/components/input/ChatInput.tsx +506 -0
  29. package/src/components/input/DropdownSelector.css +159 -0
  30. package/src/components/input/DropdownSelector.tsx +195 -0
  31. package/src/components/input/ImagePreviewModal.css +124 -0
  32. package/src/components/input/ImagePreviewModal.tsx +118 -0
  33. package/src/components/input/at-views/AtBranchView.tsx +34 -0
  34. package/src/components/input/at-views/AtBrowserView.tsx +34 -0
  35. package/src/components/input/at-views/AtChatsView.tsx +34 -0
  36. package/src/components/input/at-views/AtDocsView.tsx +34 -0
  37. package/src/components/input/at-views/AtFilesView.tsx +168 -0
  38. package/src/components/input/at-views/AtTerminalsView.tsx +34 -0
  39. package/src/components/input/at-views/AtViewStyles.css +143 -0
  40. package/src/components/input/at-views/index.ts +9 -0
  41. package/src/components/message/ContentRenderer.css +9 -0
  42. package/src/components/message/ContentRenderer.tsx +63 -0
  43. package/src/components/message/MessageBubble.css +190 -0
  44. package/src/components/message/MessageBubble.tsx +231 -0
  45. package/src/components/message/PartsRenderer.css +4 -0
  46. package/src/components/message/PartsRenderer.tsx +114 -0
  47. package/src/components/message/ToolResultRenderer.tsx +21 -0
  48. package/src/components/message/WelcomeMessage.css +221 -0
  49. package/src/components/message/WelcomeMessage.tsx +93 -0
  50. package/src/components/message/blocks/CodeBlock.tsx +60 -0
  51. package/src/components/message/blocks/TextBlock.tsx +15 -0
  52. package/src/components/message/blocks/blocks.css +141 -0
  53. package/src/components/message/blocks/index.ts +6 -0
  54. package/src/components/message/parts/CollapsibleCard.css +78 -0
  55. package/src/components/message/parts/CollapsibleCard.tsx +77 -0
  56. package/src/components/message/parts/ErrorPart.css +9 -0
  57. package/src/components/message/parts/ErrorPart.tsx +40 -0
  58. package/src/components/message/parts/ImagePart.css +50 -0
  59. package/src/components/message/parts/ImagePart.tsx +54 -0
  60. package/src/components/message/parts/SearchPart.css +44 -0
  61. package/src/components/message/parts/SearchPart.tsx +63 -0
  62. package/src/components/message/parts/TextPart.css +10 -0
  63. package/src/components/message/parts/TextPart.tsx +20 -0
  64. package/src/components/message/parts/ThinkingPart.css +9 -0
  65. package/src/components/message/parts/ThinkingPart.tsx +48 -0
  66. package/src/components/message/parts/ToolCallPart.css +220 -0
  67. package/src/components/message/parts/ToolCallPart.tsx +285 -0
  68. package/src/components/message/parts/ToolResultPart.css +68 -0
  69. package/src/components/message/parts/ToolResultPart.tsx +96 -0
  70. package/src/components/message/parts/index.ts +11 -0
  71. package/src/components/message/tool-results/DefaultToolResult.tsx +26 -0
  72. package/src/components/message/tool-results/SearchResults.tsx +69 -0
  73. package/src/components/message/tool-results/WeatherCard.tsx +63 -0
  74. package/src/components/message/tool-results/index.ts +7 -0
  75. package/src/components/message/tool-results/tool-results.css +179 -0
  76. package/src/components/message/welcome-types.ts +46 -0
  77. package/src/context/AutoRunConfigContext.tsx +13 -0
  78. package/src/context/ChatAdapterContext.tsx +8 -0
  79. package/src/context/ChatInputContext.tsx +40 -0
  80. package/src/context/RenderersContext.tsx +41 -0
  81. package/src/hooks/useChat.ts +855 -237
  82. package/src/hooks/useImageUpload.ts +253 -0
  83. package/src/index.ts +96 -39
  84. package/src/styles.css +48 -987
  85. package/src/types/index.ts +172 -103
  86. package/src/utils/fileIcon.ts +49 -0
  87. package/src/components/ChatInput.tsx +0 -368
  88. package/src/components/chat/messages/ExecutionSteps.tsx +0 -234
  89. package/src/components/chat/messages/MessageBubble.tsx +0 -130
  90. package/src/components/chat/ui/ChatHeader.tsx +0 -301
  91. package/src/components/chat/ui/WelcomeMessage.tsx +0 -107
@@ -0,0 +1,253 @@
1
+ /**
2
+ * 图片上传 hook
3
+ * 处理图片选择、粘贴、拖拽、预览等功能
4
+ * 与 Vue 版本 useImageUpload.ts 保持一致
5
+ */
6
+
7
+ import { useState, useCallback, useMemo } from 'react';
8
+
9
+ /** 内部图片数据结构(包含预览用的 dataUrl) */
10
+ export interface ImageItem {
11
+ dataUrl: string;
12
+ base64: string;
13
+ mimeType: string;
14
+ }
15
+
16
+ /** 导出给外部使用的图片数据格式 */
17
+ export interface ImageData {
18
+ base64: string;
19
+ mimeType: string;
20
+ }
21
+
22
+ /** useImageUpload 配置项 */
23
+ interface UseImageUploadOptions {
24
+ /** 最大图片数量,默认 5 */
25
+ maxImages?: number;
26
+ /** 单张图片最大大小(字节),默认 10MB */
27
+ maxSize?: number;
28
+ }
29
+
30
+ /**
31
+ * 图片上传 hook
32
+ * @param options 配置项
33
+ */
34
+ export function useImageUpload(options: UseImageUploadOptions = {}) {
35
+ const { maxImages = 5, maxSize = 10 * 1024 * 1024 } = options;
36
+
37
+ // 图片列表
38
+ const [images, setImages] = useState<ImageItem[]>([]);
39
+
40
+ // 拖拽状态
41
+ const [isDragOver, setIsDragOver] = useState(false);
42
+
43
+ // 预览状态
44
+ const [previewVisible, setPreviewVisible] = useState(false);
45
+ const [previewIndex, setPreviewIndex] = useState(0);
46
+
47
+ // 图片 URL 数组(用于预览组件)
48
+ const imageUrls = useMemo(() => images.map((img) => img.dataUrl), [images]);
49
+
50
+ // 导出格式的图片数据
51
+ const imageData = useMemo<ImageData[]>(
52
+ () =>
53
+ images.map((img) => ({
54
+ base64: img.base64,
55
+ mimeType: img.mimeType,
56
+ })),
57
+ [images]
58
+ );
59
+
60
+ // 是否有图片
61
+ const hasImages = images.length > 0;
62
+
63
+ /**
64
+ * 读取图片文件为 base64
65
+ */
66
+ const readImageFile = useCallback((file: File): Promise<ImageItem> => {
67
+ return new Promise((resolve, reject) => {
68
+ const reader = new FileReader();
69
+ reader.onload = () => {
70
+ const dataUrl = reader.result as string;
71
+ // dataUrl 格式: data:image/png;base64,xxxxx
72
+ const base64 = dataUrl.split(',')[1];
73
+ resolve({
74
+ dataUrl,
75
+ base64,
76
+ mimeType: file.type,
77
+ });
78
+ };
79
+ reader.onerror = reject;
80
+ reader.readAsDataURL(file);
81
+ });
82
+ }, []);
83
+
84
+ /**
85
+ * 处理文件列表
86
+ */
87
+ const processFiles = useCallback(
88
+ async (files: File[]) => {
89
+ const remaining = maxImages - images.length;
90
+ const toProcess = files.slice(0, remaining);
91
+
92
+ const newImages: ImageItem[] = [];
93
+ for (const file of toProcess) {
94
+ if (file.size > maxSize) {
95
+ console.warn(`图片 ${file.name} 超过大小限制 (${Math.round(maxSize / 1024 / 1024)}MB)`);
96
+ continue;
97
+ }
98
+ try {
99
+ const imageItem = await readImageFile(file);
100
+ newImages.push(imageItem);
101
+ } catch (err) {
102
+ console.error('读取图片失败:', err);
103
+ }
104
+ }
105
+
106
+ if (newImages.length > 0) {
107
+ setImages((prev) => [...prev, ...newImages]);
108
+ }
109
+ },
110
+ [images.length, maxImages, maxSize, readImageFile]
111
+ );
112
+
113
+ /**
114
+ * 处理文件选择事件
115
+ */
116
+ const handleImageSelect = useCallback(
117
+ (event: React.ChangeEvent<HTMLInputElement>) => {
118
+ const files = event.target.files;
119
+ if (files) {
120
+ processFiles(Array.from(files));
121
+ }
122
+ // 清空 input 以便再次选择相同文件
123
+ event.target.value = '';
124
+ },
125
+ [processFiles]
126
+ );
127
+
128
+ /**
129
+ * 处理粘贴事件
130
+ */
131
+ const handlePaste = useCallback(
132
+ (event: React.ClipboardEvent) => {
133
+ const items = event.clipboardData?.items;
134
+ if (!items) return;
135
+
136
+ const imageFiles: File[] = [];
137
+ for (const item of Array.from(items)) {
138
+ if (item.type.startsWith('image/')) {
139
+ const file = item.getAsFile();
140
+ if (file) imageFiles.push(file);
141
+ }
142
+ }
143
+
144
+ if (imageFiles.length > 0) {
145
+ event.preventDefault();
146
+ processFiles(imageFiles);
147
+ }
148
+ },
149
+ [processFiles]
150
+ );
151
+
152
+ /**
153
+ * 处理拖拽悬停事件
154
+ */
155
+ const handleDragOver = useCallback((event: React.DragEvent) => {
156
+ event.preventDefault();
157
+ if (event.dataTransfer?.types.includes('Files')) {
158
+ setIsDragOver(true);
159
+ }
160
+ }, []);
161
+
162
+ /**
163
+ * 处理拖拽离开事件
164
+ */
165
+ const handleDragLeave = useCallback(() => {
166
+ setIsDragOver(false);
167
+ }, []);
168
+
169
+ /**
170
+ * 处理拖拽放下事件
171
+ */
172
+ const handleDrop = useCallback(
173
+ (event: React.DragEvent) => {
174
+ event.preventDefault();
175
+ setIsDragOver(false);
176
+ const files = event.dataTransfer?.files;
177
+ if (files) {
178
+ const imageFiles = Array.from(files).filter((f) => f.type.startsWith('image/'));
179
+ processFiles(imageFiles);
180
+ }
181
+ },
182
+ [processFiles]
183
+ );
184
+
185
+ /**
186
+ * 打开图片预览
187
+ */
188
+ const openPreview = useCallback((index: number) => {
189
+ setPreviewIndex(index);
190
+ setPreviewVisible(true);
191
+ }, []);
192
+
193
+ /**
194
+ * 关闭图片预览
195
+ */
196
+ const closePreview = useCallback(() => {
197
+ setPreviewVisible(false);
198
+ }, []);
199
+
200
+ /**
201
+ * 移除指定索引的图片
202
+ */
203
+ const removeImage = useCallback((index: number) => {
204
+ setImages((prev) => prev.filter((_, i) => i !== index));
205
+ }, []);
206
+
207
+ /**
208
+ * 清空所有图片
209
+ */
210
+ const clearImages = useCallback(() => {
211
+ setImages([]);
212
+ }, []);
213
+
214
+ /**
215
+ * 添加图片文件
216
+ */
217
+ const addImages = useCallback(
218
+ (files: File[]) => {
219
+ processFiles(files);
220
+ },
221
+ [processFiles]
222
+ );
223
+
224
+ return {
225
+ // 状态
226
+ images,
227
+ isDragOver,
228
+ previewVisible,
229
+ previewIndex,
230
+ setPreviewIndex,
231
+
232
+ // 计算属性
233
+ imageUrls,
234
+ imageData,
235
+ hasImages,
236
+
237
+ // 事件处理
238
+ handleImageSelect,
239
+ handlePaste,
240
+ handleDragOver,
241
+ handleDragLeave,
242
+ handleDrop,
243
+
244
+ // 预览控制
245
+ openPreview,
246
+ closePreview,
247
+
248
+ // 图片操作
249
+ removeImage,
250
+ clearImages,
251
+ addImages,
252
+ };
253
+ }
package/src/index.ts CHANGED
@@ -3,26 +3,29 @@
3
3
  *
4
4
  * AI Chat 前端组件库 - React 版本
5
5
  *
6
- * 使用 adapter 模式,与后端通信方式解耦
7
- * 与 Vue 版本 (@huyooo/ai-chat-frontend-vue) 保持一致的 API
6
+ * 新架构:使用 ContentPart 数组渲染消息内容
7
+ * - 支持流式渲染
8
+ * - 支持自定义工具结果 UI
9
+ * - 支持思考、搜索、工具调用等多种内容类型
8
10
  */
9
11
 
10
- // bridge-electron 重新导出核心类型,确保类型一致性
11
- // 注意:createElectronAdapter 需要从 @huyooo/ai-chat-bridge-electron/renderer 单独导入
12
+ // ==================== 核心类型 ====================
13
+
14
+ // 从 bridge-electron 重新导出通信相关类型
12
15
  export type {
13
16
  ChatAdapter,
14
- ChatProgress,
15
- ChatProgressType,
17
+ ChatEvent,
18
+ ChatEventType,
16
19
  ChatOptions,
17
20
  ChatMode,
18
- ModelConfig,
19
- ModelProvider,
20
21
  ThinkingMode,
21
22
  SessionRecord,
22
23
  MessageRecord,
24
+ ModelOption,
25
+ ProviderType,
23
26
  } from '@huyooo/ai-chat-bridge-electron/renderer'
24
27
 
25
- // 导出本地定义的辅助类型
28
+ // 导出 adapter 辅助类型
26
29
  export type {
27
30
  ThinkingData,
28
31
  ToolCallData,
@@ -33,40 +36,91 @@ export type {
33
36
  UpdateSessionOptions,
34
37
  SaveMessageOptions,
35
38
  } from './adapter'
36
- export { createNullAdapter } from './adapter'
37
39
 
38
- // 导出 hooks
40
+ // 导出消息和内容类型
41
+ export type {
42
+ // 消息类型
43
+ ChatMessage,
44
+ // ContentPart 类型
45
+ ContentPart,
46
+ ContentPartType,
47
+ TextPart,
48
+ ThinkingPart,
49
+ SearchPart,
50
+ ToolCallPart,
51
+ ToolResultPart,
52
+ ImagePart,
53
+ ErrorPart,
54
+ // 搜索结果
55
+ SearchResult,
56
+ // 错误详情
57
+ ErrorDetails,
58
+ // 输入配置
59
+ ChatInputOptions,
60
+ } from './types'
61
+
62
+ // 导出工具函数
63
+ export { getMessageText } from './types'
64
+
65
+ // ==================== Hooks ====================
66
+
39
67
  export { useChat } from './hooks/useChat'
40
- export type { UseChatOptions } from './hooks/useChat'
68
+ export type { UseChatOptions, ToolCompleteEvent, SideEffect } from './hooks/useChat'
69
+
70
+ // ==================== Context ====================
71
+
72
+ export { ChatInputProvider, useChatInputContext } from './context/ChatInputContext'
73
+ export type { ChatInputContextValue } from './context/ChatInputContext'
74
+
75
+ // 渲染器上下文
76
+ export { RenderersProvider, BlockRenderersContext, ToolRenderersContext } from './context/RenderersContext'
77
+ export type { BlockRenderers, ToolRenderers } from './context/RenderersContext'
78
+
79
+ // ==================== 主组件 ====================
41
80
 
42
- // 导出主组件
43
81
  export { ChatPanel } from './components/ChatPanel'
82
+ export type { ChatPanelHandle } from './components/ChatPanel'
44
83
 
45
- // 导出输入组件
46
- export { ChatInput } from './components/ChatInput'
84
+ // ==================== 消息组件 ====================
47
85
 
48
- // 导出 Header 组件
49
- export { ChatHeader } from './components/chat/ui/ChatHeader'
86
+ export { MessageBubble } from './components/message/MessageBubble'
50
87
 
51
- // 导出欢迎消息组件
52
- export { WelcomeMessage } from './components/chat/ui/WelcomeMessage'
88
+ // 内容渲染组件(保留,兼容旧代码)
89
+ export { ContentRenderer } from './components/message/ContentRenderer'
90
+ export { TextBlock, CodeBlock } from './components/message/blocks'
91
+ export { ToolResultRenderer } from './components/message/ToolResultRenderer'
92
+ export { DefaultToolResult, WeatherCard, SearchResults } from './components/message/tool-results'
53
93
 
54
- // 导出消息组件
55
- export { MessageBubble } from './components/chat/messages/MessageBubble'
56
- export { ExecutionSteps } from './components/chat/messages/ExecutionSteps'
94
+ // ==================== 其他组件 ====================
57
95
 
58
- // 导出前端特有类型(不与 bridge-electron 重复)
96
+ // 输入组件
97
+ export { ChatInput } from './components/input/ChatInput'
98
+
99
+ // Header 组件
100
+ export { ChatHeader } from './components/header/ChatHeader'
101
+
102
+ // 欢迎消息组件
103
+ export { WelcomeMessage } from './components/message/WelcomeMessage'
104
+ export type { WelcomeConfig, WelcomeFeature, WelcomeTask } from './components/message/welcome-types'
105
+ export { defaultWelcomeConfig } from './components/message/welcome-types'
106
+
107
+ // 通用组件
108
+ export { ConfirmDialog } from './components/common/ConfirmDialog'
109
+ export { Toast } from './components/common/Toast'
110
+
111
+ // ==================== 工具渲染器相关 ====================
112
+
113
+ // 从 ai-chat-shared 重新导出(用于自定义工具渲染器)
59
114
  export type {
60
- ChatMessage,
61
- SearchResult,
62
- ToolCall,
63
- // 向后兼容
64
- ChatSession,
65
- MediaOperation,
66
- AiModel,
67
- DiffStat,
68
- } from './types'
69
- export { DEFAULT_MODELS, FileType } from './types'
115
+ ContentBlockType,
116
+ ContentBlock,
117
+ TextBlock as TextBlockType,
118
+ CodeBlock as CodeBlockType,
119
+ ToolRendererProps,
120
+ WeatherData,
121
+ SearchResultItem,
122
+ } from '@huyooo/ai-chat-shared'
123
+ export { parseContent, highlightCode, getLanguageDisplayName, renderMarkdown } from '@huyooo/ai-chat-shared'
70
124
 
71
125
  /**
72
126
  * 使用说明:
@@ -74,15 +128,18 @@ export { DEFAULT_MODELS, FileType } from './types'
74
128
  * 1. 导入样式:
75
129
  * import '@huyooo/ai-chat-frontend-react/style.css'
76
130
  *
77
- * 2. 创建 adapter 和使用组件:
131
+ * 2. 基本使用:
78
132
  * import { ChatPanel } from '@huyooo/ai-chat-frontend-react'
79
133
  * import { createElectronAdapter } from '@huyooo/ai-chat-bridge-electron/renderer'
80
134
  * const adapter = createElectronAdapter()
81
- * <ChatPanel adapter={adapter} />
135
+ * <ChatPanel adapter={adapter} cwd="/path/to/dir" />
136
+ *
137
+ * 3. 自定义工具渲染器:
138
+ * import CustomWeatherCard from './CustomWeatherCard'
139
+ * const toolRenderers = { get_weather: CustomWeatherCard }
140
+ * <ChatPanel adapter={adapter} toolRenderers={toolRenderers} />
82
141
  *
83
- * 3. 或使用 useChat hook 自定义 UI:
142
+ * 4. 使用 useChat hook 自定义 UI:
84
143
  * import { useChat } from '@huyooo/ai-chat-frontend-react'
85
- * import { createElectronAdapter } from '@huyooo/ai-chat-bridge-electron/renderer'
86
- * const adapter = createElectronAdapter()
87
- * const chat = useChat({ adapter })
144
+ * const { messages, sendMessage, ... } = useChat({ adapter })
88
145
  */