@@ -7,6 +7,7 @@ import remarkGfm from 'remark-gfm'
77import { Button } from '@/components/ui/button'
88import { Tooltip , TooltipContent , TooltipTrigger } from '@/components/ui/tooltip'
99import { InlineToolCall } from '@/lib/copilot/tools/inline-tool-call'
10+ import { usePreviewStore } from '@/stores/copilot/preview-store'
1011import { useCopilotStore } from '@/stores/copilot/store'
1112import type { CopilotMessage as CopilotMessageType } from '@/stores/copilot/types'
1213
@@ -214,8 +215,17 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
214215 messageCheckpoints : allMessageCheckpoints ,
215216 revertToCheckpoint,
216217 isRevertingCheckpoint,
218+ currentChat,
219+ messages,
220+ workflowId,
217221 } = useCopilotStore ( )
218222
223+ // Get preview store for accessing workflow YAML after rejection
224+ const { getPreviewByToolCall, getLatestPendingPreview } = usePreviewStore ( )
225+
226+ // Import COPILOT_TOOL_IDS - placing it here since it's needed in multiple functions
227+ const WORKFLOW_TOOL_NAMES = [ 'build_workflow' , 'edit_workflow' ]
228+
219229 // Get checkpoints for this message if it's a user message
220230 const messageCheckpoints = isUser ? allMessageCheckpoints [ message . id ] || [ ] : [ ]
221231 const hasCheckpoints = messageCheckpoints . length > 0
@@ -226,16 +236,187 @@ const CopilotMessage: FC<CopilotMessageProps> = memo(
226236 setShowCopySuccess ( true )
227237 }
228238
229- const handleUpvote = ( ) => {
239+ // Helper function to get the full assistant response content
240+ const getFullAssistantContent = ( message : CopilotMessageType ) => {
241+ // First try the direct content
242+ if ( message . content ?. trim ( ) ) {
243+ return message . content
244+ }
245+
246+ // If no direct content, build from content blocks
247+ if ( message . contentBlocks && message . contentBlocks . length > 0 ) {
248+ return message . contentBlocks
249+ . filter ( ( block ) => block . type === 'text' )
250+ . map ( ( block ) => block . content )
251+ . join ( '' )
252+ }
253+
254+ return message . content || ''
255+ }
256+
257+ // Helper function to find the last user query before this assistant message
258+ const getLastUserQuery = ( ) => {
259+ const messageIndex = messages . findIndex ( ( msg ) => msg . id === message . id )
260+ if ( messageIndex === - 1 ) return null
261+
262+ // Look backwards from this message to find the last user message
263+ for ( let i = messageIndex - 1 ; i >= 0 ; i -- ) {
264+ if ( messages [ i ] . role === 'user' ) {
265+ return messages [ i ] . content
266+ }
267+ }
268+ return null
269+ }
270+
271+ // Helper function to extract workflow YAML from workflow tool calls
272+ const getWorkflowYaml = ( ) => {
273+ // Step 1: Check both toolCalls array and contentBlocks for workflow tools
274+ const allToolCalls = [
275+ ...( message . toolCalls || [ ] ) ,
276+ ...( message . contentBlocks || [ ] )
277+ . filter ( ( block ) => block . type === 'tool_call' )
278+ . map ( ( block ) => ( block as any ) . toolCall ) ,
279+ ]
280+
281+ // Find workflow tools (build_workflow or edit_workflow)
282+ const workflowTools = allToolCalls . filter ( ( toolCall ) =>
283+ WORKFLOW_TOOL_NAMES . includes ( toolCall ?. name )
284+ )
285+
286+ // Extract YAML content from workflow tools in the current message
287+ for ( const toolCall of workflowTools ) {
288+ // Try various locations where YAML content might be stored
289+ const yamlContent =
290+ toolCall . result ?. yamlContent ||
291+ toolCall . result ?. data ?. yamlContent ||
292+ toolCall . input ?. yamlContent ||
293+ toolCall . input ?. data ?. yamlContent
294+
295+ if ( yamlContent && typeof yamlContent === 'string' && yamlContent . trim ( ) ) {
296+ console . log ( 'Found workflow YAML in tool call:' , {
297+ toolCallId : toolCall . id ,
298+ toolName : toolCall . name ,
299+ yamlLength : yamlContent . length ,
300+ } )
301+ return yamlContent
302+ }
303+ }
304+
305+ // Step 2: Check copilot store's preview YAML (set when workflow tools execute)
306+ if ( currentChat ?. previewYaml ?. trim ( ) ) {
307+ console . log ( 'Found workflow YAML in copilot store preview:' , {
308+ yamlLength : currentChat . previewYaml . length ,
309+ } )
310+ return currentChat . previewYaml
311+ }
312+
313+ // Step 3: Check preview store for recent workflow tool calls from this message
314+ for ( const toolCall of workflowTools ) {
315+ if ( toolCall . id ) {
316+ const preview = getPreviewByToolCall ( toolCall . id )
317+ if ( preview ?. yamlContent ?. trim ( ) ) {
318+ console . log ( 'Found workflow YAML in preview store:' , {
319+ toolCallId : toolCall . id ,
320+ previewId : preview . id ,
321+ yamlLength : preview . yamlContent . length ,
322+ } )
323+ return preview . yamlContent
324+ }
325+ }
326+ }
327+
328+ // Step 4: If this message contains workflow tools but no YAML found yet,
329+ // try to get the latest pending preview for this workflow (fallback)
330+ if ( workflowTools . length > 0 && workflowId ) {
331+ const latestPreview = getLatestPendingPreview ( workflowId , currentChat ?. id )
332+ if ( latestPreview ?. yamlContent ?. trim ( ) ) {
333+ console . log ( 'Found workflow YAML in latest pending preview:' , {
334+ previewId : latestPreview . id ,
335+ yamlLength : latestPreview . yamlContent . length ,
336+ } )
337+ return latestPreview . yamlContent
338+ }
339+ }
340+
341+ return null
342+ }
343+
344+ // Function to submit feedback
345+ const submitFeedback = async ( isPositive : boolean ) => {
346+ // Ensure we have a chat ID
347+ if ( ! currentChat ?. id ) {
348+ console . error ( 'No current chat ID available for feedback submission' )
349+ return
350+ }
351+
352+ const userQuery = getLastUserQuery ( )
353+ if ( ! userQuery ) {
354+ console . error ( 'No user query found for feedback submission' )
355+ return
356+ }
357+
358+ const agentResponse = getFullAssistantContent ( message )
359+ if ( ! agentResponse . trim ( ) ) {
360+ console . error ( 'No agent response content available for feedback submission' )
361+ return
362+ }
363+
364+ // Get workflow YAML if this message contains workflow tools
365+ const workflowYaml = getWorkflowYaml ( )
366+
367+ try {
368+ const requestBody : any = {
369+ chatId : currentChat . id ,
370+ userQuery,
371+ agentResponse,
372+ isPositiveFeedback : isPositive ,
373+ }
374+
375+ // Only include workflowYaml if it exists
376+ if ( workflowYaml ) {
377+ requestBody . workflowYaml = workflowYaml
378+ console . log ( 'Including workflow YAML in feedback:' , {
379+ yamlLength : workflowYaml . length ,
380+ yamlPreview : workflowYaml . substring ( 0 , 100 ) ,
381+ } )
382+ }
383+
384+ const response = await fetch ( '/api/copilot/feedback' , {
385+ method : 'POST' ,
386+ headers : {
387+ 'Content-Type' : 'application/json' ,
388+ } ,
389+ body : JSON . stringify ( requestBody ) ,
390+ } )
391+
392+ if ( ! response . ok ) {
393+ throw new Error ( `Failed to submit feedback: ${ response . statusText } ` )
394+ }
395+
396+ const result = await response . json ( )
397+ console . log ( 'Feedback submitted successfully:' , result )
398+ } catch ( error ) {
399+ console . error ( 'Error submitting feedback:' , error )
400+ // Could show a toast or error message to user here
401+ }
402+ }
403+
404+ const handleUpvote = async ( ) => {
230405 // Reset downvote if it was active
231406 setShowDownvoteSuccess ( false )
232407 setShowUpvoteSuccess ( true )
408+
409+ // Submit positive feedback
410+ await submitFeedback ( true )
233411 }
234412
235- const handleDownvote = ( ) => {
413+ const handleDownvote = async ( ) => {
236414 // Reset upvote if it was active
237415 setShowUpvoteSuccess ( false )
238416 setShowDownvoteSuccess ( true )
417+
418+ // Submit negative feedback
419+ await submitFeedback ( false )
239420 }
240421
241422 const handleRevertToCheckpoint = ( ) => {
0 commit comments