import { useEffect, useState } from "react"; import { ThumbsDown, ThumbsUp } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Textarea } from "@/components/ui/textarea"; import { useAIFeedbackSummary, useSubmitAIFeedback, } from "@/hooks/queries/useAIFeedback"; import { cn } from "@/lib/utils"; import type { AIFeedbackRating, AIFeedbackSubjectType, } from "@/types/ai-feedback"; export interface AIFeedbackButtonsProps { subjectType: AIFeedbackSubjectType; subjectId: number; promptKey?: string; promptVersion?: number; aiLogId?: number; entityId?: number; courseId?: number; size?: "sm" | "md"; className?: string; } /** Thumbs up/down widget for any AI-generated artefact. * * Always renders the same UI regardless of whether the user has rated the * artefact before — a previous rating will show as highlighted. Clicking the * same button again is a no-op; clicking the opposite button updates the * server-side row in place. A thumbs-down always prompts the user for a short * comment (required by the backend). */ export function AIFeedbackButtons({ subjectType, subjectId, promptKey, promptVersion, aiLogId, entityId, courseId, size = "sm", className, }: AIFeedbackButtonsProps) { const summary = useAIFeedbackSummary(subjectType, subjectId); const submit = useSubmitAIFeedback(); const [downOpen, setDownOpen] = useState(false); const [comment, setComment] = useState(""); useEffect(() => { if (downOpen) { setComment(summary.data?.my_comment ?? ""); } }, [downOpen, summary.data?.my_comment]); const myRating: AIFeedbackRating | null = summary.data?.my_rating ?? null; const submitUp = async () => { if (myRating === "up") return; try { await submit.mutateAsync({ subject_type: subjectType, subject_id: subjectId, rating: "up", prompt_key: promptKey, prompt_version: promptVersion, ai_log_id: aiLogId, entity_id: entityId, course_id: courseId, }); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to submit"); } }; const submitDown = async () => { if (!comment.trim()) { toast.error("Please tell us what was wrong."); return; } try { await submit.mutateAsync({ subject_type: subjectType, subject_id: subjectId, rating: "down", comment: comment.trim(), prompt_key: promptKey, prompt_version: promptVersion, ai_log_id: aiLogId, entity_id: entityId, course_id: courseId, }); setDownOpen(false); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to submit"); } }; const btnSize = size === "sm" ? "sm" : "default"; const iconCls = size === "sm" ? "h-3.5 w-3.5" : "h-4 w-4"; return (
What went wrong?