+ {/* Reference content */}
+ {isRef && resultText && !block.isError && (
+
+ )}
- {/* Create skill */}
- {isCreate && (
-
- {skillName && (
-
- )}
- {skillDescription && (
-
{skillDescription.slice(0, 300)}
- )}
- {String(block.input.content || '') && (
-
- {String(block.input.content).slice(0, 500)}{String(block.input.content).length > 500 ? '...' : ''}
-
- )}
- {resultText && !block.isError && (
-
- {resultText}
-
- )}
+ {/* Create skill */}
+ {isCreate && (
+
+ {skillName && (
+
)}
-
- {/* Update skill */}
- {isUpdate && (
-
- {String(block.input.content || '') && (
-
- {String(block.input.content).slice(0, 800)}{String(block.input.content).length > 800 ? '\n...' : ''}
-
- )}
- {resultText && !block.isError && (
-
- {resultText}
-
- )}
-
+ {skillDescription && (
+
{skillDescription.slice(0, 300)}
)}
-
- {/* Fallback for unknown skill tools */}
- {!isList && !isGet && !isRun && !isRef && !isCreate && !isUpdate && resultText && !block.isError && (
-
- {resultText}
+ {String(block.input.content || '') && (
+
+ {String(block.input.content).slice(0, 500)}{String(block.input.content).length > 500 ? '...' : ''}
)}
+ {resultText && !block.isError && (
+
+ {resultText}
+
+ )}
+
+ )}
- {/* Error */}
- {block.isError && resultText && (
-
- {resultText}
+ {/* Update skill */}
+ {isUpdate && (
+
+ {String(block.input.content || '') && (
+
+ {String(block.input.content).slice(0, 800)}{String(block.input.content).length > 800 ? '\n...' : ''}
)}
-
- {/* Running */}
- {isRunning && block.result === undefined && (
-
-
- {isGet ? 'Loading skill...' : isRun ? 'Running script...' : 'Working...'}
+ {resultText && !block.isError && (
+
+ {resultText}
)}
)}
-
+
+ {/* Fallback for unknown skill tools */}
+ {!isList && !isGet && !isRun && !isRef && !isCreate && !isUpdate && resultText && !block.isError && (
+
+ {resultText}
+
+ )}
+
+ {/* Error */}
+ {block.isError && resultText && (
+
+ {resultText}
+
+ )}
+
+ {/* Running */}
+ {isRunning && block.result === undefined && (
+
+
+ {isGet ? 'Loading skill...' : isRun ? 'Running script...' : 'Working...'}
+
+ )}
+
);
}
diff --git a/web/src/components/Chat/tools/SourceToolBlock.tsx b/web/src/components/Chat/tools/SourceToolBlock.tsx
index 9b5b31f..b3629ab 100644
--- a/web/src/components/Chat/tools/SourceToolBlock.tsx
+++ b/web/src/components/Chat/tools/SourceToolBlock.tsx
@@ -1,22 +1,7 @@
-import { useState } from 'react';
-import { ChevronRight, ChevronDown, Inbox, Radio, BookOpen, Loader2, Mail, Github, MessageCircle } from 'lucide-react';
+import { Inbox, Radio, BookOpen, Loader2, Mail, Github, MessageCircle } from 'lucide-react';
import type { ToolCallBlockData } from '../../../types/chat';
-
-/** Extract readable text from MCP content blocks or plain text. */
-function extractText(result: string): string {
- try {
- const parsed = JSON.parse(result);
- if (Array.isArray(parsed)) {
- return parsed
- .filter((b: any) => b.type === 'text')
- .map((b: any) => b.text)
- .join('\n');
- }
- } catch {
- // Not JSON — return as-is
- }
- return result;
-}
+import { extractText } from '../../../utils/extractResultText';
+import { CollapsibleToolBlock } from './CollapsibleToolBlock';
function sourceIcon(source: string) {
const type = source.split(':')[0];
@@ -106,7 +91,6 @@ function parseSourceMessages(text: string): { messages: SourceMessage[]; message
}
export function SourceToolBlock({ block }: { block: ToolCallBlockData }) {
- const [expanded, setExpanded] = useState(false);
const isRunning = block.status === 'running';
const isList = block.tool.includes('list_sources');
@@ -148,107 +132,99 @@ export function SourceToolBlock({ block }: { block: ToolCallBlockData }) {
}
return (
-
-
-
- {expanded && (
-
- {/* list_sources: structured source list */}
- {isList && sourceEntries.length > 0 && (
-
- {sourceEntries.map((entry, i) => (
-
- {sourceIcon(entry.name)}
- {entry.name}
- {entry.messageCount && {entry.messageCount} msgs}
- {entry.unread && parseInt(entry.unread) > 0 && (
- {entry.unread} unread
- )}
- {entry.unread === '0' && (
- 0 unread
- )}
-
- ))}
-
- )}
-
- {/* poll/read: message list */}
- {(isPoll || isRead) && parsedMessages.length > 0 && (
-
- {parsedMessages.map((msg, i) => (
-
-
- {sourceIcon(msg.source)}
- {msg.summary}
- {msg.relativeTime}
-
-
- {msg.type}
- seq:{msg.seq}
- {msg.time && {msg.time}}
-
- {msg.content && (
-
- {msg.content.length > 500 ? msg.content.slice(0, 500) + '...' : msg.content}
-
- )}
-
- ))}
+ >}
+ >
+ {/* list_sources: structured source list */}
+ {isList && sourceEntries.length > 0 && (
+
+ {sourceEntries.map((entry, i) => (
+
+ {sourceIcon(entry.name)}
+ {entry.name}
+ {entry.messageCount && {entry.messageCount} msgs}
+ {entry.unread && parseInt(entry.unread) > 0 && (
+ {entry.unread} unread
+ )}
+ {entry.unread === '0' && (
+ 0 unread
+ )}
- )}
+ ))}
+
+ )}
- {/* No messages state */}
- {isNoMessages && !isList && (
-
- No new messages
-
- )}
-
- {/* Fallback: raw text for unparsed results */}
- {!isList && parsedMessages.length === 0 && !isNoMessages && resultText && (
-
- {resultText}
-
- )}
-
- {/* list_sources fallback */}
- {isList && sourceEntries.length === 0 && resultText && (
-
- {resultText}
-
- )}
-
- {/* Error */}
- {block.isError && resultText && (
-
- {resultText}
-
- )}
-
- {/* Running state */}
- {isRunning && block.result === undefined && (
-
-
{isPoll ? 'Polling...' : isList ? 'Loading sources...' : 'Browsing...'}
+ {/* poll/read: message list */}
+ {(isPoll || isRead) && parsedMessages.length > 0 && (
+
+ {parsedMessages.map((msg, i) => (
+
+
+ {sourceIcon(msg.source)}
+ {msg.summary}
+ {msg.relativeTime}
+
+
+ {msg.type}
+ seq:{msg.seq}
+ {msg.time && {msg.time}}
+
+ {msg.content && (
+
+ {msg.content.length > 500 ? msg.content.slice(0, 500) + '...' : msg.content}
+
+ )}
- )}
+ ))}
+
+ )}
+
+ {/* No messages state */}
+ {isNoMessages && !isList && (
+
+ No new messages
+
+ )}
+
+ {/* Fallback: raw text for unparsed results */}
+ {!isList && parsedMessages.length === 0 && !isNoMessages && resultText && (
+
+ {resultText}
+
+ )}
+
+ {/* list_sources fallback */}
+ {isList && sourceEntries.length === 0 && resultText && (
+
+ {resultText}
+
+ )}
+
+ {/* Error */}
+ {block.isError && resultText && (
+
+ {resultText}
+
+ )}
+
+ {/* Running state */}
+ {isRunning && block.result === undefined && (
+
+ {isPoll ? 'Polling...' : isList ? 'Loading sources...' : 'Browsing...'}
)}
-
+
);
}
diff --git a/web/src/components/Chat/tools/TaskToolBlock.tsx b/web/src/components/Chat/tools/TaskToolBlock.tsx
index d5aee3a..201528d 100644
--- a/web/src/components/Chat/tools/TaskToolBlock.tsx
+++ b/web/src/components/Chat/tools/TaskToolBlock.tsx
@@ -1,29 +1,8 @@
-import { useState } from 'react';
-import { ChevronRight, ChevronDown, CheckSquare, ListTodo, Plus, CheckCircle, Pencil, FileText, Loader2 } from 'lucide-react';
+import { CheckSquare, ListTodo, Plus, CheckCircle, Pencil, FileText, Loader2 } from 'lucide-react';
import type { ToolCallBlockData } from '../../../types/chat';
-
-const STATUS_COLORS: Record
= {
- pending: 'bg-yellow-500/15 text-yellow-400',
- 'in-progress': 'bg-blue-500/15 text-blue-400',
- 'in_progress': 'bg-blue-500/15 text-blue-400',
- done: 'bg-green-500/15 text-green-400',
- completed: 'bg-green-500/15 text-green-400',
- deferred: 'bg-[#333] text-[#888]',
-};
-
-/** Extract readable text from MCP content blocks. */
-function extractText(result: string): string {
- try {
- const parsed = JSON.parse(result);
- if (Array.isArray(parsed)) {
- return parsed
- .filter((b: any) => b.type === 'text')
- .map((b: any) => b.text)
- .join('\n');
- }
- } catch { /* not JSON */ }
- return result;
-}
+import { extractText } from '../../../utils/extractResultText';
+import { TASK_STATUS_COLORS as STATUS_COLORS } from '../../../constants/statusStyles';
+import { CollapsibleToolBlock } from './CollapsibleToolBlock';
interface ParsedTask {
title: string;
@@ -60,7 +39,6 @@ function parseTaskList(text: string): ParsedTask[] {
}
export function TaskToolBlock({ block }: { block: ToolCallBlockData }) {
- const [expanded, setExpanded] = useState(false);
const isRunning = block.status === 'running';
const toolName = block.tool.split('__').pop() || block.tool;
@@ -86,16 +64,13 @@ export function TaskToolBlock({ block }: { block: ToolCallBlockData }) {
const taskList = isList ? parseTaskList(resultText) : [];
return (
-
-