diff options
Diffstat (limited to 'packages/ui/src/components')
| -rw-r--r-- | packages/ui/src/components/session-retry.tsx | 74 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 3 |
2 files changed, 77 insertions, 0 deletions
diff --git a/packages/ui/src/components/session-retry.tsx b/packages/ui/src/components/session-retry.tsx new file mode 100644 index 000000000..ac0eb035d --- /dev/null +++ b/packages/ui/src/components/session-retry.tsx @@ -0,0 +1,74 @@ +import { createEffect, createMemo, createSignal, on, onCleanup, Show } from "solid-js" +import type { SessionStatus } from "@opencode-ai/sdk/v2/client" +import { useI18n } from "../context/i18n" +import { Card } from "./card" +import { Tooltip } from "./tooltip" +import { Spinner } from "./spinner" + +export function SessionRetry(props: { status: SessionStatus; show?: boolean }) { + const i18n = useI18n() + const retry = createMemo(() => { + if (props.status.type !== "retry") return + return props.status + }) + const [seconds, setSeconds] = createSignal(0) + createEffect( + on(retry, (current) => { + if (!current) return + const update = () => { + const next = retry()?.next + if (!next) return + setSeconds(Math.round((next - Date.now()) / 1000)) + } + update() + const timer = setInterval(update, 1000) + onCleanup(() => clearInterval(timer)) + }), + ) + const message = createMemo(() => { + const current = retry() + if (!current) return "" + if (current.message.includes("exceeded your current quota") && current.message.includes("gemini")) { + return i18n.t("ui.sessionTurn.retry.geminiHot") + } + if (current.message.length > 80) return current.message.slice(0, 80) + "..." + return current.message + }) + const truncated = createMemo(() => { + const current = retry() + if (!current) return false + return current.message.length > 80 + }) + const info = createMemo(() => { + const current = retry() + if (!current) return "" + const count = Math.max(0, seconds()) + const delay = count > 0 ? i18n.t("ui.sessionTurn.retry.inSeconds", { seconds: count }) : "" + const retrying = i18n.t("ui.sessionTurn.retry.retrying") + const line = [retrying, delay].filter(Boolean).join(" ") + if (!line) return i18n.t("ui.sessionTurn.retry.attempt", { attempt: current.attempt }) + return i18n.t("ui.sessionTurn.retry.attemptLine", { line, attempt: current.attempt }) + }) + + return ( + <Show when={retry() && (props.show ?? true)}> + <div data-slot="session-turn-retry"> + <Card variant="error" class="error-card"> + <div class="flex items-start gap-2"> + <Spinner class="size-4 mt-0.5" /> + <div class="min-w-0"> + <Show when={truncated()} fallback={<div data-slot="session-turn-retry-message">{message()}</div>}> + <Tooltip value={retry()?.message ?? ""} placement="top"> + <div data-slot="session-turn-retry-message" class="cursor-help truncate"> + {message()} + </div> + </Tooltip> + </Show> + <Show when={info()}>{(line) => <div data-slot="session-turn-retry-info">{line()}</div>}</Show> + </div> + </div> + </Card> + </div> + </Show> + ) +} diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 3116d4b65..bd4f2843a 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -14,6 +14,7 @@ import { Collapsible } from "./collapsible" import { DiffChanges } from "./diff-changes" import { Icon } from "./icon" import { TextShimmer } from "./text-shimmer" +import { SessionRetry } from "./session-retry" import { createAutoScroll } from "../hooks" import { useI18n } from "../context/i18n" @@ -332,6 +333,7 @@ export function SessionTurn( ) const showThinking = createMemo(() => { if (!working() || !!error()) return false + if (status().type === "retry") return false if (showReasoningSummaries()) return assistantVisible() === 0 if (assistantTailVisible() === "text") return false return true @@ -384,6 +386,7 @@ export function SessionTurn( </Show> </div> </Show> + <SessionRetry status={status()} show={isLastUserMessage()} /> <Show when={edited() > 0 && !working()}> <div data-slot="session-turn-diffs"> <Collapsible open={open()} onOpenChange={setOpen} variant="ghost"> |
