fix(app): less auto-expand/collapse
This commit is contained in:
@@ -58,11 +58,9 @@ export function ContextToolGroupHeader(props: {
|
|||||||
<ToolCall
|
<ToolCall
|
||||||
variant="row"
|
variant="row"
|
||||||
icon="magnifying-glass-menu"
|
icon="magnifying-glass-menu"
|
||||||
open={!props.pending && props.open}
|
open={props.open}
|
||||||
showArrow={!props.pending}
|
showArrow
|
||||||
onOpenChange={(v) => {
|
onOpenChange={props.onOpenChange}
|
||||||
if (!props.pending) props.onOpenChange(v)
|
|
||||||
}}
|
|
||||||
trigger={
|
trigger={
|
||||||
<div data-component="context-tool-group-trigger" data-pending={props.pending || undefined}>
|
<div data-component="context-tool-group-trigger" data-pending={props.pending || undefined}>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -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 { Component, createEffect, createMemo, createSignal, For, Match, on, Show, Switch, type JSX } from "solid-js"
|
||||||
import stripAnsi from "strip-ansi"
|
import stripAnsi from "strip-ansi"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
@@ -39,7 +38,7 @@ import { TextShimmer } from "./text-shimmer"
|
|||||||
import { list } from "./text-utils"
|
import { list } from "./text-utils"
|
||||||
import { GrowBox } from "./grow-box"
|
import { GrowBox } from "./grow-box"
|
||||||
import { COLLAPSIBLE_SPRING } from "./motion"
|
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 { ContextToolGroupHeader, ContextToolExpandedList, ContextToolRollingResults } from "./context-tool-results"
|
||||||
import { ShellRollingResults } from "./shell-rolling-results"
|
import { ShellRollingResults } from "./shell-rolling-results"
|
||||||
|
|
||||||
@@ -273,19 +272,6 @@ function createGroupOpenState() {
|
|||||||
return { read, controlled, write }
|
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) {
|
function renderable(part: PartType, showReasoningSummaries = true) {
|
||||||
if (part.type === "tool") {
|
if (part.type === "tool") {
|
||||||
if (HIDDEN_TOOLS.has(part.tool)) return false
|
if (HIDDEN_TOOLS.has(part.tool)) return false
|
||||||
@@ -363,7 +349,6 @@ export function AssistantParts(props: {
|
|||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const emptyParts: PartType[] = []
|
const emptyParts: PartType[] = []
|
||||||
const pageVisible = usePageVisibility()
|
|
||||||
const groupState = createGroupOpenState()
|
const groupState = createGroupOpenState()
|
||||||
const grouped = createMemo(() => {
|
const grouped = createMemo(() => {
|
||||||
const keys: string[] = []
|
const keys: string[] = []
|
||||||
@@ -481,24 +466,9 @@ export function AssistantParts(props: {
|
|||||||
return COLLAPSIBLE_SPRING
|
return COLLAPSIBLE_SPRING
|
||||||
})
|
})
|
||||||
const contextOpen = createMemo(() => {
|
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()
|
const value = ctx()
|
||||||
if (value) return groupState.read(value.groupKey, collapse(value.afterTool, value.tail, value.parts))
|
if (value) return groupState.read(value.groupKey, true)
|
||||||
const entry = part()
|
return groupState.read(part()?.groupKey, true)
|
||||||
return groupState.read(entry?.groupKey, collapse(entry?.afterTool, entry?.groupTail, entry?.groupParts))
|
|
||||||
})
|
})
|
||||||
const visible = createMemo(() => {
|
const visible = createMemo(() => {
|
||||||
if (!context()) return true
|
if (!context()) return true
|
||||||
@@ -544,9 +514,7 @@ export function AssistantParts(props: {
|
|||||||
ctxPartsPrev = result
|
ctxPartsPrev = result
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
const ctxPendingRaw = useContextToolPending(ctxParts, () => !!(props.working && ctx()?.tail))
|
const ctxPending = useContextToolPending(ctxParts, () => !!(props.working && ctx()?.tail))
|
||||||
const ctxPending = ctxPendingRaw
|
|
||||||
const ctxHoldOpen = hold(ctxPendingRaw)
|
|
||||||
const shell = createMemo(() => {
|
const shell = createMemo(() => {
|
||||||
const value = part()
|
const value = part()
|
||||||
if (!value) return
|
if (!value) return
|
||||||
@@ -598,12 +566,20 @@ export function AssistantParts(props: {
|
|||||||
onOpenChange={(value: boolean) => groupState.write(entry().groupKey, value)}
|
onOpenChange={(value: boolean) => groupState.write(entry().groupKey, value)}
|
||||||
/>
|
/>
|
||||||
</PartGrow>
|
</PartGrow>
|
||||||
<ContextToolExpandedList parts={ctxParts()} expanded={!ctxPending() && contextOpen()} />
|
<ContextToolExpandedList parts={ctxParts()} expanded={contextOpen() && !ctxPending()} />
|
||||||
<ContextToolRollingResults parts={ctxParts()} pending={ctxHoldOpen()} />
|
<ContextToolRollingResults parts={ctxParts()} pending={contextOpen() && ctxPending()} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</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}>
|
<Show when={!shell() ? part() : undefined}>
|
||||||
{(entry) => (
|
{(entry) => (
|
||||||
<Show when={!entry().context}>
|
<Show when={!entry().context}>
|
||||||
|
|||||||
@@ -10,15 +10,7 @@ import { TextShimmer } from "./text-shimmer"
|
|||||||
import { Tooltip } from "./tooltip"
|
import { Tooltip } from "./tooltip"
|
||||||
import { GROW_SPRING } from "./motion"
|
import { GROW_SPRING } from "./motion"
|
||||||
import { useSpring } from "./motion-spring"
|
import { useSpring } from "./motion-spring"
|
||||||
import {
|
import { busy, createThrottledValue, updateScrollMask, useCollapsible, useRowWipe, useToolFade } from "./tool-utils"
|
||||||
busy,
|
|
||||||
createThrottledValue,
|
|
||||||
hold,
|
|
||||||
updateScrollMask,
|
|
||||||
useCollapsible,
|
|
||||||
useRowWipe,
|
|
||||||
useToolFade,
|
|
||||||
} from "./tool-utils"
|
|
||||||
|
|
||||||
function ShellRollingSubtitle(props: { text: string; animate?: boolean }) {
|
function ShellRollingSubtitle(props: { text: string; animate?: boolean }) {
|
||||||
let ref: HTMLSpanElement | undefined
|
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 i18n = useI18n()
|
||||||
const reduce = useReducedMotion()
|
const reduce = useReducedMotion()
|
||||||
const wiped = new Set<string>()
|
const wiped = new Set<string>()
|
||||||
const [mounted, setMounted] = createSignal(false)
|
const [mounted, setMounted] = createSignal(false)
|
||||||
const [userToggled, setUserToggled] = createSignal(false)
|
const [open, setOpen] = createSignal(props.defaultOpen ?? true)
|
||||||
const [userOpen, setUserOpen] = createSignal(false)
|
|
||||||
onMount(() => setMounted(true))
|
onMount(() => setMounted(true))
|
||||||
const state = createMemo(() => props.part.state as Record<string, any>)
|
const state = createMemo(() => props.part.state as Record<string, any>)
|
||||||
const pending = createMemo(() => busy(props.part.state.status))
|
const pending = createMemo(() => busy(props.part.state.status))
|
||||||
const autoOpen = hold(pending, 2000)
|
const expanded = createMemo(() => open() && !pending())
|
||||||
const effectiveOpen = createMemo(() => {
|
const previewOpen = createMemo(() => open() && pending())
|
||||||
if (pending()) return true
|
|
||||||
if (userToggled()) return userOpen()
|
|
||||||
return autoOpen()
|
|
||||||
})
|
|
||||||
const expanded = createMemo(() => !pending() && !autoOpen() && userToggled() && userOpen())
|
|
||||||
const previewOpen = createMemo(() => effectiveOpen() && !expanded())
|
|
||||||
const command = createMemo(() => {
|
const command = createMemo(() => {
|
||||||
const value = state().input?.command ?? state().metadata?.command
|
const value = state().input?.command ?? state().metadata?.command
|
||||||
if (typeof value === "string") return value
|
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)
|
const headerHeight = useSpring(() => (mounted() ? 37 : 0), GROW_SPRING)
|
||||||
let headerClipRef: HTMLDivElement | undefined
|
let headerClipRef: HTMLDivElement | undefined
|
||||||
const handleHeaderClick = () => {
|
const handleHeaderClick = () => {
|
||||||
if (pending()) return
|
|
||||||
const el = headerClipRef
|
const el = headerClipRef
|
||||||
const viewport = el?.closest(".scroll-view__viewport") as HTMLElement | null
|
const viewport = el?.closest(".scroll-view__viewport") as HTMLElement | null
|
||||||
const beforeY = el?.getBoundingClientRect().top ?? 0
|
const beforeY = el?.getBoundingClientRect().top ?? 0
|
||||||
setUserToggled(true)
|
setOpen((prev) => !prev)
|
||||||
setUserOpen((prev) => !prev)
|
|
||||||
if (viewport && el) {
|
if (viewport && el) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const afterY = el.getBoundingClientRect().top
|
const afterY = el.getBoundingClientRect().top
|
||||||
@@ -249,7 +232,7 @@ export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }
|
|||||||
ref={headerClipRef}
|
ref={headerClipRef}
|
||||||
data-slot="shell-rolling-header-clip"
|
data-slot="shell-rolling-header-clip"
|
||||||
data-scroll-preserve
|
data-scroll-preserve
|
||||||
data-clickable={!pending() ? "true" : "false"}
|
data-clickable="true"
|
||||||
onClick={handleHeaderClick}
|
onClick={handleHeaderClick}
|
||||||
style={{ height: `${skip() ? (mounted() ? 37 : 0) : headerHeight()}px`, overflow: "clip" }}
|
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()} />
|
<TextShimmer text={i18n.t("ui.tool.shell")} active={pending()} />
|
||||||
</span>
|
</span>
|
||||||
<Show when={subtitle()}>{(text) => <ShellRollingSubtitle text={text()} animate={props.animate} />}</Show>
|
<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-actions">
|
<span data-slot="shell-rolling-arrow" data-open={open() ? "true" : "false"}>
|
||||||
<span data-slot="shell-rolling-arrow" data-open={effectiveOpen() ? "true" : "false"}>
|
<Icon name="chevron-down" size="small" />
|
||||||
<Icon name="chevron-down" size="small" />
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</Show>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
Reference in New Issue
Block a user