@huyooo/ai-chat-frontend-react 0.2.14 → 0.2.16

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 (76) hide show
  1. package/dist/index.css +0 -1
  2. package/dist/index.js +1 -5418
  3. package/package.json +4 -5
  4. package/dist/index.css.map +0 -1
  5. package/dist/index.js.map +0 -1
  6. package/src/adapter.ts +0 -68
  7. package/src/components/ChatPanel.tsx +0 -553
  8. package/src/components/common/ConfirmDialog.css +0 -136
  9. package/src/components/common/ConfirmDialog.tsx +0 -91
  10. package/src/components/common/CopyButton.css +0 -22
  11. package/src/components/common/CopyButton.tsx +0 -46
  12. package/src/components/common/IndexingSettings.css +0 -207
  13. package/src/components/common/IndexingSettings.tsx +0 -398
  14. package/src/components/common/SettingsPanel.css +0 -337
  15. package/src/components/common/SettingsPanel.tsx +0 -215
  16. package/src/components/common/Toast.css +0 -50
  17. package/src/components/common/Toast.tsx +0 -38
  18. package/src/components/common/ToggleSwitch.css +0 -52
  19. package/src/components/common/ToggleSwitch.tsx +0 -20
  20. package/src/components/header/ChatHeader.css +0 -285
  21. package/src/components/header/ChatHeader.tsx +0 -376
  22. package/src/components/input/AtFilePicker.css +0 -147
  23. package/src/components/input/AtFilePicker.tsx +0 -519
  24. package/src/components/input/ChatInput.css +0 -283
  25. package/src/components/input/ChatInput.tsx +0 -575
  26. package/src/components/input/DropdownSelector.css +0 -231
  27. package/src/components/input/DropdownSelector.tsx +0 -333
  28. package/src/components/input/ImagePreviewModal.css +0 -124
  29. package/src/components/input/ImagePreviewModal.tsx +0 -118
  30. package/src/components/input/at-views/AtBranchView.tsx +0 -34
  31. package/src/components/input/at-views/AtBrowserView.tsx +0 -34
  32. package/src/components/input/at-views/AtChatsView.tsx +0 -34
  33. package/src/components/input/at-views/AtDocsView.tsx +0 -34
  34. package/src/components/input/at-views/AtFilesView.tsx +0 -168
  35. package/src/components/input/at-views/AtTerminalsView.tsx +0 -34
  36. package/src/components/input/at-views/AtViewStyles.css +0 -143
  37. package/src/components/input/at-views/index.ts +0 -9
  38. package/src/components/message/ContentRenderer.css +0 -9
  39. package/src/components/message/MessageBubble.css +0 -193
  40. package/src/components/message/MessageBubble.tsx +0 -240
  41. package/src/components/message/PartsRenderer.css +0 -12
  42. package/src/components/message/PartsRenderer.tsx +0 -168
  43. package/src/components/message/WelcomeMessage.css +0 -221
  44. package/src/components/message/WelcomeMessage.tsx +0 -93
  45. package/src/components/message/parts/CollapsibleCard.css +0 -80
  46. package/src/components/message/parts/CollapsibleCard.tsx +0 -80
  47. package/src/components/message/parts/ErrorPart.css +0 -9
  48. package/src/components/message/parts/ErrorPart.tsx +0 -40
  49. package/src/components/message/parts/ImagePart.css +0 -49
  50. package/src/components/message/parts/ImagePart.tsx +0 -54
  51. package/src/components/message/parts/SearchPart.css +0 -44
  52. package/src/components/message/parts/SearchPart.tsx +0 -63
  53. package/src/components/message/parts/TextPart.css +0 -579
  54. package/src/components/message/parts/TextPart.tsx +0 -213
  55. package/src/components/message/parts/ThinkingPart.css +0 -9
  56. package/src/components/message/parts/ThinkingPart.tsx +0 -48
  57. package/src/components/message/parts/ToolCallPart.css +0 -246
  58. package/src/components/message/parts/ToolCallPart.tsx +0 -289
  59. package/src/components/message/parts/ToolResultPart.css +0 -67
  60. package/src/components/message/parts/index.ts +0 -13
  61. package/src/components/message/parts/visual-predicate.ts +0 -43
  62. package/src/components/message/parts/visual-render.ts +0 -19
  63. package/src/components/message/parts/visual.ts +0 -12
  64. package/src/components/message/welcome-types.ts +0 -46
  65. package/src/context/AutoRunConfigContext.tsx +0 -13
  66. package/src/context/ChatAdapterContext.tsx +0 -8
  67. package/src/context/ChatInputContext.tsx +0 -40
  68. package/src/context/RenderersContext.tsx +0 -35
  69. package/src/hooks/useChat.ts +0 -1569
  70. package/src/hooks/useImageUpload.ts +0 -345
  71. package/src/hooks/useVoiceInput.ts +0 -454
  72. package/src/hooks/useVoiceToTextInput.ts +0 -87
  73. package/src/index.ts +0 -151
  74. package/src/styles.css +0 -330
  75. package/src/types/index.ts +0 -196
  76. package/src/utils/fileIcon.ts +0 -49
