google-labs-jules[bot] commited on
Commit
79383fa
·
1 Parent(s): e3c8c05

Add feature to download sample or original images and fix deprecation warning

Browse files
src/components/grabber/Lightbox.tsx CHANGED
@@ -25,17 +25,39 @@ export function Lightbox({ post, onClose }: { post: NormalizedPost | null; onClo
25
  {post.rating && <Badge variant="outline">{post.rating}</Badge>}
26
  {post.score !== undefined && <Badge variant="secondary">★ {post.score}</Badge>}
27
  </div>
28
- {post.source && (
29
- <a
30
- href={post.source}
31
- target="_blank"
32
- rel="noreferrer"
33
- className="text-primary underline break-all block"
34
- >
35
- source
36
- </a>
37
- )}
38
- <div className="text-xs text-muted-foreground">tags</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  <div className="flex gap-1 flex-wrap max-h-96 overflow-y-auto">
40
  {post.tags.map((t) => (
41
  <Badge key={t} variant="outline" className="font-normal">
 
25
  {post.rating && <Badge variant="outline">{post.rating}</Badge>}
26
  {post.score !== undefined && <Badge variant="secondary">★ {post.score}</Badge>}
27
  </div>
28
+ <div className="space-y-1">
29
+ {post.fileUrl && (
30
+ <a
31
+ href={post.fileUrl}
32
+ target="_blank"
33
+ rel="noreferrer"
34
+ className="text-primary underline text-xs block"
35
+ >
36
+ View Original Image ({post.width && post.height ? `${post.width}x${post.height}` : "Original"})
37
+ </a>
38
+ )}
39
+ {post.sampleUrl && (
40
+ <a
41
+ href={post.sampleUrl}
42
+ target="_blank"
43
+ rel="noreferrer"
44
+ className="text-primary underline text-xs block"
45
+ >
46
+ View Sample Image
47
+ </a>
48
+ )}
49
+ {post.source && (
50
+ <a
51
+ href={post.source}
52
+ target="_blank"
53
+ rel="noreferrer"
54
+ className="text-primary underline text-xs break-all block"
55
+ >
56
+ Source Link
57
+ </a>
58
+ )}
59
+ </div>
60
+ <div className="text-xs text-muted-foreground pt-2">tags</div>
61
  <div className="flex gap-1 flex-wrap max-h-96 overflow-y-auto">
62
  {post.tags.map((t) => (
63
  <Badge key={t} variant="outline" className="font-normal">
src/lib/grabber/search.functions.ts CHANGED
@@ -46,7 +46,7 @@ export type SearchResult = {
46
  };
47
 
48
  export const searchPosts = createServerFn({ method: "POST" })
49
- .inputValidator((input: unknown) => InputSchema.parse(input))
50
  .handler(async ({ data }): Promise<SearchResult> => {
51
  const { site, tags, page, limit, login, apiKey } = data;
52
  const pageParam = (site.pageStartsAt ?? 1) === 0 ? page - 1 : page;
 
46
  };
47
 
48
  export const searchPosts = createServerFn({ method: "POST" })
49
+ .validator((input: unknown) => InputSchema.parse(input))
50
  .handler(async ({ data }): Promise<SearchResult> => {
51
  const { site, tags, page, limit, login, apiKey } = data;
52
  const pageParam = (site.pageStartsAt ?? 1) === 0 ? page - 1 : page;
src/routes/api/download-zip.ts CHANGED
@@ -26,6 +26,7 @@ const BodySchema = z.object({
26
  posts: z.array(PostSchema).min(1).max(200),
27
  excludeTags: z.array(z.string()).default([]),
28
  zipName: z.string().optional(),
 
29
  });
30
 
31
  function sanitize(s: string) {
@@ -107,9 +108,23 @@ export const Route = createFileRoute("/api/download-zip")({
107
  await Promise.all(
108
  body.posts.map(async (post) => {
109
  const idSafe = sanitize(post.id);
110
- const extSafe = sanitize(post.ext || "jpg");
111
  const base = `${siteName}_${idSafe}`;
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  // Filter out tags that user specified to be missing
114
  const filteredTags = post.tags.filter((t) => !excludeSet.has(t.toLowerCase()));
115
 
@@ -121,7 +136,7 @@ export const Route = createFileRoute("/api/download-zip")({
121
 
122
  // fetch the image itself
123
  try {
124
- const res = await fetch(post.fileUrl, {
125
  headers: { "User-Agent": "LovableGrabber/1.0" },
126
  });
127
  if (!res.ok) {
 
26
  posts: z.array(PostSchema).min(1).max(200),
27
  excludeTags: z.array(z.string()).default([]),
28
  zipName: z.string().optional(),
29
+ downloadType: z.enum(["original", "sample"]).default("original"),
30
  });
31
 
32
  function sanitize(s: string) {
 
108
  await Promise.all(
109
  body.posts.map(async (post) => {
110
  const idSafe = sanitize(post.id);
 
111
  const base = `${siteName}_${idSafe}`;
112
 
113
+ // Determine image URL and file extension based on downloadType
114
+ let targetUrl = post.fileUrl;
115
+ if (body.downloadType === "sample" && post.sampleUrl) {
116
+ targetUrl = post.sampleUrl;
117
+ }
118
+
119
+ let extSafe = sanitize(post.ext || "jpg");
120
+ // If fetching sample, attempt to detect extension from sampleUrl if available
121
+ if (body.downloadType === "sample" && post.sampleUrl) {
122
+ const parsedExt = post.sampleUrl.split(".").pop()?.split("?")[0];
123
+ if (parsedExt && parsedExt.length <= 5) {
124
+ extSafe = sanitize(parsedExt);
125
+ }
126
+ }
127
+
128
  // Filter out tags that user specified to be missing
129
  const filteredTags = post.tags.filter((t) => !excludeSet.has(t.toLowerCase()));
130
 
 
136
 
137
  // fetch the image itself
138
  try {
139
+ const res = await fetch(targetUrl, {
140
  headers: { "User-Agent": "LovableGrabber/1.0" },
141
  });
142
  if (!res.ok) {
src/routes/api/upload-to-dataset.ts CHANGED
@@ -31,6 +31,7 @@ const BodySchema = z.object({
31
  datasetName: z.string().min(1, "Dataset name is required"),
32
  zipName: z.string().optional(),
33
  subfolder: z.string().optional(),
 
34
  });
35
 
36
  function sanitize(s: string) {
@@ -113,16 +114,28 @@ export const Route = createFileRoute("/api/upload-to-dataset")({
113
  await Promise.all(
114
  body.posts.map(async (post) => {
115
  const idSafe = sanitize(post.id);
116
- const extSafe = sanitize(post.ext || "jpg");
117
  const base = `${siteName}_${idSafe}`;
118
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  const filteredTags = post.tags.filter((t) => !excludeSet.has(t.toLowerCase()));
120
 
121
  files[`${base}.txt`] = strToU8(filteredTags.join(" "));
122
  files[`${idSafe}.md`] = strToU8(buildMarkdown(post, excludeSet));
123
 
124
  try {
125
- const res = await fetch(post.fileUrl, {
126
  headers: { "User-Agent": "LovableGrabber/1.0" },
127
  });
128
  if (!res.ok) {
 
31
  datasetName: z.string().min(1, "Dataset name is required"),
32
  zipName: z.string().optional(),
33
  subfolder: z.string().optional(),
34
+ downloadType: z.enum(["original", "sample"]).default("original"),
35
  });
36
 
37
  function sanitize(s: string) {
 
114
  await Promise.all(
115
  body.posts.map(async (post) => {
116
  const idSafe = sanitize(post.id);
 
117
  const base = `${siteName}_${idSafe}`;
118
 
119
+ let targetUrl = post.fileUrl;
120
+ if (body.downloadType === "sample" && post.sampleUrl) {
121
+ targetUrl = post.sampleUrl;
122
+ }
123
+
124
+ let extSafe = sanitize(post.ext || "jpg");
125
+ if (body.downloadType === "sample" && post.sampleUrl) {
126
+ const parsedExt = post.sampleUrl.split(".").pop()?.split("?")[0];
127
+ if (parsedExt && parsedExt.length <= 5) {
128
+ extSafe = sanitize(parsedExt);
129
+ }
130
+ }
131
+
132
  const filteredTags = post.tags.filter((t) => !excludeSet.has(t.toLowerCase()));
133
 
134
  files[`${base}.txt`] = strToU8(filteredTags.join(" "));
135
  files[`${idSafe}.md`] = strToU8(buildMarkdown(post, excludeSet));
136
 
137
  try {
138
+ const res = await fetch(targetUrl, {
139
  headers: { "User-Agent": "LovableGrabber/1.0" },
140
  });
141
  if (!res.ok) {
src/routes/index.tsx CHANGED
@@ -48,6 +48,7 @@ function Grabber() {
48
  const [lightbox, setLightbox] = useState<NormalizedPost | null>(null);
49
  const [downloading, setDownloading] = useState(false);
50
  const [excludeTags, setExcludeTags] = useState("");
 
51
  const [hfToken, setHfToken] = useState("");
52
  const [datasetName, setDatasetName] = useState("");
53
  const [uploading, setUploading] = useState(false);
@@ -280,6 +281,7 @@ function Grabber() {
280
  .map((t) => t.trim().toLowerCase())
281
  .filter(Boolean),
282
  zipName: customFilename.trim() || undefined,
 
283
  }),
284
  });
285
  if (!res.ok) {
@@ -339,6 +341,7 @@ function Grabber() {
339
  datasetName: datasetName.trim(),
340
  zipName: customFilename.trim() || undefined,
341
  subfolder: customSubfolder.trim() || undefined,
 
342
  }),
343
  });
344
  const data = await res.json();
@@ -473,6 +476,15 @@ function Grabber() {
473
  {selected.size === posts.length && posts.length > 0 ? "Deselect all" : "Select all"}
474
  </Button>
475
  <span className="text-sm text-muted-foreground">selected: {selected.size}</span>
 
 
 
 
 
 
 
 
 
476
  <Input
477
  placeholder="Custom ZIP / Filename"
478
  value={customFilename}
 
48
  const [lightbox, setLightbox] = useState<NormalizedPost | null>(null);
49
  const [downloading, setDownloading] = useState(false);
50
  const [excludeTags, setExcludeTags] = useState("");
51
+ const [downloadType, setDownloadType] = useState<"original" | "sample">("original");
52
  const [hfToken, setHfToken] = useState("");
53
  const [datasetName, setDatasetName] = useState("");
54
  const [uploading, setUploading] = useState(false);
 
281
  .map((t) => t.trim().toLowerCase())
282
  .filter(Boolean),
283
  zipName: customFilename.trim() || undefined,
284
+ downloadType,
285
  }),
286
  });
287
  if (!res.ok) {
 
341
  datasetName: datasetName.trim(),
342
  zipName: customFilename.trim() || undefined,
343
  subfolder: customSubfolder.trim() || undefined,
344
+ downloadType,
345
  }),
346
  });
347
  const data = await res.json();
 
476
  {selected.size === posts.length && posts.length > 0 ? "Deselect all" : "Select all"}
477
  </Button>
478
  <span className="text-sm text-muted-foreground">selected: {selected.size}</span>
479
+ <Select value={downloadType} onValueChange={(val: "original" | "sample") => setDownloadType(val)}>
480
+ <SelectTrigger className="w-36">
481
+ <SelectValue />
482
+ </SelectTrigger>
483
+ <SelectContent>
484
+ <SelectItem value="original">Original Image</SelectItem>
485
+ <SelectItem value="sample">Sample Image</SelectItem>
486
+ </SelectContent>
487
+ </Select>
488
  <Input
489
  placeholder="Custom ZIP / Filename"
490
  value={customFilename}