/* eslint-disable react-refresh/only-export-components -- renderer constant is co-located with its inline component by design */
import type { ToolResultBlock } from '@agentscope-ai/agentscope/message';
import { ChevronRight } from 'lucide-react';
import { useState } from 'react';
import { CornerLine, getFilePath, ToolStateIcon } from './_shared';
import type { TFunction, ToolCallWithResult, ToolRenderer } from './types';
import { Button } from '@/components/ui/button.tsx';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { formatNumber } from '@/utils/common';
/** Count lines in a tool result's output string, including text content from
* non-text blocks. Returns 0 when the result is missing or empty. */
function countResultLines(result?: ToolResultBlock): number {
if (!result) return 0;
let str: string;
if (typeof result.output === 'string') {
str = result.output;
} else {
str = result.output.map((b) => (b.type === 'text' ? b.text : '')).join('\n');
}
if (!str) return 0;
return str.split('\n').length;
}
/** Collapse consecutive Read calls of the same `file_path` into one bucket so
* the path is shown once, followed by one corner-row per call. Order is
* preserved; a different path or a re-occurrence after another path starts a
* new bucket. */
function groupByConsecutivePath(
calls: ToolCallWithResult[],
): Array<{ path: string; calls: ToolCallWithResult[] }> {
const groups: Array<{ path: string; calls: ToolCallWithResult[] }> = [];
for (const item of calls) {
const path = getFilePath(item.call.input);
const last = groups[groups.length - 1];
if (last && last.path === path) {
last.calls.push(item);
} else {
groups.push({ path, calls: [item] });
}
}
return groups;
}
function ReadGroup({ calls, t }: { calls: ToolCallWithResult[]; t: TFunction }) {
const [open, setOpen] = useState(false);
const name = t('tool.read.name');
return (