mirror of
https://github.com/ostris/ai-toolkit.git
synced 2026-02-28 08:13:58 +00:00
Add a Download button on samples to download all the samples as a zip file
This commit is contained in:
@@ -50,6 +50,7 @@ export async function GET(request: NextRequest, { params }: { params: { filePath
|
||||
'.svg': 'image/svg+xml',
|
||||
'.bmp': 'image/bmp',
|
||||
'.safetensors': 'application/octet-stream',
|
||||
'.zip': 'application/zip',
|
||||
// Videos
|
||||
'.mp4': 'video/mp4',
|
||||
'.avi': 'video/x-msvideo',
|
||||
|
||||
78
ui/src/app/api/zip/route.ts
Normal file
78
ui/src/app/api/zip/route.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable */
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import fs from 'fs';
|
||||
import fsp from 'fs/promises';
|
||||
import path from 'path';
|
||||
import archiver from 'archiver';
|
||||
import { getTrainingFolder } from '@/server/settings';
|
||||
|
||||
export const runtime = 'nodejs'; // ensure Node APIs are available
|
||||
export const dynamic = 'force-dynamic'; // long-running, non-cached
|
||||
|
||||
type PostBody = {
|
||||
zipTarget: 'samples'; //only samples for now
|
||||
jobName: string;
|
||||
};
|
||||
|
||||
async function resolveSafe(p: string) {
|
||||
// resolve symlinks + normalize
|
||||
return await fsp.realpath(p);
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = (await request.json()) as PostBody;
|
||||
if (!body || !body.jobName) {
|
||||
return NextResponse.json({ error: 'jobName is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const trainingRoot = await resolveSafe(await getTrainingFolder());
|
||||
const folderPath = await resolveSafe(path.join(trainingRoot, body.jobName, 'samples'));
|
||||
const outputPath = path.resolve(trainingRoot, body.jobName, 'samples.zip');
|
||||
|
||||
// Must be a directory
|
||||
let stat: fs.Stats;
|
||||
try {
|
||||
stat = await fsp.stat(folderPath);
|
||||
} catch {
|
||||
return new NextResponse('Folder not found', { status: 404 });
|
||||
}
|
||||
if (!stat.isDirectory()) {
|
||||
return new NextResponse('Not a directory', { status: 400 });
|
||||
}
|
||||
|
||||
// delete current one if it exists
|
||||
if (fs.existsSync(outputPath)) {
|
||||
await fsp.unlink(outputPath);
|
||||
}
|
||||
|
||||
// Create write stream & archive
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const output = fs.createWriteStream(outputPath);
|
||||
const archive = archiver('zip', { zlib: { level: 9 } });
|
||||
|
||||
output.on('close', () => resolve());
|
||||
output.on('error', reject);
|
||||
archive.on('error', reject);
|
||||
|
||||
archive.pipe(output);
|
||||
|
||||
// Add the directory contents (place them under the folder's base name in the zip)
|
||||
const rootName = path.basename(folderPath);
|
||||
archive.directory(folderPath, rootName);
|
||||
|
||||
archive.finalize().catch(reject);
|
||||
});
|
||||
|
||||
// Return the absolute path so your existing /api/files/[...filePath] can serve it
|
||||
// Example download URL (client-side): `/api/files/${encodeURIComponent(resolvedOutPath)}`
|
||||
return NextResponse.json({
|
||||
ok: true,
|
||||
zipPath: outputPath,
|
||||
fileName: path.basename(outputPath),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Zip error:', err);
|
||||
return new NextResponse('Internal Server Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user