summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFilip <[email protected]>2026-03-01 13:48:11 +0100
committerGitHub <[email protected]>2026-03-01 06:48:11 -0600
commit438610aa647116e40815c0720eb472791259c13d (patch)
tree07e7a065d71342c92abd63043264a23d067aa6fd
parentc4c0b23bff52878014007e53de7657a59df95915 (diff)
downloadopencode-438610aa647116e40815c0720eb472791259c13d.tar.gz
opencode-438610aa647116e40815c0720eb472791259c13d.zip
fix(app): show proper usage limit errors (#15496)
-rw-r--r--packages/ui/src/components/session-retry.tsx74
-rw-r--r--packages/ui/src/components/session-turn.tsx3
-rw-r--r--packages/ui/src/i18n/ar.ts3
-rw-r--r--packages/ui/src/i18n/br.ts3
-rw-r--r--packages/ui/src/i18n/bs.ts3
-rw-r--r--packages/ui/src/i18n/da.ts3
-rw-r--r--packages/ui/src/i18n/de.ts3
-rw-r--r--packages/ui/src/i18n/en.ts3
-rw-r--r--packages/ui/src/i18n/es.ts3
-rw-r--r--packages/ui/src/i18n/fr.ts3
-rw-r--r--packages/ui/src/i18n/ja.ts3
-rw-r--r--packages/ui/src/i18n/ko.ts3
-rw-r--r--packages/ui/src/i18n/no.ts3
-rw-r--r--packages/ui/src/i18n/pl.ts3
-rw-r--r--packages/ui/src/i18n/ru.ts3
-rw-r--r--packages/ui/src/i18n/th.ts3
-rw-r--r--packages/ui/src/i18n/tr.ts3
-rw-r--r--packages/ui/src/i18n/zh.ts3
-rw-r--r--packages/ui/src/i18n/zht.ts3
19 files changed, 128 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">
diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts
index 4eb1b4755..1f4b30aa7 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 a2c3fb642..b08a7f57f 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 c75e158ba..9f2eb7cd2 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 59c18e8e9..26c7db0c5 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 b3fb61061..467fa4e2e 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 ba4ff62fb..60e169dfa 100644
--- a/packages/ui/src/i18n/en.ts
+++ b/packages/ui/src/i18n/en.ts
@@ -41,6 +41,9 @@ export const dict: Record<string, string> = {
"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 0b4566c9b..0a06f0d9d 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 4092fac9a..86d277308 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 ea855d1b7..ca8c48efe 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 80de94e95..226de0d22 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 77f6df554..5243a61b8 100644
--- a/packages/ui/src/i18n/no.ts
+++ b/packages/ui/src/i18n/no.ts
@@ -43,6 +43,9 @@ export const dict: Record<Keys, string> = {
"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 877e6505f..339694c26 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 545be1b63..ca3ce8e05 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 54b3db649..2043bbdc8 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 b68a9b257..da660034b 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 9a00e2dc9..78006482a 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 81a140d93..044697bf6 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": "新增點數",