enzostvs HF Staff commited on
Commit
15a77af
·
1 Parent(s): 890f017

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 images
43
  const formData = await req.formData();
44
- const imageFiles = formData.getAll("images") as File[];
45
 
46
- if (!imageFiles || imageFiles.length === 0) {
47
  return NextResponse.json(
48
  {
49
  ok: false,
50
- error: "At least one image file is required under the 'images' key",
51
  },
52
  { status: 400 }
53
  );
54
  }
55
 
56
  const files: File[] = [];
57
- for (const file of imageFiles) {
58
  if (!(file instanceof File)) {
59
  return NextResponse.json(
60
  {
@@ -65,18 +65,30 @@ export async function POST(
65
  );
66
  }
67
 
68
- if (!file.type.startsWith('image/')) {
 
 
 
 
 
69
  return NextResponse.json(
70
  {
71
  ok: false,
72
- error: `File ${file.name} is not an image`,
73
  },
74
  { status: 400 }
75
  );
76
  }
77
 
78
- // Create File object with images/ folder prefix
79
- const fileName = `images/${file.name}`;
 
 
 
 
 
 
 
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} image(s)`,
95
  });
96
 
97
  return NextResponse.json({
98
  ok: true,
99
- message: `Successfully uploaded ${files.length} image(s) to ${namespace}/${repoId}/images/`,
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 images:', error);
105
  return NextResponse.json(
106
  {
107
  ok: false,
108
- error: "Failed to upload images",
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" && fileInfo.path === "images") {
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
- <Image
25
- src={file}
26
- alt="uploaded image"
27
- className="size-12 rounded-md object-cover"
28
- width={40}
29
- height={40}
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 Custom Images
80
  </p>
81
  <p className="text-sm text-neutral-500 mt-1.5">
82
- Upload images to your project and use them with DeepSite!
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 Images
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
- <div
94
- key={file}
95
- className="select-none relative cursor-pointer bg-white rounded-md border-[2px] border-white hover:shadow-2xl transition-all duration-300"
96
- onClick={() =>
97
- setSelectedFiles(
98
- selectedFiles.includes(file)
99
- ? selectedFiles.filter((f) => f !== file)
100
- : [...selectedFiles, file]
101
- )
102
- }
103
- >
104
- <Image
105
- src={file}
106
- alt="uploaded image"
107
- width={56}
108
- height={56}
109
- className="object-cover w-full rounded-sm aspect-square"
110
- />
111
- {selectedFiles.includes(file) && (
112
- <div className="absolute top-0 right-0 h-full w-full flex items-center justify-center bg-black/50 rounded-md">
113
- <CheckCircle className="size-6 text-neutral-100" />
114
- </div>
115
- )}
116
- </div>
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 images uploaded yet
123
  </p>
124
  )}
125
  </div>
126
  <div>
127
  <p className="text-xs text-left text-neutral-700 mb-2">
128
- Or import images from your computer
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 image(s)...
143
  </>
144
  ) : (
145
  <>
146
  <Upload className="size-4" />
147
- Upload Images
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 images = Array.from(files).filter((file) => {
195
- return file.type.startsWith("image/");
 
 
196
  });
197
 
198
  const data = new FormData();
199
- images.forEach((image) => {
200
- data.append("images", image);
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(