fix: hide tool result user messages and improve visibility
- Hide tool result messages (role: user) from chat display They're internal API messages, not actual user input - Improve pattern matching to catch all tool result formats - Clean up "Tool execution results:" and "Called tool:" text - Detect and hide HTML garbage from failed fetch attempts - Add fetch result type with proper styling - Improve text visibility in fallback displays 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,12 +21,16 @@
|
||||
// Pattern to find fenced code blocks
|
||||
const CODE_BLOCK_PATTERN = /```(\w+)?\n([\s\S]*?)```/g;
|
||||
|
||||
// Pattern to detect tool execution results
|
||||
const TOOL_RESULT_PATTERN = /Tool execution results:\s*\n(Tool (?:result|error):[\s\S]*?)(?=\n\nBased on these results|$)/;
|
||||
// Pattern to detect tool results in various formats
|
||||
const TOOL_RESULT_PATTERN = /Tool result:\s*(\{[\s\S]*?\}|\S[\s\S]*?)(?=\n\n|$)/;
|
||||
const TOOL_ERROR_PATTERN = /Tool error:\s*(.+?)(?=\n\n|$)/;
|
||||
|
||||
// Pattern for "Called tool:" text (redundant with ToolCallDisplay)
|
||||
const CALLED_TOOL_PATTERN = /Called tool:\s*\w+\([^)]*\)\s*\n*/g;
|
||||
|
||||
// Pattern for "Tool execution results:" header
|
||||
const TOOL_EXEC_HEADER_PATTERN = /Tool execution results:\s*\n?/g;
|
||||
|
||||
// Languages that should show a preview
|
||||
const PREVIEW_LANGUAGES = ['html', 'htm'];
|
||||
|
||||
@@ -46,10 +50,14 @@
|
||||
let modalImage = $state<string | null>(null);
|
||||
|
||||
/**
|
||||
* Clean redundant "Called tool:" text (shown via ToolCallDisplay)
|
||||
* Clean redundant tool text (shown via ToolCallDisplay)
|
||||
*/
|
||||
function cleanCalledToolText(text: string): string {
|
||||
return text.replace(CALLED_TOOL_PATTERN, '').trim();
|
||||
function cleanToolText(text: string): string {
|
||||
return text
|
||||
.replace(CALLED_TOOL_PATTERN, '')
|
||||
.replace(TOOL_EXEC_HEADER_PATTERN, '')
|
||||
.replace(/^Based on these results.*$/gm, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +217,7 @@
|
||||
}
|
||||
|
||||
// Clean and parse content into parts
|
||||
const cleanedContent = $derived(cleanCalledToolText(content));
|
||||
const cleanedContent = $derived(cleanToolText(content));
|
||||
const contentParts = $derived(parseContent(cleanedContent));
|
||||
</script>
|
||||
|
||||
|
||||
@@ -40,6 +40,15 @@
|
||||
const hasContent = $derived(node.message.content.length > 0);
|
||||
const hasToolCalls = $derived(node.message.toolCalls && node.message.toolCalls.length > 0);
|
||||
|
||||
// Detect tool result messages (sent as user role but should be hidden or styled differently)
|
||||
const isToolResultMessage = $derived(
|
||||
isUser && (
|
||||
node.message.content.startsWith('Tool execution results:') ||
|
||||
node.message.content.startsWith('Tool result:') ||
|
||||
node.message.content.startsWith('Tool error:')
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Start editing a message
|
||||
*/
|
||||
@@ -80,6 +89,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Hide tool result messages - they're internal API messages -->
|
||||
{#if isToolResultMessage}
|
||||
<!-- Tool results are handled in the assistant message display -->
|
||||
{:else}
|
||||
<article
|
||||
class="group mb-6 flex gap-4"
|
||||
class:justify-end={isUser}
|
||||
@@ -218,3 +231,4 @@
|
||||
</div>
|
||||
{/if}
|
||||
</article>
|
||||
{/if}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
let { content }: Props = $props();
|
||||
|
||||
interface ParsedResult {
|
||||
type: 'location' | 'search' | 'error' | 'text' | 'json';
|
||||
type: 'location' | 'search' | 'error' | 'text' | 'json' | 'fetch';
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
@@ -39,36 +39,62 @@
|
||||
results?: SearchResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to extract JSON from text
|
||||
*/
|
||||
function extractJSON(text: string): unknown | null {
|
||||
// Try to find JSON object in text
|
||||
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
||||
if (jsonMatch) {
|
||||
try {
|
||||
return JSON.parse(jsonMatch[0]);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the tool result content
|
||||
*/
|
||||
function parseResult(text: string): ParsedResult {
|
||||
// Try to extract JSON from "Tool result: {...}" format
|
||||
const jsonMatch = text.match(/Tool result:\s*(\{[\s\S]*\})/);
|
||||
if (!jsonMatch) {
|
||||
// Check for error
|
||||
if (text.includes('Tool error:')) {
|
||||
const errorMatch = text.match(/Tool error:\s*(.+)/);
|
||||
return { type: 'error', data: errorMatch?.[1] || text };
|
||||
}
|
||||
return { type: 'text', data: text };
|
||||
// Check for error first
|
||||
if (text.includes('Tool error:') || text.includes('HTTP 403') || text.includes('HTTP 4') || text.includes('HTTP 5')) {
|
||||
const errorMatch = text.match(/(?:Tool error:\s*)?(.+)/);
|
||||
return { type: 'error', data: errorMatch?.[1]?.trim() || text };
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(jsonMatch[1]);
|
||||
// Try to extract JSON
|
||||
const json = extractJSON(text);
|
||||
if (json && typeof json === 'object') {
|
||||
const data = json as Record<string, unknown>;
|
||||
|
||||
// Detect result type
|
||||
if (data.location && (data.location.city || data.location.latitude)) {
|
||||
if (data.location && typeof data.location === 'object') {
|
||||
return { type: 'location', data };
|
||||
}
|
||||
if (data.results && Array.isArray(data.results) && data.query) {
|
||||
return { type: 'search', data };
|
||||
}
|
||||
if (data.title || data.text || data.url) {
|
||||
return { type: 'fetch', data };
|
||||
}
|
||||
|
||||
return { type: 'json', data };
|
||||
} catch {
|
||||
return { type: 'text', data: text };
|
||||
}
|
||||
|
||||
// Plain text result (might be scraped content)
|
||||
const cleanText = text.replace(/^Tool result:\s*/i, '').trim();
|
||||
if (cleanText.length > 0) {
|
||||
// Check if it looks like HTML garbage
|
||||
if (cleanText.includes('<script') || cleanText.includes('googletagmanager')) {
|
||||
return { type: 'error', data: 'Could not extract meaningful content from page' };
|
||||
}
|
||||
return { type: 'text', data: cleanText };
|
||||
}
|
||||
|
||||
return { type: 'text', data: text };
|
||||
}
|
||||
|
||||
const parsed = $derived(parseResult(content));
|
||||
@@ -139,6 +165,28 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if parsed.type === 'fetch'}
|
||||
{@const data = parsed.data as Record<string, unknown>}
|
||||
<div class="my-3 overflow-hidden rounded-xl border border-violet-500/30 bg-gradient-to-r from-violet-500/10 to-purple-500/10">
|
||||
<div class="px-4 py-3">
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span>🌐</span>
|
||||
{#if data.title}
|
||||
<span class="font-medium text-slate-200">{data.title}</span>
|
||||
{:else if data.url}
|
||||
<a href={String(data.url)} target="_blank" rel="noopener noreferrer" class="text-violet-400 hover:underline">
|
||||
{data.url}
|
||||
</a>
|
||||
{:else}
|
||||
<span class="text-slate-400">Fetched content</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if data.text && typeof data.text === 'string'}
|
||||
<p class="mt-2 line-clamp-4 text-sm text-slate-400">{data.text.substring(0, 300)}{data.text.length > 300 ? '...' : ''}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if parsed.type === 'json'}
|
||||
{@const data = parsed.data as Record<string, unknown>}
|
||||
<div class="my-3 rounded-xl border border-slate-700/50 bg-slate-800/50 p-3">
|
||||
@@ -146,6 +194,10 @@
|
||||
</div>
|
||||
|
||||
{:else}
|
||||
<!-- Fallback: just show the text -->
|
||||
<p class="text-slate-300">{parsed.data}</p>
|
||||
<!-- Fallback: just show the text (if not empty/whitespace) -->
|
||||
{#if typeof parsed.data === 'string' && parsed.data.trim().length > 0}
|
||||
<div class="my-3 rounded-xl border border-slate-700/50 bg-slate-800/50 p-3">
|
||||
<p class="text-sm text-slate-300">{parsed.data}</p>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user