Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	allow music and video
Browse files
    	
        app/api/me/projects/[namespace]/[repoId]/images/route.ts
    CHANGED
    
    | @@ -39,22 +39,22 @@ export async function POST( | |
| 39 | 
             
                  );
         | 
| 40 | 
             
                }
         | 
| 41 |  | 
| 42 | 
            -
                // Parse the FormData to get the  | 
| 43 | 
             
                const formData = await req.formData();
         | 
| 44 | 
            -
                const  | 
| 45 |  | 
| 46 | 
            -
                if (! | 
| 47 | 
             
                  return NextResponse.json(
         | 
| 48 | 
             
                    {
         | 
| 49 | 
             
                      ok: false,
         | 
| 50 | 
            -
                      error: "At least one  | 
| 51 | 
             
                    },
         | 
| 52 | 
             
                    { status: 400 }
         | 
| 53 | 
             
                  );
         | 
| 54 | 
             
                }
         | 
| 55 |  | 
| 56 | 
             
                const files: File[] = [];
         | 
| 57 | 
            -
                for (const file of  | 
| 58 | 
             
                  if (!(file instanceof File)) {
         | 
| 59 | 
             
                    return NextResponse.json(
         | 
| 60 | 
             
                      {
         | 
| @@ -65,18 +65,30 @@ export async function POST( | |
| 65 | 
             
                    );
         | 
| 66 | 
             
                  }
         | 
| 67 |  | 
| 68 | 
            -
                  if  | 
|  | |
|  | |
|  | |
|  | |
|  | |
| 69 | 
             
                    return NextResponse.json(
         | 
| 70 | 
             
                      {
         | 
| 71 | 
             
                        ok: false,
         | 
| 72 | 
            -
                        error: `File ${file.name} is not  | 
| 73 | 
             
                      },
         | 
| 74 | 
             
                      { status: 400 }
         | 
| 75 | 
             
                    );
         | 
| 76 | 
             
                  }
         | 
| 77 |  | 
| 78 | 
            -
                  // Create File object with  | 
| 79 | 
            -
                   | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 80 | 
             
                  const processedFile = new File([file], fileName, { type: file.type });
         | 
| 81 | 
             
                  files.push(processedFile);
         | 
| 82 | 
             
                }
         | 
| @@ -91,21 +103,21 @@ export async function POST( | |
| 91 | 
             
                  repo,
         | 
| 92 | 
             
                  files,
         | 
| 93 | 
             
                  accessToken: user.token as string,
         | 
| 94 | 
            -
                  commitTitle: `Upload ${files.length}  | 
| 95 | 
             
                });
         | 
| 96 |  | 
| 97 | 
             
                return NextResponse.json({ 
         | 
| 98 | 
             
                  ok: true, 
         | 
| 99 | 
            -
                  message: `Successfully uploaded ${files.length}  | 
| 100 | 
             
                  uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
         | 
| 101 | 
             
                }, { status: 200 });
         | 
| 102 |  | 
| 103 | 
             
              } catch (error) {
         | 
| 104 | 
            -
                console.error('Error uploading  | 
| 105 | 
             
                return NextResponse.json(
         | 
| 106 | 
             
                  {
         | 
| 107 | 
             
                    ok: false,
         | 
| 108 | 
            -
                    error: "Failed to upload  | 
| 109 | 
             
                  },
         | 
| 110 | 
             
                  { status: 500 }
         | 
| 111 | 
             
                );
         | 
|  | |
| 39 | 
             
                  );
         | 
| 40 | 
             
                }
         | 
| 41 |  | 
| 42 | 
            +
                // Parse the FormData to get the media files
         | 
| 43 | 
             
                const formData = await req.formData();
         | 
| 44 | 
            +
                const mediaFiles = formData.getAll("images") as File[];
         | 
| 45 |  | 
| 46 | 
            +
                if (!mediaFiles || mediaFiles.length === 0) {
         | 
| 47 | 
             
                  return NextResponse.json(
         | 
| 48 | 
             
                    {
         | 
| 49 | 
             
                      ok: false,
         | 
| 50 | 
            +
                      error: "At least one media file is required under the 'images' key",
         | 
| 51 | 
             
                    },
         | 
| 52 | 
             
                    { status: 400 }
         | 
| 53 | 
             
                  );
         | 
| 54 | 
             
                }
         | 
| 55 |  | 
| 56 | 
             
                const files: File[] = [];
         | 
| 57 | 
            +
                for (const file of mediaFiles) {
         | 
| 58 | 
             
                  if (!(file instanceof File)) {
         | 
| 59 | 
             
                    return NextResponse.json(
         | 
| 60 | 
             
                      {
         | 
|  | |
| 65 | 
             
                    );
         | 
| 66 | 
             
                  }
         | 
| 67 |  | 
| 68 | 
            +
                  // Check if file is a supported media type
         | 
| 69 | 
            +
                  const isImage = file.type.startsWith('image/');
         | 
| 70 | 
            +
                  const isVideo = file.type.startsWith('video/');
         | 
| 71 | 
            +
                  const isAudio = file.type.startsWith('audio/');
         | 
| 72 | 
            +
                  
         | 
| 73 | 
            +
                  if (!isImage && !isVideo && !isAudio) {
         | 
| 74 | 
             
                    return NextResponse.json(
         | 
| 75 | 
             
                      {
         | 
| 76 | 
             
                        ok: false,
         | 
| 77 | 
            +
                        error: `File ${file.name} is not a supported media type (image, video, or audio)`,
         | 
| 78 | 
             
                      },
         | 
| 79 | 
             
                      { status: 400 }
         | 
| 80 | 
             
                    );
         | 
| 81 | 
             
                  }
         | 
| 82 |  | 
| 83 | 
            +
                  // Create File object with appropriate folder prefix
         | 
| 84 | 
            +
                  let folderPrefix = 'images/';
         | 
| 85 | 
            +
                  if (isVideo) {
         | 
| 86 | 
            +
                    folderPrefix = 'videos/';
         | 
| 87 | 
            +
                  } else if (isAudio) {
         | 
| 88 | 
            +
                    folderPrefix = 'audio/';
         | 
| 89 | 
            +
                  }
         | 
| 90 | 
            +
                  
         | 
| 91 | 
            +
                  const fileName = `${folderPrefix}${file.name}`;
         | 
| 92 | 
             
                  const processedFile = new File([file], fileName, { type: file.type });
         | 
| 93 | 
             
                  files.push(processedFile);
         | 
| 94 | 
             
                }
         | 
|  | |
| 103 | 
             
                  repo,
         | 
| 104 | 
             
                  files,
         | 
| 105 | 
             
                  accessToken: user.token as string,
         | 
| 106 | 
            +
                  commitTitle: `Upload ${files.length} media file(s)`,
         | 
| 107 | 
             
                });
         | 
| 108 |  | 
| 109 | 
             
                return NextResponse.json({ 
         | 
| 110 | 
             
                  ok: true, 
         | 
| 111 | 
            +
                  message: `Successfully uploaded ${files.length} media file(s) to ${namespace}/${repoId}/`,
         | 
| 112 | 
             
                  uploadedFiles: files.map((file) => `https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${file.name}`),
         | 
| 113 | 
             
                }, { status: 200 });
         | 
| 114 |  | 
| 115 | 
             
              } catch (error) {
         | 
| 116 | 
            +
                console.error('Error uploading media files:', error);
         | 
| 117 | 
             
                return NextResponse.json(
         | 
| 118 | 
             
                  {
         | 
| 119 | 
             
                    ok: false,
         | 
| 120 | 
            +
                    error: "Failed to upload media files",
         | 
| 121 | 
             
                  },
         | 
| 122 | 
             
                  { status: 500 }
         | 
| 123 | 
             
                );
         | 
    	
        app/api/me/projects/[namespace]/[repoId]/route.ts
    CHANGED
    
    | @@ -105,7 +105,7 @@ export async function GET( | |
| 105 | 
             
                const htmlFiles: Page[] = [];
         | 
| 106 | 
             
                const files: string[] = [];
         | 
| 107 |  | 
| 108 | 
            -
                const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif"];
         | 
| 109 |  | 
| 110 | 
             
                for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
         | 
| 111 | 
             
                  if (fileInfo.path.endsWith(".html")) {
         | 
| @@ -126,7 +126,7 @@ export async function GET( | |
| 126 | 
             
                      });
         | 
| 127 | 
             
                    }
         | 
| 128 | 
             
                  }
         | 
| 129 | 
            -
                  if (fileInfo.type === "directory" &&  | 
| 130 | 
             
                    for await (const imageInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
         | 
| 131 | 
             
                      if (allowedFilesExtensions.includes(imageInfo.path.split(".").pop() || "")) {
         | 
| 132 | 
             
                        files.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${imageInfo.path}`);
         | 
|  | |
| 105 | 
             
                const htmlFiles: Page[] = [];
         | 
| 106 | 
             
                const files: string[] = [];
         | 
| 107 |  | 
| 108 | 
            +
                const allowedFilesExtensions = ["jpg", "jpeg", "png", "gif", "svg", "webp", "avif", "heic", "heif", "ico", "bmp", "tiff", "tif", "mp4", "webm", "ogg", "avi", "mov", "mp3", "wav", "ogg", "aac", "m4a"];
         | 
| 109 |  | 
| 110 | 
             
                for await (const fileInfo of listFiles({repo, accessToken: user.token as string})) {
         | 
| 111 | 
             
                  if (fileInfo.path.endsWith(".html")) {
         | 
|  | |
| 126 | 
             
                      });
         | 
| 127 | 
             
                    }
         | 
| 128 | 
             
                  }
         | 
| 129 | 
            +
                  if (fileInfo.type === "directory" && ["videos", "images", "audio"].includes(fileInfo.path)) {
         | 
| 130 | 
             
                    for await (const imageInfo of listFiles({repo, accessToken: user.token as string, path: fileInfo.path})) {
         | 
| 131 | 
             
                      if (allowedFilesExtensions.includes(imageInfo.path.split(".").pop() || "")) {
         | 
| 132 | 
             
                        files.push(`https://huggingface.co/spaces/${namespace}/${repoId}/resolve/main/${imageInfo.path}`);
         | 
    	
        components/editor/ask-ai/selected-files.tsx
    CHANGED
    
    | @@ -1,7 +1,8 @@ | |
| 1 | 
             
            import Image from "next/image";
         | 
| 2 |  | 
| 3 | 
             
            import { Button } from "@/components/ui/button";
         | 
| 4 | 
            -
            import { Minus } from "lucide-react";
         | 
|  | |
| 5 |  | 
| 6 | 
             
            export const SelectedFiles = ({
         | 
| 7 | 
             
              files,
         | 
| @@ -21,13 +22,19 @@ export const SelectedFiles = ({ | |
| 21 | 
             
                        key={file}
         | 
| 22 | 
             
                        className="flex items-center relative justify-start gap-2 p-1 bg-neutral-700 rounded-md"
         | 
| 23 | 
             
                      >
         | 
| 24 | 
            -
                         | 
| 25 | 
            -
                           | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 31 | 
             
                        <Button
         | 
| 32 | 
             
                          size="iconXsss"
         | 
| 33 | 
             
                          variant="secondary"
         | 
|  | |
| 1 | 
             
            import Image from "next/image";
         | 
| 2 |  | 
| 3 | 
             
            import { Button } from "@/components/ui/button";
         | 
| 4 | 
            +
            import { Minus, Video, Music } from "lucide-react";
         | 
| 5 | 
            +
            import { getFileType } from "./uploader";
         | 
| 6 |  | 
| 7 | 
             
            export const SelectedFiles = ({
         | 
| 8 | 
             
              files,
         | 
|  | |
| 22 | 
             
                        key={file}
         | 
| 23 | 
             
                        className="flex items-center relative justify-start gap-2 p-1 bg-neutral-700 rounded-md"
         | 
| 24 | 
             
                      >
         | 
| 25 | 
            +
                        {getFileType(file) === "image" ? (
         | 
| 26 | 
            +
                          <Image
         | 
| 27 | 
            +
                            src={file}
         | 
| 28 | 
            +
                            alt="uploaded image"
         | 
| 29 | 
            +
                            className="size-12 rounded-md object-cover"
         | 
| 30 | 
            +
                            width={40}
         | 
| 31 | 
            +
                            height={40}
         | 
| 32 | 
            +
                          />
         | 
| 33 | 
            +
                        ) : getFileType(file) === "video" ? (
         | 
| 34 | 
            +
                          <Video className="size-12 rounded-md object-cover" />
         | 
| 35 | 
            +
                        ) : getFileType(file) === "audio" ? (
         | 
| 36 | 
            +
                          <Music className="size-12 rounded-md object-cover" />
         | 
| 37 | 
            +
                        ) : null}
         | 
| 38 | 
             
                        <Button
         | 
| 39 | 
             
                          size="iconXsss"
         | 
| 40 | 
             
                          variant="secondary"
         | 
    	
        components/editor/ask-ai/uploader.tsx
    CHANGED
    
    | @@ -6,6 +6,9 @@ import { | |
| 6 | 
             
              Link,
         | 
| 7 | 
             
              Paperclip,
         | 
| 8 | 
             
              Upload,
         | 
|  | |
|  | |
|  | |
| 9 | 
             
            } from "lucide-react";
         | 
| 10 | 
             
            import Image from "next/image";
         | 
| 11 |  | 
| @@ -22,6 +25,18 @@ import { useEditor } from "@/hooks/useEditor"; | |
| 22 | 
             
            import { useAi } from "@/hooks/useAi";
         | 
| 23 | 
             
            import { useLoginModal } from "@/components/contexts/login-context";
         | 
| 24 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 25 | 
             
            export const Uploader = ({ project }: { project: Project | undefined }) => {
         | 
| 26 | 
             
              const { user } = useUser();
         | 
| 27 | 
             
              const { openLoginModal } = useLoginModal();
         | 
| @@ -31,6 +46,20 @@ export const Uploader = ({ project }: { project: Project | undefined }) => { | |
| 31 | 
             
              const [open, setOpen] = useState(false);
         | 
| 32 | 
             
              const fileInputRef = useRef<HTMLInputElement>(null);
         | 
| 33 |  | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 34 | 
             
              if (!user)
         | 
| 35 | 
             
                return (
         | 
| 36 | 
             
                  <Button
         | 
| @@ -69,63 +98,88 @@ export const Uploader = ({ project }: { project: Project | undefined }) => { | |
| 69 | 
             
                            π¨
         | 
| 70 | 
             
                          </div>
         | 
| 71 | 
             
                          <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
         | 
| 72 | 
            -
                             | 
| 73 | 
             
                          </div>
         | 
| 74 | 
             
                          <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
         | 
| 75 | 
             
                            π»
         | 
| 76 | 
             
                          </div>
         | 
| 77 | 
             
                        </div>
         | 
| 78 | 
             
                        <p className="text-xl font-semibold text-neutral-950">
         | 
| 79 | 
            -
                          Add  | 
| 80 | 
             
                        </p>
         | 
| 81 | 
             
                        <p className="text-sm text-neutral-500 mt-1.5">
         | 
| 82 | 
            -
                          Upload images  | 
| 83 | 
             
                        </p>
         | 
| 84 | 
             
                      </header>
         | 
| 85 | 
             
                      <main className="space-y-4 p-5">
         | 
| 86 | 
             
                        <div>
         | 
| 87 | 
             
                          <p className="text-xs text-left text-neutral-700 mb-2">
         | 
| 88 | 
            -
                            Uploaded  | 
| 89 | 
             
                          </p>
         | 
| 90 | 
             
                          {files?.length > 0 ? (
         | 
| 91 | 
             
                            <div className="grid grid-cols-4 gap-1 flex-wrap max-h-40 overflow-y-auto">
         | 
| 92 | 
            -
                              {files.map((file: string) =>  | 
| 93 | 
            -
                                 | 
| 94 | 
            -
             | 
| 95 | 
            -
                                   | 
| 96 | 
            -
             | 
| 97 | 
            -
                                     | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
                                         | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
                                     | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
                                       | 
| 114 | 
            -
                                     | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
|  | |
| 118 | 
             
                            </div>
         | 
| 119 | 
             
                          ) : (
         | 
| 120 | 
             
                            <p className="text-sm text-muted-foreground font-mono flex flex-col items-center gap-1 pt-2">
         | 
| 121 | 
             
                              <ImageIcon className="size-4" />
         | 
| 122 | 
            -
                              No  | 
| 123 | 
             
                            </p>
         | 
| 124 | 
             
                          )}
         | 
| 125 | 
             
                        </div>
         | 
| 126 | 
             
                        <div>
         | 
| 127 | 
             
                          <p className="text-xs text-left text-neutral-700 mb-2">
         | 
| 128 | 
            -
                            Or import  | 
| 129 | 
             
                          </p>
         | 
| 130 | 
             
                          <Button
         | 
| 131 | 
             
                            variant="black"
         | 
| @@ -139,12 +193,12 @@ export const Uploader = ({ project }: { project: Project | undefined }) => { | |
| 139 | 
             
                                  overlay={false}
         | 
| 140 | 
             
                                  className="ml-2 size-4 animate-spin"
         | 
| 141 | 
             
                                />
         | 
| 142 | 
            -
                                Uploading  | 
| 143 | 
             
                              </>
         | 
| 144 | 
             
                            ) : (
         | 
| 145 | 
             
                              <>
         | 
| 146 | 
             
                                <Upload className="size-4" />
         | 
| 147 | 
            -
                                Upload  | 
| 148 | 
             
                              </>
         | 
| 149 | 
             
                            )}
         | 
| 150 | 
             
                          </Button>
         | 
| @@ -153,7 +207,7 @@ export const Uploader = ({ project }: { project: Project | undefined }) => { | |
| 153 | 
             
                            type="file"
         | 
| 154 | 
             
                            className="hidden"
         | 
| 155 | 
             
                            multiple
         | 
| 156 | 
            -
                            accept="image | 
| 157 | 
             
                            onChange={(e) => uploadFiles(e.target.files, project!)}
         | 
| 158 | 
             
                          />
         | 
| 159 | 
             
                        </div>
         | 
|  | |
| 6 | 
             
              Link,
         | 
| 7 | 
             
              Paperclip,
         | 
| 8 | 
             
              Upload,
         | 
| 9 | 
            +
              Video,
         | 
| 10 | 
            +
              Music,
         | 
| 11 | 
            +
              FileVideo,
         | 
| 12 | 
             
            } from "lucide-react";
         | 
| 13 | 
             
            import Image from "next/image";
         | 
| 14 |  | 
|  | |
| 25 | 
             
            import { useAi } from "@/hooks/useAi";
         | 
| 26 | 
             
            import { useLoginModal } from "@/components/contexts/login-context";
         | 
| 27 |  | 
| 28 | 
            +
            export const getFileType = (url: string) => {
         | 
| 29 | 
            +
              const extension = url.split(".").pop()?.toLowerCase();
         | 
| 30 | 
            +
              if (["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(extension || "")) {
         | 
| 31 | 
            +
                return "image";
         | 
| 32 | 
            +
              } else if (["mp4", "webm", "ogg", "avi", "mov"].includes(extension || "")) {
         | 
| 33 | 
            +
                return "video";
         | 
| 34 | 
            +
              } else if (["mp3", "wav", "ogg", "aac", "m4a"].includes(extension || "")) {
         | 
| 35 | 
            +
                return "audio";
         | 
| 36 | 
            +
              }
         | 
| 37 | 
            +
              return "unknown";
         | 
| 38 | 
            +
            };
         | 
| 39 | 
            +
             | 
| 40 | 
             
            export const Uploader = ({ project }: { project: Project | undefined }) => {
         | 
| 41 | 
             
              const { user } = useUser();
         | 
| 42 | 
             
              const { openLoginModal } = useLoginModal();
         | 
|  | |
| 46 | 
             
              const [open, setOpen] = useState(false);
         | 
| 47 | 
             
              const fileInputRef = useRef<HTMLInputElement>(null);
         | 
| 48 |  | 
| 49 | 
            +
              const getFileIcon = (url: string) => {
         | 
| 50 | 
            +
                const fileType = getFileType(url);
         | 
| 51 | 
            +
                switch (fileType) {
         | 
| 52 | 
            +
                  case "image":
         | 
| 53 | 
            +
                    return <ImageIcon className="size-4" />;
         | 
| 54 | 
            +
                  case "video":
         | 
| 55 | 
            +
                    return <Video className="size-4" />;
         | 
| 56 | 
            +
                  case "audio":
         | 
| 57 | 
            +
                    return <Music className="size-4" />;
         | 
| 58 | 
            +
                  default:
         | 
| 59 | 
            +
                    return <FileVideo className="size-4" />;
         | 
| 60 | 
            +
                }
         | 
| 61 | 
            +
              };
         | 
| 62 | 
            +
             | 
| 63 | 
             
              if (!user)
         | 
| 64 | 
             
                return (
         | 
| 65 | 
             
                  <Button
         | 
|  | |
| 98 | 
             
                            π¨
         | 
| 99 | 
             
                          </div>
         | 
| 100 | 
             
                          <div className="size-11 rounded-full bg-amber-200 shadow-2xl flex items-center justify-center text-2xl z-2">
         | 
| 101 | 
            +
                            π
         | 
| 102 | 
             
                          </div>
         | 
| 103 | 
             
                          <div className="size-9 rounded-full bg-sky-200 shadow-2xs flex items-center justify-center text-xl opacity-50">
         | 
| 104 | 
             
                            π»
         | 
| 105 | 
             
                          </div>
         | 
| 106 | 
             
                        </div>
         | 
| 107 | 
             
                        <p className="text-xl font-semibold text-neutral-950">
         | 
| 108 | 
            +
                          Add Media Files
         | 
| 109 | 
             
                        </p>
         | 
| 110 | 
             
                        <p className="text-sm text-neutral-500 mt-1.5">
         | 
| 111 | 
            +
                          Upload images, videos, and audio files to your project!
         | 
| 112 | 
             
                        </p>
         | 
| 113 | 
             
                      </header>
         | 
| 114 | 
             
                      <main className="space-y-4 p-5">
         | 
| 115 | 
             
                        <div>
         | 
| 116 | 
             
                          <p className="text-xs text-left text-neutral-700 mb-2">
         | 
| 117 | 
            +
                            Uploaded Media Files
         | 
| 118 | 
             
                          </p>
         | 
| 119 | 
             
                          {files?.length > 0 ? (
         | 
| 120 | 
             
                            <div className="grid grid-cols-4 gap-1 flex-wrap max-h-40 overflow-y-auto">
         | 
| 121 | 
            +
                              {files.map((file: string) => {
         | 
| 122 | 
            +
                                const fileType = getFileType(file);
         | 
| 123 | 
            +
                                return (
         | 
| 124 | 
            +
                                  <div
         | 
| 125 | 
            +
                                    key={file}
         | 
| 126 | 
            +
                                    className="select-none relative cursor-pointer bg-white rounded-md border-[2px] border-white hover:shadow-2xl transition-all duration-300"
         | 
| 127 | 
            +
                                    onClick={() =>
         | 
| 128 | 
            +
                                      setSelectedFiles(
         | 
| 129 | 
            +
                                        selectedFiles.includes(file)
         | 
| 130 | 
            +
                                          ? selectedFiles.filter((f) => f !== file)
         | 
| 131 | 
            +
                                          : [...selectedFiles, file]
         | 
| 132 | 
            +
                                      )
         | 
| 133 | 
            +
                                    }
         | 
| 134 | 
            +
                                  >
         | 
| 135 | 
            +
                                    {fileType === "image" ? (
         | 
| 136 | 
            +
                                      <Image
         | 
| 137 | 
            +
                                        src={file}
         | 
| 138 | 
            +
                                        alt="uploaded image"
         | 
| 139 | 
            +
                                        width={56}
         | 
| 140 | 
            +
                                        height={56}
         | 
| 141 | 
            +
                                        className="object-cover w-full rounded-sm aspect-square"
         | 
| 142 | 
            +
                                      />
         | 
| 143 | 
            +
                                    ) : fileType === "video" ? (
         | 
| 144 | 
            +
                                      <div className="w-full h-14 rounded-sm bg-gray-100 flex items-center justify-center relative">
         | 
| 145 | 
            +
                                        <video
         | 
| 146 | 
            +
                                          src={file}
         | 
| 147 | 
            +
                                          className="w-full h-full object-cover rounded-sm"
         | 
| 148 | 
            +
                                          muted
         | 
| 149 | 
            +
                                          preload="metadata"
         | 
| 150 | 
            +
                                        />
         | 
| 151 | 
            +
                                        <div className="absolute inset-0 flex items-center justify-center bg-black/20 rounded-sm">
         | 
| 152 | 
            +
                                          <Video className="size-4 text-white" />
         | 
| 153 | 
            +
                                        </div>
         | 
| 154 | 
            +
                                      </div>
         | 
| 155 | 
            +
                                    ) : fileType === "audio" ? (
         | 
| 156 | 
            +
                                      <div className="w-full h-14 rounded-sm bg-gradient-to-br from-purple-100 to-pink-100 flex items-center justify-center">
         | 
| 157 | 
            +
                                        <Music className="size-6 text-purple-600" />
         | 
| 158 | 
            +
                                      </div>
         | 
| 159 | 
            +
                                    ) : (
         | 
| 160 | 
            +
                                      <div className="w-full h-14 rounded-sm bg-gray-100 flex items-center justify-center">
         | 
| 161 | 
            +
                                        {getFileIcon(file)}
         | 
| 162 | 
            +
                                      </div>
         | 
| 163 | 
            +
                                    )}
         | 
| 164 | 
            +
                                    {selectedFiles.includes(file) && (
         | 
| 165 | 
            +
                                      <div className="absolute top-0 right-0 h-full w-full flex items-center justify-center bg-black/50 rounded-md">
         | 
| 166 | 
            +
                                        <CheckCircle className="size-6 text-neutral-100" />
         | 
| 167 | 
            +
                                      </div>
         | 
| 168 | 
            +
                                    )}
         | 
| 169 | 
            +
                                  </div>
         | 
| 170 | 
            +
                                );
         | 
| 171 | 
            +
                              })}
         | 
| 172 | 
             
                            </div>
         | 
| 173 | 
             
                          ) : (
         | 
| 174 | 
             
                            <p className="text-sm text-muted-foreground font-mono flex flex-col items-center gap-1 pt-2">
         | 
| 175 | 
             
                              <ImageIcon className="size-4" />
         | 
| 176 | 
            +
                              No media files uploaded yet
         | 
| 177 | 
             
                            </p>
         | 
| 178 | 
             
                          )}
         | 
| 179 | 
             
                        </div>
         | 
| 180 | 
             
                        <div>
         | 
| 181 | 
             
                          <p className="text-xs text-left text-neutral-700 mb-2">
         | 
| 182 | 
            +
                            Or import media files from your computer
         | 
| 183 | 
             
                          </p>
         | 
| 184 | 
             
                          <Button
         | 
| 185 | 
             
                            variant="black"
         | 
|  | |
| 193 | 
             
                                  overlay={false}
         | 
| 194 | 
             
                                  className="ml-2 size-4 animate-spin"
         | 
| 195 | 
             
                                />
         | 
| 196 | 
            +
                                Uploading media file(s)...
         | 
| 197 | 
             
                              </>
         | 
| 198 | 
             
                            ) : (
         | 
| 199 | 
             
                              <>
         | 
| 200 | 
             
                                <Upload className="size-4" />
         | 
| 201 | 
            +
                                Upload Media Files
         | 
| 202 | 
             
                              </>
         | 
| 203 | 
             
                            )}
         | 
| 204 | 
             
                          </Button>
         | 
|  | |
| 207 | 
             
                            type="file"
         | 
| 208 | 
             
                            className="hidden"
         | 
| 209 | 
             
                            multiple
         | 
| 210 | 
            +
                            accept="image/*,video/*,audio/*,.mp3,.mp4,.wav,.aac,.m4a,.ogg,.webm,.avi,.mov"
         | 
| 211 | 
             
                            onChange={(e) => uploadFiles(e.target.files, project!)}
         | 
| 212 | 
             
                          />
         | 
| 213 | 
             
                        </div>
         | 
    	
        hooks/useEditor.ts
    CHANGED
    
    | @@ -191,13 +191,15 @@ export const useEditor = (namespace?: string, repoId?: string) => { | |
| 191 |  | 
| 192 | 
             
              const uploadFilesMutation = useMutation({
         | 
| 193 | 
             
                mutationFn: async ({ files, project }: { files: FileList; project: Project }) => {
         | 
| 194 | 
            -
                  const  | 
| 195 | 
            -
                    return file.type.startsWith("image/") | 
|  | |
|  | |
| 196 | 
             
                  });
         | 
| 197 |  | 
| 198 | 
             
                  const data = new FormData();
         | 
| 199 | 
            -
                   | 
| 200 | 
            -
                    data.append("images",  | 
| 201 | 
             
                  });
         | 
| 202 |  | 
| 203 | 
             
                  const response = await fetch(
         | 
|  | |
| 191 |  | 
| 192 | 
             
              const uploadFilesMutation = useMutation({
         | 
| 193 | 
             
                mutationFn: async ({ files, project }: { files: FileList; project: Project }) => {
         | 
| 194 | 
            +
                  const mediaFiles = Array.from(files).filter((file) => {
         | 
| 195 | 
            +
                    return file.type.startsWith("image/") || 
         | 
| 196 | 
            +
                           file.type.startsWith("video/") || 
         | 
| 197 | 
            +
                           file.type.startsWith("audio/");
         | 
| 198 | 
             
                  });
         | 
| 199 |  | 
| 200 | 
             
                  const data = new FormData();
         | 
| 201 | 
            +
                  mediaFiles.forEach((file) => {
         | 
| 202 | 
            +
                    data.append("images", file); // Keep using "images" key for backward compatibility
         | 
| 203 | 
             
                  });
         | 
| 204 |  | 
| 205 | 
             
                  const response = await fetch(
         | 
