Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | 80x 80x 80x 76x 4x 4x 80x 80x 45x 80x 80x 80x 80x 80x 80x 80x 80x 80x 80x 80x 80x 247x 10x | import { useState, type JSX, type ReactNode } from "react";
import { ChevronRight, FilePen, FileText } from "lucide-react";
import type { ToolCardProps } from "./ToolCardProps.js";
import { ICON_SM, ICON_MD } from "../../utils/iconSize.js";
import styles from "./toolCards.module.scss";
/** Extracts the file path from tool args (handles both `file_path` and `path` variants). */
function getFilePath(args: unknown): string {
Iif (args === null || args === undefined || typeof args !== "object") {
return "";
}
const a = args as Record<string, unknown>;
if (typeof a.file_path === "string") {
return a.file_path;
}
if (typeof a.path === "string") {
return a.path;
}
return "";
}
/** Extracts the basename from a file path (handles both / and \ separators). */
function basename(filePath: string): string {
const parts = filePath.split(/[/\\]/);
return parts[parts.length - 1] || filePath;
}
/** Number of preview lines shown when collapsed. */
const PREVIEW_LINES: number = 5;
/** Extra props for FileReadCard to support write variant styling. */
interface FileReadCardProps extends ToolCardProps {
/** When true, uses green accent and write icon instead of blue/read. */
writeVariant?: boolean;
}
/** Renders a file read/write tool call with syntax-highlighted content preview. */
export function FileReadCard({
tool,
args,
result,
isError,
writeVariant,
}: FileReadCardProps): JSX.Element {
const [expanded, setExpanded] = useState(false);
const filePath = getFilePath(args);
const name = basename(filePath);
const inProgress = result === undefined;
const accentClass: string = isError
? styles.cardRed
: writeVariant
? styles.cardGreen
: styles.cardBlue;
const accentColor: string = writeVariant ? "var(--accent-green)" : "var(--accent-blue)";
const icon: ReactNode = writeVariant ? <FilePen size={ICON_MD} /> : <FileText size={ICON_MD} />;
const testId: string = writeVariant ? "tool-card-file-write" : "tool-card-file-read";
const lines = result?.split("\n") ?? [];
const hasMore = lines.length > PREVIEW_LINES;
const displayLines = expanded ? lines : lines.slice(0, PREVIEW_LINES);
return (
<div
className={`${styles.card} ${accentClass} ${inProgress ? styles.inProgress : ""}`}
data-testid={testId}
>
<div className={styles.header}>
<span className={styles.icon}>{icon}</span>
<span className={styles.toolName} style={{ color: accentColor }}>
{tool}
</span>
{name && (
<span className={styles.fileName} title={filePath}>
{name}
</span>
)}
{!inProgress && lines.length > 0 && (
<>
<span className={styles.spacer} />
<span className={styles.badge}>{lines.length} lines</span>
</>
)}
</div>
{isError && result && (
<pre className={styles.pre} data-testid="tool-card-error">
{result}
</pre>
)}
{!isError && !inProgress && lines.length > 0 && (
<>
<pre className={styles.pre} data-testid="tool-card-content">
{displayLines.map((line, i) => (
<span key={i} className={styles.diffLine}>
<span
style={{
color: "var(--text-tertiary)",
userSelect: "none",
marginRight: "var(--space-sm)",
display: "inline-block",
width: "3ch",
textAlign: "right",
}}
>
{i + 1}
</span>
{line}
</span>
))}
</pre>
{hasMore && (
<button
type="button"
className={styles.bodyToggle}
onClick={() => {
setExpanded((v) => !v);
}}
aria-expanded={expanded}
data-testid="tool-card-toggle"
>
<span
className={`${styles.chevron} ${expanded ? styles.chevronExpanded : ""}`}
aria-hidden="true"
>
<ChevronRight size={ICON_SM} />
</span>
{expanded ? "collapse" : `${lines.length - PREVIEW_LINES} more lines`}
</button>
)}
</>
)}
</div>
);
}
|