diff --git a/backend/backend/application/context/llm_context.py b/backend/backend/application/context/llm_context.py index 0e36aef..b9c563e 100644 --- a/backend/backend/application/context/llm_context.py +++ b/backend/backend/application/context/llm_context.py @@ -256,7 +256,7 @@ def process_prompt(self, sid: str, channel_id: str, chat_id: str, chat_message_i check_oss_api_key_configured() self.persist_prompt_status(chat_message_id=chat_message_id, status=ChatMessageStatus.RUNNING) - content = "Preparing database information..." + content = "Gathering context..." self.send_and_persist_thought_chain( sid=sid, channel_id=channel_id, diff --git a/frontend/src/ide/chat-ai/Conversation.jsx b/frontend/src/ide/chat-ai/Conversation.jsx index 0ec0670..62921cd 100644 --- a/frontend/src/ide/chat-ai/Conversation.jsx +++ b/frontend/src/ide/chat-ai/Conversation.jsx @@ -3,7 +3,6 @@ import PropTypes from "prop-types"; import { Alert, Divider, Space } from "antd"; import { PromptInfo } from "./PromptInfo"; -import { EnhancedPromptInfo } from "./EnhancedPromptInfo"; import { MarkdownView } from "./MarkdownView"; import { UserPrompt } from "./UserPrompt"; import { useUserStore } from "../../store/user-store"; @@ -16,7 +15,6 @@ const Conversation = memo(function Conversation({ savePrompt, message, chatIntents, - llmModels, isPromptRunning, isLastConversation, selectedChatId, @@ -67,36 +65,11 @@ const Conversation = memo(function Conversation({ return null; }, [detectedAction]); - // Derive display names from IDs - const llmModelDisplayName = useMemo(() => { - return llmModels.find((m) => m?.model === message?.llm_model_architect) - ?.display_name; - }, [llmModels, message?.llm_model_architect]); - - const coderLlmModelDisplayName = useMemo(() => { - return llmModels.find((m) => m?.model === message?.llm_model_developer) - ?.display_name; - }, [llmModels, message?.llm_model_developer]); - - const chatIntentName = useMemo(() => { - return chatIntents.find( - (intent) => intent?.chat_intent_id === message?.chat_intent - )?.name; - }, [chatIntents, message?.chat_intent]); - - // Check if we have any "thought chain" data - const isThoughtChainReceived = useMemo(() => { - return !!(message?.response?.length && message.response[0]?.length); - }, [message?.response]); - // Check if transformation is in progress const isTransformRunning = useMemo(() => { return message?.transformation_status === "RUNNING"; }, [message?.transformation_status]); - // Feature flag: Use enhanced UI components (can be toggled) - const useEnhancedUI = true; // Set to false to use legacy components - /** -------------------------------------------------------------------- * Derive the intent once; re-computes only when its deps change. * ------------------------------------------------------------------- */ @@ -108,6 +81,15 @@ const Conversation = memo(function Conversation({ [chatIntents, message?.chat_intent] ); + // Memoize errorDetails to prevent unnecessary re-renders of PromptInfo + const errorDetailsMemo = useMemo( + () => ({ + transformation_error_message: message?.transformation_error_message, + prompt_error_message: message?.prompt_error_message, + }), + [message?.transformation_error_message, message?.prompt_error_message] + ); + return (
{/* PROMPT */} @@ -141,33 +123,14 @@ const Conversation = memo(function Conversation({
)} - {/* Thought Chain - Enhanced or Legacy */} + {/* Thought Chain */}
- {useEnhancedUI ? ( - - ) : ( - - )} +
@@ -217,7 +180,6 @@ Conversation.propTypes = { savePrompt: PropTypes.func.isRequired, message: PropTypes.object.isRequired, chatIntents: PropTypes.array.isRequired, - llmModels: PropTypes.array.isRequired, isPromptRunning: PropTypes.bool.isRequired, isLastConversation: PropTypes.bool.isRequired, selectedChatId: PropTypes.string.isRequired, diff --git a/frontend/src/ide/chat-ai/EnhancedPromptInfo.jsx b/frontend/src/ide/chat-ai/EnhancedPromptInfo.jsx deleted file mode 100644 index 1d0e8e3..0000000 --- a/frontend/src/ide/chat-ai/EnhancedPromptInfo.jsx +++ /dev/null @@ -1,487 +0,0 @@ -import { memo, useState } from "react"; -import PropTypes from "prop-types"; -import { Typography, Popover, Button, notification } from "antd"; -import { - DatabaseOutlined, - BulbOutlined, - CodeOutlined, - SafetyCertificateOutlined, - PlayCircleOutlined, - InfoCircleOutlined, - CheckCircleFilled, - LoadingOutlined, - SyncOutlined, - CopyOutlined, -} from "@ant-design/icons"; - -import "./ThoughtChainEnhancements.css"; - -const { Text } = Typography; - -/** - * EnhancedPromptInfo - Modern thought chain with shimmer effects and info icons - * Detects message patterns and applies appropriate styling - */ -const EnhancedPromptInfo = memo(function EnhancedPromptInfo({ - isThoughtChainReceived, - shouldStream, - thoughtChain = [], - llmModel, - coderLlmModel, - chatIntent, - errorState, - errorDetails = {}, -}) { - const [openPopoverId, setOpenPopoverId] = useState(null); - - // Copy error details to clipboard - const copyErrorToClipboard = ( - message, - attemptNumber, - errorStage, - parsedErrorDetails = null - ) => { - const timestamp = new Date().toLocaleString(); - const { transformation_error_message, prompt_error_message } = errorDetails; - - // Get the most relevant error message from multiple sources - let detailedError = null; - if (parsedErrorDetails) { - // Use error from structured message (visitran-ai agent errors) - detailedError = - typeof parsedErrorDetails === "string" - ? parsedErrorDetails - : JSON.stringify(parsedErrorDetails, null, 2); - } else if (transformation_error_message) { - // Use transformation error from backend - detailedError = - typeof transformation_error_message === "string" - ? transformation_error_message - : JSON.stringify(transformation_error_message, null, 2); - } else if (prompt_error_message) { - // Use prompt error from backend - detailedError = - typeof prompt_error_message === "string" - ? prompt_error_message - : JSON.stringify(prompt_error_message, null, 2); - } - - const errorText = ` -Attempt ${attemptNumber || 1} - Error Details -======================================== -Timestamp: ${timestamp} -Error Stage: ${errorStage || "Unknown"} -Message: ${message} -${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} - `.trim(); - - navigator.clipboard - .writeText(errorText) - .then(() => { - notification.success({ - message: "Copied to clipboard", - description: "Error details have been copied successfully", - placement: "topRight", - duration: 2, - }); - }) - .catch((err) => { - notification.error({ - message: "Copy failed", - description: "Could not copy to clipboard", - placement: "topRight", - duration: 2, - }); - }); - }; - - // Detect message type and extract metadata - const parseMessage = (msg) => { - // Handle structured messages from visitran-ai (agent disapprovals) - let message; - let errorDetails = null; - let attemptNumber = null; - let source = null; - - let errorSummary = null; - let retryMessage = null; - - if (typeof msg === "object" && msg !== null && msg.display) { - // Structured message with error details - message = msg.display; - errorDetails = msg.error_details; - errorSummary = msg.error_summary; // Concise summary - retryMessage = msg.retry_message; // Dynamic retry message - attemptNumber = msg.attempt; - source = msg.source; - } else { - // Plain string message - message = String(msg); - } - - // Detect stage based on keywords - let stage = "general"; - let icon = null; - - if ( - message.toLowerCase().includes("preparing") || - message.toLowerCase().includes("database") - ) { - stage = "preparation"; - icon = ; - } else if (message.toLowerCase().includes("planning")) { - stage = "planning"; - icon = ; - } else if ( - message.toLowerCase().includes("creating") || - message.toLowerCase().includes("generating") - ) { - stage = "generation"; - icon = ; - } else if (message.toLowerCase().includes("validat")) { - stage = "validation"; - icon = ; - } else if ( - message.toLowerCase().includes("execut") || - message.toLowerCase().includes("running") - ) { - stage = "execution"; - icon = ; - } - - // Detect message type - const isError = - message.includes("failed") || - message.includes("error") || - message.match(/❌|⚠️/) || - message.toLowerCase().includes("disapproved"); - const isSuccess = - message.includes("✓") || - message.includes("successfully") || - message.includes("created"); - const isRetry = - message.includes("retry") || - message.includes("retrying") || - message.match(/🔄|attempt/i); - const isProgress = message.match(/\(\d+\s+of\s+\d+\)/); - - // Extract attempt number if not already from structured message - if (!attemptNumber) { - const attemptMatch = message.match(/attempt\s+(\d+)/i); - attemptNumber = attemptMatch ? parseInt(attemptMatch[1]) : null; - } - - return { - original: message, - stage, - icon, - isError, - isSuccess, - isRetry, - isProgress, - attemptNumber, - errorDetails, // Full error details from structured messages - errorSummary, // Concise summary for quick view - retryMessage, // Dynamic retry message - source, // Source from structured messages - }; - }; - - // Create error popover content - const createErrorPopover = ( - message, - attemptNumber, - parsedErrorDetails = null, - errorSummary = null, - retryMsg = null, - source = null - ) => { - const { transformation_error_message, prompt_error_message } = errorDetails; - - // Get detailed error from multiple sources: - // 1. From structured message (agent disapprovals from visitran-ai) - // 2. From chat message error fields (transformation errors from backend) - let detailedError = null; - - if (parsedErrorDetails) { - // Use error details from structured message (visitran-ai agent errors) - detailedError = - typeof parsedErrorDetails === "string" - ? parsedErrorDetails - : JSON.stringify(parsedErrorDetails, null, 2); - } else if (transformation_error_message) { - // Use transformation error from backend - detailedError = - typeof transformation_error_message === "string" - ? transformation_error_message - : JSON.stringify(transformation_error_message, null, 2); - } else if (prompt_error_message) { - // Use prompt error from backend - detailedError = - typeof prompt_error_message === "string" - ? prompt_error_message - : JSON.stringify(prompt_error_message, null, 2); - } - - // Use concise summary if available, otherwise use full error - const displayError = errorSummary || detailedError; - - return ( -
- {/* Removed error-popover-header with red X icon */} -
-
- Attempt {attemptNumber || 1} Failed -
-
- - {displayError && ( -
-
-
- {errorSummary ? "Issues Identified" : "Detailed Error"} -
- {/* Copy button moved here */} -
-
- {displayError} -
- {/* Commented out Show Full Details button - {hasFullDetails && ( - - )} - */} -
- )} - - {/* Dynamic retry message without title */} - {retryMsg && ( -
-
- {retryMsg} -
-
- )} -
- ); - }; - - // Render enhanced thought chain - const renderEnhancedThoughtChain = () => { - if (!thoughtChain || thoughtChain.length === 0) return null; - - // Pre-process thought chain to associate disapproval reasons with attempt messages - const processedChain = []; - for (let i = 0; i < thoughtChain.length; i++) { - const currentMsg = thoughtChain[i]; - const nextMsg = thoughtChain[i + 1]; - - // Check if current message is an "Attempt" message - const currentStr = - typeof currentMsg === "string" - ? currentMsg - : currentMsg?.display || String(currentMsg); - const isAttemptMsg = currentStr.match( - /attempt\s+\d+.*disapproved.*retrying/i - ); - - if (isAttemptMsg && nextMsg) { - const nextStr = - typeof nextMsg === "string" - ? nextMsg - : nextMsg?.display || String(nextMsg); - const isDisapprovalReason = - nextStr.match(/\[DISAPPROVE\s+REASON\]/i) || - nextStr.match(/^[^:]+:\s*.+/); - - if (isDisapprovalReason) { - // Extract the disapproval reason (remove the prefix) - const reasonText = nextStr - .replace(/^\[DISAPPROVE\s+REASON\]:\s*/i, "") - .trim(); - - // Create enhanced attempt message with embedded error details - const enhancedMsg = { - display: currentStr, - error_details: reasonText, - error_summary: - reasonText.length > 150 - ? reasonText.substring(0, 150) + "..." - : reasonText, - retry_message: "Fixing the issues and regenerating...", - attempt: currentStr.match(/attempt\s+(\d+)/i)?.[1] || 1, - source: "Agent Visitran Critic", - }; - - processedChain.push(enhancedMsg); - i++; // Skip the next message since we've incorporated it - continue; - } - } - - processedChain.push(currentMsg); - } - - return ( -
-
- {processedChain.map((msg, index) => { - const parsed = parseMessage(msg); - const isLast = index === processedChain.length - 1; - const isInProgress = shouldStream && isLast; - - // Determine item class - let itemClass = "thought-chain-item"; - if (isInProgress || parsed.isProgress) { - itemClass += " in-progress"; - } else if (parsed.isSuccess) { - itemClass += " success"; - } else if (parsed.isError) { - itemClass += " error"; - } else if (parsed.isRetry) { - itemClass += " warning"; - } - - return ( -
- {parsed.icon && ( -
- {isInProgress ? : parsed.icon} -
- )} - -
- {isInProgress || (parsed.isProgress && shouldStream) ? ( - {parsed.original} - ) : parsed.isError ? ( - - {parsed.original} - {(() => { - return null; - })()} - {parsed.attemptNumber && ( - - setOpenPopoverId(visible ? `error-${index}` : null) - } - overlayStyle={{ maxWidth: 500 }} - > - setOpenPopoverId(`error-${index}`)} - > - - - - )} - - ) : parsed.isSuccess ? ( - - - {parsed.original} - - ) : parsed.isRetry ? ( - - - {parsed.original} - - ) : ( - {parsed.original} - )} -
-
- ); - })} -
-
- ); - }; - - // Show enhanced thought chain if we have messages - if (thoughtChain && thoughtChain.length > 0) { - return renderEnhancedThoughtChain(); - } - - // Fallback to bubble with shimmer - return ( -
- Processing... -
- ); -}); - -EnhancedPromptInfo.propTypes = { - isThoughtChainReceived: PropTypes.bool.isRequired, - shouldStream: PropTypes.bool, - thoughtChain: PropTypes.array, - llmModel: PropTypes.string, - coderLlmModel: PropTypes.string, - chatIntent: PropTypes.string, - errorState: PropTypes.string, - errorDetails: PropTypes.shape({ - transformation_error_message: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object, - ]), - prompt_error_message: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.object, - ]), - }), -}; - -EnhancedPromptInfo.displayName = "EnhancedPromptInfo"; - -export { EnhancedPromptInfo }; diff --git a/frontend/src/ide/chat-ai/ExistingChat.jsx b/frontend/src/ide/chat-ai/ExistingChat.jsx index a63f3ca..edc2b51 100644 --- a/frontend/src/ide/chat-ai/ExistingChat.jsx +++ b/frontend/src/ide/chat-ai/ExistingChat.jsx @@ -389,7 +389,6 @@ const ExistingChat = memo(function ExistingChat({ key={message.chat_message_id} message={message} chatIntents={chatIntents} - llmModels={llmModels} isPromptRunning={isPromptRunning} isLastConversation={idx === chatMessages.length - 1} selectedChatId={selectedChatId} diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index c6442d3..bf63389 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -1,51 +1,592 @@ -import { memo } from "react"; +import { memo, useState, useMemo } from "react"; import PropTypes from "prop-types"; -import { Bubble } from "@ant-design/x"; -import { Space, Typography } from "antd"; +import { Typography, Popover, Button, notification, Collapse } from "antd"; +import { + DatabaseOutlined, + BulbOutlined, + CodeOutlined, + SafetyCertificateOutlined, + PlayCircleOutlined, + InfoCircleOutlined, + CheckCircleFilled, + LoadingOutlined, + SyncOutlined, + CopyOutlined, + RightOutlined, +} from "@ant-design/icons"; +import "./ThoughtChainEnhancements.css"; + +const { Text } = Typography; + +/** + * PromptInfo - Modern thought chain with shimmer effects and info icons + * Detects message patterns and applies appropriate styling + */ const PromptInfo = memo(function PromptInfo({ - isThoughtChainReceived, shouldStream, thoughtChain = [], - llmModel, - coderLlmModel, - chatIntent, + errorState, + errorDetails = {}, }) { - const renderThoughtChain = () => ( -
- - {thoughtChain?.map((msg) => { - return ( - { + if (!thoughtChain || thoughtChain.length === 0) return []; + + const result = []; + for (let i = 0; i < thoughtChain.length; i++) { + const currentMsg = thoughtChain[i]; + const nextMsg = thoughtChain[i + 1]; + + // Check if current message is an "Attempt" message + const currentStr = + typeof currentMsg === "string" + ? currentMsg + : currentMsg?.display || String(currentMsg); + const isAttemptMsg = currentStr.match( + /attempt\s+\d+.*disapproved.*retrying/i + ); + + if (isAttemptMsg && nextMsg) { + const nextStr = + typeof nextMsg === "string" + ? nextMsg + : nextMsg?.display || String(nextMsg); + const isDisapprovalReason = nextStr.match(/\[DISAPPROVE\s+REASON\]/i); + + if (isDisapprovalReason) { + // Extract the disapproval reason (remove the prefix) + const reasonText = nextStr + .replace(/^\[DISAPPROVE\s+REASON\]:\s*/i, "") + .trim(); + + // Create enhanced attempt message with embedded error details + const enhancedMsg = { + display: currentStr, + error_details: reasonText, + error_summary: + reasonText.length > 150 + ? reasonText.substring(0, 150) + "..." + : reasonText, + retry_message: "Fixing the issues and regenerating...", + attempt: currentStr.match(/attempt\s+(\d+)/i)?.[1] || 1, + source: "Agent Visitran Critic", + }; + + result.push(enhancedMsg); + i++; // Skip the next message since we've incorporated it + continue; + } + } + + result.push(currentMsg); + } + return result; + }, [thoughtChain]); + + // Copy error details to clipboard + const copyErrorToClipboard = ( + message, + attemptNumber, + errorStage, + parsedErrorDetails = null + ) => { + const timestamp = new Date().toLocaleString(); + const { transformation_error_message, prompt_error_message } = errorDetails; + + // Get the most relevant error message from multiple sources + let detailedError = null; + if (parsedErrorDetails) { + // Use error from structured message (visitran-ai agent errors) + detailedError = + typeof parsedErrorDetails === "string" + ? parsedErrorDetails + : JSON.stringify(parsedErrorDetails, null, 2); + } else if (transformation_error_message) { + // Use transformation error from backend + detailedError = + typeof transformation_error_message === "string" + ? transformation_error_message + : JSON.stringify(transformation_error_message, null, 2); + } else if (prompt_error_message) { + // Use prompt error from backend + detailedError = + typeof prompt_error_message === "string" + ? prompt_error_message + : JSON.stringify(prompt_error_message, null, 2); + } + + const errorText = ` +Attempt ${attemptNumber || 1} - Error Details +======================================== +Timestamp: ${timestamp} +Error Stage: ${errorStage || "Unknown"} +Message: ${message} +${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} + `.trim(); + + navigator.clipboard + .writeText(errorText) + .then(() => { + notification.success({ + message: "Copied to clipboard", + description: "Error details have been copied successfully", + placement: "topRight", + duration: 2, + }); + }) + .catch((err) => { + notification.error({ + message: "Copy failed", + description: "Could not copy to clipboard", + placement: "topRight", + duration: 2, + }); + }); + }; + + // Detect message type and extract metadata + const parseMessage = (msg) => { + // Handle structured messages from visitran-ai (agent disapprovals) + let message; + let msgErrorDetails = null; + let attemptNumber = null; + let source = null; + + let errorSummary = null; + let retryMessage = null; + + if (typeof msg === "object" && msg !== null && msg.display) { + // Structured message with error details + message = msg.display; + msgErrorDetails = msg.error_details; + errorSummary = msg.error_summary; // Concise summary + retryMessage = msg.retry_message; // Dynamic retry message + attemptNumber = msg.attempt; + source = msg.source; + } else { + // Plain string message + message = String(msg); + } + + // Detect stage based on keywords + let stage = "general"; + let icon = null; + + if ( + message.toLowerCase().includes("preparing") || + message.toLowerCase().includes("database") + ) { + stage = "preparation"; + icon = ; + } else if (message.toLowerCase().includes("planning")) { + stage = "planning"; + icon = ; + } else if ( + message.toLowerCase().includes("creating") || + message.toLowerCase().includes("generating") + ) { + stage = "generation"; + icon = ; + } else if (message.toLowerCase().includes("validat")) { + stage = "validation"; + icon = ; + } else if ( + message.toLowerCase().includes("execut") || + message.toLowerCase().includes("running") + ) { + stage = "execution"; + icon = ; + } + + // Detect message type + const isError = + message.includes("failed") || + message.includes("error") || + message.match(/❌|⚠️/) || + message.toLowerCase().includes("disapproved"); + const isSuccess = + message.includes("✓") || + message.includes("successfully") || + message.includes("created"); + const isRetry = + message.includes("retry") || + message.includes("retrying") || + message.match(/🔄|attempt/i); + const isProgress = message.match(/\(\d+\s+of\s+\d+\)/); + + // Extract attempt number if not already from structured message + if (!attemptNumber) { + const attemptMatch = message.match(/attempt\s+(\d+)/i); + attemptNumber = attemptMatch ? parseInt(attemptMatch[1]) : null; + } + + return { + original: message, + stage, + icon, + isError, + isSuccess, + isRetry, + isProgress, + attemptNumber, + errorDetails: msgErrorDetails, // Full error details from structured messages + errorSummary, // Concise summary for quick view + retryMessage, // Dynamic retry message + source, // Source from structured messages + }; + }; + + // Create error popover content + const createErrorPopover = ( + message, + attemptNumber, + parsedErrorDetails = null, + errorSummary = null, + retryMsg = null, + source = null + ) => { + const { transformation_error_message, prompt_error_message } = errorDetails; + + // Get detailed error from multiple sources: + // 1. From structured message (agent disapprovals from visitran-ai) + // 2. From chat message error fields (transformation errors from backend) + let detailedError = null; + + if (parsedErrorDetails) { + // Use error details from structured message (visitran-ai agent errors) + detailedError = + typeof parsedErrorDetails === "string" + ? parsedErrorDetails + : JSON.stringify(parsedErrorDetails, null, 2); + } else if (transformation_error_message) { + // Use transformation error from backend + detailedError = + typeof transformation_error_message === "string" + ? transformation_error_message + : JSON.stringify(transformation_error_message, null, 2); + } else if (prompt_error_message) { + // Use prompt error from backend + detailedError = + typeof prompt_error_message === "string" + ? prompt_error_message + : JSON.stringify(prompt_error_message, null, 2); + } + + // Use concise summary if available, otherwise use full error + const displayError = errorSummary || detailedError; + + return ( +
+ {/* Removed error-popover-header with red X icon */} +
+
+ Attempt {attemptNumber || 1} Failed +
+
+ + {displayError && ( +
+
- {msg} - - ); - })} - -
- ); +
+ {errorSummary ? "Issues Identified" : "Detailed Error"} +
+ {/* Copy button moved here */} +
+
+ {displayError} +
+ {/* Commented out Show Full Details button + {hasFullDetails && ( + + )} + */} +
+ )} + + {/* Dynamic retry message without title */} + {retryMsg && ( +
+
+ {retryMsg} +
+
+ )} +
+ ); + }; + + // Render a single thought chain message + const renderMessage = (msg, index, totalCount, isLatestMessage = false) => { + const parsed = parseMessage(msg); + const isInProgress = shouldStream && isLatestMessage; + // Determine item class + let itemClass = "thought-chain-item"; + if (isInProgress || parsed.isProgress) { + itemClass += " in-progress"; + } else if (parsed.isSuccess) { + itemClass += " success"; + } else if (parsed.isError) { + itemClass += " error"; + } else if (parsed.isRetry) { + itemClass += " warning"; + } + + const msgKey = typeof msg === "object" && msg !== null ? msg.display : msg; + + return ( +
+ {parsed.icon && ( +
+ {isInProgress ? : parsed.icon} +
+ )} + +
+ {isInProgress || (parsed.isProgress && shouldStream) ? ( + {parsed.original} + ) : parsed.isError ? ( + + {parsed.original} + {parsed.attemptNumber && ( + + setOpenPopoverId(visible ? `error-${index}` : null) + } + overlayStyle={{ maxWidth: 500 }} + > + { + e.stopPropagation(); + setOpenPopoverId(`error-${index}`); + }} + > + + + + )} + + ) : parsed.isSuccess ? ( + + + {parsed.original} + + ) : parsed.isRetry ? ( + + + {parsed.original} + + ) : ( + {parsed.original} + )} +
+
+ ); + }; + + // Render thought chain + const renderThoughtChain = () => { + if (processedChain.length === 0) return null; + const latestMessage = processedChain[processedChain.length - 1]; + const previousMessages = processedChain.slice(0, -1); + const previousCount = previousMessages.length; + + // Render the latest message for the collapse header + const latestParsed = parseMessage(latestMessage); + const isInProgress = shouldStream; + + const collapseLabel = ( +
+ {!isExpanded && ( +
+ {latestParsed.icon && ( + + {isInProgress ? : latestParsed.icon} + + )} + + {isInProgress ? ( + {latestParsed.original} + ) : latestParsed.isError ? ( + + {latestParsed.original} + {latestParsed.attemptNumber && ( + + setOpenPopoverId(visible ? "error-latest" : null) + } + overlayStyle={{ maxWidth: 500 }} + > + { + e.stopPropagation(); + setOpenPopoverId("error-latest"); + }} + > + + + + )} + + ) : latestParsed.isSuccess ? ( + + + {latestParsed.original} + + ) : latestParsed.isRetry ? ( + + + {latestParsed.original} + + ) : ( + {latestParsed.original} + )} + + {previousCount > 0 && ( + + ({previousCount} previous{" "} + {previousCount === 1 ? "step" : "steps"}) + + )} +
+ )} +
+ ); + + return ( +
+ setIsExpanded(!isExpanded)} + expandIcon={({ isActive }) => ( + + )} + className="thought-chain-collapse" + items={[ + { + key: "1", + label: collapseLabel, + children: ( +
+ {processedChain.map((msg, index) => + renderMessage( + msg, + index, + processedChain.length, + index === processedChain.length - 1 + ) + )} +
+ ), + }, + ]} + /> +
+ ); + }; + + // Show thought chain if we have messages + if (thoughtChain && thoughtChain.length > 0) { + return renderThoughtChain(); + } + + // Only show loading state when actively streaming + if (!shouldStream) return null; + + // Fallback to bubble with shimmer return ( - +
+ Processing... +
); }); PromptInfo.propTypes = { - isThoughtChainReceived: PropTypes.bool.isRequired, shouldStream: PropTypes.bool, thoughtChain: PropTypes.array, - llmModel: PropTypes.string, - coderLlmModel: PropTypes.string, - chatIntent: PropTypes.string, + errorState: PropTypes.string, + errorDetails: PropTypes.shape({ + transformation_error_message: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + ]), + prompt_error_message: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.object, + ]), + }), }; PromptInfo.displayName = "PromptInfo"; diff --git a/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css b/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css index 2cba370..40f2ba4 100644 --- a/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css +++ b/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css @@ -475,6 +475,63 @@ animation: checkmark 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); } +/* Collapsible thought chain */ +.modern-thought-chain.collapsible { + padding: 0; + background: transparent; + border: none; +} + +.thought-chain-collapse { + background: linear-gradient(135deg, #667eea11 0%, #764ba211 100%); + border: 1px solid rgba(102, 126, 234, 0.1); + border-radius: 12px; +} + +.thought-chain-collapse .ant-collapse-item { + border: none; +} + +.thought-chain-collapse .ant-collapse-item:last-child > .ant-collapse-content { + border-radius: 0 0 12px 12px; +} + +.thought-chain-collapse .ant-collapse-header { + padding: 12px 16px !important; + align-items: center !important; +} + +.thought-chain-collapse .ant-collapse-content-box { + padding: 16px !important; +} + +.thought-chain-collapse-header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + gap: 12px; +} + +.thought-chain-collapse-content { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + min-width: 0; +} + +.thought-chain-collapse-message { + flex: 1; + min-width: 0; +} + +.thought-chain-collapse-count { + font-size: 12px; + white-space: nowrap; + flex-shrink: 0; +} + /* Responsive design */ @media (max-width: 768px) { .modern-thought-chain {