feat: clear chat history manually, auto-expire old history after 90 days
Manual: DELETE /api/v1/ai/chat-history (scoped by recipeId or scope=general, matching GET's scoping) plus a trash-icon button + confirm dialog in both chat panels' headers. Automatic: new internal cron endpoint (chat-cleanup), same shared-secret pattern as the existing leftover-reminders/weekly-digest crons, deletes any chat_messages older than 90 days. Wired into cron/crontab (daily, 03:00 UTC) and the Dockerfile's cron stage. Verified locally: cleared a real conversation through the actual UI and confirmed it didn't come back on reopen (not just cleared client-side); inserted a 100-day-old and a 5-day-old message directly, called the cron endpoint with the real shared-secret check, confirmed only the old one was deleted and the recent one survived; confirmed the endpoint 401s with no/wrong secret.
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
|
||||
export function ClearChatHistoryButton({
|
||||
recipeId,
|
||||
disabled,
|
||||
onCleared,
|
||||
}: {
|
||||
recipeId?: string;
|
||||
disabled?: boolean;
|
||||
onCleared: () => void;
|
||||
}) {
|
||||
const t = useTranslations("chatHistory");
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [clearing, setClearing] = useState(false);
|
||||
|
||||
async function handleClear() {
|
||||
setClearing(true);
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
if (recipeId) params.set("recipeId", recipeId);
|
||||
else params.set("scope", "general");
|
||||
const res = await fetch(`/api/v1/ai/chat-history?${params.toString()}`, { method: "DELETE" });
|
||||
if (!res.ok) {
|
||||
toast.error(t("clearFailed"));
|
||||
return;
|
||||
}
|
||||
onCleared();
|
||||
toast.success(t("cleared"));
|
||||
} catch {
|
||||
toast.error(t("clearFailed"));
|
||||
} finally {
|
||||
setClearing(false);
|
||||
setConfirmOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmOpen(true)}
|
||||
disabled={disabled || clearing}
|
||||
className="p-1.5 rounded-md hover:bg-muted transition-colors text-muted-foreground disabled:opacity-40"
|
||||
aria-label={t("clearAriaLabel")}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
|
||||
<AlertDialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("clearConfirmTitle")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{t("clearConfirmDescription")}</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("clearCancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => { void handleClear(); }}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{t("clearConfirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user