File size: 4,634 Bytes
3e05655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
138
139
140
import { Collapsible } from "@kobalte/core/collapsible"
import { type ComponentProps, type JSX, For, Show, createMemo, splitProps } from "solid-js"
import { DiffChanges } from "@opencode-ai/ui/v2/diff-changes-v2"
import { TextShimmerV2 } from "@opencode-ai/ui/v2/text-shimmer-v2"
import "./basic-tool-v2.css"

function ChevronIcon() {
  return (
    <svg
      data-slot="basic-tool-v2-chevron"
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      aria-hidden="true"
    >
      <path
        d="M6.75194 10.6243C6.41861 10.8187 6 10.5783 6 10.1924V5.80837C6 5.42247 6.41861 5.18204 6.75194 5.37648L10.5096 7.56846C10.8404 7.7614 10.8404 8.2393 10.5096 8.43224L6.75194 10.6243Z"
        fill="currentColor"
      />
    </svg>
  )
}

export interface BasicToolV2TriggerTitle {
  title: string
  subtitle?: string
  args?: string[]
  changes?: { additions: number; deletions: number } | { additions: number; deletions: number }[]
  action?: JSX.Element
}

const isTriggerTitle = (val: unknown): val is BasicToolV2TriggerTitle =>
  typeof val === "object" && val !== null && "title" in val && (typeof Node === "undefined" || !(val instanceof Node))

export interface BasicToolV2Props extends Omit<ComponentProps<"div">, "children" | "title"> {
  trigger: BasicToolV2TriggerTitle | JSX.Element
  children?: JSX.Element
  status?: string
  open?: boolean
  defaultOpen?: boolean
  onOpenChange?: (open: boolean) => void
  onSubtitleClick?: () => void
}

export function BasicToolV2(props: BasicToolV2Props) {
  const [local, rest] = splitProps(props, [
    "trigger",
    "children",
    "status",
    "open",
    "defaultOpen",
    "onOpenChange",
    "onSubtitleClick",
    "class",
    "classList",
  ])

  const pending = createMemo(() => local.status === "pending" || local.status === "running")

  const hasChildren = createMemo(() => {
    const c = local.children
    if (c == null) return false
    return true
  })

  const canExpand = createMemo(() => hasChildren() && !pending())

  const handleOpenChange = (value: boolean) => {
    if (pending()) return
    local.onOpenChange?.(value)
  }

  return (
    <Collapsible
      {...rest}
      data-component="basic-tool-v2"
      open={local.open}
      defaultOpen={local.defaultOpen}
      onOpenChange={handleOpenChange}
      disabled={!canExpand()}
      classList={{
        ...local.classList,
        [local.class ?? ""]: !!local.class,
      }}
    >
      <Collapsible.Trigger as="div" role="button" data-slot="basic-tool-v2-trigger">
        <div data-slot="basic-tool-v2-labels">
          <Show when={isTriggerTitle(local.trigger) && local.trigger} fallback={local.trigger as JSX.Element}>
            {(title) => (
              <>
                <span data-slot="basic-tool-v2-title">
                  <TextShimmerV2 text={title().title} active={pending()} />
                </span>
                <Show when={!pending() && title().subtitle}>
                  <span data-slot="basic-tool-v2-sep" aria-hidden="true">
                    ·
                  </span>
                  <span
                    data-slot="basic-tool-v2-subtitle"
                    style={local.onSubtitleClick ? { cursor: "pointer" } : undefined}
                    onClick={(e) => {
                      if (local.onSubtitleClick) {
                        e.stopPropagation()
                        local.onSubtitleClick()
                      }
                    }}
                  >
                    {title().subtitle}
                  </span>
                </Show>
                <Show when={!pending() && title().args?.length}>
                  <For each={title().args}>{(arg) => <span data-slot="basic-tool-v2-arg">{arg}</span>}</For>
                </Show>
                <Show when={!pending() && title().changes}>
                  <span data-slot="basic-tool-v2-diff">
                    <DiffChanges changes={title().changes!} />
                  </span>
                </Show>
                <Show when={!pending() && title().action}>{(action) => action()}</Show>
              </>
            )}
          </Show>
          <Show when={canExpand()}>
            <span data-slot="basic-tool-v2-chevron-wrap">
              <ChevronIcon />
            </span>
          </Show>
        </div>
      </Collapsible.Trigger>
      <Show when={canExpand()}>
        <Collapsible.Content data-slot="basic-tool-v2-content">
          <div data-slot="basic-tool-v2-content-inner">{local.children}</div>
        </Collapsible.Content>
      </Show>
    </Collapsible>
  )
}