Add fix ctas at message levelg

This commit is contained in:
akhisud3195 2025-07-13 16:45:14 +05:30
parent a3bf3046e3
commit 9d3eccbf87
7 changed files with 261 additions and 31 deletions

View file

@ -163,3 +163,12 @@ body {
.animate-float { .animate-float {
animation: float 5s ease-in-out infinite, pulse-mascot 4s infinite; animation: float 5s ease-in-out infinite, pulse-mascot 4s infinite;
} }
/* Feedback modal textarea overrides */
.feedback-modal textarea,
.feedback-modal textarea:focus {
font-size: 0.75rem !important; /* Tailwind's text-xs */
box-shadow: none !important;
outline: none !important;
border-color: #d1d5db !important; /* Tailwind's gray-300 */
}

View file

@ -26,6 +26,7 @@ export function App({
isInitialState = false, isInitialState = false,
onPanelClick, onPanelClick,
projectTools, projectTools,
triggerCopilotChat,
}: { }: {
hidden?: boolean; hidden?: boolean;
projectId: string; projectId: string;
@ -36,6 +37,7 @@ export function App({
isInitialState?: boolean; isInitialState?: boolean;
onPanelClick?: () => void; onPanelClick?: () => void;
projectTools: z.infer<typeof WorkflowTool>[]; projectTools: z.infer<typeof WorkflowTool>[];
triggerCopilotChat?: (message: string) => void;
}) { }) {
const [counter, setCounter] = useState<number>(0); const [counter, setCounter] = useState<number>(0);
const [testProfile, setTestProfile] = useState<WithStringId<z.infer<typeof TestProfile>> | null>(null); const [testProfile, setTestProfile] = useState<WithStringId<z.infer<typeof TestProfile>> | null>(null);
@ -187,6 +189,7 @@ export function App({
onCopyClick={(fn) => { getCopyContentRef.current = fn; }} onCopyClick={(fn) => { getCopyContentRef.current = fn; }}
showDebugMessages={showDebugMessages} showDebugMessages={showDebugMessages}
projectTools={projectTools} projectTools={projectTools}
triggerCopilotChat={triggerCopilotChat}
/> />
</div> </div>
</Panel> </Panel>

View file

@ -13,6 +13,8 @@ import { ProfileContextBox } from "./profile-context-box";
import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags"; import { USE_TESTING_FEATURE } from "@/app/lib/feature_flags";
import { BillingUpgradeModal } from "@/components/common/billing-upgrade-modal"; import { BillingUpgradeModal } from "@/components/common/billing-upgrade-modal";
import { ChevronDownIcon } from "@heroicons/react/24/outline"; import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { FeedbackModal } from "./feedback-modal";
import { FIX_WORKFLOW_PROMPT, FIX_WORKFLOW_PROMPT_WITH_FEEDBACK } from "../copilot-prompts";
export function Chat({ export function Chat({
chat, chat,
@ -29,6 +31,7 @@ export function Chat({
showDebugMessages = true, showDebugMessages = true,
showJsonMode = false, showJsonMode = false,
projectTools, projectTools,
triggerCopilotChat,
}: { }: {
chat: z.infer<typeof PlaygroundChat>; chat: z.infer<typeof PlaygroundChat>;
projectId: string; projectId: string;
@ -44,6 +47,7 @@ export function Chat({
showDebugMessages?: boolean; showDebugMessages?: boolean;
showJsonMode?: boolean; showJsonMode?: boolean;
projectTools: z.infer<typeof WorkflowTool>[]; projectTools: z.infer<typeof WorkflowTool>[];
triggerCopilotChat?: (message: string) => void;
}) { }) {
const [messages, setMessages] = useState<z.infer<typeof Message>[]>(chat.messages); const [messages, setMessages] = useState<z.infer<typeof Message>[]>(chat.messages);
const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false); const [loadingAssistantResponse, setLoadingAssistantResponse] = useState<boolean>(false);
@ -53,6 +57,9 @@ export function Chat({
const [lastAgenticResponse, setLastAgenticResponse] = useState<unknown | null>(null); const [lastAgenticResponse, setLastAgenticResponse] = useState<unknown | null>(null);
const [optimisticMessages, setOptimisticMessages] = useState<z.infer<typeof Message>[]>(chat.messages); const [optimisticMessages, setOptimisticMessages] = useState<z.infer<typeof Message>[]>(chat.messages);
const [isLastInteracted, setIsLastInteracted] = useState(false); const [isLastInteracted, setIsLastInteracted] = useState(false);
const [showFeedbackModal, setShowFeedbackModal] = useState(false);
const [pendingFixMessage, setPendingFixMessage] = useState<string | null>(null);
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
// --- Scroll/auto-scroll/unread bubble logic --- // --- Scroll/auto-scroll/unread bubble logic ---
const scrollContainerRef = useRef<HTMLDivElement>(null); const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -101,6 +108,37 @@ export function Chat({
setOptimisticMessages(messages); setOptimisticMessages(messages);
}, [messages]); }, [messages]);
// Handle fix functionality
const handleFix = useCallback((message: string) => {
setPendingFixMessage(message);
setShowFeedbackModal(true);
}, []);
const handleFeedbackSubmit = useCallback((feedback: string) => {
if (!pendingFixMessage) return;
// Create the copilot prompt
const prompt = feedback.trim()
? FIX_WORKFLOW_PROMPT_WITH_FEEDBACK
.replace('{chat_turn}', pendingFixMessage)
.replace('{feedback}', feedback)
: FIX_WORKFLOW_PROMPT
.replace('{chat_turn}', pendingFixMessage);
// Use the triggerCopilotChat function if available, otherwise fall back to localStorage
if (triggerCopilotChat) {
triggerCopilotChat(prompt);
// Show a subtle success indication
setShowSuccessMessage(true);
setTimeout(() => setShowSuccessMessage(false), 3000);
} else {
// Fallback for standalone playground
localStorage.setItem(`project_prompt_${projectId}`, prompt);
alert('Fix request submitted! Redirecting to workflow editor...');
window.location.href = `/projects/${projectId}/workflow`;
}
}, [pendingFixMessage, projectId, triggerCopilotChat]);
// collect published tool call results // collect published tool call results
const toolCallResults: Record<string, z.infer<typeof ToolMessage>> = {}; const toolCallResults: Record<string, z.infer<typeof ToolMessage>> = {};
optimisticMessages optimisticMessages
@ -302,6 +340,7 @@ export function Chat({
showSystemMessage={false} showSystemMessage={false}
showDebugMessages={showDebugMessages} showDebugMessages={showDebugMessages}
showJsonMode={showJsonMode} showJsonMode={showJsonMode}
onFix={handleFix}
/> />
{showUnreadBubble && ( {showUnreadBubble && (
<button <button
@ -322,6 +361,19 @@ export function Chat({
</div> </div>
<div className="sticky bottom-0 bg-white dark:bg-zinc-900 pt-4 pb-2"> <div className="sticky bottom-0 bg-white dark:bg-zinc-900 pt-4 pb-2">
{showSuccessMessage && (
<div className="mb-4 p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800
rounded-lg flex gap-2 justify-between items-center">
<p className="text-green-600 dark:text-green-400 text-sm">Skipper will suggest fixes for you now.</p>
<Button
size="sm"
color="success"
onPress={() => setShowSuccessMessage(false)}
>
Dismiss
</Button>
</div>
)}
{fetchResponseError && ( {fetchResponseError && (
<div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 <div className="mb-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800
rounded-lg flex gap-2 justify-between items-center"> rounded-lg flex gap-2 justify-between items-center">
@ -354,5 +406,11 @@ export function Chat({
onClose={() => setBillingError(null)} onClose={() => setBillingError(null)}
errorMessage={billingError || ''} errorMessage={billingError || ''}
/> />
<FeedbackModal
isOpen={showFeedbackModal}
onClose={() => setShowFeedbackModal(false)}
onSubmit={handleFeedbackSubmit}
title="Fix Assistant"
/>
</div>; </div>;
} }

View file

@ -0,0 +1,58 @@
'use client';
import { useState } from "react";
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, Textarea } from "@heroui/react";
interface FeedbackModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (feedback: string) => void;
title?: string;
}
export function FeedbackModal({ isOpen, onClose, onSubmit, title = "Provide Feedback" }: FeedbackModalProps) {
const [feedback, setFeedback] = useState("");
const handleSubmit = () => {
onSubmit(feedback);
setFeedback("");
onClose();
};
const handleCancel = () => {
setFeedback("");
onClose();
};
return (
<Modal isOpen={isOpen} onClose={handleCancel} size="md">
<ModalContent className="feedback-modal">
<ModalHeader className="flex flex-col gap-1">
{title}
</ModalHeader>
<p className="text-xs text-gray-600 dark:text-gray-400 px-6 pt-1 pb-0">
Tell Skipper what needs to be fixed
</p>
<ModalBody>
<div className="space-y-3">
<Textarea
placeholder="Describe the issue..."
value={feedback}
onChange={(e) => setFeedback(e.target.value)}
minRows={3}
maxRows={6}
className="w-full !text-xs focus:ring-0 focus:shadow-none focus:border-gray-300"
/>
</div>
</ModalBody>
<ModalFooter>
<Button variant="bordered" onPress={handleCancel}>
Cancel
</Button>
<Button color="primary" onPress={handleSubmit}>
Submit
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}

View file

@ -5,7 +5,7 @@ import z from "zod";
import { Workflow } from "@/app/lib/types/workflow_types"; import { Workflow } from "@/app/lib/types/workflow_types";
import { WorkflowTool } from "@/app/lib/types/workflow_types"; import { WorkflowTool } from "@/app/lib/types/workflow_types";
import MarkdownContent from "@/app/lib/components/markdown-content"; import MarkdownContent from "@/app/lib/components/markdown-content";
import { ChevronRightIcon, ChevronDownIcon, ChevronUpIcon, CodeIcon, CheckCircleIcon, FileTextIcon, EyeIcon, EyeOffIcon, WrapTextIcon, ArrowRightFromLineIcon, BracesIcon, TextIcon } from "lucide-react"; import { ChevronRightIcon, ChevronDownIcon, ChevronUpIcon, CodeIcon, CheckCircleIcon, FileTextIcon, EyeIcon, EyeOffIcon, WrapTextIcon, ArrowRightFromLineIcon, BracesIcon, TextIcon, FlagIcon } from "lucide-react";
import { TestProfile } from "@/app/lib/types/testing_types"; import { TestProfile } from "@/app/lib/types/testing_types";
import { ProfileContextBox } from "./profile-context-box"; import { ProfileContextBox } from "./profile-context-box";
import { Message, ToolMessage, AssistantMessageWithToolCalls } from "@/app/lib/types/types"; import { Message, ToolMessage, AssistantMessageWithToolCalls } from "@/app/lib/types/types";
@ -30,7 +30,7 @@ function UserMessage({ content }: { content: string }) {
); );
} }
function InternalAssistantMessage({ content, sender, latency, delta, showJsonMode = false }: { content: string, sender: string | null | undefined, latency: number, delta: number, showJsonMode?: boolean }) { function InternalAssistantMessage({ content, sender, latency, delta, showJsonMode = false, onFix, showDebugMessages, isFirstAssistant }: { content: string, sender: string | null | undefined, latency: number, delta: number, showJsonMode?: boolean, onFix?: (message: string) => void, showDebugMessages?: boolean, isFirstAssistant?: boolean }) {
const isJsonContent = useMemo(() => { const isJsonContent = useMemo(() => {
try { try {
JSON.parse(content); JSON.parse(content);
@ -84,10 +84,20 @@ function InternalAssistantMessage({ content, sender, latency, delta, showJsonMod
return ( return (
<div className="self-start flex flex-col gap-1 my-5"> <div className="self-start flex flex-col gap-1 my-5">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
{sender ?? 'Assistant'}
</div>
<div className="max-w-[85%] inline-block"> <div className="max-w-[85%] inline-block">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1 flex justify-between items-center mb-2">
<span>{sender ?? 'Assistant'}</span>
{showDebugMessages && onFix && !isFirstAssistant && (
<button
onClick={() => onFix(content)}
className="flex items-center gap-1 text-xs text-orange-700 dark:text-orange-400 hover:text-orange-800 dark:hover:text-orange-300 hover:underline"
title="Fix this response"
>
<FlagIcon size={12} />
Fix
</button>
)}
</div>
<div className="bg-gray-50 dark:bg-zinc-800 px-4 py-2.5 rounded-2xl rounded-bl-lg text-sm leading-relaxed text-gray-700 dark:text-gray-200 border-none shadow-sm animate-slideUpAndFade flex flex-col items-stretch"> <div className="bg-gray-50 dark:bg-zinc-800 px-4 py-2.5 rounded-2xl rounded-bl-lg text-sm leading-relaxed text-gray-700 dark:text-gray-200 border-none shadow-sm animate-slideUpAndFade flex flex-col items-stretch">
<div className="text-left mb-2"> <div className="text-left mb-2">
{isJsonContent && hasResponseKey && ( {isJsonContent && hasResponseKey && (
@ -134,13 +144,37 @@ function InternalAssistantMessage({ content, sender, latency, delta, showJsonMod
); );
} }
function AssistantMessage({ content, sender, latency }: { content: string, sender: string | null | undefined, latency: number }) { function AssistantMessage({
content,
sender,
latency,
onFix,
showDebugMessages,
isFirstAssistant
}: {
content: string,
sender: string | null | undefined,
latency: number,
onFix?: (message: string) => void,
showDebugMessages?: boolean,
isFirstAssistant?: boolean
}) {
return ( return (
<div className="self-start flex flex-col gap-1 my-5"> <div className="self-start flex flex-col gap-1 my-5">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1">
{sender ?? 'Assistant'}
</div>
<div className="max-w-[85%] inline-block"> <div className="max-w-[85%] inline-block">
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1 flex justify-between items-center mb-2">
<span>{sender ?? 'Assistant'}</span>
{showDebugMessages && onFix && !isFirstAssistant && (
<button
onClick={() => onFix(content)}
className="flex items-center gap-1 text-xs text-orange-700 dark:text-orange-400 hover:text-orange-800 dark:hover:text-orange-300 hover:underline"
title="Fix this response"
>
<FlagIcon size={12} />
Fix
</button>
)}
</div>
<div className="bg-purple-50 dark:bg-purple-900/30 px-4 py-2.5 <div className="bg-purple-50 dark:bg-purple-900/30 px-4 py-2.5
rounded-2xl rounded-bl-lg text-sm leading-relaxed rounded-2xl rounded-bl-lg text-sm leading-relaxed
text-gray-800 dark:text-purple-100 text-gray-800 dark:text-purple-100
@ -182,7 +216,10 @@ function ToolCalls({
workflow, workflow,
testProfile = null, testProfile = null,
systemMessage, systemMessage,
delta delta,
onFix,
showDebugMessages,
isFirstAssistant
}: { }: {
toolCalls: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls']; toolCalls: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'];
results: Record<string, z.infer<typeof ToolMessage>>; results: Record<string, z.infer<typeof ToolMessage>>;
@ -193,9 +230,12 @@ function ToolCalls({
testProfile: z.infer<typeof TestProfile> | null; testProfile: z.infer<typeof TestProfile> | null;
systemMessage: string | undefined; systemMessage: string | undefined;
delta: number; delta: number;
onFix?: (message: string) => void;
showDebugMessages?: boolean;
isFirstAssistant?: boolean;
}) { }) {
return <div className="flex flex-col gap-4"> return <div className="flex flex-col gap-4">
{toolCalls.map(toolCall => { {toolCalls.map((toolCall, idx) => {
return <ToolCall return <ToolCall
key={toolCall.id} key={toolCall.id}
toolCall={toolCall} toolCall={toolCall}
@ -203,6 +243,9 @@ function ToolCalls({
sender={sender} sender={sender}
workflow={workflow} workflow={workflow}
delta={delta} delta={delta}
onFix={onFix}
showDebugMessages={showDebugMessages}
isFirstAssistant={isFirstAssistant && idx === 0}
/> />
})} })}
</div>; </div>;
@ -213,13 +256,19 @@ function ToolCall({
result, result,
sender, sender,
workflow, workflow,
delta delta,
onFix,
showDebugMessages,
isFirstAssistant
}: { }: {
toolCall: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'][number]; toolCall: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'][number];
result: z.infer<typeof ToolMessage> | undefined; result: z.infer<typeof ToolMessage> | undefined;
sender: string | null | undefined; sender: string | null | undefined;
workflow: z.infer<typeof Workflow>; workflow: z.infer<typeof Workflow>;
delta: number; delta: number;
onFix?: (message: string) => void;
showDebugMessages?: boolean;
isFirstAssistant?: boolean;
}) { }) {
let matchingWorkflowTool: z.infer<typeof WorkflowTool> | undefined; let matchingWorkflowTool: z.infer<typeof WorkflowTool> | undefined;
for (const tool of workflow.tools) { for (const tool of workflow.tools) {
@ -242,6 +291,8 @@ function ToolCall({
sender={sender ?? ''} sender={sender ?? ''}
workflow={workflow} workflow={workflow}
delta={delta} delta={delta}
onFix={onFix}
showDebugMessages={showDebugMessages}
/>; />;
} }
@ -280,13 +331,17 @@ function ClientToolCall({
result: availableResult, result: availableResult,
sender, sender,
workflow, workflow,
delta delta,
onFix,
showDebugMessages
}: { }: {
toolCall: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'][number]; toolCall: z.infer<typeof AssistantMessageWithToolCalls>['toolCalls'][number];
result: z.infer<typeof ToolMessage> | undefined; result: z.infer<typeof ToolMessage> | undefined;
sender: string | null | undefined; sender: string | null | undefined;
workflow: z.infer<typeof Workflow>; workflow: z.infer<typeof Workflow>;
delta: number; delta: number;
onFix?: (message: string) => void;
showDebugMessages?: boolean;
}) { }) {
const [wrapText, setWrapText] = useState(true); const [wrapText, setWrapText] = useState(true);
const [paramsExpanded, setParamsExpanded] = useState(false); const [paramsExpanded, setParamsExpanded] = useState(false);
@ -299,8 +354,18 @@ function ClientToolCall({
return ( return (
<div className="self-start flex flex-col gap-1 my-5"> <div className="self-start flex flex-col gap-1 my-5">
{sender && ( {sender && (
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1"> <div className="text-gray-500 dark:text-gray-400 text-xs pl-1 flex justify-between items-center">
{sender} <span>{sender}</span>
{showDebugMessages && onFix && (
<button
onClick={() => onFix(`Tool call: ${toolCall.function.name}`)}
className="flex items-center gap-1 text-xs text-orange-700 dark:text-orange-400 hover:text-orange-800 dark:hover:text-orange-300 hover:underline"
title="Fix this tool call"
>
<FlagIcon size={12} />
Fix
</button>
)}
</div> </div>
)} )}
<div className="min-w-[85%]"> <div className="min-w-[85%]">
@ -309,6 +374,7 @@ function ClientToolCall({
bg-gray-50 dark:bg-gray-800 shadow-sm dark:shadow-gray-950/20"> bg-gray-50 dark:bg-gray-800 shadow-sm dark:shadow-gray-950/20">
<div className="flex flex-col gap-1 min-w-0"> <div className="flex flex-col gap-1 min-w-0">
<div className="shrink-0 flex gap-2 items-center flex-nowrap"> <div className="shrink-0 flex gap-2 items-center flex-nowrap">
<div className="flex items-center gap-2 min-w-0 flex-nowrap">
{!availableResult && <Spinner size="sm" />} {!availableResult && <Spinner size="sm" />}
{availableResult && <CheckCircleIcon size={16} className="text-green-500" />} {availableResult && <CheckCircleIcon size={16} className="text-green-500" />}
<div className="flex items-center font-medium text-xs gap-2 min-w-0 flex-nowrap"> <div className="flex items-center font-medium text-xs gap-2 min-w-0 flex-nowrap">
@ -318,6 +384,7 @@ function ClientToolCall({
</span> </span>
</div> </div>
</div> </div>
</div>
{hasExpandedContent && ( {hasExpandedContent && (
<div className="flex justify-start mt-2"> <div className="flex justify-start mt-2">
<button <button
@ -362,8 +429,18 @@ function ClientToolCall({
return ( return (
<div className="self-start flex flex-col gap-1 my-5"> <div className="self-start flex flex-col gap-1 my-5">
{sender && ( {sender && (
<div className="text-gray-500 dark:text-gray-400 text-xs pl-1"> <div className="text-gray-500 dark:text-gray-400 text-xs pl-1 flex justify-between items-center">
{sender} <span>{sender}</span>
{showDebugMessages && onFix && (
<button
onClick={() => onFix(`Tool call: ${toolCall.function.name}`)}
className="flex items-center gap-1 text-xs text-orange-700 dark:text-orange-400 hover:text-orange-800 dark:hover:text-orange-300 hover:underline"
title="Fix this tool call"
>
<FlagIcon size={12} />
Fix
</button>
)}
</div> </div>
)} )}
<div className="w-full"> <div className="w-full">
@ -372,15 +449,17 @@ function ClientToolCall({
bg-gray-50 dark:bg-gray-800 shadow-sm dark:shadow-gray-950/20 w-full"> bg-gray-50 dark:bg-gray-800 shadow-sm dark:shadow-gray-950/20 w-full">
<div className="flex flex-col gap-1 w-full"> <div className="flex flex-col gap-1 w-full">
<div className="shrink-0 flex gap-2 items-center w-full flex-nowrap"> <div className="shrink-0 flex gap-2 items-center w-full flex-nowrap">
<div className="flex items-center gap-2 min-w-0 flex-nowrap">
{!availableResult && <Spinner size="sm" />} {!availableResult && <Spinner size="sm" />}
{availableResult && <CheckCircleIcon size={16} className="text-green-500" />} {availableResult && <CheckCircleIcon size={16} className="text-green-500" />}
<div className="flex items-center font-medium text-xs gap-2 w-full min-w-0 flex-nowrap"> <div className="flex items-center font-medium text-xs gap-2 min-w-0 flex-nowrap">
<span>Invoked Tool:</span> <span>Invoked Tool:</span>
<span className="px-2 py-0.5 rounded-full bg-purple-50 text-purple-800 dark:bg-purple-900/30 dark:text-purple-100 text-xs align-middle truncate min-w-0 max-w-full"> <span className="px-2 py-0.5 rounded-full bg-purple-50 text-purple-800 dark:bg-purple-900/30 dark:text-purple-100 text-xs align-middle truncate min-w-0 max-w-full">
{toolCall.function.name} {toolCall.function.name}
</span> </span>
</div> </div>
</div> </div>
</div>
{hasExpandedContent && ( {hasExpandedContent && (
<div className="flex justify-start mt-2"> <div className="flex justify-start mt-2">
<button <button
@ -503,6 +582,7 @@ export function Messages({
showSystemMessage, showSystemMessage,
showDebugMessages = true, showDebugMessages = true,
showJsonMode = false, showJsonMode = false,
onFix,
}: { }: {
projectId: string; projectId: string;
messages: z.infer<typeof Message>[]; messages: z.infer<typeof Message>[];
@ -515,6 +595,7 @@ export function Messages({
showSystemMessage: boolean; showSystemMessage: boolean;
showDebugMessages?: boolean; showDebugMessages?: boolean;
showJsonMode?: boolean; showJsonMode?: boolean;
onFix?: (message: string) => void;
}) { }) {
// Remove scroll/auto-scroll state and logic // Remove scroll/auto-scroll state and logic
// const scrollContainerRef = useRef<HTMLDivElement>(null); // const scrollContainerRef = useRef<HTMLDivElement>(null);
@ -522,7 +603,11 @@ export function Messages({
// const [showUnreadBubble, setShowUnreadBubble] = useState(false); // const [showUnreadBubble, setShowUnreadBubble] = useState(false);
// Remove handleScroll and useEffect for scroll // Remove handleScroll and useEffect for scroll
// Find the index of the first assistant message
const firstAssistantIdx = messages.findIndex(m => m.role === 'assistant');
const renderMessage = (message: z.infer<typeof Message>, index: number) => { const renderMessage = (message: z.infer<typeof Message>, index: number) => {
const isFirstAssistant = message.role === 'assistant' && index === firstAssistantIdx;
if (message.role === 'assistant') { if (message.role === 'assistant') {
// TODO: add latency support // TODO: add latency support
// let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp; // let latency = new Date(message.createdAt).getTime() - lastUserMessageTimestamp;
@ -548,6 +633,9 @@ export function Messages({
testProfile={testProfile} testProfile={testProfile}
systemMessage={systemMessage} systemMessage={systemMessage}
delta={latency} delta={latency}
onFix={onFix}
showDebugMessages={showDebugMessages}
isFirstAssistant={isFirstAssistant}
/> />
); );
} }
@ -565,6 +653,9 @@ export function Messages({
latency={latency} latency={latency}
delta={latency} delta={latency}
showJsonMode={showJsonMode} showJsonMode={showJsonMode}
onFix={onFix}
showDebugMessages={showDebugMessages}
isFirstAssistant={isFirstAssistant}
/> />
); );
} }
@ -575,6 +666,9 @@ export function Messages({
content={message.content ?? ''} content={message.content ?? ''}
sender={message.agentName ?? ''} sender={message.agentName ?? ''}
latency={latency} latency={latency}
onFix={onFix}
showDebugMessages={showDebugMessages}
isFirstAssistant={isFirstAssistant}
/> />
); );
} }

View file

@ -0,0 +1,7 @@
export const FIX_WORKFLOW_PROMPT = `There is an issue with this turn of chat: "{chat_turn}"
Fix the issue by updating necessary agents and tools.`;
export const FIX_WORKFLOW_PROMPT_WITH_FEEDBACK = `${FIX_WORKFLOW_PROMPT}
Here are more details: {feedback}`;

View file

@ -1028,6 +1028,7 @@ export function WorkflowEditor({
isInitialState={isInitialState} isInitialState={isInitialState}
onPanelClick={handlePlaygroundClick} onPanelClick={handlePlaygroundClick}
projectTools={projectTools} projectTools={projectTools}
triggerCopilotChat={triggerCopilotChat}
/> />
{state.present.selection?.type === "agent" && <AgentConfig {state.present.selection?.type === "agent" && <AgentConfig
key={`agent-${state.present.workflow.agents.findIndex(agent => agent.name === state.present.selection!.name)}`} key={`agent-${state.present.workflow.agents.findIndex(agent => agent.name === state.present.selection!.name)}`}