From eddf87fc5898f8fa44277c451c8222caae32df2c Mon Sep 17 00:00:00 2001 From: rafavalls Date: Mon, 1 Jun 2026 18:29:56 -0300 Subject: [PATCH 1/6] feat(feedback): add feedback button and per-message thumbs up/down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Feedback menu item to the account popover that opens a general feedback modal. Adds thumbs up/thumbs down actions on each assistant chat message — thumbs up tracks a positive signal, thumbs down opens a structured "Share feedback" modal with reason chips and optional details. All events are sent to PostHog with thread_id, message_id, message content snippet, and a session_replay_url for direct recording links. Removes Terms of Use and Privacy Policy from the account menu. Co-Authored-By: Claude Sonnet 4.6 --- .../src/web/components/account-popover.tsx | 24 ++-- .../chat/message-feedback-dialog.tsx | 129 ++++++++++++++++++ .../chat/message/parts/text-part.tsx | 76 ++++++++++- .../src/web/components/feedback-dialog.tsx | 89 ++++++++++++ apps/mesh/src/web/lib/posthog-client.ts | 14 ++ 5 files changed, 314 insertions(+), 18 deletions(-) create mode 100644 apps/mesh/src/web/components/chat/message-feedback-dialog.tsx create mode 100644 apps/mesh/src/web/components/feedback-dialog.tsx diff --git a/apps/mesh/src/web/components/account-popover.tsx b/apps/mesh/src/web/components/account-popover.tsx index 0b27bfb7c0..6d047904a7 100644 --- a/apps/mesh/src/web/components/account-popover.tsx +++ b/apps/mesh/src/web/components/account-popover.tsx @@ -22,15 +22,14 @@ import { useIsMobile } from "@deco/ui/hooks/use-mobile.ts"; import { Check, Copy01, - File06, Globe01, LogOut01, + MessageChatCircle, Monitor01, Moon01, Plus, SearchMd, Settings02, - Shield01, Sun, Users03, VolumeMax, @@ -43,6 +42,7 @@ import { authClient } from "@/web/lib/auth-client"; import { track } from "@/web/lib/posthog-client"; import { clearPersistedQueryCache } from "@/web/lib/query-persist"; import { CreateOrganizationDialog } from "@/web/components/create-organization-dialog"; +import { FeedbackDialog } from "@/web/components/feedback-dialog"; import { usePreferences, type ThemeMode } from "@/web/hooks/use-preferences.ts"; import { toast } from "@deco/ui/components/sonner.js"; @@ -534,6 +534,7 @@ export function AccountPopover() { const [open, setOpen] = useState(false); const [creatingOrg, setCreatingOrg] = useState(false); + const [feedbackOpen, setFeedbackOpen] = useState(false); const user = session?.user; const userImage = (user as { image?: string } | undefined)?.image; @@ -571,18 +572,12 @@ export function AccountPopover() { }, }, { - key: "terms", - label: "Terms of Use", - icon: , - href: "https://www.decocms.com/terms-of-use", - external: true, - }, - { - key: "privacy", - label: "Privacy Policy", - icon: , - href: "https://www.decocms.com/privacy-policy", - external: true, + key: "feedback", + label: "Feedback", + icon: , + onClick: () => { + setFeedbackOpen(true); + }, }, { key: "github", @@ -732,6 +727,7 @@ export function AccountPopover() { open={creatingOrg} onOpenChange={setCreatingOrg} /> + ); } diff --git a/apps/mesh/src/web/components/chat/message-feedback-dialog.tsx b/apps/mesh/src/web/components/chat/message-feedback-dialog.tsx new file mode 100644 index 0000000000..3a5f158e69 --- /dev/null +++ b/apps/mesh/src/web/components/chat/message-feedback-dialog.tsx @@ -0,0 +1,129 @@ +import { useState } from "react"; +import { Button } from "@deco/ui/components/button.tsx"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@deco/ui/components/dialog.tsx"; +import { Textarea } from "@deco/ui/components/textarea.tsx"; +import { cn } from "@deco/ui/lib/utils.ts"; +import { toast } from "@deco/ui/components/sonner.js"; +import { track } from "@/web/lib/posthog-client"; + +const REASONS = [ + "Incorrect or incomplete", + "Not what I asked for", + "Slow or buggy", + "Style or tone", + "Safety or legal concern", + "Other", +] as const; + +type Reason = (typeof REASONS)[number]; + +interface MessageFeedbackDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + messageId: string; + threadId: string | null; + messageContent: string; + sessionReplayUrl: string | null; +} + +export function MessageFeedbackDialog({ + open, + onOpenChange, + messageId, + threadId, + messageContent, + sessionReplayUrl, +}: MessageFeedbackDialogProps) { + const [selected, setSelected] = useState>(new Set()); + const [details, setDetails] = useState(""); + + const toggle = (reason: Reason) => { + setSelected((prev) => { + const next = new Set(prev); + if (next.has(reason)) { + next.delete(reason); + } else { + next.add(reason); + } + return next; + }); + }; + + const handleSubmit = () => { + track("chat_message_feedback_negative", { + message_id: messageId, + thread_id: threadId, + reasons: [...selected], + details: details.trim() || undefined, + message_content: messageContent.slice(0, 500) || undefined, + session_replay_url: sessionReplayUrl, + }); + setSelected(new Set()); + setDetails(""); + onOpenChange(false); + toast.success("Feedback sent — thank you!"); + }; + + const handleOpenChange = (next: boolean) => { + if (!next) { + setSelected(new Set()); + setDetails(""); + } + onOpenChange(next); + }; + + return ( + + + + + Share feedback + + + +
+ {REASONS.map((reason) => ( + + ))} +
+ +