From 8cf75e42f23e3e53d888e87e6e414d698e6042bf Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:04:53 +0530 Subject: [PATCH 01/13] fix: update initial thought chain message to be more generic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change "Preparing database information..." to "Gathering context..." for a more accurate description of the initial processing step. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- backend/backend/application/context/llm_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From 196322fade576227137f2fbb83d74a9d1c899af2 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:05:13 +0530 Subject: [PATCH 02/13] refactor: consolidate PromptInfo component and remove unused code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove EnhancedPromptInfo.jsx (renamed to PromptInfo.jsx as the only implementation) - Remove unused props: isThoughtChainReceived, llmModel, coderLlmModel, chatIntent - Remove unused memoized values from Conversation.jsx - Memoize processedChain with useMemo to prevent re-computation - Memoize errorDetails object to prevent unnecessary re-renders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/Conversation.jsx | 68 +- .../src/ide/chat-ai/EnhancedPromptInfo.jsx | 487 -------------- frontend/src/ide/chat-ai/PromptInfo.jsx | 596 +++++++++++++++++- 3 files changed, 579 insertions(+), 572 deletions(-) delete mode 100644 frontend/src/ide/chat-ai/EnhancedPromptInfo.jsx diff --git a/frontend/src/ide/chat-ai/Conversation.jsx b/frontend/src/ide/chat-ai/Conversation.jsx index 0ec0670..29968bb 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"; @@ -67,36 +66,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 +82,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 +124,14 @@ const Conversation = memo(function Conversation({
)} - {/* Thought Chain - Enhanced or Legacy */} + {/* Thought Chain */}
- {useEnhancedUI ? ( - - ) : ( - - )} +
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/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index c6442d3..d2ad9e1 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -1,51 +1,581 @@ -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) || + 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", + }; + + 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 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 && ( +
+
- {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"; + } + + 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 = ( +
+
+ {latestParsed.icon && ( + + {isInProgress && !isExpanded ? : latestParsed.icon} + + )} + + {isInProgress && !isExpanded ? ( + {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} + )} + +
+ {!isExpanded && ( + + ({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(); + } + + // 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"; From 8af32240a388a0f3af4d7de74f82c9655effb3ab Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:05:32 +0530 Subject: [PATCH 03/13] feat: add collapsible thought chain UI styles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add CSS for collapsible thought chain component: - Collapsed by default, shows latest message with step count - Expand to see all messages in chronological order - Proper padding and styling for collapsed/expanded states 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../ide/chat-ai/ThoughtChainEnhancements.css | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css b/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css index 2cba370..ea68115 100644 --- a/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css +++ b/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css @@ -475,6 +475,59 @@ 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-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 { From d7048d6a13ff8747f267fdcf40216341ff346b0e Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:07:29 +0530 Subject: [PATCH 04/13] style: apply eslint formatting fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index d2ad9e1..3ea495f 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -457,7 +457,11 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""}
{latestParsed.icon && ( - {isInProgress && !isExpanded ? : latestParsed.icon} + {isInProgress && !isExpanded ? ( + + ) : ( + latestParsed.icon + )} )} @@ -538,7 +542,12 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} children: (
{processedChain.map((msg, index) => - renderMessage(msg, index, processedChain.length, index === processedChain.length - 1) + renderMessage( + msg, + index, + processedChain.length, + index === processedChain.length - 1 + ) )}
), From 89f9403a2e49a6a8d7333e777623d0f518af4ea3 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:18:21 +0530 Subject: [PATCH 05/13] fix: only show Processing shimmer when actively streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent showing indefinite "Processing..." shimmer for completed conversations that have no thought chain data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index 3ea495f..fa23ea1 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -563,6 +563,9 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} return renderThoughtChain(); } + // Only show loading state when actively streaming + if (!shouldStream) return null; + // Fallback to bubble with shimmer return (
From d55300c9ad084c81fa9013778356fb1943b9a2c4 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:19:37 +0530 Subject: [PATCH 06/13] fix: hide step count when there are no previous steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only show "(N previous steps)" label when previousCount > 0 to avoid displaying misleading "(0 previous steps)" for single-message chains. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index fa23ea1..97fd798 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -518,7 +518,7 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} )}
- {!isExpanded && ( + {!isExpanded && previousCount > 0 && ( ({previousCount} previous {previousCount === 1 ? "step" : "steps"}) From 06e58fec567c4392a69bc8b07ba08ff14ee8d0e9 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:21:01 +0530 Subject: [PATCH 07/13] fix: rename local errorDetails to avoid shadowing prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename local variable in parseMessage from errorDetails to msgErrorDetails to prevent shadowing the component-level errorDetails prop. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index 97fd798..fad613f 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -154,7 +154,7 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} const parseMessage = (msg) => { // Handle structured messages from visitran-ai (agent disapprovals) let message; - let errorDetails = null; + let msgErrorDetails = null; let attemptNumber = null; let source = null; @@ -164,7 +164,7 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} if (typeof msg === "object" && msg !== null && msg.display) { // Structured message with error details message = msg.display; - errorDetails = msg.error_details; + msgErrorDetails = msg.error_details; errorSummary = msg.error_summary; // Concise summary retryMessage = msg.retry_message; // Dynamic retry message attemptNumber = msg.attempt; @@ -235,7 +235,7 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} isRetry, isProgress, attemptNumber, - errorDetails, // Full error details from structured messages + errorDetails: msgErrorDetails, // Full error details from structured messages errorSummary, // Concise summary for quick view retryMessage, // Dynamic retry message source, // Source from structured messages From f025a76c3e853db26018cadcecc00a449acc98db Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 14:22:02 +0530 Subject: [PATCH 08/13] fix: use meaningful React key for object messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract display text from object messages for the key instead of getting "[object Object]" from string coercion. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index fad613f..3878dab 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -376,8 +376,10 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} itemClass += " warning"; } + const msgKey = typeof msg === "object" && msg !== null ? msg.display : msg; + return ( -
+
{parsed.icon && (
{isInProgress ? : parsed.icon} From 900f2f3a45680df92b5ecfe4739081e3942c0a82 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 15:22:37 +0530 Subject: [PATCH 09/13] fix: hide header message when collapse is expanded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent duplicate display of latest message by hiding the header content when expanded, since all messages are shown in the body. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 125 ++++++++++++------------ 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index 3878dab..da37871 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -456,74 +456,73 @@ ${detailedError ? `\nDetailed Error:\n${detailedError}` : ""} const collapseLabel = (
-
- {latestParsed.icon && ( - - {isInProgress && !isExpanded ? ( - + {!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.icon + {latestParsed.original} )} - )} - - {isInProgress && !isExpanded ? ( - {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} + {previousCount > 0 && ( + + ({previousCount} previous{" "} + {previousCount === 1 ? "step" : "steps"}) - ) : ( - {latestParsed.original} )} - -
- {!isExpanded && previousCount > 0 && ( - - ({previousCount} previous {previousCount === 1 ? "step" : "steps"}) - +
)}
); From 9986874a01a2294e4d72268391252c3c4b3cd368 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 15:52:13 +0530 Subject: [PATCH 10/13] fix: add rounded bottom corners to collapse content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Override Ant Design's default 8px border-radius on .ant-collapse-content to match the parent container's 12px border-radius. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/ThoughtChainEnhancements.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css b/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css index ea68115..40f2ba4 100644 --- a/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css +++ b/frontend/src/ide/chat-ai/ThoughtChainEnhancements.css @@ -492,6 +492,10 @@ 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; From 42e461b2691f000ef9e35565bfa048f0eed9737f Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 15:55:49 +0530 Subject: [PATCH 11/13] refactor: remove unused llmModels prop from Conversation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The llmModels prop was no longer used after removing the llmModelDisplayName and coderLlmModelDisplayName memoized values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/Conversation.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/ide/chat-ai/Conversation.jsx b/frontend/src/ide/chat-ai/Conversation.jsx index 29968bb..62921cd 100644 --- a/frontend/src/ide/chat-ai/Conversation.jsx +++ b/frontend/src/ide/chat-ai/Conversation.jsx @@ -15,7 +15,6 @@ const Conversation = memo(function Conversation({ savePrompt, message, chatIntents, - llmModels, isPromptRunning, isLastConversation, selectedChatId, @@ -181,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, From c11d097c137a7ad63810e18a5b0aca93e3c5d013 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 16:10:21 +0530 Subject: [PATCH 12/13] fix: remove overly broad disapproval reason detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fallback regex /^[^:]+:\s*.+/ matched any string with a colon, which could incorrectly consume legitimate messages like "Creating: model.sql". Keep only the explicit [DISAPPROVE REASON] prefix check. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/PromptInfo.jsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/ide/chat-ai/PromptInfo.jsx b/frontend/src/ide/chat-ai/PromptInfo.jsx index da37871..bf63389 100644 --- a/frontend/src/ide/chat-ai/PromptInfo.jsx +++ b/frontend/src/ide/chat-ai/PromptInfo.jsx @@ -55,9 +55,7 @@ const PromptInfo = memo(function PromptInfo({ typeof nextMsg === "string" ? nextMsg : nextMsg?.display || String(nextMsg); - const isDisapprovalReason = - nextStr.match(/\[DISAPPROVE\s+REASON\]/i) || - nextStr.match(/^[^:]+:\s*.+/); + const isDisapprovalReason = nextStr.match(/\[DISAPPROVE\s+REASON\]/i); if (isDisapprovalReason) { // Extract the disapproval reason (remove the prefix) From 22c71e9e1b7a5a6561bccf86d16b288cf944f1b8 Mon Sep 17 00:00:00 2001 From: Tahier Hussain Date: Tue, 7 Apr 2026 16:14:59 +0530 Subject: [PATCH 13/13] fix: remove stale llmModels prop passed to Conversation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Conversation component no longer uses llmModels, so stop passing it from ExistingChat. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/src/ide/chat-ai/ExistingChat.jsx | 1 - 1 file changed, 1 deletion(-) 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}