Add result and job management APIs
This commit is contained in:
@@ -185,6 +185,7 @@
|
||||
renderProgress(job);
|
||||
|
||||
if (job.status === "done") return job;
|
||||
if (job.status === "cancelled") throw new Error("Job wurde abgebrochen");
|
||||
if (job.status === "error") throw new Error(job.errors?.join("\n") || "Job fehlgeschlagen");
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 700));
|
||||
|
||||
@@ -11,7 +11,7 @@ const MAX_PARALLEL_SUMMARIES = 2;
|
||||
const MAX_ACTIVE_JOBS = Number(process.env.BMP_MAX_ACTIVE_SUMMARY_JOBS ?? 10);
|
||||
const EXPECTED_LLM_CALLS_PER_COMPANY = 13;
|
||||
|
||||
type JobStatus = "queued" | "running" | "done" | "error";
|
||||
type JobStatus = "queued" | "running" | "done" | "error" | "cancelled";
|
||||
|
||||
interface SummaryJob {
|
||||
id: string;
|
||||
@@ -203,6 +203,10 @@ function activeJobCount(): number {
|
||||
return [...jobs.values()].filter((job) => job.status === "queued" || job.status === "running").length;
|
||||
}
|
||||
|
||||
function isCancelled(job: SummaryJob): boolean {
|
||||
return job.status === "cancelled";
|
||||
}
|
||||
|
||||
const jobs = new Map<string, SummaryJob>();
|
||||
|
||||
function createJob(stem: string, model: string, total: number): SummaryJob {
|
||||
@@ -262,12 +266,14 @@ async function runWithConcurrency<T>(
|
||||
}
|
||||
|
||||
async function executeJob(job: SummaryJob): Promise<void> {
|
||||
if (isCancelled(job)) return;
|
||||
job.status = "running";
|
||||
job.statusMessage = "Auswertung wird vorbereitet.";
|
||||
const companies = job.stem === "__all__" ? await listCompanies() : [job.stem];
|
||||
|
||||
try {
|
||||
await runWithConcurrency(companies, MAX_PARALLEL_SUMMARIES, async (company) => {
|
||||
if (isCancelled(job)) return;
|
||||
job.current = company;
|
||||
job.statusMessage = `LLM-Auswertung für ${company} läuft.`;
|
||||
const summary = await summarizeCompany(company, job.model, {
|
||||
@@ -286,17 +292,31 @@ async function executeJob(job: SummaryJob): Promise<void> {
|
||||
},
|
||||
});
|
||||
job.statusMessage = `Downloads für ${company} werden vorbereitet.`;
|
||||
if (isCancelled(job)) return;
|
||||
const outputPaths = await writeSummary(company, summary, OUTPUTS_DIR);
|
||||
job.files.push(...outputPaths.map((path) => basename(path)));
|
||||
job.downloads.push(...outputPaths.map((path) => buildDownload(company, path)));
|
||||
job.completed += 1;
|
||||
});
|
||||
|
||||
if (isCancelled(job)) {
|
||||
job.current = undefined;
|
||||
job.statusMessage = "Auswertung abgebrochen.";
|
||||
job.finishedAt = new Date().toISOString();
|
||||
return;
|
||||
}
|
||||
|
||||
job.status = "done";
|
||||
job.current = undefined;
|
||||
job.statusMessage = "Auswertung fertig.";
|
||||
job.finishedAt = new Date().toISOString();
|
||||
} catch (error) {
|
||||
if (isCancelled(job)) {
|
||||
job.current = undefined;
|
||||
job.statusMessage = "Auswertung abgebrochen.";
|
||||
job.finishedAt = new Date().toISOString();
|
||||
return;
|
||||
}
|
||||
job.status = "error";
|
||||
job.errors.push(String(error));
|
||||
job.statusMessage = "Auswertung fehlgeschlagen.";
|
||||
@@ -368,6 +388,15 @@ export const routes = {
|
||||
}),
|
||||
},
|
||||
|
||||
"/api/summarizer/jobs": {
|
||||
GET: withAuth(async () => {
|
||||
const summaries = [...jobs.values()]
|
||||
.toSorted((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt))
|
||||
.map(publicJob);
|
||||
return Response.json({ jobs: summaries });
|
||||
}),
|
||||
},
|
||||
|
||||
"/api/summarizer/jobs/:id/llm-calls": {
|
||||
GET: withAuth(async (req: Request) => {
|
||||
const id = (req as Request & { params: Record<string, string> }).params.id;
|
||||
@@ -402,5 +431,24 @@ export const routes = {
|
||||
}
|
||||
return Response.json(publicJob(job));
|
||||
}),
|
||||
DELETE: withAuth(async (req: Request) => {
|
||||
const id = (req as Request & { params: Record<string, string> }).params.id;
|
||||
const job = jobs.get(id);
|
||||
if (!job) {
|
||||
return Response.json({ error: "Job not found" }, { status: 404 });
|
||||
}
|
||||
if (job.status === "queued" || job.status === "running") {
|
||||
job.status = "cancelled";
|
||||
job.current = undefined;
|
||||
job.statusMessage = "Auswertung abgebrochen.";
|
||||
job.finishedAt = new Date().toISOString();
|
||||
return Response.json({ ok: true, job: publicJob(job) });
|
||||
}
|
||||
jobs.delete(id);
|
||||
return Response.json({ ok: true, deleted: true });
|
||||
}, {
|
||||
csrf: true,
|
||||
limit: { key: "summarizer-job-delete", max: 30, windowMs: 60_000 },
|
||||
}),
|
||||
},
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user