diff --git a/frontend/src/components/workspace/messages/feedback-dialog.tsx b/frontend/src/components/workspace/messages/feedback-dialog.tsx new file mode 100644 index 000000000..4ca8a740c --- /dev/null +++ b/frontend/src/components/workspace/messages/feedback-dialog.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useState } from "react"; + +import { FEEDBACK_TAG_SLUGS, type FeedbackTagSlug } from "@/core/api/feedback"; +import { useI18n } from "@/core/i18n/hooks"; +import { cn } from "@/lib/utils"; + +import { Button } from "../../ui/button"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../../ui/dialog"; +import { Textarea } from "../../ui/textarea"; + +/** Maps language-neutral tag slugs to their i18n label keys. */ +const TAG_LABEL_KEYS: Record = { + incorrect: "incorrect", + not_as_expected: "notAsExpected", + slow: "slow", + style_tone: "styleTone", + safety_legal: "safetyLegal", + other: "other", +}; + +/** + * Thumbs-down follow-up dialog (ChatGPT-style): reason chips + optional + * details. The rating itself is already stored when this opens; submitting + * issues a second idempotent PUT that enriches the same record with + * tags/comment. Closing without submitting keeps the plain thumbs-down. + */ +export function FeedbackDialog({ + open, + onOpenChange, + onSubmit, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + onSubmit: (tags: string[], comment: string) => Promise; +}) { + const { t } = useI18n(); + const [selected, setSelected] = useState>(new Set()); + const [comment, setComment] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const toggleTag = (slug: FeedbackTagSlug) => { + setSelected((prev) => { + const next = new Set(prev); + if (next.has(slug)) { + next.delete(slug); + } else { + next.add(slug); + } + return next; + }); + }; + + const canSubmit = (selected.size > 0 || comment.trim().length > 0) && !isSubmitting; + + const handleSubmit = async () => { + if (!canSubmit) return; + setIsSubmitting(true); + try { + await onSubmit([...selected], comment.trim()); + onOpenChange(false); + setSelected(new Set()); + setComment(""); + } finally { + setIsSubmitting(false); + } + }; + + return ( + + + + {t.feedback.dialogTitle} + +
+ {FEEDBACK_TAG_SLUGS.map((slug) => ( + + ))} +
+