@lobehub/chat 1.114.5 → 1.115.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 (68) hide show
  1. package/.cursor/rules/project-introduce.mdc +1 -15
  2. package/.cursor/rules/project-structure.mdc +227 -0
  3. package/.cursor/rules/testing-guide/db-model-test.mdc +5 -3
  4. package/.cursor/rules/testing-guide/testing-guide.mdc +153 -168
  5. package/.github/workflows/claude.yml +1 -1
  6. package/.github/workflows/test.yml +9 -0
  7. package/.prettierignore +0 -1
  8. package/.vscode/settings.json +86 -80
  9. package/CHANGELOG.md +50 -0
  10. package/CLAUDE.md +11 -27
  11. package/changelog/v1.json +10 -0
  12. package/docs/development/basic/feature-development.mdx +1 -1
  13. package/docs/development/basic/feature-development.zh-CN.mdx +1 -1
  14. package/package.json +5 -5
  15. package/packages/const/src/image.ts +28 -0
  16. package/packages/const/src/index.ts +1 -0
  17. package/packages/database/package.json +4 -2
  18. package/packages/database/src/repositories/aiInfra/index.ts +1 -1
  19. package/packages/database/tests/setup-db.ts +3 -0
  20. package/packages/database/vitest.config.mts +33 -0
  21. package/packages/model-runtime/src/utils/modelParse.ts +1 -1
  22. package/packages/utils/src/client/imageDimensions.test.ts +95 -0
  23. package/packages/utils/src/client/imageDimensions.ts +54 -0
  24. package/packages/utils/src/number.test.ts +3 -1
  25. package/packages/utils/src/number.ts +1 -2
  26. package/src/app/[variants]/(main)/files/[id]/page.tsx +0 -2
  27. package/src/app/[variants]/(main)/image/@menu/components/SeedNumberInput/index.tsx +1 -1
  28. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/DimensionControlGroup.tsx +0 -1
  29. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUpload.tsx +206 -185
  30. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrl.tsx +16 -4
  31. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/ImageUrlsUpload.tsx +52 -3
  32. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/ImageManageModal.tsx +33 -19
  33. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/MultiImagesUpload/index.tsx +40 -12
  34. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useAutoDimensions.ts +56 -0
  35. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/hooks/useUploadFilesValidation.ts +77 -0
  36. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/index.tsx +82 -5
  37. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/dimensionConstraints.test.ts +235 -0
  38. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/__tests__/imageValidation.test.ts +401 -0
  39. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/dimensionConstraints.ts +54 -0
  40. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/utils/imageValidation.ts +117 -0
  41. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItem.tsx +3 -1
  42. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicList.tsx +15 -2
  43. package/src/app/[variants]/(main)/image/features/GenerationFeed/GenerationItem/utils.ts +5 -4
  44. package/src/libs/standard-parameters/index.ts +4 -1
  45. package/src/locales/default/components.ts +8 -0
  46. package/src/server/services/generation/index.ts +1 -1
  47. package/src/store/aiInfra/slices/aiProvider/__tests__/action.test.ts +29 -29
  48. package/src/store/aiInfra/slices/aiProvider/action.ts +80 -36
  49. package/src/store/chat/slices/builtinTool/actions/dalle.test.ts +20 -13
  50. package/src/store/file/slices/upload/action.ts +18 -7
  51. package/src/store/image/slices/generationConfig/hooks.ts +11 -1
  52. package/tsconfig.json +1 -10
  53. package/packages/const/src/imageGeneration.ts +0 -16
  54. package/src/app/(backend)/trpc/desktop/[trpc]/route.ts +0 -26
  55. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/AspectRatioSelect.tsx +0 -24
  56. package/src/app/[variants]/(main)/image/@menu/features/ConfigPanel/components/SizeSliderInput.tsx +0 -15
  57. package/src/app/[variants]/(main)/image/@topic/features/Topics/TopicItemContainer.tsx +0 -91
  58. package/src/app/desktop/devtools/page.tsx +0 -89
  59. package/src/app/desktop/layout.tsx +0 -31
  60. /package/apps/desktop/{vitest.config.ts → vitest.config.mts} +0 -0
  61. /package/packages/database/{vitest.config.ts → vitest.config.server.mts} +0 -0
  62. /package/packages/electron-server-ipc/{vitest.config.ts → vitest.config.mts} +0 -0
  63. /package/packages/file-loaders/{vitest.config.ts → vitest.config.mts} +0 -0
  64. /package/packages/model-runtime/{vitest.config.ts → vitest.config.mts} +0 -0
  65. /package/packages/prompts/{vitest.config.ts → vitest.config.mts} +0 -0
  66. /package/packages/utils/{vitest.config.ts → vitest.config.mts} +0 -0
  67. /package/packages/web-crawler/{vitest.config.ts → vitest.config.mts} +0 -0
  68. /package/{vitest.config.ts → vitest.config.mts} +0 -0
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
2
 
