diff --git a/src/app/[id]/edit/page.tsx b/src/app/[id]/edit/page.tsx index 7408c14..8e9f4b3 100644 --- a/src/app/[id]/edit/page.tsx +++ b/src/app/[id]/edit/page.tsx @@ -3,6 +3,7 @@ import { getBillByIdFromDB } from "@/server/get-bill-by-id-from-db"; import { requireAuthenticatedUser } from "@/lib/auth-guards"; import { BASE_PATH } from "@/utils/basePath"; import { Button } from "@/components/ui/button"; +import { ReprocessButton } from "@/components/ReprocessButton/reprocess-button.component"; interface Params { params: Promise<{ id: string }>; @@ -25,6 +26,9 @@ export default async function EditBillPage({ params }: Params) { return (

Edit Bill

+
+ +
}, +) { + const session = await getServerSession(authOptions); + if (!session?.user?.email) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + await connectToDatabase(); + + // Verify user is on the allowlist; do not create + const dbUser = await User.findOne({ + emailLower: session.user.email.toLowerCase(), + }); + if (!dbUser) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { id } = await params; + + const existing = (await Bill.findOne({ billId: id }) + .lean() + .exec()) as BillDocument | null; + if (!existing) { + return NextResponse.json({ error: "Bill not found" }, { status: 404 }); + } + + // Prefer the freshest source from the Civics Project API; fall back to the + // source stored on the existing bill. + let source: string | undefined = existing.source; + try { + const apiBill = await getBillFromCivicsProjectApi(id); + const apiSource = + apiBill?.source || + (apiBill?.billTexts?.[0] as { url?: string } | undefined)?.url; + if (apiSource) { + source = apiSource; + } + } catch (error) { + console.error(`Reprocess ${id}: failed to fetch latest source`, error); + } + + if (!source) { + return NextResponse.json( + { error: "No bill text source available to reprocess" }, + { status: 422 }, + ); + } + + const markdown = await fetchBillMarkdown(source); + if (!markdown) { + return NextResponse.json( + { error: "Failed to fetch bill text from source" }, + { status: 502 }, + ); + } + + const analysis = await summarizeBillText(markdown); + + await Bill.updateOne( + { billId: id }, + { + $set: { + summary: analysis.summary, + short_title: analysis.short_title ?? existing.short_title, + tenet_evaluations: analysis.tenet_evaluations, + final_judgment: analysis.final_judgment, + rationale: analysis.rationale, + needs_more_info: analysis.needs_more_info, + missing_details: analysis.missing_details, + steel_man: analysis.steel_man, + question_period_questions: analysis.question_period_questions ?? [], + source, + lastUpdatedOn: new Date(), + }, + }, + { upsert: false }, + ); + + return NextResponse.json({ ok: true }); +} diff --git a/src/components/ReprocessButton/reprocess-button.component.tsx b/src/components/ReprocessButton/reprocess-button.component.tsx new file mode 100644 index 0000000..a03a429 --- /dev/null +++ b/src/components/ReprocessButton/reprocess-button.component.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { BASE_PATH } from "@/utils/basePath"; + +interface ReprocessButtonProps { + billId: string; +} + +export const ReprocessButton = ({ billId }: ReprocessButtonProps) => { + const router = useRouter(); + const [isPending, setIsPending] = useState(false); + const [error, setError] = useState(null); + + const handleReprocess = async () => { + const confirmed = window.confirm( + "Re-run the AI analysis for this bill? This will overwrite the summary, judgment, rationale, steel man, tenet evaluations and Question Period questions.", + ); + if (!confirmed) return; + + setIsPending(true); + setError(null); + try { + const res = await fetch(`${BASE_PATH}/api/${billId}/reprocess`, { + method: "POST", + }); + if (!res.ok) { + const body = (await res.json().catch(() => null)) as { + error?: string; + } | null; + throw new Error(body?.error || `Request failed (${res.status})`); + } + // Reload so the form re-renders with the freshly generated analysis. + router.refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : "Something went wrong"); + } finally { + setIsPending(false); + } + }; + + return ( +
+
+ +

+ Regenerates all AI fields from the latest bill text. +

+
+ {error &&

{error}

} +
+ ); +};