diff options
| -rw-r--r-- | packages/desktop/src/pages/layout.tsx | 10 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.css | 14 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 46 |
3 files changed, 62 insertions, 8 deletions
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx index 79470cf14..6cf7a2b0e 100644 --- a/packages/desktop/src/pages/layout.tsx +++ b/packages/desktop/src/pages/layout.tsx @@ -413,11 +413,11 @@ export default function Layout(props: ParentProps) { const updated = createMemo(() => DateTime.fromMillis(props.session.time.updated)) const notifications = createMemo(() => notification.session.unseen(props.session.id)) const hasError = createMemo(() => notifications().some((n) => n.type === "error")) - const isWorking = createMemo( - () => - props.session.id !== params.id && - globalSync.child(props.project.worktree)[0].session_status[props.session.id]?.type === "busy", - ) + const isWorking = createMemo(() => { + if (props.session.id === params.id) return false + const status = globalSync.child(props.project.worktree)[0].session_status[props.session.id] + return status?.type === "busy" || status?.type === "retry" + }) return ( <> <div diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 5f95b2c30..c0408cb0c 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -238,6 +238,10 @@ justify-content: space-between; width: 100%; gap: 20px; + + [data-expandable="false"] { + pointer-events: none; + } } [data-slot="session-turn-file-info"] { @@ -323,6 +327,16 @@ height: 14px; } } + [data-slot="session-turn-retry-message"] { + font-weight: 500; + color: var(--syntax-critical); + } + [data-slot="session-turn-retry-seconds"] { + color: var(--text-weak); + } + [data-slot="session-turn-retry-attempt"] { + color: var(--text-weak); + } [data-slot="session-turn-details-text"] { font-size: 13px; /* text-12-medium */ diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 5bf3c0bbd..ade9a04ab 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -3,7 +3,7 @@ import { useData } from "../context" import { useDiffComponent } from "../context/diff" import { getDirectory, getFilename } from "@opencode-ai/util/path" import { checksum } from "@opencode-ai/util/encode" -import { createEffect, createMemo, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js" +import { createEffect, createMemo, createSignal, For, Match, onCleanup, ParentProps, Show, Switch } from "solid-js" import { createResizeObserver } from "@solid-primitives/resize-observer" import { DiffChanges } from "./diff-changes" import { Typewriter } from "./typewriter" @@ -49,6 +49,29 @@ export function SessionTurn( }, ) const working = createMemo(() => status()?.type !== "idle") + const retry = createMemo(() => { + const s = status() + if (s.type !== "retry") return + return s + }) + const [retrySeconds, setRetrySeconds] = createSignal(0) + + createEffect(() => { + const r = retry() + if (!r) { + setRetrySeconds(0) + return + } + + const updateSeconds = () => { + const next = r.next + if (next) setRetrySeconds(Math.max(0, Math.round((next - Date.now()) / 1000))) + } + updateSeconds() + + const timer = setInterval(updateSeconds, 1000) + onCleanup(() => clearInterval(timer)) + }) let scrollRef: HTMLDivElement | undefined const [state, setState] = createStore({ @@ -300,10 +323,12 @@ export function SessionTurn( {/* Trigger (sticky) */} <div ref={(el) => setState("stickyTriggerRef", el)} data-slot="session-turn-response-trigger"> <Button + data-expandable={assistantMessages().length > 0} data-slot="session-turn-collapsible-trigger-content" variant="ghost" size="small" onClick={() => { + if (assistantMessages().length === 0) return const next = !store.stepsExpanded setStore("stepsExpanded", next) props.onStepsExpandedChange?.(next) @@ -313,17 +338,32 @@ export function SessionTurn( <Spinner /> </Show> <Switch> + <Match when={retry()}> + <span data-slot="session-turn-retry-message"> + {(() => { + const r = retry() + if (!r) return "" + return r.message.length > 60 ? r.message.slice(0, 60) + "..." : r.message + })()} + </span> + <span data-slot="session-turn-retry-seconds"> + · retrying {retrySeconds() > 0 ? `in ${retrySeconds()}s ` : ""} + </span> + <span data-slot="session-turn-retry-attempt">(#{retry()?.attempt})</span> + </Match> <Match when={working()}>{store.status ?? "Considering next steps"}</Match> <Match when={store.stepsExpanded}>Hide steps</Match> <Match when={!store.stepsExpanded}>Show steps</Match> </Switch> <span>·</span> <span>{store.duration}</span> - <Icon name="chevron-grabber-vertical" size="small" /> + <Show when={assistantMessages().length > 0}> + <Icon name="chevron-grabber-vertical" size="small" /> + </Show> </Button> </div> {/* Response */} - <Show when={store.stepsExpanded}> + <Show when={store.stepsExpanded && assistantMessages().length > 0}> <div data-slot="session-turn-collapsible-content-inner"> <For each={assistantMessages()}> {(assistantMessage) => { |