3
- import { MAX_SEED, calculateThumbnailDimensions, generateUniqueSeeds } from './number';
3
+ import { MAX_SEED } from '@/const/image';
4
+
5
+ import { calculateThumbnailDimensions, generateUniqueSeeds } from './number';
4
6
 
5
7
  describe('number utilities', () => {
6
8
  describe('MAX_SEED constant', () => {
@@ -1,8 +1,7 @@
1
1
  import prand from 'pure-rand';
2
2
 
3
- import { IMAGE_GENERATION_CONFIG } from '@/const/imageGeneration';
3
+ import { IMAGE_GENERATION_CONFIG, MAX_SEED } from '@/const/image';
4
4
 
5
- export const MAX_SEED = 2 ** 31 - 1;
6
5
  export function generateUniqueSeeds(seedCount: number): number[] {
7
6
  // Use current timestamp as the initial seed
8
7
  const initialSeed = Date.now();
@@ -39,5 +39,3 @@ const FilePage = async (props: PagePropsWithId) => {
39
39
  };
40
40
 
41
41
  export default FilePage;
42
-
43
- export const dynamic = 'force-static';
@@ -6,7 +6,7 @@ import { CSSProperties, memo, useCallback } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
- import { MAX_SEED } from '@/libs/standard-parameters/index';
9
+ import { MAX_SEED } from '@/const/image';
10
10
  import { generateUniqueSeeds } from '@/utils/number';
11
11
 
12
12
  export interface SeedNumberInputProps {
@@ -15,7 +15,6 @@ const styles = {
15
15
  width: '100%',
16
16
  },
17
17
  label: {
18
- fontSize: 12,
19
18
  fontWeight: 500,
20
19
  },
21
20
  } as const;
@@ -12,14 +12,20 @@ import { useFileStore } from '@/store/file';
12
12
  import { FileUploadStatus } from '@/types/files/upload';
13
13
 
14
14
  import { useDragAndDrop } from '../hooks/useDragAndDrop';
15
+ import { useUploadFilesValidation } from '../hooks/useUploadFilesValidation';
15
16
  import { useConfigPanelStyles } from '../style';
16
17
 
17
18
  // ======== Business Types ======== //
18
19
 
19
20
  export interface ImageUploadProps {
20
- // Callback when URL changes
21
+ // Callback when URL changes - supports both old API (string) and new API (object with dimensions)
21
22
  className?: string; // Image URL
22
- onChange?: (url?: string) => void;
23
+ maxFileSize?: number;
24
+ onChange?: (
25
+ data?:
26
+ | string // Old API: just URL
27
+ | { dimensions?: { height: number, width: number; }, url: string; }, // New API: URL with dimensions
28
+ ) => void;
23
29
  style?: React.CSSProperties;
24
30
  value?: string | null;
25
31
  }
@@ -414,212 +420,227 @@ SuccessDisplay.displayName = 'SuccessDisplay';
414
420
 
415
421
  // ======== Main Component ======== //
416
422
 
417
- const ImageUpload: FC<ImageUploadProps> = memo(({ value, onChange, style, className }) => {
418
- const inputRef = useRef<HTMLInputElement>(null);
419
- const uploadWithProgress = useFileStore((s) => s.uploadWithProgress);
420
- const [uploadState, setUploadState] = useState<UploadState | null>(null);
421
- const { t } = useTranslation('components');
422
- const { message } = App.useApp();
423
-
424
- // Cleanup blob URLs to prevent memory leaks
425
- useEffect(() => {
426
- return () => {
427
- if (uploadState?.previewUrl && isLocalBlobUrl(uploadState.previewUrl)) {
428
- URL.revokeObjectURL(uploadState.previewUrl);
429
- }
423
+ const ImageUpload: FC<ImageUploadProps> = memo(
424
+ ({ value, onChange, style, className, maxFileSize }) => {
425
+ const inputRef = useRef<HTMLInputElement>(null);
426
+ const uploadWithProgress = useFileStore((s) => s.uploadWithProgress);
427
+ const [uploadState, setUploadState] = useState<UploadState | null>(null);
428
+ const { t } = useTranslation('components');
429
+ const { message } = App.useApp();
430
+ const { validateFiles } = useUploadFilesValidation(undefined, maxFileSize);
431
+
432
+ // Cleanup blob URLs to prevent memory leaks
433
+ useEffect(() => {
434
+ return () => {
435
+ if (uploadState?.previewUrl && isLocalBlobUrl(uploadState.previewUrl)) {
436
+ URL.revokeObjectURL(uploadState.previewUrl);
437
+ }
438
+ };
439
+ }, [uploadState?.previewUrl]);
440
+
441
+ const handleFileSelect = () => {
442
+ inputRef.current?.click();
430
443
  };
431
- }, [uploadState?.previewUrl]);
432
444
 
433
- const handleFileSelect = () => {
434
- inputRef.current?.click();
435
- };
445
+ const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
446
+ const file = event.target.files?.[0];
447
+ if (!file) return;
436
448
 
437
- const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
438
- const file = event.target.files?.[0];
439
- if (!file) return;
449
+ // Validate file using unified validation hook
450
+ if (!validateFiles([file])) return;
440
451
 
441
- // Create preview URL
442
- const previewUrl = URL.createObjectURL(file);
452
+ // Create preview URL
453
+ const previewUrl = URL.createObjectURL(file);
443
454
 
444
- // Set initial upload state
445
- setUploadState({
446
- previewUrl,
447
- progress: 0,
448
- status: 'pending',
449
- });
450
-
451
- try {
452
- // Start upload
453
- const result = await uploadWithProgress({
454
- file,
455
- onStatusUpdate: (updateData) => {
456
- if (updateData.type === 'updateFile') {
457
- setUploadState((prev) => {
458
- if (!prev) return null;
459
-
460
- const fileStatus = updateData.value.status;
461
- if (!fileStatus) return prev;
462
-
463
- return {
464
- ...prev,
465
- error: fileStatus === 'error' ? 'Upload failed' : undefined,
466
- progress: updateData.value.uploadState?.progress || 0,
467
- status: fileStatus,
468
- };
469
- });
470
- } else if (updateData.type === 'removeFile') {
471
- // Handle file removal
472
- setUploadState(null);
473
- }
474
- },
475
- skipCheckFileType: true,
455
+ // Set initial upload state
456
+ setUploadState({
457
+ previewUrl,
458
+ progress: 0,
459
+ status: 'pending',
476
460
  });
477
461
 
478
- if (result?.url) {
479
- // Upload successful
480
- onChange?.(result.url);
481
- }
482
- } catch {
483
- // Upload failed
484
- setUploadState((prev) =>
485
- prev
486
- ? {
487
- ...prev,
488
- error: 'Upload failed',
489
- status: 'error',
462
+ try {
463
+ // Start upload
464
+ const result = await uploadWithProgress({
465
+ file,
466
+ onStatusUpdate: (updateData) => {
467
+ if (updateData.type === 'updateFile') {
468
+ setUploadState((prev) => {
469
+ if (!prev) return null;
470
+
471
+ const fileStatus = updateData.value.status;
472
+ if (!fileStatus) return prev;
473
+
474
+ return {
475
+ ...prev,
476
+ error: fileStatus === 'error' ? 'Upload failed' : undefined,
477
+ progress: updateData.value.uploadState?.progress || 0,
478
+ status: fileStatus,
479
+ };
480
+ });
481
+ } else if (updateData.type === 'removeFile') {
482
+ // Handle file removal
483
+ setUploadState(null);
490
484
  }
491
- : null,
492
- );
493
- } finally {
494
- // Cleanup
495
- if (isLocalBlobUrl(previewUrl)) {
496
- URL.revokeObjectURL(previewUrl);
485
+ },
486
+ skipCheckFileType: true,
487
+ });
488
+
489
+ if (result?.url) {
490
+ // Upload successful - pass dimensions if available
491
+ const callbackData = result.dimensions
492
+ ? { dimensions: result.dimensions, url: result.url }
493
+ : result.url;
494
+ onChange?.(callbackData);
495
+ }
496
+ } catch {
497
+ // Upload failed
498
+ setUploadState((prev) =>
499
+ prev
500
+ ? {
501
+ ...prev,
502
+ error: 'Upload failed',
503
+ status: 'error',
504
+ }
505
+ : null,
506
+ );
507
+ } finally {
508
+ // Cleanup
509
+ if (isLocalBlobUrl(previewUrl)) {
510
+ URL.revokeObjectURL(previewUrl);
511
+ }
512
+
513
+ // Clear upload state after a delay to show completion
514
+ setTimeout(() => {
515
+ setUploadState(null);
516
+ }, 1000);
497
517
  }
518
+ };
498
519
 
499
- // Clear upload state after a delay to show completion
500
- setTimeout(() => {
501
- setUploadState(null);
502
- }, 1000);
503
- }
504
- };
505
-
506
- const handleDelete = () => {
507
- onChange?.(undefined);
508
- };
509
-
510
- const handleDrop = async (files: File[]) => {
511
- // Show warning if multiple files detected
512
- if (files.length > 1) {
513
- message.warning(t('ImageUpload.actions.dropMultipleFiles'));
514
- }
515
-
516
- // Take the first image file
517
- const file = files[0];
520
+ const handleDelete = () => {
521
+ onChange?.(undefined);
522
+ };
518
523
 
519
- // Create preview URL
520
- const previewUrl = URL.createObjectURL(file);
524
+ const handleDrop = async (files: File[]) => {
525
+ // Show warning if multiple files detected
526
+ if (files.length > 1) {
527
+ message.warning(t('ImageUpload.actions.dropMultipleFiles'));
528
+ }
521
529
 
522
- // Set initial upload state
523
- setUploadState({
524
- previewUrl,
525
- progress: 0,
526
- status: 'pending',
527
- });
530
+ // Take the first image file
531
+ const file = files[0];
528
532
 
529
- try {
530
- // Start upload using the same logic as handleFileChange
531
- const result = await uploadWithProgress({
532
- file,
533
- onStatusUpdate: (updateData) => {
534
- if (updateData.type === 'updateFile') {
535
- setUploadState((prev) => {
536
- if (!prev) return null;
533
+ // Validate file using unified validation hook
534
+ if (!validateFiles([file])) return;
537
535
 
538
- const fileStatus = updateData.value.status;
539
- if (!fileStatus) return prev;
536
+ // Create preview URL
537
+ const previewUrl = URL.createObjectURL(file);
540
538
 
541
- return {
542
- ...prev,
543
- error: fileStatus === 'error' ? 'Upload failed' : undefined,
544
- progress: updateData.value.uploadState?.progress || 0,
545
- status: fileStatus,
546
- };
547
- });
548
- } else if (updateData.type === 'removeFile') {
549
- setUploadState(null);
550
- }
551
- },
552
- skipCheckFileType: true,
539
+ // Set initial upload state
540
+ setUploadState({
541
+ previewUrl,
542
+ progress: 0,
543
+ status: 'pending',
553
544
  });
554
545
 
555
- if (result?.url) {
556
- // Upload successful
557
- onChange?.(result.url);
558
- }
559
- } catch {
560
- // Upload failed
561
- setUploadState((prev) =>
562
- prev
563
- ? {
564
- ...prev,
565
- error: 'Upload failed',
566
- status: 'error',
546
+ try {
547
+ // Start upload using the same logic as handleFileChange
548
+ const result = await uploadWithProgress({
549
+ file,
550
+ onStatusUpdate: (updateData) => {
551
+ if (updateData.type === 'updateFile') {
552
+ setUploadState((prev) => {
553
+ if (!prev) return null;
554
+
555
+ const fileStatus = updateData.value.status;
556
+ if (!fileStatus) return prev;
557
+
558
+ return {
559
+ ...prev,
560
+ error: fileStatus === 'error' ? 'Upload failed' : undefined,
561
+ progress: updateData.value.uploadState?.progress || 0,
562
+ status: fileStatus,
563
+ };
564
+ });
565
+ } else if (updateData.type === 'removeFile') {
566
+ setUploadState(null);
567
567
  }
568
- : null,
569
- );
570
- } finally {
571
- // Cleanup
572
- if (isLocalBlobUrl(previewUrl)) {
573
- URL.revokeObjectURL(previewUrl);
568
+ },
569
+ skipCheckFileType: true,
570
+ });
571
+
572
+ if (result?.url) {
573
+ // Upload successful - pass dimensions if available
574
+ const callbackData = result.dimensions
575
+ ? { dimensions: result.dimensions, url: result.url }
576
+ : result.url;
577
+ onChange?.(callbackData);
578
+ }
579
+ } catch {
580
+ // Upload failed
581
+ setUploadState((prev) =>
582
+ prev
583
+ ? {
584
+ ...prev,
585
+ error: 'Upload failed',
586
+ status: 'error',
587
+ }
588
+ : null,
589
+ );
590
+ } finally {
591
+ // Cleanup
592
+ if (isLocalBlobUrl(previewUrl)) {
593
+ URL.revokeObjectURL(previewUrl);
594
+ }
595
+
596
+ // Clear upload state after a delay to show completion
597
+ setTimeout(() => {
598
+ setUploadState(null);
599
+ }, 1000);
574
600
  }
601
+ };
575
602
 
576
- // Clear upload state after a delay to show completion
577
- setTimeout(() => {
578
- setUploadState(null);
579
- }, 1000);
580
- }
581
- };
582
-
583
- const { isDragOver, dragHandlers } = useDragAndDrop({
584
- accept: 'image/*',
585
- onDrop: handleDrop,
586
- });
587
-
588
- // Determine which view to render
589
- const hasImage = Boolean(value);
590
- const isUploading = Boolean(uploadState);
603
+ const { isDragOver, dragHandlers } = useDragAndDrop({
604
+ accept: 'image/*',
605
+ onDrop: handleDrop,
606
+ });
591
607
 
592
- return (
593
- <div className={className} {...dragHandlers} style={style}>
594
- {/* Hidden file input */}
595
- <input
596
- accept="image/*"
597
- onChange={handleFileChange}
598
- onClick={(e) => {
599
- // Reset value to allow re-selecting the same file
600
- e.currentTarget.value = '';
601
- }}
602
- ref={inputRef}
603
- style={{ display: 'none' }}
604
- type="file"
605
- />
608
+ // Determine which view to render
609
+ const hasImage = Boolean(value);
610
+ const isUploading = Boolean(uploadState);
606
611
 
607
- {/* Conditional rendering based on state */}
608
- {isUploading && uploadState ? (
609
- <UploadingDisplay previewUrl={uploadState.previewUrl} progress={uploadState.progress} />
610
- ) : hasImage ? (
611
- <SuccessDisplay
612
- imageUrl={value!}
613
- isDragOver={isDragOver}
614
- onChangeImage={handleFileSelect}
615
- onDelete={handleDelete}
612
+ return (
613
+ <div className={className} {...dragHandlers} style={style}>
614
+ {/* Hidden file input */}
615
+ <input
616
+ accept="image/*"
617
+ onChange={handleFileChange}
618
+ onClick={(e) => {
619
+ // Reset value to allow re-selecting the same file
620
+ e.currentTarget.value = '';
621
+ }}
622
+ ref={inputRef}
623
+ style={{ display: 'none' }}
624
+ type="file"
616
625
  />
617
- ) : (
618
- <Placeholder isDragOver={isDragOver} onClick={handleFileSelect} />
619
- )}
620
- </div>
621
- );
622
- });
626
+
627
+ {/* Conditional rendering based on state */}
628
+ {isUploading && uploadState ? (
629
+ <UploadingDisplay previewUrl={uploadState.previewUrl} progress={uploadState.progress} />
630
+ ) : hasImage ? (
631
+ <SuccessDisplay
632
+ imageUrl={value!}
633
+ isDragOver={isDragOver}
634
+ onChangeImage={handleFileSelect}
635
+ onDelete={handleDelete}
636
+ />
637
+ ) : (
638
+ <Placeholder isDragOver={isDragOver} onClick={handleFileSelect} />
639
+ )}
640
+ </div>
641
+ );
642
+ },
643
+ );
623
644
 
624
645
  ImageUpload.displayName = 'ImageUpload';
625
646
 
@@ -2,17 +2,29 @@ import { memo } from 'react';
2
2
 
3
3
  import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks';
4
4
 
5
+ import { useAutoDimensions } from '../hooks/useAutoDimensions';
5
6
  import ImageUpload from './ImageUpload';
6
7
 
7
8
  const ImageUrl = memo(() => {
8
- const { value: imageUrl, setValue } = useGenerationConfigParam('imageUrl');
9
+ const { value: imageUrl, setValue, maxFileSize } = useGenerationConfigParam('imageUrl');
10
+ const { autoSetDimensions, extractUrlAndDimensions } = useAutoDimensions();
11
+
12
+ const handleChange = (
13
+ data?:
14
+ | string // Old API: just URL
15
+ | { dimensions?: { height: number; width: number }; url: string }, // New API: URL with dimensions
16
+ ) => {
17
+ const { url, dimensions } = extractUrlAndDimensions(data);
9
18
 
10
- // Extract the first URL from the array for single image display
11
- const handleChange = (url?: string) => {
12
19
  setValue(url ?? null);
20
+
21
+ // Auto-set dimensions if available
22
+ if (dimensions) {
23
+ autoSetDimensions(dimensions);
24
+ }
13
25
  };
14
26
 
15
- return <ImageUpload onChange={handleChange} value={imageUrl} />;
27
+ return <ImageUpload maxFileSize={maxFileSize} onChange={handleChange} value={imageUrl} />;
16
28
  });
17
29
 
18
30
  export default ImageUrl;
@@ -2,18 +2,67 @@ import { memo } from 'react';
2
2
 
3
3
  import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks';
4
4
 
5
+ import { useAutoDimensions } from '../hooks/useAutoDimensions';
6
+ import ImageUpload from './ImageUpload';
5
7
  import MultiImagesUpload from './MultiImagesUpload';
6
8
 
7
9
  const ImageUrlsUpload = memo(() => {
8
- const { value, setValue } = useGenerationConfigParam('imageUrls');
10
+ const { value, setValue, maxCount, maxFileSize } = useGenerationConfigParam('imageUrls');
11
+ const { autoSetDimensions, extractUrlAndDimensions } = useAutoDimensions();
12
+
13
+ // When maxCount is 1, use ImageUpload for single image upload
14
+ if (maxCount === 1) {
15
+ const handleSingleChange = (
16
+ data?:
17
+ | string // Old API: just URL
18
+ | { dimensions?: { height: number; width: number }; url: string }, // New API: URL with dimensions
19
+ ) => {
20
+ const { url, dimensions } = extractUrlAndDimensions(data);
21
+
22
+ setValue(url ? [url] : []);
23
+
24
+ // Auto-set dimensions if available
25
+ if (dimensions) {
26
+ autoSetDimensions(dimensions);
27
+ }
28
+ };
29
+
30
+ return (
31
+ <ImageUpload
32
+ maxFileSize={maxFileSize}
33
+ onChange={handleSingleChange}
34
+ value={value?.[0] ?? null}
35
+ />
36
+ );
37
+ }
38
+
39
+ // Otherwise use MultiImagesUpload for multiple images
40
+ const handleChange = (
41
+ data:
42
+ | string[] // Old API: just URLs
43
+ | { dimensions?: { height: number; width: number }; urls: string[] }, // New API: URLs with first image dimensions
44
+ ) => {
45
+ const urls = Array.isArray(data) ? data : data.urls;
46
+ const dimensions = Array.isArray(data) ? undefined : data.dimensions;
9
47
 
10
- const handleChange = (urls: string[]) => {
11
48
  // Directly set the URLs to the store
12
49
  // The store will handle URL to path conversion when needed
13
50
  setValue(urls);
51
+
52
+ // Only auto-set dimensions if no existing images and only uploading one image
53
+ if (!value?.length && urls.length === 1 && dimensions) {
54
+ autoSetDimensions(dimensions);
55
+ }
14
56
  };
15
57
 
16
- return <MultiImagesUpload onChange={handleChange} value={value} />;
58
+ return (
59
+ <MultiImagesUpload
60
+ maxCount={maxCount}
61
+ maxFileSize={maxFileSize}
62
+ onChange={handleChange}
63
+ value={value}
64
+ />
65
+ );
17
66
  });
18
67
 
19
68
  export default ImageUrlsUpload;