KeywordSearchTool.tsx

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