GenericTool.tsx 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { Box, Text } from "ink"
  2. import * as theme from "../../theme.js"
  3. import { Icon } from "../Icon.js"
  4. import type { ToolRendererProps } from "./types.js"
  5. import { truncateText, sanitizeContent, getToolDisplayName, getToolIconName } from "./utils.js"
  6. const MAX_CONTENT_LINES = 12
  7. export function GenericTool({ toolData, rawContent }: ToolRendererProps) {
  8. const iconName = getToolIconName(toolData.tool)
  9. const displayName = getToolDisplayName(toolData.tool)
  10. // Gather all available information
  11. const path = toolData.path
  12. const content = toolData.content ? sanitizeContent(toolData.content) : ""
  13. const reason = toolData.reason ? sanitizeContent(toolData.reason) : ""
  14. const mode = toolData.mode
  15. // Build display content from available fields
  16. let displayContent = content || reason || ""
  17. // If we have no structured content but have raw content, try to parse it
  18. if (!displayContent && rawContent) {
  19. try {
  20. const parsed = JSON.parse(rawContent)
  21. // Extract any content-like fields
  22. displayContent = sanitizeContent(parsed.content || parsed.output || parsed.result || parsed.reason || "")
  23. } catch {
  24. // Use raw content as-is if not JSON
  25. displayContent = sanitizeContent(rawContent)
  26. }
  27. }
  28. const { text: previewContent, truncated, hiddenLines } = truncateText(displayContent, MAX_CONTENT_LINES)
  29. return (
  30. <Box flexDirection="column" paddingX={1}>
  31. {/* Header */}
  32. <Box>
  33. <Icon name={iconName} color={theme.toolHeader} />
  34. <Text bold color={theme.toolHeader}>
  35. {" "}
  36. {displayName}
  37. </Text>
  38. </Box>
  39. {/* Path if present */}
  40. {path && (
  41. <Box marginLeft={2}>
  42. <Text color={theme.dimText}>path: </Text>
  43. <Text color={theme.text} bold>
  44. {path}
  45. </Text>
  46. {toolData.isOutsideWorkspace && (
  47. <Text color={theme.warningColor} dimColor>
  48. {" "}
  49. ⚠ outside workspace
  50. </Text>
  51. )}
  52. {toolData.isProtected && <Text color={theme.errorColor}> 🔒 protected</Text>}
  53. </Box>
  54. )}
  55. {/* Mode if present */}
  56. {mode && (
  57. <Box marginLeft={2}>
  58. <Text color={theme.dimText}>mode: </Text>
  59. <Text color={theme.userHeader} bold>
  60. {mode}
  61. </Text>
  62. </Box>
  63. )}
  64. {/* Content */}
  65. {previewContent && (
  66. <Box flexDirection="column" marginLeft={2} marginTop={path || mode ? 1 : 0}>
  67. {previewContent.split("\n").map((line, i) => (
  68. <Text key={i} color={theme.toolText}>
  69. {line}
  70. </Text>
  71. ))}
  72. {truncated && (
  73. <Text color={theme.dimText} dimColor>
  74. ... ({hiddenLines} more lines)
  75. </Text>
  76. )}
  77. </Box>
  78. )}
  79. </Box>
  80. )
  81. }