Jay V 8 месяцев назад
Родитель
Сommit
6e4ef585d8
2 измененных файлов с 93 добавлено и 62 удалено
  1. 61 53
      packages/web/src/components/Share.tsx
  2. 32 9
      packages/web/src/components/share.module.css

+ 61 - 53
packages/web/src/components/Share.tsx

@@ -172,13 +172,29 @@ function flattenToolArgs(obj: any, prefix: string = ""): Array<[string, any]> {
   return entries
 }
 
-export function getDiagnostics(
+function formatErrorString(error: string): JSX.Element {
+  const errorMarker = "Error: "
+  const startsWithError = error.startsWith(errorMarker)
+
+  return startsWithError ? (
+    <p>
+      <span data-color="red" data-marker="label" data-separator>
+        Error
+      </span>
+      <span>{error.slice(errorMarker.length)}</span>
+    </p>
+  ) : (
+    <p><span data-color="dimmed">{error}</span></p>
+  )
+}
+
+function getDiagnostics(
   diagnosticsByFile: Record<string, Diagnostic[]>,
   currentFile: string
-): string[] {
+): JSX.Element[] {
   // Return a flat array of error diagnostics, in the format:
-  // "ERROR [65:20] Property 'x' does not exist on type 'Y'"
-  const result: string[] = []
+  // "Error [65:20] Property 'x' does not exist on type 'Y'"
+  const result: JSX.Element[] = []
 
   if (
     diagnosticsByFile === undefined || diagnosticsByFile[currentFile] === undefined
@@ -192,7 +208,15 @@ export function getDiagnostics(
       const line = d.range.start.line + 1 // 1-based
       const column = d.range.start.character + 1 // 1-based
 
-      result.push(`\x1b[31mERROR\x1b[0m \x1b[2m[${line}:${column}]\x1b[0m ${d.message}`)
+      result.push(
+        <p>
+          <span data-color="red" data-marker="label">Error</span>
+          <span data-color="dimmed" data-separator>
+            [{line}:{column}]
+          </span>
+          <span>{d.message}</span>
+        </p>
+      )
     }
   }
 
@@ -324,46 +348,44 @@ function TextPart(props: TextPartProps) {
   )
 }
 
-interface LspPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
-  text: string
+interface ErrorPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
   expand?: boolean
 }
