feat(admin): manual narrative generation

This commit is contained in:
Dave 2026-09-16 16:25:58 +02:00
parent 4a278fa56b
commit ee65f2238d
2 changed files with 49 additions and 1 deletions

View file

@ -0,0 +1,31 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireSession } from "@/lib/auth/session";
import { narrateProject } from "@/lib/ai/narrate";
/** See the security note at the top of src/app/actions/cards.ts. */
export async function narrateProjectAction(input: {
projectId: string;
}): Promise<{ written: number; message: string }> {
const session = await requireSession();
if (session.role !== "owner") throw new Error("Not found");
const result = await narrateProject(input.projectId);
revalidatePath(`/admin/projects/${input.projectId}`);
if (result.skipped) {
return { written: 0, message: result.error ?? "No AI provider is configured." };
}
if (result.considered === 0) {
return { written: 0, message: "Every narrative is up to date." };
}
return {
written: result.written,
message: `${result.written} of ${result.considered} narratives written${
result.error ? ` · ${result.error}` : ""
}`,
};
}

View file

@ -1,12 +1,13 @@
"use client";
import { useState, useTransition } from "react";
import { Sparkles, RotateCcw } from "lucide-react";
import { Sparkles, RotateCcw, BookText } from "lucide-react";
import {
retryFailedSummariesAction,
summariseProjectAction,
} from "@/app/actions/summarise";
import { narrateProjectAction } from "@/app/actions/narrative";
export function SummaryPanel({
projectId,
@ -83,6 +84,22 @@ export function SummaryPanel({
</button>
)}
<button
type="button"
disabled={pending}
onClick={() =>
startTransition(async () => {
setMessage("Writing narratives…");
const result = await narrateProjectAction({ projectId });
setMessage(result.message);
})
}
className="inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-2.5 py-1.5 text-xs text-text-secondary hover:bg-surface-raised disabled:opacity-50"
>
<BookText size={13} strokeWidth={2} />
Write card narratives
</button>
{message && <span className="text-xs text-text-muted">{message}</span>}
</div>
</section>