From 0add5fff9b4f7ac024454564ff1b87c767d704f6 Mon Sep 17 00:00:00 2001 From: neriousy Date: Sat, 28 Feb 2026 23:21:50 +0100 Subject: [PATCH 1/2] feat: session retry component --- packages/ui/src/components/session-retry.tsx | 78 ++++++++++++++++++++ packages/ui/src/components/session-turn.tsx | 3 + packages/ui/src/i18n/ar.ts | 3 + packages/ui/src/i18n/br.ts | 3 + packages/ui/src/i18n/bs.ts | 3 + packages/ui/src/i18n/da.ts | 3 + packages/ui/src/i18n/de.ts | 3 + packages/ui/src/i18n/en.ts | 3 + packages/ui/src/i18n/es.ts | 3 + packages/ui/src/i18n/fr.ts | 3 + packages/ui/src/i18n/ja.ts | 3 + packages/ui/src/i18n/ko.ts | 3 + packages/ui/src/i18n/no.ts | 3 + packages/ui/src/i18n/pl.ts | 3 + packages/ui/src/i18n/ru.ts | 3 + packages/ui/src/i18n/th.ts | 3 + packages/ui/src/i18n/tr.ts | 3 + packages/ui/src/i18n/zh.ts | 3 + packages/ui/src/i18n/zht.ts | 3 + 19 files changed, 132 insertions(+) create mode 100644 packages/ui/src/components/session-retry.tsx diff --git a/packages/ui/src/components/session-retry.tsx b/packages/ui/src/components/session-retry.tsx new file mode 100644 index 00000000000..c8ea50b77a8 --- /dev/null +++ b/packages/ui/src/components/session-retry.tsx @@ -0,0 +1,78 @@ +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)) + }, + { defer: true }, + ), + ) + 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 > 120 + }) + 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 ( + +
+ +
+ +
+ {message()}
}> + +
+ {message()} +
+
+ + {(line) =>
{line()}
}
+
+
+ + +
+ ) +} diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 3116d4b65c0..bd4f2843aa5 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( + 0 && !working()}>
diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index 4eb1b475567..1f4b30aa7d6 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "إعادة المحاولة", "ui.sessionTurn.retry.inSeconds": "خلال {{seconds}} ثواني", + "ui.sessionTurn.retry.attempt": "المحاولة رقم {{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - المحاولة رقم {{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini مزدحم حاليا", "ui.sessionTurn.error.freeUsageExceeded": "تم تجاوز حد الاستخدام المجاني", "ui.sessionTurn.error.addCredits": "إضافة رصيد", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index a2c3fb642c0..b08a7f57f7c 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "tentando novamente", "ui.sessionTurn.retry.inSeconds": "em {{seconds}}s", + "ui.sessionTurn.retry.attempt": "tentativa #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - tentativa #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini está muito sobrecarregado agora", "ui.sessionTurn.error.freeUsageExceeded": "Limite de uso gratuito excedido", "ui.sessionTurn.error.addCredits": "Adicionar créditos", diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index c75e158ba3b..9f2eb7cd2e6 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -44,6 +44,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "ponovni pokušaj", "ui.sessionTurn.retry.inSeconds": "za {{seconds}}s", + "ui.sessionTurn.retry.attempt": "pokušaj #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - pokušaj #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini je trenutno preopterećen", "ui.sessionTurn.error.freeUsageExceeded": "Besplatna upotreba premašena", "ui.sessionTurn.error.addCredits": "Dodaj kredite", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 59c18e8e92f..26c7db0c50b 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "prøver igen", "ui.sessionTurn.retry.inSeconds": "om {{seconds}}s", + "ui.sessionTurn.retry.attempt": "forsøg #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - forsøg #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini er meget overbelastet lige nu", "ui.sessionTurn.error.freeUsageExceeded": "Gratis forbrug overskredet", "ui.sessionTurn.error.addCredits": "Tilføj kreditter", diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index b3fb610615d..467fa4e2e9a 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -45,6 +45,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "erneuter Versuch", "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", + "ui.sessionTurn.retry.attempt": "Versuch #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - Versuch #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini ist gerade sehr überlastet", "ui.sessionTurn.error.freeUsageExceeded": "Kostenloses Nutzungslimit überschritten", "ui.sessionTurn.error.addCredits": "Guthaben aufladen", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index ba4ff62fb40..60e169dfa00 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -41,6 +41,9 @@ export const dict: Record = { "ui.sessionTurn.retry.retrying": "retrying", "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", + "ui.sessionTurn.retry.attempt": "attempt #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - attempt #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini is way too hot right now", "ui.sessionTurn.error.freeUsageExceeded": "Free usage exceeded", "ui.sessionTurn.error.addCredits": "Add credits", diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 0b4566c9bc5..0a06f0d9d9f 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "reintentando", "ui.sessionTurn.retry.inSeconds": "en {{seconds}}s", + "ui.sessionTurn.retry.attempt": "intento #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - intento #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini está demasiado saturado", "ui.sessionTurn.error.freeUsageExceeded": "Límite de uso gratuito excedido", "ui.sessionTurn.error.addCredits": "Añadir créditos", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index 4092fac9a3f..86d277308e6 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "nouvelle tentative", "ui.sessionTurn.retry.inSeconds": "dans {{seconds}}s", + "ui.sessionTurn.retry.attempt": "tentative n°{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - tentative n°{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini est en surchauffe", "ui.sessionTurn.error.freeUsageExceeded": "Limite d'utilisation gratuite dépassée", "ui.sessionTurn.error.addCredits": "Ajouter des crédits", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index ea855d1b726..ca8c48efe0c 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "再試行中", "ui.sessionTurn.retry.inSeconds": "{{seconds}}秒後", + "ui.sessionTurn.retry.attempt": "{{attempt}}回目", + "ui.sessionTurn.retry.attemptLine": "{{line}} - {{attempt}}回目", + "ui.sessionTurn.retry.geminiHot": "gemini が混雑しています", "ui.sessionTurn.error.freeUsageExceeded": "無料使用制限に達しました", "ui.sessionTurn.error.addCredits": "クレジットを追加", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index 80de94e95d6..226de0d22fa 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -40,6 +40,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "재시도 중", "ui.sessionTurn.retry.inSeconds": "{{seconds}}초 후", + "ui.sessionTurn.retry.attempt": "{{attempt}}번째", + "ui.sessionTurn.retry.attemptLine": "{{line}} - {{attempt}}번째", + "ui.sessionTurn.retry.geminiHot": "gemini가 현재 과부하 상태입니다", "ui.sessionTurn.error.freeUsageExceeded": "무료 사용량 초과", "ui.sessionTurn.error.addCredits": "크레딧 추가", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index 77f6df55458..5243a61b8ba 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -43,6 +43,9 @@ export const dict: Record = { "ui.sessionTurn.retry.retrying": "Prøver igjen", "ui.sessionTurn.retry.inSeconds": "om {{seconds}}s", + "ui.sessionTurn.retry.attempt": "forsøk #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - forsøk #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini er veldig overbelastet nå", "ui.sessionTurn.error.freeUsageExceeded": "Gratis bruk overskredet", "ui.sessionTurn.error.addCredits": "Legg til kreditt", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index 877e6505fcd..339694c263d 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "ponawianie", "ui.sessionTurn.retry.inSeconds": "za {{seconds}}s", + "ui.sessionTurn.retry.attempt": "próba #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - próba #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini jest teraz mocno przeciążony", "ui.sessionTurn.error.freeUsageExceeded": "Przekroczono limit darmowego użytkowania", "ui.sessionTurn.error.addCredits": "Dodaj kredyty", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index 545be1b63a6..ca3ce8e0544 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -39,6 +39,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "повтор", "ui.sessionTurn.retry.inSeconds": "через {{seconds}}с", + "ui.sessionTurn.retry.attempt": "попытка №{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - попытка №{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini сейчас перегружен", "ui.sessionTurn.error.freeUsageExceeded": "Лимит бесплатного использования превышен", "ui.sessionTurn.error.addCredits": "Добавить кредиты", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 54b3db649a6..2043bbdc8be 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -41,6 +41,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "กำลังลองใหม่", "ui.sessionTurn.retry.inSeconds": "ใน {{seconds}}วิ", + "ui.sessionTurn.retry.attempt": "ครั้งที่ {{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - ครั้งที่ {{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini กำลังใช้งานหนาแน่นมาก", "ui.sessionTurn.error.freeUsageExceeded": "เกินขีดจำกัดการใช้งานฟรี", "ui.sessionTurn.error.addCredits": "เพิ่มเครดิต", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index b68a9b257b4..da660034b58 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -36,6 +36,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "yeniden deneniyor", "ui.sessionTurn.retry.inSeconds": "{{seconds}}sn içinde", + "ui.sessionTurn.retry.attempt": "deneme #{{attempt}}", + "ui.sessionTurn.retry.attemptLine": "{{line}} - deneme #{{attempt}}", + "ui.sessionTurn.retry.geminiHot": "gemini şu anda aşırı yoğun", "ui.sessionTurn.error.freeUsageExceeded": "Ücretsiz kullanım aşıldı", "ui.sessionTurn.error.addCredits": "Kredi ekle", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index 9a00e2dc96c..78006482ae6 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -44,6 +44,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "重试中", "ui.sessionTurn.retry.inSeconds": "{{seconds}} 秒后", + "ui.sessionTurn.retry.attempt": "第 {{attempt}} 次", + "ui.sessionTurn.retry.attemptLine": "{{line}} - 第 {{attempt}} 次", + "ui.sessionTurn.retry.geminiHot": "gemini 当前过载", "ui.sessionTurn.error.freeUsageExceeded": "免费使用额度已用完", "ui.sessionTurn.error.addCredits": "添加积分", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 81a140d933e..044697bf690 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -44,6 +44,9 @@ export const dict = { "ui.sessionTurn.retry.retrying": "重試中", "ui.sessionTurn.retry.inSeconds": "{{seconds}} 秒後", + "ui.sessionTurn.retry.attempt": "第 {{attempt}} 次", + "ui.sessionTurn.retry.attemptLine": "{{line}} - 第 {{attempt}} 次", + "ui.sessionTurn.retry.geminiHot": "gemini 目前過載", "ui.sessionTurn.error.freeUsageExceeded": "免費使用額度已用完", "ui.sessionTurn.error.addCredits": "新增點數", From 624c69e7ce98e460ec6aea243361f02bb38948d8 Mon Sep 17 00:00:00 2001 From: neriousy Date: Sat, 28 Feb 2026 23:30:24 +0100 Subject: [PATCH 2/2] fix: copilot review fixes --- packages/ui/src/components/session-retry.tsx | 28 +++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/packages/ui/src/components/session-retry.tsx b/packages/ui/src/components/session-retry.tsx index c8ea50b77a8..ac0eb035d96 100644 --- a/packages/ui/src/components/session-retry.tsx +++ b/packages/ui/src/components/session-retry.tsx @@ -13,21 +13,17 @@ export function SessionRetry(props: { status: SessionStatus; show?: boolean }) { }) 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)) - }, - { defer: true }, - ), + 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() @@ -41,7 +37,7 @@ export function SessionRetry(props: { status: SessionStatus; show?: boolean }) { const truncated = createMemo(() => { const current = retry() if (!current) return false - return current.message.length > 120 + return current.message.length > 80 }) const info = createMemo(() => { const current = retry()