BrowserEvalTool.tsx

  1import React, { useState } from "react";
  2import { LLMContent } from "../types";
  3
  4interface BrowserEvalToolProps {
  5  // For tool_use (pending state)
  6  toolInput?: unknown; // { expression: string }
  7  isRunning?: boolean;
  8
  9  // For tool_result (completed state)
 10  toolResult?: LLMContent[];
 11  hasError?: boolean;
 12  executionTime?: string;
 13}
 14
 15function BrowserEvalTool({
 16  toolInput,
 17  isRunning,
 18  toolResult,
 19  hasError,
 20  executionTime,
 21}: BrowserEvalToolProps) {
 22  const [isExpanded, setIsExpanded] = useState(false);
 23
 24  // Extract expression from toolInput
 25  const expression =
 26    typeof toolInput === "object" &&
 27    toolInput !== null &&
 28    "expression" in toolInput &&
 29    typeof (toolInput as { expression?: unknown }).expression === "string"
 30      ? (toolInput as { expression: string }).expression
 31      : typeof toolInput === "string"
 32        ? toolInput
 33        : "";
 34
 35  // Extract result from toolResult
 36  const result =
 37    toolResult && toolResult.length > 0 && toolResult[0].Text ? toolResult[0].Text : "";
 38
 39  // Truncate expression for display
 40  const truncateText = (text: string, maxLen: number = 300) => {
 41    if (text.length <= maxLen) return text;
 42    return text.substring(0, maxLen) + "...";
 43  };
 44
 45  const displayExpression = truncateText(expression);
 46  const isComplete = !isRunning && toolResult !== undefined;
 47
 48  return (
 49    <div className="tool" data-testid={isComplete ? "tool-call-completed" : "tool-call-running"}>
 50      <div className="tool-header" onClick={() => setIsExpanded(!isExpanded)}>
 51        <div className="tool-summary">
 52          <span className={`tool-emoji ${isRunning ? "running" : ""}`}></span>
 53          <span className="tool-command">{displayExpression}</span>
 54          {isComplete && hasError && <span className="tool-error"></span>}
 55          {isComplete && !hasError && <span className="tool-success"></span>}
 56        </div>
 57        <button
 58          className="tool-toggle"
 59          aria-label={isExpanded ? "Collapse" : "Expand"}
 60          aria-expanded={isExpanded}
 61        >
 62          <svg
 63            width="12"
 64            height="12"
 65            viewBox="0 0 12 12"
 66            fill="none"
 67            xmlns="http://www.w3.org/2000/svg"
 68            style={{
 69              transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
 70              transition: "transform 0.2s",
 71            }}
 72          >
 73            <path
 74              d="M4.5 3L7.5 6L4.5 9"
 75              stroke="currentColor"
 76              strokeWidth="1.5"
 77              strokeLinecap="round"
 78              strokeLinejoin="round"
 79            />
 80          </svg>
 81        </button>
 82      </div>
 83
 84      {isExpanded && (
 85        <div className="tool-details">
 86          <div className="tool-section">
 87            <div className="tool-label">Expression:</div>
 88            <pre className="tool-code">{expression}</pre>
 89          </div>
 90
 91          {isComplete && (
 92            <div className="tool-section">
 93              <div className="tool-label">
 94                Result{hasError ? " (Error)" : ""}:
 95                {executionTime && <span className="tool-time">{executionTime}</span>}
 96              </div>
 97              <pre className={`tool-code ${hasError ? "error" : ""}`}>
 98                {result || "(no result)"}
 99              </pre>
100            </div>
101          )}
102        </div>
103      )}
104    </div>
105  );
106}
107
108export default BrowserEvalTool;