Sfoglia il codice sorgente

fix(app): less auto-expand/collapse

Adam 6 mesi fa
parent
commit
c53d1d3ad8

+ 3 - 5
packages/ui/src/components/context-tool-results.tsx

@@ -58,11 +58,9 @@ export function ContextToolGroupHeader(props: {
     <ToolCall
       variant="row"
       icon="magnifying-glass-menu"
-      open={!props.pending && props.open}
-      showArrow={!props.pending}
-      onOpenChange={(v) => {
-        if (!props.pending) props.onOpenChange(v)
-      }}
+      open={props.open}
+      showArrow
+      onOpenChange={props.onOpenChange}
       trigger={
         <div data-component="context-tool-group-trigger" data-pending={props.pending || undefined}>
           <span

+ 15 - 39
packages/ui/src/components/message-part.tsx

@@ -1,4 +1,3 @@
-import { usePageVisibility } from "@solid-primitives/page-visibility"
 import { Component, createEffect, createMemo, createSignal, For, Match, on, Show, Switch, type JSX } from "solid-js"
 import stripAnsi from "strip-ansi"
 import { createStore } from "solid-js/store"
@@ -39,7 +38,7 @@ import { TextShimmer } from "./text-shimmer"
 import { list } from "./text-utils"
 import { GrowBox } from "./grow-box"
 import { COLLAPSIBLE_SPRING } from "./motion"
-import { busy, hold, createThrottledValue, useToolFade, useContextToolPending } from "./tool-utils"
+import { busy, createThrottledValue, useToolFade, useContextToolPending } from "./tool-utils"
 import { ContextToolGroupHeader, ContextToolExpandedList, ContextToolRollingResults } from "./context-tool-results"
 import { ShellRollingResults } from "./shell-rolling-results"
 
@@ -273,19 +272,6 @@ function createGroupOpenState() {
   return { read, controlled, write }
 }
 
-function shouldCollapseGroup(
-  statuses: (string | undefined)[],
-  opts: { afterTool?: boolean; groupTail?: boolean; working?: boolean },
-  pageVisible: () => boolean,
-) {
-  if (opts.afterTool) return true
-  if (opts.groupTail === false) return true
-  if (!pageVisible()) return false
-  if (opts.working) return false
-  if (!statuses.length) return false
-  return !statuses.some((s) => busy(s))
-}
-
 function renderable(part: PartType, showReasoningSummaries = true) {
   if (part.type === "tool") {
     if (HIDDEN_TOOLS.has(part.tool)) return false
@@ -363,7 +349,6 @@ export function AssistantParts(props: {
 }) {
   const data = useData()
   const emptyParts: PartType[] = []
-  const pageVisible = usePageVisibility()
   const groupState = createGroupOpenState()
   const grouped = createMemo(() => {
     const keys: string[] = []
@@ -481,24 +466,9 @@ export function AssistantParts(props: {
             return COLLAPSIBLE_SPRING
           })
           const contextOpen = createMemo(() => {
-            const collapse = (
-              afterTool?: boolean,
-              groupTail?: boolean,
-              group?: { part: ToolPart; message: AssistantMessage }[],
-            ) =>
-              shouldCollapseGroup(
-                group?.map((item) => item.part.state.status) ?? [],
-                {
-                  afterTool,
-                  groupTail,
-                  working: props.working,
-                },
-                pageVisible,
-              )
             const value = ctx()
-            if (value) return groupState.read(value.groupKey, collapse(value.afterTool, value.tail, value.parts))
-            const entry = part()
-            return groupState.read(entry?.groupKey, collapse(entry?.afterTool, entry?.groupTail, entry?.groupParts))
+            if (value) return groupState.read(value.groupKey, true)
+            return groupState.read(part()?.groupKey, true)
           })
           const visible = createMemo(() => {
             if (!context()) return true
@@ -544,9 +514,7 @@ export function AssistantParts(props: {
             ctxPartsPrev = result
             return result
           })
-          const ctxPendingRaw = useContextToolPending(ctxParts, () => !!(props.working && ctx()?.tail))
-          const ctxPending = ctxPendingRaw
-          const ctxHoldOpen = hold(ctxPendingRaw)
+          const ctxPending = useContextToolPending(ctxParts, () => !!(props.working && ctx()?.tail))
           const shell = createMemo(() => {
             const value = part()
             if (!value) return
@@ -598,12 +566,20 @@ export function AssistantParts(props: {
                           onOpenChange={(value: boolean) => groupState.write(entry().groupKey, value)}
                         />
                       </PartGrow>
-                      <ContextToolExpandedList parts={ctxParts()} expanded={!ctxPending() && contextOpen()} />
-                      <ContextToolRollingResults parts={ctxParts()} pending={ctxHoldOpen()} />
+                      <ContextToolExpandedList parts={ctxParts()} expanded={contextOpen() && !ctxPending()} />
+                      <ContextToolRollingResults parts={ctxParts()} pending={contextOpen() && ctxPending()} />
                     </>
                   )}
                 </Show>
-                <Show when={shell()}>{(value) => <ShellRollingResults part={value()} animate={props.animate} />}</Show>
+                <Show when={shell()}>
+                  {(value) => (
+                    <ShellRollingResults
+                      part={value()}
+                      animate={props.animate}
+                      defaultOpen={props.shellToolDefaultOpen}
+                    />
+                  )}
+                </Show>
                 <Show when={!shell() ? part() : undefined}>
                   {(entry) => (
                     <Show when={!entry().context}>

+ 11 - 30
packages/ui/src/components/shell-rolling-results.tsx

@@ -10,15 +10,7 @@ import { TextShimmer } from "./text-shimmer"
 import { Tooltip } from "./tooltip"
 import { GROW_SPRING } from "./motion"
 import { useSpring } from "./motion-spring"
-import {
-  busy,
-  createThrottledValue,
-  hold,
-  updateScrollMask,
-  useCollapsible,
-  useRowWipe,
-  useToolFade,
-} from "./tool-utils"
+import { busy, createThrottledValue, updateScrollMask, useCollapsible, useRowWipe, useToolFade } from "./tool-utils"
 
 function ShellRollingSubtitle(props: { text: string; animate?: boolean }) {
   let ref: HTMLSpanElement | undefined
@@ -176,24 +168,17 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
   )
 }
 
-export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }) {
+export function ShellRollingResults(props: { part: ToolPart; animate?: boolean; defaultOpen?: boolean }) {
   const i18n = useI18n()
   const reduce = useReducedMotion()
   const wiped = new Set<string>()
   const [mounted, setMounted] = createSignal(false)
-  const [userToggled, setUserToggled] = createSignal(false)
-  const [userOpen, setUserOpen] = createSignal(false)
+  const [open, setOpen] = createSignal(props.defaultOpen ?? true)
   onMount(() => setMounted(true))
   const state = createMemo(() => props.part.state as Record<string, any>)
   const pending = createMemo(() => busy(props.part.state.status))
-  const autoOpen = hold(pending, 2000)
-  const effectiveOpen = createMemo(() => {
-    if (pending()) return true
-    if (userToggled()) return userOpen()
-    return autoOpen()
-  })
-  const expanded = createMemo(() => !pending() && !autoOpen() && userToggled() && userOpen())
-  const previewOpen = createMemo(() => effectiveOpen() && !expanded())
+  const expanded = createMemo(() => open() && !pending())
+  const previewOpen = createMemo(() => open() && pending())
   const command = createMemo(() => {
     const value = state().input?.command ?? state().metadata?.command
     if (typeof value === "string") return value
@@ -217,12 +202,10 @@ export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }
   const headerHeight = useSpring(() => (mounted() ? 37 : 0), GROW_SPRING)
   let headerClipRef: HTMLDivElement | undefined
   const handleHeaderClick = () => {
-    if (pending()) return
     const el = headerClipRef
     const viewport = el?.closest(".scroll-view__viewport") as HTMLElement | null
     const beforeY = el?.getBoundingClientRect().top ?? 0
-    setUserToggled(true)
-    setUserOpen((prev) => !prev)
+    setOpen((prev) => !prev)
     if (viewport && el) {
       requestAnimationFrame(() => {
         const afterY = el.getBoundingClientRect().top
@@ -249,7 +232,7 @@ export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }
         ref={headerClipRef}
         data-slot="shell-rolling-header-clip"
         data-scroll-preserve
-        data-clickable={!pending() ? "true" : "false"}
+        data-clickable="true"
         onClick={handleHeaderClick}
         style={{ height: `${skip() ? (mounted() ? 37 : 0) : headerHeight()}px`, overflow: "clip" }}
       >
@@ -258,13 +241,11 @@ export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }
             <TextShimmer text={i18n.t("ui.tool.shell")} active={pending()} />
           </span>
           <Show when={subtitle()}>{(text) => <ShellRollingSubtitle text={text()} animate={props.animate} />}</Show>
-          <Show when={!pending()}>
-            <span data-slot="shell-rolling-actions">
-              <span data-slot="shell-rolling-arrow" data-open={effectiveOpen() ? "true" : "false"}>
-                <Icon name="chevron-down" size="small" />
-              </span>
+          <span data-slot="shell-rolling-actions">
+            <span data-slot="shell-rolling-arrow" data-open={open() ? "true" : "false"}>
+              <Icon name="chevron-down" size="small" />
             </span>
-          </Show>
+          </span>
         </div>
       </div>
       <div