mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-05-03 12:22:38 +02:00
Copilot context fixes (#174)
* Update copilot context dynamically * Fix agent context logic
This commit is contained in:
parent
44a951c5d2
commit
e735795e7f
2 changed files with 53 additions and 18 deletions
|
|
@ -54,12 +54,18 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
const cancelRef = useRef<any>(null);
|
const cancelRef = useRef<any>(null);
|
||||||
const [statusBar, setStatusBar] = useState<any>(null);
|
const [statusBar, setStatusBar] = useState<any>(null);
|
||||||
|
|
||||||
|
// Always use effectiveContext for the user's current selection
|
||||||
|
const effectiveContext = discardContext ? null : chatContext;
|
||||||
|
|
||||||
|
// Context locking state
|
||||||
|
const [lockedContext, setLockedContext] = useState<any>(effectiveContext);
|
||||||
|
const [pendingContext, setPendingContext] = useState<any>(effectiveContext);
|
||||||
|
const [isStreaming, setIsStreaming] = useState(false);
|
||||||
|
|
||||||
// Keep workflow ref up to date
|
// Keep workflow ref up to date
|
||||||
workflowRef.current = workflow;
|
workflowRef.current = workflow;
|
||||||
|
|
||||||
// Get the effective context based on user preference
|
// Copilot streaming state
|
||||||
const effectiveContext = discardContext ? null : chatContext;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
streamingResponse,
|
streamingResponse,
|
||||||
loading: loadingResponse,
|
loading: loadingResponse,
|
||||||
|
|
@ -102,13 +108,16 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
setDiscardContext(false);
|
setDiscardContext(false);
|
||||||
}, [chatContext]);
|
}, [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, {
|
setMessages(currentMessages => [...currentMessages, {
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content: prompt
|
content: prompt
|
||||||
}]);
|
}]);
|
||||||
setIsLastInteracted(true);
|
setIsLastInteracted(true);
|
||||||
}
|
}, [setMessages, setIsLastInteracted, pendingContext, setLockedContext]);
|
||||||
|
|
||||||
// Effect for getting copilot response
|
// Effect for getting copilot response
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -134,6 +143,34 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
return () => currentCancel();
|
return () => currentCancel();
|
||||||
}, [messages, responseError]);
|
}, [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(() => {
|
const handleCopyChat = useCallback(() => {
|
||||||
if (onCopyJson) {
|
if (onCopyJson) {
|
||||||
onCopyJson({
|
onCopyJson({
|
||||||
|
|
@ -145,7 +182,7 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleCopyChat,
|
handleCopyChat,
|
||||||
handleUserMessage
|
handleUserMessage
|
||||||
}), [handleCopyChat]);
|
}), [handleCopyChat, handleUserMessage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CopilotContext.Provider value={{ workflow: workflowRef.current, dispatch }}>
|
<CopilotContext.Provider value={{ workflow: workflowRef.current, dispatch }}>
|
||||||
|
|
@ -159,8 +196,7 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
onStatusBarChange={status => setStatusBar({
|
onStatusBarChange={status => setStatusBar({
|
||||||
...status,
|
...status,
|
||||||
context: effectiveContext,
|
context: lockedContext,
|
||||||
onCloseContext: () => setDiscardContext(true)
|
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -187,7 +223,6 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Remove the separate context label here */}
|
|
||||||
<ComposeBoxCopilot
|
<ComposeBoxCopilot
|
||||||
handleUserMessage={handleUserMessage}
|
handleUserMessage={handleUserMessage}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
|
|
@ -196,7 +231,7 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message
|
||||||
shouldAutoFocus={isLastInteracted}
|
shouldAutoFocus={isLastInteracted}
|
||||||
onFocus={() => setIsLastInteracted(true)}
|
onFocus={() => setIsLastInteracted(true)}
|
||||||
onCancel={cancel}
|
onCancel={cancel}
|
||||||
statusBar={statusBar || { context: effectiveContext, onCloseContext: () => setDiscardContext(true) }}
|
statusBar={statusBar || { context: lockedContext }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
import { Spinner } from "@heroui/react";
|
import { Spinner } from "@heroui/react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState, useCallback } from "react";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Workflow} from "@/app/lib/types/workflow_types";
|
import { Workflow} from "@/app/lib/types/workflow_types";
|
||||||
import MarkdownContent from "@/app/lib/components/markdown-content";
|
import MarkdownContent from "@/app/lib/components/markdown-content";
|
||||||
|
|
@ -218,8 +218,8 @@ function AssistantMessage({
|
||||||
const pendingCount = Math.max(0, totalActions - appliedCount);
|
const pendingCount = Math.max(0, totalActions - appliedCount);
|
||||||
const allApplied = pendingCount === 0 && totalActions > 0;
|
const allApplied = pendingCount === 0 && totalActions > 0;
|
||||||
|
|
||||||
// Apply a single action
|
// Memoized applyAction for useCallback dependencies
|
||||||
const applyAction = (action: any, actionIndex: number) => {
|
const applyAction = useCallback((action: any, actionIndex: number) => {
|
||||||
// Only apply, do not update appliedActions here
|
// Only apply, do not update appliedActions here
|
||||||
if (action.action === 'create_new') {
|
if (action.action === 'create_new') {
|
||||||
switch (action.config_type) {
|
switch (action.config_type) {
|
||||||
|
|
@ -276,10 +276,10 @@ function AssistantMessage({
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
// Apply All: batch apply all unapplied actions and update state once
|
// Memoized handleApplyAll for useEffect dependencies
|
||||||
const handleApplyAll = () => {
|
const handleApplyAll = useCallback(() => {
|
||||||
// Find all unapplied action indices
|
// Find all unapplied action indices
|
||||||
const unapplied = cardBlocks
|
const unapplied = cardBlocks
|
||||||
.filter(({ part, actionIndex }) => part.type === 'action' && !appliedActions.has(actionIndex))
|
.filter(({ part, actionIndex }) => part.type === 'action' && !appliedActions.has(actionIndex))
|
||||||
|
|
@ -296,7 +296,7 @@ function AssistantMessage({
|
||||||
unapplied.forEach(({ actionIndex }) => next.add(actionIndex));
|
unapplied.forEach(({ actionIndex }) => next.add(actionIndex));
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
};
|
}, [cardBlocks, appliedActions, setAppliedActions, applyAction]);
|
||||||
|
|
||||||
// Manual single apply (from card)
|
// Manual single apply (from card)
|
||||||
const handleSingleApply = (action: any, actionIndex: number) => {
|
const handleSingleApply = (action: any, actionIndex: number) => {
|
||||||
|
|
@ -426,7 +426,7 @@ function AssistantMessage({
|
||||||
handleApplyAll,
|
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
|
// Render all cards inline, not in a panel
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue