Stderr Display & Gemini output display in conversation history (#78)
- Implement noramlized output for Gemini - Display stderr messages in conversation history - Do not leak message history to stderr
This commit is contained in:
@@ -11,6 +11,9 @@ import {
|
||||
Settings,
|
||||
Brain,
|
||||
Hammer,
|
||||
AlertCircle,
|
||||
ChevronRight,
|
||||
ChevronUp,
|
||||
} from 'lucide-react';
|
||||
import { makeRequest } from '@/lib/api';
|
||||
import type {
|
||||
@@ -39,6 +42,9 @@ const getEntryIcon = (entryType: NormalizedEntryType) => {
|
||||
if (entryType.type === 'thinking') {
|
||||
return <Brain className="h-4 w-4 text-purple-600" />;
|
||||
}
|
||||
if (entryType.type === 'error_message') {
|
||||
return <AlertCircle className="h-4 w-4 text-red-600" />;
|
||||
}
|
||||
if (entryType.type === 'tool_use') {
|
||||
const { action_type } = entryType;
|
||||
if (action_type.action === 'file_read') {
|
||||
@@ -74,6 +80,10 @@ const getContentClassName = (entryType: NormalizedEntryType) => {
|
||||
return `${baseClasses} font-mono`;
|
||||
}
|
||||
|
||||
if (entryType.type === 'error_message') {
|
||||
return `${baseClasses} text-red-600 font-mono bg-red-50 dark:bg-red-950/20 px-2 py-1 rounded`;
|
||||
}
|
||||
|
||||
return baseClasses;
|
||||
};
|
||||
|
||||
@@ -86,6 +96,19 @@ export function NormalizedConversationViewer({
|
||||
useState<NormalizedConversation | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [expandedErrors, setExpandedErrors] = useState<Set<number>>(new Set());
|
||||
|
||||
const toggleErrorExpansion = (index: number) => {
|
||||
setExpandedErrors((prev) => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(index)) {
|
||||
newSet.delete(index);
|
||||
} else {
|
||||
newSet.add(index);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const fetchNormalizedLogs = useCallback(
|
||||
async (isPolling = false) => {
|
||||
@@ -203,18 +226,64 @@ export function NormalizedConversationViewer({
|
||||
|
||||
{/* Display conversation entries */}
|
||||
<div className="space-y-2">
|
||||
{conversation.entries.map((entry, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
{getEntryIcon(entry.entry_type)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className={getContentClassName(entry.entry_type)}>
|
||||
{entry.content}
|
||||
{conversation.entries.map((entry, index) => {
|
||||
const isErrorMessage = entry.entry_type.type === 'error_message';
|
||||
const isExpanded = expandedErrors.has(index);
|
||||
const hasMultipleLines =
|
||||
isErrorMessage && entry.content.includes('\n');
|
||||
|
||||
return (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
{isErrorMessage && hasMultipleLines ? (
|
||||
<button
|
||||
onClick={() => toggleErrorExpansion(index)}
|
||||
className="transition-colors hover:opacity-70"
|
||||
>
|
||||
{getEntryIcon(entry.entry_type)}
|
||||
</button>
|
||||
) : (
|
||||
getEntryIcon(entry.entry_type)
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
{isErrorMessage && hasMultipleLines ? (
|
||||
<div className={isExpanded ? 'space-y-2' : ''}>
|
||||
<div className={getContentClassName(entry.entry_type)}>
|
||||
{isExpanded ? (
|
||||
entry.content
|
||||
) : (
|
||||
<>
|
||||
{entry.content.split('\n')[0]}
|
||||
<button
|
||||
onClick={() => toggleErrorExpansion(index)}
|
||||
className="ml-2 inline-flex items-center gap-1 text-xs text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
||||
>
|
||||
<ChevronRight className="h-3 w-3" />
|
||||
Show more
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<button
|
||||
onClick={() => toggleErrorExpansion(index)}
|
||||
className="flex items-center gap-1 text-xs text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 transition-colors"
|
||||
>
|
||||
<ChevronUp className="h-3 w-3" />
|
||||
Show less
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className={getContentClassName(entry.entry_type)}>
|
||||
{entry.content}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -578,7 +578,7 @@ export function TaskDetailsPanel({
|
||||
);
|
||||
}
|
||||
|
||||
// When setup failed, show error message and stderr
|
||||
// When setup failed, show error message and conversation
|
||||
if (isSetupFailed) {
|
||||
const setupProcess = executionState.setup_process_id
|
||||
? attemptData.runningProcessDetails[executionState.setup_process_id]
|
||||
@@ -598,20 +598,17 @@ export function TaskDetailsPanel({
|
||||
</div>
|
||||
|
||||
{setupProcess && (
|
||||
<div className="font-mono text-sm whitespace-pre-wrap text-muted-foreground">
|
||||
{(() => {
|
||||
const stderr = setupProcess.stderr || '';
|
||||
const stdout = setupProcess.stdout || '';
|
||||
const combined = [stderr, stdout].filter(Boolean).join('\n');
|
||||
return combined || 'No error output available';
|
||||
})()}
|
||||
</div>
|
||||
<NormalizedConversationViewer
|
||||
executionProcess={setupProcess}
|
||||
projectId={projectId}
|
||||
onConversationUpdate={handleConversationUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// When coding agent failed, show error message and stderr
|
||||
// When coding agent failed, show error message and conversation
|
||||
if (isCodingAgentFailed) {
|
||||
const codingAgentProcess = executionState.coding_agent_process_id
|
||||
? attemptData.runningProcessDetails[
|
||||
@@ -633,14 +630,11 @@ export function TaskDetailsPanel({
|
||||
</div>
|
||||
|
||||
{codingAgentProcess && (
|
||||
<div className="font-mono text-sm whitespace-pre-wrap text-muted-foreground">
|
||||
{(() => {
|
||||
const stderr = codingAgentProcess.stderr || '';
|
||||
const stdout = codingAgentProcess.stdout || '';
|
||||
const combined = [stderr, stdout].filter(Boolean).join('\n');
|
||||
return combined || 'No error output available';
|
||||
})()}
|
||||
</div>
|
||||
<NormalizedConversationViewer
|
||||
executionProcess={codingAgentProcess}
|
||||
projectId={projectId}
|
||||
onConversationUpdate={handleConversationUpdate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user