From e9b82ee02869a96f8f90aec2977506e6c68ff18a Mon Sep 17 00:00:00 2001
From: Emir Karabeg
Date: Fri, 3 Apr 2026 20:43:02 -0700
Subject: [PATCH 1/2] feat: mothership/copilot feedback
---
.../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..43f108d5c7c 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, submitFeedback])
+
+ 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')
- }}
+
- {
- event.stopPropagation()
- if (requestId) {
- void copyToClipboard(requestId, 'request')
- }
- }}
+
+
+
-
-
+
+
+
+
+
+
+ Give feedback
+
+
+
+ {pendingFeedback === 'up' ? 'What did you like?' : 'What could be improved?'}
+
+
+
+
+
+
+
+
+
+ >
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx
index 2cc87d84368..dc6a032bc67 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/special-tags/special-tags.tsx
@@ -473,9 +473,9 @@ function MothershipErrorDisplay({ data }: { data: MothershipErrorTagData }) {
const detail = data.code ? `${data.message} (${data.code})` : data.message
return (
-
+
{detail}
-
+
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx
index 384666e3e07..53ccff13e6d 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-chat/mothership-chat.tsx
@@ -35,6 +35,7 @@ interface MothershipChatProps {
onSendQueuedMessage: (id: string) => Promise
onEditQueuedMessage: (id: string) => void
userId?: string
+ chatId?: string
onContextAdd?: (context: ChatContext) => void
editValue?: string
onEditValueConsumed?: () => void
@@ -53,7 +54,7 @@ const LAYOUT_STYLES = {
userRow: 'flex flex-col items-end gap-[6px] pt-3',
attachmentWidth: 'max-w-[70%]',
userBubble: 'max-w-[70%] overflow-hidden rounded-[16px] bg-[var(--surface-5)] px-3.5 py-2',
- assistantRow: 'group/msg relative pb-5',
+ assistantRow: 'group/msg',
footer: 'flex-shrink-0 px-[24px] pb-[16px]',
footerInner: 'mx-auto max-w-[42rem]',
},
@@ -63,7 +64,7 @@ const LAYOUT_STYLES = {
userRow: 'flex flex-col items-end gap-[6px] pt-2',
attachmentWidth: 'max-w-[85%]',
userBubble: 'max-w-[85%] overflow-hidden rounded-[16px] bg-[var(--surface-5)] px-3 py-2',
- assistantRow: 'group/msg relative pb-3',
+ assistantRow: 'group/msg',
footer: 'flex-shrink-0 px-3 pb-3',
footerInner: '',
},
@@ -80,6 +81,7 @@ export function MothershipChat({
onSendQueuedMessage,
onEditQueuedMessage,
userId,
+ chatId,
onContextAdd,
editValue,
onEditValueConsumed,
@@ -147,20 +149,28 @@ export function MothershipChat({
}
const isLastMessage = index === messages.length - 1
+ const precedingUserMsg = [...messages]
+ .slice(0, index)
+ .reverse()
+ .find((m) => m.role === 'user')
return (
- {!isThisStreaming && (msg.content || msg.contentBlocks?.length) && (
-
-
-
- )}
+ {!isThisStreaming && (msg.content || msg.contentBlocks?.length) && (
+
+
+
+ )}
)
})}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx
index 6338c65cfa9..d2d72250261 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/mothership-view.tsx
@@ -115,7 +115,7 @@ export const MothershipView = memo(