@@ -1,345 +0,0 @@
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
- /** 图片最大宽度,默认 4000px(避免超过 API 像素限制) */
29
- maxWidth?: number;
30
- /** 图片最大高度,默认 4000px(避免超过 API 像素限制) */
31
- maxHeight?: number;
32
- /** 压缩质量 0-1,默认 0.85 */
33
- quality?: number;
34
- }
35
-
36
- /**
37
- * 图片上传 hook
38
- * @param options 配置项
39
- */
40
- export function useImageUpload(options: UseImageUploadOptions = {}) {
41
- const {
42
- maxImages = 5,
43
- maxSize = 10 * 1024 * 1024,
44
- maxWidth = 4000,
45
- maxHeight = 4000,
46
- quality = 0.85
47
- } = options;
48
-
49
- // 图片列表
50
- const [images, setImages] = useState<ImageItem[]>([]);
51
-
52
- // 拖拽状态
53
- const [isDragOver, setIsDragOver] = useState(false);
54
-
55
- // 预览状态
56
- const [previewVisible, setPreviewVisible] = useState(false);
57
- const [previewIndex, setPreviewIndex] = useState(0);
58
-
59
- // 图片 URL 数组(用于预览组件)
60
- const imageUrls = useMemo(() => images.map((img) => img.dataUrl), [images]);
61
-
62
- // 导出格式的图片数据
63
- const imageData = useMemo<ImageData[]>(
64
- () =>
65
- images.map((img) => ({
66
- base64: img.base64,
67
- mimeType: img.mimeType,
68
- })),
69
- [images]
70
- );
71
-
72
- // 是否有图片
73
- const hasImages = images.length > 0;
74
-
75
- /**
76
- * 压缩图片到指定尺寸
77
- * @param file 原始图片文件
78
- * @returns 压缩后的 ImageItem
79
- */
80
- const compressImage = useCallback((file: File): Promise<ImageItem> => {
81
- return new Promise((resolve, reject) => {
82
- // 使用 FileReader 读取文件为 data URL(避免 CSP 对 blob URL 的限制)
83
- const reader = new FileReader();
84
-
85
- reader.onload = () => {
86
- const dataUrl = reader.result as string;
87
- const img = new Image();
88
-
89
- img.onload = () => {
90
- let { width, height } = img;
91
-
92
- // 计算缩放比例
93
- if (width > maxWidth || height > maxHeight) {
94
- const ratio = Math.min(maxWidth / width, maxHeight / height);
95
- width = Math.round(width * ratio);
96
- height = Math.round(height * ratio);
97
- }
98
-
99
- // 创建 canvas 进行压缩
100
- const canvas = document.createElement('canvas');
101
- canvas.width = width;
102
- canvas.height = height;
103
-
104
- const ctx = canvas.getContext('2d');
105
- if (!ctx) {
106
- reject(new Error('无法创建 canvas context'));
107
- return;
108
- }
109
-
110
- ctx.drawImage(img, 0, 0, width, height);
111
-
112
- // 转换为 dataUrl(JPEG 格式以减小体积)
113
- const mimeType = file.type === 'image/png' ? 'image/png' : 'image/jpeg';
114
- const compressedDataUrl = canvas.toDataURL(mimeType, quality);
115
- const base64 = compressedDataUrl.split(',')[1];
116
-
117
- resolve({
118
- dataUrl: compressedDataUrl,
119
- base64,
120
- mimeType,
121
- });
122
- };
123
-
124
- img.onerror = () => {
125
- reject(new Error('图片加载失败'));
126
- };
127
-
128
- img.src = dataUrl;
129
- };
130
-
131
- reader.onerror = () => {
132
- reject(new Error('读取文件失败'));
133
- };
134
-
135
- reader.readAsDataURL(file);
136
- });
137
- }, [maxWidth, maxHeight, quality]);
138
-
139
- /**
140
- * 读取图片文件为 base64(带压缩)
141
- */
142
- const readImageFile = useCallback((file: File): Promise<ImageItem> => {
143
- return compressImage(file);
144
- }, [compressImage]);
145
-
146
- /**
147
- * 处理文件列表
148
- */
149
- const processFiles = useCallback(
150
- async (files: File[]) => {
151
- const remaining = maxImages - images.length;
152
- const toProcess = files.slice(0, remaining);
153
-
154
- const newImages: ImageItem[] = [];
155
- for (const file of toProcess) {
156
- if (file.size > maxSize) {
157
- console.warn(`图片 ${file.name} 超过大小限制 (${Math.round(maxSize / 1024 / 1024)}MB)`);
158
- continue;
159
- }
160
- try {
161
- const imageItem = await readImageFile(file);
162
- newImages.push(imageItem);
163
- } catch (err) {
164
- console.error('读取图片失败:', err);
165
- }
166
- }
167
-
168
- if (newImages.length > 0) {
169
- setImages((prev) => [...prev, ...newImages]);
170
- }
171
- },
172
- [images.length, maxImages, maxSize, readImageFile]
173
- );
174
-
175
- /**
176
- * 处理文件选择事件
177
- */
178
- const handleImageSelect = useCallback(
179
- (event: React.ChangeEvent<HTMLInputElement>) => {
180
- const files = event.target.files;
181
- if (files) {
182
- processFiles(Array.from(files));
183
- }
184
- // 清空 input 以便再次选择相同文件
185
- event.target.value = '';
186
- },
187
- [processFiles]
188
- );
189
-
190
- /**
191
- * 处理粘贴事件
192
- */
193
- const handlePaste = useCallback(
194
- (event: React.ClipboardEvent) => {
195
- const items = event.clipboardData?.items;
196
- if (!items) return;
197
-
198
- const imageFiles: File[] = [];
199
- for (const item of Array.from(items)) {
200
- if (item.type.startsWith('image/')) {
201
- const file = item.getAsFile();
202
- if (file) imageFiles.push(file);
203
- }
204
- }
205
-
206
- if (imageFiles.length > 0) {
207
- event.preventDefault();
208
- processFiles(imageFiles);
209
- }
210
- },
211
- [processFiles]
212
- );
213
-
214
- /**
215
- * 处理拖拽悬停事件
216
- */
217
- const handleDragOver = useCallback((event: React.DragEvent) => {
218
- event.preventDefault();
219
- if (event.dataTransfer?.types.includes('Files')) {
220
- setIsDragOver(true);
221
- }
222
- }, []);
223
-
224
- /**
225
- * 处理拖拽离开事件
226
- */
227
- const handleDragLeave = useCallback(() => {
228
- setIsDragOver(false);
229
- }, []);
230
-
231
- /**
232
- * 处理拖拽放下事件
233
- */
234
- const handleDrop = useCallback(
235
- (event: React.DragEvent) => {
236
- event.preventDefault();
237
- setIsDragOver(false);
238
- const files = event.dataTransfer?.files;
239
- if (files) {
240
- const imageFiles = Array.from(files).filter((f) => f.type.startsWith('image/'));
241
- processFiles(imageFiles);
242
- }
243
- },
244
- [processFiles]
245
- );
246
-
247
- /**
248
- * 打开图片预览
249
- */
250
- const openPreview = useCallback((index: number) => {
251
- setPreviewIndex(index);
252
- setPreviewVisible(true);
253
- }, []);
254
-
255
- /**
256
- * 关闭图片预览
257
- */
258
- const closePreview = useCallback(() => {
259
- setPreviewVisible(false);
260
- }, []);
261
-
262
- /**
263
- * 移除指定索引的图片
264
- */
265
- const removeImage = useCallback((index: number) => {
266
- setImages((prev) => prev.filter((_, i) => i !== index));
267
- }, []);
268
-
269
- /**
270
- * 清空所有图片
271
- */
272
- const clearImages = useCallback(() => {
273
- setImages([]);
274
- }, []);
275
-
276
- /**
277
- * 添加图片文件
278
- */
279
- const addImages = useCallback(
280
- (files: File[]) => {
281
- processFiles(files);
282
- },
283
- [processFiles]
284
- );
285
-
286
- /**
287
- * 从 data URL 字符串数组初始化图片(用于历史消息编辑)
288
- * @param data data URL 字符串数组
289
- */
290
- const initImages = useCallback((data: string[]) => {
291
- const items: ImageItem[] = [];
292
- for (const item of data) {
293
- if (item.startsWith('data:')) {
294
- // data URL 格式: data:image/png;base64,xxxxx
295
- const matches = item.match(/^data:([^;]+);base64,(.+)$/);
296
- if (matches) {
297
- items.push({
298
- dataUrl: item,
299
- base64: matches[2],
300
- mimeType: matches[1],
301
- });
302
- }
303
- } else {
304
- // 普通 URL,只能作为预览显示
305
- items.push({
306
- dataUrl: item,
307
- base64: '',
308
- mimeType: 'image/unknown',
309
- });
310
- }
311
- }
312
- setImages(items);
313
- }, []);
314
-
315
- return {
316
- // 状态
317
- images,
318
- isDragOver,
319
- previewVisible,
320
- previewIndex,
321
- setPreviewIndex,
322
-
323
- // 计算属性
324
- imageUrls,
325
- imageData,
326
- hasImages,
327
-
328
- // 事件处理
329
- handleImageSelect,
330
- handlePaste,
331
- handleDragOver,
332
- handleDragLeave,
333
- handleDrop,
334
-
335
- // 预览控制
336
- openPreview,
337
- closePreview,
338
-
339
- // 图片操作
340
- removeImage,
341
- clearImages,
342
- addImages,
343
- initImages,
344
- };
345
- }