Jay V 8 месяцев назад
Родитель
Сommit
a74f27e59a
2 измененных файлов с 200 добавлено и 3 удалено
  1. 167 2
      packages/web/src/components/Share.tsx
  2. 33 1
      packages/web/src/components/share.module.css

+ 167 - 2
packages/web/src/components/Share.tsx

@@ -21,7 +21,9 @@ import {
   IconCommandLine,
   IconChevronRight,
   IconPencilSquare,
+  IconRectangleStack,
   IconWrenchScrewdriver,
+  IconDocumentArrowDown,
 } from "./icons"
 import DiffView from "./DiffView"
 import CodeBlock from "./CodeBlock"
@@ -158,10 +160,12 @@ function ProviderIcon(props: { provider: string; size?: number }) {
 }
 
 interface ResultsButtonProps extends JSX.HTMLAttributes<HTMLButtonElement> {
+  showCopy?: string
+  hideCopy?: string
   results: boolean
 }
 function ResultsButton(props: ResultsButtonProps) {
-  const [local, rest] = splitProps(props, ["results"])
+  const [local, rest] = splitProps(props, ["results", "showCopy", "hideCopy"])
   return (
     <button
       type="button"
@@ -169,7 +173,11 @@ function ResultsButton(props: ResultsButtonProps) {
       data-element-button-more
       {...rest}
     >
-      <span>{local.results ? "Hide results" : "Show results"}</span>
+      <span>
+        {local.results
+          ? local.hideCopy || "Hide results"
+          : local.showCopy || "Show results"}
+      </span>
       <span data-button-icon>
         <Show
           when={local.results}
@@ -726,6 +734,163 @@ export default function Share(props: { api: string }) {
                             </div>
                           )}
                         </Match>
+                        {/* LS tool */}
+                        <Match
+                          when={
+                            msg.role === "assistant" &&
+                            part.type === "tool-invocation" &&
+                            part.toolInvocation.toolName === "opencode_list" &&
+                            part
+                          }
+                        >
+                          {(part) => {
+                            const metadata = createMemo(() =>
+                              msg.metadata?.tool[part().toolInvocation.toolCallId]
+                            )
+                            const args = part().toolInvocation.args
+                            const path = args.path
+
+                            const duration = createMemo(() =>
+                              DateTime.fromMillis(metadata()?.time.end || 0).diff(
+                                DateTime.fromMillis(metadata()?.time.start || 0),
+                              ).toMillis(),
+                            )
+
+                            return (
+                              <div data-section="part" data-part-type="tool-list">
+                                <div data-section="decoration">
+                                  <div title="List files">
+                                    <IconRectangleStack width={18} height={18} />
+                                  </div>
+                                  <div></div>
+                                </div>
+                                <div data-section="content">
+                                  <div data-part-tool-body>
+                                    <span data-part-title data-size="md">
+                                      <span data-element-label>LS</span>
+                                      <b>{path}</b>
+                                    </span>
+                                    <Switch>
+                                      <Match
+                                        when={
+                                          part().toolInvocation.state ===
+                                          "result" &&
+                                          part().toolInvocation.result
+                                        }
+                                      >
+                                        <div data-part-tool-result>
+                                          <ResultsButton
+                                            results={results()}
+                                            onClick={() => showResults((e) => !e)}
+                                          />
+                                          <Show when={results()}>
+                                            <TextPart
+                                              expand
+                                              data-size="sm"
+                                              data-color="dimmed"
+                                              text={part().toolInvocation.result}
+                                            />
+                                          </Show>
+                                        </div>
+                                      </Match>
+                                    </Switch>
+                                  </div>
+                                  <ToolFooter time={duration()} />
+                                </div>
+                              </div>
+                            )
+                          }}
+                        </Match>
+                        {/* Read tool */}
+                        <Match
+                          when={
+                            msg.role === "assistant" &&
+                            part.type === "tool-invocation" &&
+                            part.toolInvocation.toolName === "opencode_read" &&
+                            part
+                          }
+                        >
+                          {(part) => {
+                            const metadata = createMemo(() => msg.metadata?.tool[part().toolInvocation.toolCallId])
+                            const args = part().toolInvocation.args
+                            const filePath = args.filePath
+                            const hasError = metadata()?.error
+                            const preview = metadata()?.preview
+                            const result = part().toolInvocation.state === "result" && part().toolInvocation.result
+
+                            const duration = createMemo(() =>
+                              DateTime.fromMillis(metadata()?.time.end || 0).diff(
+                                DateTime.fromMillis(metadata()?.time.start || 0),
+                              ).toMillis(),
+                            )
+
+                            return (
+                              <div data-section="part" data-part-type="tool-read">
+                                <div data-section="decoration">
+                                  <div title="Read file">
+                                    <IconDocumentArrowDown width={18} height={18} />
+                                  </div>
+                                  <div></div>
+                                </div>
+                                <div data-section="content">
+                                  <div data-part-tool-body>
+                                    <span data-part-title data-size="md">
+                                      <span data-element-label>Read</span>
+                                      <b>{filePath}</b>
+                                    </span>
+                                    <Switch>
+                                      <Match when={hasError}>
+                                        <div data-part-tool-result>
+                                          <TextPart
+                                            expand
+                                            text={result}
+                                            data-size="sm"
+                                            data-color="dimmed"
+                                          />
+                                        </div>
+                                      </Match>
+                                      <Match when={preview}>
+                                        <div data-part-tool-result>
+                                          <ResultsButton
+                                            showCopy="Show preview"
+                                            hideCopy="Hide preview"
+                                            results={results()}
+                                            onClick={() => showResults((e) => !e)}
+                                          />
+                                          <Show when={results()}>
+                                            <div data-part-tool-code>
+                                              <CodeBlock
+                                                lang={getFileType(filePath)}
+                                                code={preview}
+                                              />
+                                            </div>
+                                          </Show>
+                                        </div>
+                                      </Match>
+                                      <Match when={result}>
+                                        <div data-part-tool-result>
+                                          <ResultsButton
+                                            results={results()}
+                                            onClick={() => showResults((e) => !e)}
+                                          />
+                                          <Show when={results()}>
+                                            <TextPart
+                                              expand
+                                              text={result}
+                                              data-size="sm"
+                                              data-color="dimmed"
+                                            />
+                                          </Show>
+                                        </div>
+                                      </Match>
+                                    </Switch>
+                                  </div>
+                                  <ToolFooter time={duration()} />
+                                </div>
+                              </div>
+                            )
+                          }}
+                        </Match>
                         {/* Edit tool */}
                         <Match
                           when={

+ 33 - 1
packages/web/src/components/share.module.css

@@ -314,8 +314,40 @@
     }
   }
 
+  [data-part-type="tool-list"],
+  [data-part-type="tool-read"] {
+    & > [data-section="content"] > [data-part-tool-body] {
+      gap: 0.5rem;
+    }
+    [data-part-title] {
+      display: flex;
+      align-items: flex-start;
+      gap: 0.5rem;
+
+      b {
+        color: var(--sl-color-text);
+        word-break: break-all;
+      }
+    }
+  }
+
+  [data-part-type="tool-read"] {
+    [data-part-tool-result] {
+      [data-part-tool-code] {
+        border: 1px solid var(--sl-color-divider);
+        border-radius: 0.25rem;
+        padding: 0.5rem calc(0.5rem + 3px);
+
+        pre {
+          line-height: 1.6;
+          font-size: 0.75rem;
+        }
+      }
+    }
+  }
+
   [data-part-type="tool-edit"] {
-    [data-part-tool-body] {
+    & > [data-section="content"] > [data-part-tool-body] {
       gap: 0.5rem;
     }
     [data-part-title] {