Jay V 7 месяцев назад
Родитель
Сommit
749e7838a4

+ 1 - 0
packages/web/src/components/Share.tsx

@@ -340,6 +340,7 @@ export default function Share(props: {
                   const filteredParts = createMemo(() =>
                     msg.parts.filter((x, index) => {
                       if (x.type === "step-start" && index > 0) return false
+                      if (x.type === "snapshot") return false
                       if (x.type === "step-finish") return false
                       if (x.type === "text" && x.synthetic === true) return false
                       if (x.type === "tool" && x.tool === "todoread") return false

+ 8 - 0
packages/web/src/components/icons/custom.tsx

@@ -58,3 +58,11 @@ export function IconMeta(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
     </svg>
   )
 }
+
+// https://icones.js.org/collection/ri?s=robot&icon=ri:robot-2-line
+export function IconRobot(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
+  return (
+    <svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+      <path fill="currentColor" d="M13.5 2c0 .444-.193.843-.5 1.118V5h5a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V8a3 3 0 0 1 3-3h5V3.118A1.5 1.5 0 1 1 13.5 2M6 7a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1zm-4 3H0v6h2zm20 0h2v6h-2zM9 14.5a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3m6 0a1.5 1.5 0 1 0 0-3a1.5 1.5 0 0 0 0 3" /></svg>
+  )
+}

+ 4 - 12
packages/web/src/components/share/content-markdown.module.css

@@ -1,21 +1,13 @@
 .root {
-  border: 1px solid var(--sl-color-blue-high);
-  padding: 0.5rem calc(0.5rem + 3px);
-  border-radius: 0.25rem;
   display: flex;
   flex-direction: column;
   align-items: flex-start;
   gap: 1rem;
-  align-self: flex-start;
-
-  &[data-highlight="true"] {
-    background-color: var(--sl-color-blue-low);
-  }
 
   [data-slot="expand-button"] {
     flex: 0 0 auto;
     padding: 2px 0;
-    font-size: 0.75rem;
+    font-size: 0.857em;
   }
 
   [data-slot="markdown"] {
@@ -29,7 +21,7 @@
       display: block;
     }
 
-    font-size: 0.875rem;
+    font-size: 1em;
     line-height: 1.5;
 
     p,
@@ -61,7 +53,7 @@
     h4,
     h5,
     h6 {
-      font-size: 0.875rem;
+      font-size: 1em;
       font-weight: 600;
       margin-bottom: 0.5rem;
     }
@@ -75,7 +67,7 @@
       background-color: var(--sl-color-bg-surface) !important;
       padding: 0.5rem 0.75rem;
       line-height: 1.6;
-      font-size: 0.75rem;
+      font-size: 0.857em;
       white-space: pre-wrap;
       word-break: break-word;
 

+ 5 - 5
packages/web/src/components/share/content-markdown.tsx

@@ -1,10 +1,10 @@
-import style from "./content-markdown.module.css"
-import { createResource, createSignal } from "solid-js"
-import { createOverflow } from "./common"
-import { transformerNotationDiff } from "@shikijs/transformers"
 import { marked } from "marked"
-import markedShiki from "marked-shiki"
 import { codeToHtml } from "shiki"
+import markedShiki from "marked-shiki"
+import { createOverflow } from "./common"
+import { createResource, createSignal } from "solid-js"
+import { transformerNotationDiff } from "@shikijs/transformers"
+import style from "./content-markdown.module.css"
 
 const markedWithShiki = marked.use(
   markedShiki({

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

@@ -103,7 +103,7 @@
   [data-component="content"] {
     flex: 1 1 auto;
     min-width: 0;
-    padding: 0 0 0.375rem;
+    padding: 0 0 1rem;
     display: flex;
     flex-direction: column;
     gap: 1rem;
@@ -135,6 +135,14 @@
     gap: 1rem;
     flex-grow: 1;
     max-width: var(--md-tool-width);
+
+    & > [data-component="assistant-text-markdown"] {
+      align-self: flex-start;
+      font-size: 0.875rem;
+      border: 1px solid var(--sl-color-blue-high);
+      padding: 0.5rem calc(0.5rem + 3px);
+      border-radius: 0.25rem;
+    }
   }
 
   [data-component="step-start"] {
@@ -239,6 +247,24 @@
         max-width: var(--lg-tool-width);
       }
     }
+    &[data-tool="task"] {
+      [data-component="tool-input"] {
+        font-size: 0.75rem;
+        line-height: 1.5;
+        max-width: var(--sm-tool-width);
+        display: -webkit-box;
+        -webkit-line-clamp: 3;
+        -webkit-box-orient: vertical;
+        overflow: hidden;
+      }
+      [data-component="tool-output"] {
+        max-width: var(--sm-tool-width);
+        font-size: 0.75rem;
+        border: 1px solid var(--sl-color-divider);
+        padding: 0.5rem calc(0.5rem + 3px);
+        border-radius: 0.25rem;
+      }
+    }
   }
 
   [data-component="tool-title"] {

+ 40 - 20
packages/web/src/components/share/part.tsx

@@ -19,25 +19,25 @@ import {
   IconMagnifyingGlass,
   IconDocumentMagnifyingGlass,
 } from "../icons"
-import { IconMeta, IconOpenAI, IconGemini, IconAnthropic } from "../icons/custom"
-import { formatDuration } from "../share/common"
+import { IconMeta, IconRobot, IconOpenAI, IconGemini, IconAnthropic } from "../icons/custom"
 import { ContentCode } from "./content-code"
 import { ContentDiff } from "./content-diff"
 import { ContentText } from "./content-text"
+import { ContentBash } from "./content-bash"
 import { ContentError } from "./content-error"
+import { formatDuration } from "../share/common"
 import { ContentMarkdown } from "./content-markdown"
-import { ContentBash } from "./content-bash"
 import type { MessageV2 } from "opencode/session/message-v2"
 import type { Diagnostic } from "vscode-languageserver-types"
 
 import styles from "./part.module.css"
 
-const MIN_DURATION = 2
+const MIN_DURATION = 2000
 
 export interface PartProps {
   index: number
   message: MessageV2.Info
-  part: MessageV2.AssistantPart | MessageV2.UserPart
+  part: MessageV2.Part
   last: boolean
 }
 
@@ -114,7 +114,7 @@ export function Part(props: PartProps) {
                 <IconGlobeAlt width={18} height={18} />
               </Match>
               <Match when={props.part.type === "tool" && props.part.tool === "task"}>
-                <IconRectangleStack width={18} height={18} />
+                <IconRobot width={18} height={18} />
               </Match>
               <Match when={true}>
                 <IconSparkles width={18} height={18} />
@@ -131,12 +131,13 @@ export function Part(props: PartProps) {
         {props.message.role === "user" && props.part.type === "text" && (
           <div data-component="user-text">
             <ContentText text={props.part.text} expand={props.last} />
-            <Spacer />
           </div>
         )}
         {props.message.role === "assistant" && props.part.type === "text" && (
           <div data-component="assistant-text">
-            <ContentMarkdown expand={props.last} text={props.part.text} />
+            <div data-component="assistant-text-markdown">
+              <ContentMarkdown expand={props.last} text={props.part.text} />
+            </div>
             {props.last && props.message.role === "assistant" && props.message.time.completed && (
               <Footer
                 title={DateTime.fromMillis(props.message.time.completed).toLocaleString(
@@ -146,7 +147,6 @@ export function Part(props: PartProps) {
                 {DateTime.fromMillis(props.message.time.completed).toLocaleString(DateTime.DATETIME_MED)}
               </Footer>
             )}
-            <Spacer />
           </div>
         )}
         {props.message.role === "user" && props.part.type === "file" && (
@@ -245,6 +245,14 @@ export function Part(props: PartProps) {
                       state={props.part.state}
                     />
                   </Match>
+                  <Match when={props.part.tool === "task"}>
+                    <TaskTool
+                      id={props.part.id}
+                      tool={props.part.tool}
+                      message={props.message}
+                      state={props.part.state}
+                    />
+                  </Match>
                   <Match when={true}>
                     <FallbackTool
                       message={props.message}
@@ -256,11 +264,10 @@ export function Part(props: PartProps) {
                 </Switch>
               </div>
               <ToolFooter
-                time={
-                  DateTime.fromMillis(props.part.state.time.start)
-                    .diff(DateTime.fromMillis(props.part.state.time.end))
-                    .toMillis()
-                } />
+                time={DateTime.fromMillis(props.part.state.time.end)
+                  .diff(DateTime.fromMillis(props.part.state.time.start))
+                  .toMillis()}
+              />
             </>
           )}
       </div>
@@ -636,12 +643,25 @@ function Footer(props: ParentProps<{ title: string }>) {
 }
 
 function ToolFooter(props: { time: number }) {
-  return props.time > MIN_DURATION ? (
-    <Footer title={`${props.time}ms`}>
-      {formatDuration(props.time)}
-    </Footer>
-  ) : (
-    <Spacer />
+  return props.time > MIN_DURATION && <Footer title={`${props.time}ms`}>{formatDuration(props.time)}</Footer>
+}
+
+function TaskTool(props: ToolProps) {
+  return (
+    <>
+      <div data-component="tool-title">
+        <span data-slot="name">Task</span>
+        <span data-slot="target">{props.state.input.description}</span>
+      </div>
+      <div data-component="tool-input">
+        &ldquo;{props.state.input.prompt}&rdquo;
+      </div>
+      <ResultsButton showCopy="Show output" hideCopy="Hide output">
+        <div data-component="tool-output">
+          <ContentMarkdown expand text={props.state.output} />
+        </div>
+      </ResultsButton>
+    </>
   )
 }