From b0cb95be2f8a55625f2ffcecaa88af45f3756ce7 Mon Sep 17 00:00:00 2001 From: Emir Karabeg <78010029+emir-karabeg@users.noreply.github.com> Date: Sat, 4 Apr 2026 10:46:49 -0700 Subject: [PATCH 1/4] feat: mothership/copilot feedback (#3940) * feat: mothership/copilot feedback * fix(feedback): remove mutation object from useCallback deps --- .../message-actions/message-actions.tsx | 185 +++++-- .../components/special-tags/special-tags.tsx | 4 +- .../mothership-chat/mothership-chat.tsx | 24 +- .../mothership-view/mothership-view.tsx | 2 +- .../app/workspace/[workspaceId]/home/home.tsx | 1 + .../w/[workflowId]/components/panel/panel.tsx | 1 + .../components/help-modal/help-modal.tsx | 15 +- .../w/components/sidebar/sidebar.tsx | 521 +++++++++--------- apps/sim/components/emcn/icons/index.ts | 2 + .../sim/components/emcn/icons/thumbs-down.tsx | 28 + apps/sim/components/emcn/icons/thumbs-up.tsx | 26 + apps/sim/hooks/queries/copilot-feedback.ts | 39 ++ 12 files changed, 535 insertions(+), 313 deletions(-) create mode 100644 apps/sim/components/emcn/icons/thumbs-down.tsx create mode 100644 apps/sim/components/emcn/icons/thumbs-up.tsx create mode 100644 apps/sim/hooks/queries/copilot-feedback.ts diff --git a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx index cda3f8c9ccb..77607befa95 100644 --- a/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx +++ b/apps/sim/app/workspace/[workspaceId]/components/message-actions/message-actions.tsx @@ -1,22 +1,59 @@ 'use client' import { useCallback, useEffect, useRef, useState } from 'react' -import { Check, Copy, Ellipsis, Hash } from 'lucide-react' import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, + Button, + Check, + Copy, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + Textarea, + ThumbsDown, + ThumbsUp, } from '@/components/emcn' +import { useSubmitCopilotFeedback } from '@/hooks/queries/copilot-feedback' + +const SPECIAL_TAGS = 'thinking|options|usage_upgrade|credential|mothership-error|file' + +function toPlainText(raw: string): string { + return ( + raw + // Strip special tags and their contents + .replace(new RegExp(`<\\/?(${SPECIAL_TAGS})(?:>[\\s\\S]*?<\\/(${SPECIAL_TAGS})>|>)`, 'g'), '') + // Strip markdown + .replace(/^#{1,6}\s+/gm, '') + .replace(/\*\*(.+?)\*\*/g, '$1') + .replace(/\*(.+?)\*/g, '$1') + .replace(/`{3}[\s\S]*?`{3}/g, '') + .replace(/`(.+?)`/g, '$1') + .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') + .replace(/^[>\-*]\s+/gm, '') + .replace(/!\[[^\]]*\]\([^)]+\)/g, '') + // Normalize whitespace + .replace(/\n{3,}/g, '\n\n') + .trim() + ) +} + +const ICON_CLASS = 'h-[14px] w-[14px]' +const BUTTON_CLASS = + 'flex h-[26px] w-[26px] items-center justify-center rounded-[6px] text-[var(--text-icon)] transition-colors hover-hover:bg-[var(--surface-hover)] focus-visible:outline-none' interface MessageActionsProps { content: string - requestId?: string + chatId?: string + userQuery?: string } -export function MessageActions({ content, requestId }: MessageActionsProps) { - const [copied, setCopied] = useState<'message' | 'request' | null>(null) +export function MessageActions({ content, chatId, userQuery }: MessageActionsProps) { + const [copied, setCopied] = useState(false) + const [pendingFeedback, setPendingFeedback] = useState<'up' | 'down' | null>(null) + const [feedbackText, setFeedbackText] = useState('') const resetTimeoutRef = useRef(null) + const submitFeedback = useSubmitCopilotFeedback() useEffect(() => { return () => { @@ -26,59 +63,119 @@ export function MessageActions({ content, requestId }: MessageActionsProps) { } }, []) - const copyToClipboard = useCallback(async (text: string, type: 'message' | 'request') => { + const copyToClipboard = useCallback(async () => { + if (!content) return + const text = toPlainText(content) + if (!text) return try { await navigator.clipboard.writeText(text) - setCopied(type) + setCopied(true) if (resetTimeoutRef.current !== null) { window.clearTimeout(resetTimeoutRef.current) } - resetTimeoutRef.current = window.setTimeout(() => setCopied(null), 1500) + resetTimeoutRef.current = window.setTimeout(() => setCopied(false), 1500) } catch { + /* clipboard unavailable */ + } + }, [content]) + + const handleFeedbackClick = useCallback( + (type: 'up' | 'down') => { + if (chatId && userQuery) { + setPendingFeedback(type) + setFeedbackText('') + } + }, + [chatId, userQuery] + ) + + const handleSubmitFeedback = useCallback(() => { + if (!pendingFeedback || !chatId || !userQuery) return + const text = feedbackText.trim() + if (!text) { + setPendingFeedback(null) + setFeedbackText('') return } + submitFeedback.mutate({ + chatId, + userQuery, + agentResponse: content, + isPositiveFeedback: pendingFeedback === 'up', + feedback: text, + }) + setPendingFeedback(null) + setFeedbackText('') + }, [pendingFeedback, chatId, userQuery, content, feedbackText]) + + const handleModalClose = useCallback((open: boolean) => { + if (!open) { + setPendingFeedback(null) + setFeedbackText('') + } }, []) - if (!content && !requestId) { - return null - } + if (!content) return null return ( - - + <> +
- - - { - event.stopPropagation() - void copyToClipboard(content, 'message') - }} + + +
+ + + + Give feedback + +
+

+ {pendingFeedback === 'up' ? 'What did you like?' : 'What could be improved?'} +

+