basic-tool.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import { createEffect, createSignal, For, Match, Show, Switch, type JSX } from "solid-js"
  2. import { Collapsible } from "./collapsible"
  3. import { Icon, IconProps } from "./icon"
  4. export type TriggerTitle = {
  5. title: string
  6. titleClass?: string
  7. subtitle?: string
  8. subtitleClass?: string
  9. args?: string[]
  10. argsClass?: string
  11. action?: JSX.Element
  12. }
  13. const isTriggerTitle = (val: any): val is TriggerTitle => {
  14. return (
  15. typeof val === "object" && val !== null && "title" in val && (typeof Node === "undefined" || !(val instanceof Node))
  16. )
  17. }
  18. export interface BasicToolProps {
  19. icon: IconProps["name"]
  20. trigger: TriggerTitle | JSX.Element
  21. children?: JSX.Element
  22. hideDetails?: boolean
  23. defaultOpen?: boolean
  24. forceOpen?: boolean
  25. locked?: boolean
  26. onSubtitleClick?: () => void
  27. }
  28. export function BasicTool(props: BasicToolProps) {
  29. const [open, setOpen] = createSignal(props.defaultOpen ?? false)
  30. createEffect(() => {
  31. if (props.forceOpen) setOpen(true)
  32. })
  33. const handleOpenChange = (value: boolean) => {
  34. if (props.locked && !value) return
  35. setOpen(value)
  36. }
  37. return (
  38. <Collapsible open={open()} onOpenChange={handleOpenChange}>
  39. <Collapsible.Trigger>
  40. <div data-component="tool-trigger">
  41. <div data-slot="basic-tool-tool-trigger-content">
  42. <Icon name={props.icon} size="small" />
  43. <div data-slot="basic-tool-tool-info">
  44. <Switch>
  45. <Match when={isTriggerTitle(props.trigger) && props.trigger}>
  46. {(trigger) => (
  47. <div data-slot="basic-tool-tool-info-structured">
  48. <div data-slot="basic-tool-tool-info-main">
  49. <span
  50. data-slot="basic-tool-tool-title"
  51. classList={{
  52. [trigger().titleClass ?? ""]: !!trigger().titleClass,
  53. }}
  54. >
  55. {trigger().title}
  56. </span>
  57. <Show when={trigger().subtitle}>
  58. <span
  59. data-slot="basic-tool-tool-subtitle"
  60. classList={{
  61. [trigger().subtitleClass ?? ""]: !!trigger().subtitleClass,
  62. clickable: !!props.onSubtitleClick,
  63. }}
  64. onClick={(e) => {
  65. if (props.onSubtitleClick) {
  66. e.stopPropagation()
  67. props.onSubtitleClick()
  68. }
  69. }}
  70. >
  71. {trigger().subtitle}
  72. </span>
  73. </Show>
  74. <Show when={trigger().args?.length}>
  75. <For each={trigger().args}>
  76. {(arg) => (
  77. <span
  78. data-slot="basic-tool-tool-arg"
  79. classList={{
  80. [trigger().argsClass ?? ""]: !!trigger().argsClass,
  81. }}
  82. >
  83. {arg}
  84. </span>
  85. )}
  86. </For>
  87. </Show>
  88. </div>
  89. <Show when={trigger().action}>{trigger().action}</Show>
  90. </div>
  91. )}
  92. </Match>
  93. <Match when={true}>{props.trigger as JSX.Element}</Match>
  94. </Switch>
  95. </div>
  96. </div>
  97. <Show when={props.children && !props.hideDetails && !props.locked}>
  98. <Collapsible.Arrow />
  99. </Show>
  100. </div>
  101. </Collapsible.Trigger>
  102. <Show when={props.children && !props.hideDetails}>
  103. <Collapsible.Content>{props.children}</Collapsible.Content>
  104. </Show>
  105. </Collapsible>
  106. )
  107. }
  108. export function GenericTool(props: { tool: string; hideDetails?: boolean }) {
  109. return <BasicTool icon="mcp" trigger={{ title: props.tool }} hideDetails={props.hideDetails} />
  110. }