summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/desktop/src/pages/layout.tsx10
-rw-r--r--packages/ui/src/components/session-turn.css14
-rw-r--r--packages/ui/src/components/session-turn.tsx46
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) => {