From e735795e7fdc66bc2b8cdbce89eab5261814eacc Mon Sep 17 00:00:00 2001 From: Akhilesh Sudhakar <55130408+akhisud3195@users.noreply.github.com> Date: Tue, 15 Jul 2025 19:54:16 +0530 Subject: [PATCH] Copilot context fixes (#174) * Update copilot context dynamically * Fix agent context logic --- .../app/projects/[projectId]/copilot/app.tsx | 55 +++++++++++++++---- .../copilot/components/messages.tsx | 16 +++--- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx index 20755995..8461787d 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx @@ -54,12 +54,18 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message const cancelRef = useRef(null); const [statusBar, setStatusBar] = useState(null); + // Always use effectiveContext for the user's current selection + const effectiveContext = discardContext ? null : chatContext; + + // Context locking state + const [lockedContext, setLockedContext] = useState(effectiveContext); + const [pendingContext, setPendingContext] = useState(effectiveContext); + const [isStreaming, setIsStreaming] = useState(false); + // Keep workflow ref up to date workflowRef.current = workflow; - // Get the effective context based on user preference - const effectiveContext = discardContext ? null : chatContext; - + // Copilot streaming state const { streamingResponse, loading: loadingResponse, @@ -102,13 +108,16 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message setDiscardContext(false); }, [chatContext]); - function handleUserMessage(prompt: string) { + // Memoized handleUserMessage for useImperativeHandle and hooks + const handleUserMessage = useCallback((prompt: string) => { + // Before starting streaming, lock the context to the current pendingContext + setLockedContext(pendingContext); setMessages(currentMessages => [...currentMessages, { role: 'user', content: prompt }]); setIsLastInteracted(true); - } + }, [setMessages, setIsLastInteracted, pendingContext, setLockedContext]); // Effect for getting copilot response useEffect(() => { @@ -134,6 +143,34 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message return () => currentCancel(); }, [messages, responseError]); + // --- CONTEXT LOCKING LOGIC --- + // Always update pendingContext to the latest effectiveContext + useEffect(() => { + setPendingContext(effectiveContext); + }, [effectiveContext]); + + // Lock/unlock context based on streaming state + useEffect(() => { + if (loadingResponse) { + // Streaming started: lock context to the value at the start + setIsStreaming(true); + setLockedContext((prev: any) => prev ?? pendingContext); // lock to previous if already set, else to pending + } else { + // Streaming ended: update lockedContext to the last pendingContext + setIsStreaming(false); + setLockedContext(pendingContext); + } + }, [loadingResponse, pendingContext]); + + // After streaming ends, update lockedContext live as effectiveContext changes + useEffect(() => { + if (!isStreaming) { + setLockedContext(effectiveContext); + } + // If streaming, do not update lockedContext + }, [effectiveContext, isStreaming]); + // --- END CONTEXT LOCKING LOGIC --- + const handleCopyChat = useCallback(() => { if (onCopyJson) { onCopyJson({ @@ -145,7 +182,7 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message useImperativeHandle(ref, () => ({ handleCopyChat, handleUserMessage - }), [handleCopyChat]); + }), [handleCopyChat, handleUserMessage]); return ( @@ -159,8 +196,7 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message dispatch={dispatch} onStatusBarChange={status => setStatusBar({ ...status, - context: effectiveContext, - onCloseContext: () => setDiscardContext(true) + context: lockedContext, })} /> @@ -187,7 +223,6 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message )} - {/* Remove the separate context label here */} void; handleUserMessage: (message shouldAutoFocus={isLastInteracted} onFocus={() => setIsLastInteracted(true)} onCancel={cancel} - statusBar={statusBar || { context: effectiveContext, onCloseContext: () => setDiscardContext(true) }} + statusBar={statusBar || { context: lockedContext }} /> diff --git a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx index c82bb302..68520471 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx @@ -1,6 +1,6 @@ 'use client'; import { Spinner } from "@heroui/react"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, useCallback } from "react"; import { z } from "zod"; import { Workflow} from "@/app/lib/types/workflow_types"; import MarkdownContent from "@/app/lib/components/markdown-content"; @@ -218,8 +218,8 @@ function AssistantMessage({ const pendingCount = Math.max(0, totalActions - appliedCount); const allApplied = pendingCount === 0 && totalActions > 0; - // Apply a single action - const applyAction = (action: any, actionIndex: number) => { + // Memoized applyAction for useCallback dependencies + const applyAction = useCallback((action: any, actionIndex: number) => { // Only apply, do not update appliedActions here if (action.action === 'create_new') { switch (action.config_type) { @@ -276,10 +276,10 @@ function AssistantMessage({ break; } } - }; + }, [dispatch]); - // Apply All: batch apply all unapplied actions and update state once - const handleApplyAll = () => { + // Memoized handleApplyAll for useEffect dependencies + const handleApplyAll = useCallback(() => { // Find all unapplied action indices const unapplied = cardBlocks .filter(({ part, actionIndex }) => part.type === 'action' && !appliedActions.has(actionIndex)) @@ -296,7 +296,7 @@ function AssistantMessage({ unapplied.forEach(({ actionIndex }) => next.add(actionIndex)); return next; }); - }; + }, [cardBlocks, appliedActions, setAppliedActions, applyAction]); // Manual single apply (from card) const handleSingleApply = (action: any, actionIndex: number) => { @@ -426,7 +426,7 @@ function AssistantMessage({ handleApplyAll, }); } - }, [allCardsLoaded, allApplied, appliedCount, pendingCount, streamingLine, completedSummary, hasPanelWarning]); + }, [allCardsLoaded, allApplied, appliedCount, pendingCount, streamingLine, completedSummary, hasPanelWarning, handleApplyAll, onStatusBarChange]); // Render all cards inline, not in a panel return (