Download apps/vscode/webview-ui/src/components/inputarea/hooks/useMediaUpload.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 4.67 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vscode/webview-ui/src/components/inputarea/hooks/useMediaUpload.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/apps/vscode/webview-ui/src/components/inputarea/hooks/useMediaUpload.ts
-
curl -L -o useMediaUpload.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/apps/vscode/webview-ui/src/components/inputarea/hooks/useMediaUpload.ts
4.67 kB
| import { useCallback, useEffect } from "react"; | |
| import { toast } from "@/components/ui/sonner"; | |
| import { bridge } from "@/services"; | |
| import { validateMediaFile, validateTotalSize, processMediaFile, getMediaType } from "@/lib/media-utils"; | |
| import { useChatStore } from "@/stores"; | |
| import { MEDIA_CONFIG } from "@/services/config"; | |
| interface UseMediaUploadResult { | |
| canAddMedia: boolean; | |
| handlePaste: (e: React.ClipboardEvent) => void; | |
| handlePickMedia: () => Promise<void>; | |
| addMediaFiles: (files: File[]) => void; | |
| } | |
| export function useMediaUpload(): UseMediaUploadResult { | |
| const { draftMedia, addDraftMedia, updateDraftMedia, removeDraftMedia } = useChatStore(); | |
| const hasProcessing = draftMedia.some((m) => !m.dataUri); | |
| const canAddMedia = draftMedia.length < MEDIA_CONFIG.maxCount; | |
| const getExistingDataUris = useCallback((): string[] => { | |
| return draftMedia.filter((m) => m.dataUri).map((m) => m.dataUri!); | |
| }, [draftMedia]); | |
| const processFile = useCallback( | |
| async (file: File) => { | |
| const error = validateMediaFile(file, draftMedia.length); | |
| if (error) { | |
| toast.error(error.message); | |
| return; | |
| } | |
| const id = crypto.randomUUID(); | |
| addDraftMedia(id); | |
| try { | |
| const dataUri = await processMediaFile(file); | |
| const totalError = validateTotalSize(getExistingDataUris(), dataUri); | |
| if (totalError) { | |
| removeDraftMedia(id); | |
| toast.error(totalError.message); | |
| return; | |
| } | |
| updateDraftMedia(id, dataUri); | |
| } catch { | |
| removeDraftMedia(id); | |
| toast.error("Failed to process media file"); | |
| } | |
| }, | |
| [draftMedia.length, addDraftMedia, updateDraftMedia, removeDraftMedia, getExistingDataUris], | |
| ); | |
| const addMediaFiles = useCallback( | |
| (files: File[]) => { | |
| for (const file of files) { | |
| void processFile(file); | |
| } | |
| }, | |
| [processFile], | |
| ); | |
| const handlePaste = useCallback( | |
| (e: React.ClipboardEvent) => { | |
| const items = e.clipboardData.items; | |
| const mediaFiles: File[] = []; | |
| for (const item of items) { | |
| if (item.type.startsWith("image/") || item.type.startsWith("video/")) { | |
| const file = item.getAsFile(); | |
| if (file) { | |
| mediaFiles.push(file); | |
| } | |
| } | |
| } | |
| if (mediaFiles.length === 0) { | |
| return; | |
| } | |
| e.preventDefault(); | |
| addMediaFiles(mediaFiles); | |
| }, | |
| [addMediaFiles], | |
| ); | |
| const handlePickMedia = useCallback(async () => { | |
| if (hasProcessing || draftMedia.length >= MEDIA_CONFIG.maxCount) { | |
| return; | |
| } | |
| const remaining = MEDIA_CONFIG.maxCount - draftMedia.length; | |
| try { | |
| const media = await bridge.pickMedia(remaining, true); | |
| const existingUris = getExistingDataUris(); | |
| for (const dataUri of media) { | |
| const totalError = validateTotalSize(existingUris, dataUri); | |
| if (totalError) { | |
| toast.error(totalError.message); | |
| break; | |
| } | |
| existingUris.push(dataUri); | |
| addDraftMedia(crypto.randomUUID(), dataUri); | |
| } | |
| } catch { | |
| toast.error("Failed to pick media"); | |
| } | |
| }, [hasProcessing, draftMedia.length, addDraftMedia, getExistingDataUris]); | |
| useEffect(() => { | |
| const isMediaFile = (file: File) => getMediaType(file) !== null; | |
| const handleDocDragEnter = (e: DragEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }; | |
| const handleDocDragOver = (e: DragEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (e.dataTransfer) { | |
| e.dataTransfer.dropEffect = "copy"; | |
| } | |
| }; | |
| const handleDocDrop = (e: DragEvent) => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| if (hasProcessing) { | |
| return; | |
| } | |
| if (draftMedia.length >= MEDIA_CONFIG.maxCount) { | |
| toast.error(`Maximum ${MEDIA_CONFIG.maxCount} media files allowed`); | |
| return; | |
| } | |
| const files = Array.from(e.dataTransfer?.files || []).filter(isMediaFile); | |
| if (files.length === 0) { | |
| return; | |
| } | |
| addMediaFiles(files); | |
| }; | |
| document.addEventListener("dragenter", handleDocDragEnter); | |
| document.addEventListener("dragover", handleDocDragOver); | |
| document.addEventListener("drop", handleDocDrop); | |
| return () => { | |
| document.removeEventListener("dragenter", handleDocDragEnter); | |
| document.removeEventListener("dragover", handleDocDragOver); | |
| document.removeEventListener("drop", handleDocDrop); | |
| }; | |
| }, [hasProcessing, draftMedia.length, addMediaFiles]); | |
| return { | |
| canAddMedia, | |
| handlePaste, | |
| handlePickMedia, | |
| addMediaFiles, | |
| }; | |
| } | |