From 12323375a5e5d454573b67b7b1e8f5987049ce22 Mon Sep 17 00:00:00 2001 From: icarus Date: Sun, 12 Oct 2025 07:00:23 +0800 Subject: [PATCH] feat(video): add image reference upload with validation implement image reference upload functionality in video panel add validation for file format and size (max 5MB) replace lodash merge with custom deepUpdate utility add error messages for invalid uploads --- src/renderer/src/i18n/locales/en-us.json | 4 + src/renderer/src/pages/video/VideoPage.tsx | 4 +- src/renderer/src/pages/video/VideoPanel.tsx | 97 +++++++++++++++++---- src/renderer/src/utils/deepUpdate.ts | 35 ++++++++ 4 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 src/renderer/src/utils/deepUpdate.ts diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index e27bd9cd0a..b3fb0bdf0d 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -4659,6 +4659,10 @@ }, "input_reference": { "add": { + "error": { + "format": "Not a image", + "size": "This image is too large. It should be under 5MB." + }, "tooltip": "Add image reference" } }, diff --git a/src/renderer/src/pages/video/VideoPage.tsx b/src/renderer/src/pages/video/VideoPage.tsx index 646fd02241..7ec571d3ae 100644 --- a/src/renderer/src/pages/video/VideoPage.tsx +++ b/src/renderer/src/pages/video/VideoPage.tsx @@ -5,9 +5,9 @@ import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { useProvider } from '@renderer/hooks/useProvider' import { SystemProviderIds } from '@renderer/types' import { CreateVideoParams } from '@renderer/types/video' +import { deepUpdate } from '@renderer/utils/deepUpdate' import { isVideoModel } from '@renderer/utils/model/video' import { DeepPartial } from 'ai' -import { merge } from 'lodash' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -33,7 +33,7 @@ export const VideoPage = () => { }) const updateParams = useCallback((update: DeepPartial>) => { - setParams((prev) => merge({}, prev, update)) + setParams((prev) => deepUpdate(prev, update)) }, []) const updateModelId = useCallback( diff --git a/src/renderer/src/pages/video/VideoPanel.tsx b/src/renderer/src/pages/video/VideoPanel.tsx index 7fab56af7d..18b5f21c96 100644 --- a/src/renderer/src/pages/video/VideoPanel.tsx +++ b/src/renderer/src/pages/video/VideoPanel.tsx @@ -1,13 +1,15 @@ -import { Button, Skeleton, Textarea, Tooltip } from '@heroui/react' +import { Button, cn, Image, Skeleton, Textarea, Tooltip } from '@heroui/react' import { loggerService } from '@logger' import { useAddOpenAIVideo } from '@renderer/hooks/video/useAddOpenAIVideo' import { createVideo } from '@renderer/services/ApiService' import { Provider } from '@renderer/types' import { CreateVideoParams, Video } from '@renderer/types/video' import { getErrorMessage } from '@renderer/utils' +import { MB } from '@shared/config/constant' import { DeepPartial } from 'ai' -import { ArrowUp, ImageIcon } from 'lucide-react' -import { useCallback, useState } from 'react' +import { isEmpty } from 'lodash' +import { ArrowUp, CircleXIcon, ImageIcon } from 'lucide-react' +import { useCallback, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { VideoViewer } from './VideoViewer' @@ -23,11 +25,18 @@ const logger = loggerService.withContext('VideoPanel') export const VideoPanel = ({ provider, video, params, updateParams }: VideoPanelProps) => { const { t } = useTranslation() - const [isProcessing, setIsProcessing] = useState(false) const addOpenAIVideo = useAddOpenAIVideo(provider.id) + const [isProcessing, setIsProcessing] = useState(false) + const fileInputRef = useRef(null) + const inputReference = params.params.input_reference + + const couldCreateVideo = useMemo( + () => !isProcessing && !isEmpty(params.params.prompt), + [isProcessing, params.params.prompt] + ) const handleCreateVideo = useCallback(async () => { - if (isProcessing) return + if (!couldCreateVideo) return setIsProcessing(true) try { const result = await createVideo(params) @@ -44,10 +53,49 @@ export const VideoPanel = ({ provider, video, params, updateParams }: VideoPanel } finally { setIsProcessing(false) } - }, [addOpenAIVideo, isProcessing, params, t]) + }, [addOpenAIVideo, couldCreateVideo, params, t]) + + const handleUploadFile = useCallback(() => { + fileInputRef.current?.click() + }, []) const setPrompt = useCallback((value: string) => updateParams({ params: { prompt: value } }), [updateParams]) + const UploadImageReferenceButton = useCallback(() => { + const content = inputReference ? ( +
+ +
+ ) : ( + t('video.input_reference.add.tooltip') + ) + return ( + <> + +