Upload folder using huggingface_hub
Browse files- src/routes/api/download-zip.ts +10 -4
- src/routes/index.tsx +11 -0
src/routes/api/download-zip.ts
CHANGED
|
@@ -24,6 +24,7 @@ const PostSchema = z.object({
|
|
| 24 |
const BodySchema = z.object({
|
| 25 |
siteName: z.string().default("grabber"),
|
| 26 |
posts: z.array(PostSchema).min(1).max(200),
|
|
|
|
| 27 |
});
|
| 28 |
|
| 29 |
function sanitize(s: string) {
|
|
@@ -38,7 +39,7 @@ function humanSize(n?: number): string {
|
|
| 38 |
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
| 39 |
}
|
| 40 |
|
| 41 |
-
function buildMarkdown(post: z.infer<typeof PostSchema>): string {
|
| 42 |
const dims = post.width && post.height ? `${post.width} x ${post.height}` : "";
|
| 43 |
const rows: [string, string][] = [
|
| 44 |
["Source", post.source ?? ""],
|
|
@@ -56,7 +57,8 @@ function buildMarkdown(post: z.infer<typeof PostSchema>): string {
|
|
| 56 |
];
|
| 57 |
|
| 58 |
const meta = rows.map(([k, v]) => `- **${k}:** ${v}`).join("\n");
|
| 59 |
-
const
|
|
|
|
| 60 |
let raw = "{}";
|
| 61 |
if (post.rawJson) {
|
| 62 |
try {
|
|
@@ -99,6 +101,7 @@ export const Route = createFileRoute("/api/download-zip")({
|
|
| 99 |
const files: Record<string, Uint8Array> = {};
|
| 100 |
const errors: string[] = [];
|
| 101 |
const siteName = sanitize(body.siteName);
|
|
|
|
| 102 |
|
| 103 |
await Promise.all(
|
| 104 |
body.posts.map(async (post) => {
|
|
@@ -106,11 +109,14 @@ export const Route = createFileRoute("/api/download-zip")({
|
|
| 106 |
const extSafe = sanitize(post.ext || "jpg");
|
| 107 |
const base = `${siteName}_${idSafe}`;
|
| 108 |
|
|
|
|
|
|
|
|
|
|
| 109 |
// tags .txt — same basename as the image
|
| 110 |
-
files[`${base}.txt`] = strToU8(
|
| 111 |
|
| 112 |
// per-post markdown metadata
|
| 113 |
-
files[`${idSafe}.md`] = strToU8(buildMarkdown(post));
|
| 114 |
|
| 115 |
// fetch the image itself
|
| 116 |
try {
|
|
|
|
| 24 |
const BodySchema = z.object({
|
| 25 |
siteName: z.string().default("grabber"),
|
| 26 |
posts: z.array(PostSchema).min(1).max(200),
|
| 27 |
+
excludeTags: z.array(z.string()).default([]),
|
| 28 |
});
|
| 29 |
|
| 30 |
function sanitize(s: string) {
|
|
|
|
| 39 |
return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
|
| 40 |
}
|
| 41 |
|
| 42 |
+
function buildMarkdown(post: z.infer<typeof PostSchema>, excludeSet: Set<string>): string {
|
| 43 |
const dims = post.width && post.height ? `${post.width} x ${post.height}` : "";
|
| 44 |
const rows: [string, string][] = [
|
| 45 |
["Source", post.source ?? ""],
|
|
|
|
| 57 |
];
|
| 58 |
|
| 59 |
const meta = rows.map(([k, v]) => `- **${k}:** ${v}`).join("\n");
|
| 60 |
+
const filteredTags = post.tags.filter((t) => !excludeSet.has(t.toLowerCase()));
|
| 61 |
+
const tags = filteredTags.length ? filteredTags.join(" ") : "";
|
| 62 |
let raw = "{}";
|
| 63 |
if (post.rawJson) {
|
| 64 |
try {
|
|
|
|
| 101 |
const files: Record<string, Uint8Array> = {};
|
| 102 |
const errors: string[] = [];
|
| 103 |
const siteName = sanitize(body.siteName);
|
| 104 |
+
const excludeSet = new Set((body.excludeTags || []).map((t) => t.toLowerCase()));
|
| 105 |
|
| 106 |
await Promise.all(
|
| 107 |
body.posts.map(async (post) => {
|
|
|
|
| 109 |
const extSafe = sanitize(post.ext || "jpg");
|
| 110 |
const base = `${siteName}_${idSafe}`;
|
| 111 |
|
| 112 |
+
// Filter out tags that user specified to be missing
|
| 113 |
+
const filteredTags = post.tags.filter((t) => !excludeSet.has(t.toLowerCase()));
|
| 114 |
+
|
| 115 |
// tags .txt — same basename as the image
|
| 116 |
+
files[`${base}.txt`] = strToU8(filteredTags.join(" "));
|
| 117 |
|
| 118 |
// per-post markdown metadata
|
| 119 |
+
files[`${idSafe}.md`] = strToU8(buildMarkdown(post, excludeSet));
|
| 120 |
|
| 121 |
// fetch the image itself
|
| 122 |
try {
|
src/routes/index.tsx
CHANGED
|
@@ -47,6 +47,7 @@ function Grabber() {
|
|
| 47 |
const [addOpen, setAddOpen] = useState(false);
|
| 48 |
const [lightbox, setLightbox] = useState<NormalizedPost | null>(null);
|
| 49 |
const [downloading, setDownloading] = useState(false);
|
|
|
|
| 50 |
|
| 51 |
const site = useMemo(() => sites.find((s) => s.id === siteId) ?? sites[0], [sites, siteId]);
|
| 52 |
|
|
@@ -90,6 +91,10 @@ function Grabber() {
|
|
| 90 |
body: JSON.stringify({
|
| 91 |
siteName: site.id,
|
| 92 |
posts: chosen,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
}),
|
| 94 |
});
|
| 95 |
if (!res.ok) {
|
|
@@ -193,6 +198,12 @@ function Grabber() {
|
|
| 193 |
{selected.size === posts.length && posts.length > 0 ? "Deselect all" : "Select all"}
|
| 194 |
</Button>
|
| 195 |
<span className="text-sm text-muted-foreground">selected: {selected.size}</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
<Button onClick={downloadZip} disabled={!selected.size || downloading}>
|
| 197 |
{downloading ? (
|
| 198 |
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|
|
|
|
| 47 |
const [addOpen, setAddOpen] = useState(false);
|
| 48 |
const [lightbox, setLightbox] = useState<NormalizedPost | null>(null);
|
| 49 |
const [downloading, setDownloading] = useState(false);
|
| 50 |
+
const [excludeTags, setExcludeTags] = useState("");
|
| 51 |
|
| 52 |
const site = useMemo(() => sites.find((s) => s.id === siteId) ?? sites[0], [sites, siteId]);
|
| 53 |
|
|
|
|
| 91 |
body: JSON.stringify({
|
| 92 |
siteName: site.id,
|
| 93 |
posts: chosen,
|
| 94 |
+
excludeTags: excludeTags
|
| 95 |
+
.split(/\s+|,/)
|
| 96 |
+
.map((t) => t.trim().toLowerCase())
|
| 97 |
+
.filter(Boolean),
|
| 98 |
}),
|
| 99 |
});
|
| 100 |
if (!res.ok) {
|
|
|
|
| 198 |
{selected.size === posts.length && posts.length > 0 ? "Deselect all" : "Select all"}
|
| 199 |
</Button>
|
| 200 |
<span className="text-sm text-muted-foreground">selected: {selected.size}</span>
|
| 201 |
+
<Input
|
| 202 |
+
placeholder="Exclude tags from ZIP (e.g. fluffy, safety)"
|
| 203 |
+
value={excludeTags}
|
| 204 |
+
onChange={(e) => setExcludeTags(e.target.value)}
|
| 205 |
+
className="w-64 max-w-xs"
|
| 206 |
+
/>
|
| 207 |
<Button onClick={downloadZip} disabled={!selected.size || downloading}>
|
| 208 |
{downloading ? (
|
| 209 |
<Loader2 className="w-4 h-4 mr-1 animate-spin" />
|