-function LspPart(props: LspPartProps) {
-  const [local, rest] = splitProps(props, ["text", "expand"])
+function ErrorPart(props: ErrorPartProps) {
+  const [local, rest] = splitProps(props, ["expand", "children"])
   const [expanded, setExpanded] = createSignal(false)
   const [overflowed, setOverflowed] = createSignal(false)
   let preEl: HTMLElement | undefined
 
   function checkOverflow() {
-    if (!preEl) return
-
-    const code = preEl.getElementsByTagName("code")[0]
-
-    if (code && !local.expand) {
-      setOverflowed(preEl.clientHeight < code.offsetHeight)
+    if (preEl && !local.expand) {
+      setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
     }
   }
 
   onMount(() => {
+    checkOverflow()
     window.addEventListener("resize", checkOverflow)
   })
 
+  createEffect(() => {
+    local.children
+    setTimeout(checkOverflow, 0)
+  })
+
   onCleanup(() => {
     window.removeEventListener("resize", checkOverflow)
   })
 
   return (
     <div
-      class={styles["message-lsp"]}
+      class={styles["message-error"]}
       data-expanded={expanded() || local.expand === true}
       {...rest}
     >
-      <CodeBlock
-        lang="ansi"
-        code={local.text}
-        onRendered={checkOverflow}
-        ref={(el) => (preEl = el)}
-      />
+      <div data-section="content" ref={(el) => (preEl = el)}>
+        {local.children}
+      </div>
       {((!local.expand && overflowed()) || expanded()) && (
         <button
           type="button"
@@ -512,6 +534,7 @@ export default function Share(props: {
   info: SessionInfo
   messages: Record<string, SessionMessage>
 }) {
+  let hasScrolled = false
   const id = props.id
 
   const anchorId = createMemo<string | null>(() => {
@@ -586,8 +609,9 @@ export default function Share(props: {
             const [, messageID] = splits
             setStore("messages", messageID, reconcile(d.content))
 
-            if (messageID === anchorId()) {
+            if (!hasScrolled && messageID === anchorId()) {
               scrollToAnchor(window.location.hash.slice(1))
+              hasScrolled = true
             }
           }
         } catch (error) {
@@ -1241,12 +1265,9 @@ export default function Share(props: {
                                     <Switch>
                                       <Match when={hasError()}>
                                         <div data-part-tool-result>
-                                          <TextPart
-                                            expand
-                                            text={toolData()?.result}
-                                            data-size="sm"
-                                            data-color="dimmed"
-                                          />
+                                          <ErrorPart>
+                                            {formatErrorString(toolData()?.result)}
+                                          </ErrorPart>
                                         </div>
                                       </Match>
                                       <Match when={preview()}>
@@ -1339,19 +1360,14 @@ export default function Share(props: {
                                       <b>{filePath()}</b>
                                     </div>
                                     <Show when={diagnostics().length > 0}>
-                                      <LspPart
-                                        text={diagnostics().join("\n\n")}
-                                      />
+                                      <ErrorPart>{diagnostics()}</ErrorPart>
                                     </Show>
                                     <Switch>
                                       <Match when={hasError()}>
                                         <div data-part-tool-result>
-                                          <TextPart
-                                            expand
-                                            text={toolData()?.result}
-                                            data-size="sm"
-                                            data-color="dimmed"
-                                          />
+                                          <ErrorPart>
+                                            {formatErrorString(toolData()?.result)}
+                                          </ErrorPart>
                                         </div>
                                       </Match>
                                       <Match when={content()}>
@@ -1429,12 +1445,9 @@ export default function Share(props: {
                                     <Switch>
                                       <Match when={hasError()}>
                                         <div data-part-tool-result>
-                                          <TextPart
-                                            expand
-                                            data-size="sm"
-                                            data-color="dimmed"
-                                            text={message()}
-                                          />
+                                          <ErrorPart>
+                                            {formatErrorString(message())}
+                                          </ErrorPart>
                                         </div>
                                       </Match>
                                       <Match when={diff()}>
@@ -1448,9 +1461,7 @@ export default function Share(props: {
                                       </Match>
                                     </Switch>
                                     <Show when={diagnostics().length > 0}>
-                                      <LspPart
-                                        text={diagnostics().join("\n\n")}
-                                      />
+                                      <ErrorPart>{diagnostics()}</ErrorPart>
                                     </Show>
                                   </div>
                                   <ToolFooter time={toolData()?.duration || 0} />
@@ -1601,12 +1612,9 @@ export default function Share(props: {
                                     <Switch>
                                       <Match when={hasError()}>
                                         <div data-part-tool-result>
-                                          <TextPart
-                                            expand
-                                            text={toolData()?.result}
-                                            data-size="sm"
-                                            data-color="dimmed"
-                                          />
+                                          <ErrorPart>
+                                            {formatErrorString(toolData()?.result)}
+                                          </ErrorPart>
                                         </div>
                                       </Match>
                                       <Match when={toolData()?.result}>

+ 32 - 9
packages/web/src/components/share.module.css

@@ -421,7 +421,7 @@
   }
 }
 
-.message-lsp {
+.message-error {
   background-color: var(--sl-color-bg-surface);
   padding: 0.5rem calc(0.5rem + 3px);
   border-radius: 0.25rem;
@@ -432,24 +432,47 @@
   align-self: flex-start;
   max-width: var(--md-tool-width);
 
-    padding: 0.5rem calc(0.5rem + 3px);
-
-    pre {
-      --shiki-dark-bg: var(--sl-color-bg-surface) !important;
-      background-color: var(--sl-color-bg-surface) !important;
-      line-height: 1.4;
+  [data-section="content"] {
+    p {
+      margin-bottom: 0.5rem;
+      line-height: 1.5;
       font-size: 0.75rem;
       white-space: pre-wrap;
       word-break: break-word;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      span {
+        margin-right: 0.25rem;
+        &:last-child {
+          margin-right: 0;
+        }
+      }
+      span[data-color="red"] {
+        color: var(--sl-color-red);
+      }
+      span[data-color="dimmed"] {
+        color: var(--sl-color-text-dimmed);
+      }
+      span[data-marker="label"] {
+        text-transform: uppercase;
+        letter-spacing: -0.5px;
+      }
+      span[data-separator] {
+        margin-right: 0.375rem;
+      }
     }
+  }
 
   &[data-expanded="true"] {
-    pre {
+    [data-section="content"] {
       display: block;
     }
   }
   &[data-expanded="false"] {
-    pre {
+    [data-section="content"] {
       display: -webkit-box;
       -webkit-box-orient: vertical;
       -webkit-line-clamp: 7;