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 */}
@@ -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 */}
-
}
- onClick={() =>
- copyErrorToClipboard(
- message,
- attemptNumber,
- source || errorState,
- parsedErrorDetails
- )
- }
- style={{ padding: "4px 8px" }}
- title="Copy Error"
- />
-
-
- {displayError}
-
- {/* Commented out Show Full Details button
- {hasFullDetails && (
-
setShowFullError(prev => ({ ...prev, [popoverId]: !prev[popoverId] }))}
- style={{ padding: '4px 0', marginTop: '8px' }}
- >
- {showFullError[popoverId] ? 'Show Less' : 'Show Full Details'}
-
- )}
- */}
-
- )}
-
- {/* Dynamic retry message without title */}
- {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 */}
+
}
+ onClick={() =>
+ copyErrorToClipboard(
+ message,
+ attemptNumber,
+ source || errorState,
+ parsedErrorDetails
+ )
+ }
+ style={{ padding: "4px 8px" }}
+ title="Copy Error"
+ />
+
+
+ {displayError}
+
+ {/* Commented out Show Full Details button
+ {hasFullDetails && (
+
setShowFullError(prev => ({ ...prev, [popoverId]: !prev[popoverId] }))}
+ style={{ padding: '4px 0', marginTop: '8px' }}
+ >
+ {showFullError[popoverId] ? 'Show Less' : 'Show Full Details'}
+
+ )}
+ */}
+
+ )}
+
+ {/* Dynamic retry message without title */}
+ {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 {