Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ Write each change in both `### English` and `### 中文` under `## Unreleased`.

### English

- Show missing Qoder account quota as loading or unavailable instead of incorrectly labeling it as requiring login

### 中文

- 将 Qoder 账号缺失的额度信息显示为“额度获取中”或“额度不可用”,避免误显示为需要登录

## 0.4.5 - 2026-09-10

### English
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/account/AccountCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ function stateCopyFor(state: ReturnType<typeof accountState>, cooldown: string,
if (state === 'ready') return t('ready')
if (state === 'cooling') return cooldown ? `${t('cooling')} ${cooldown}` : t('cooling')
if (state === 'quota_exhausted') return t('quotaExceeded')
if (state === 'loading') return t('quotaLoading')
if (state === 'starting') return t('starting')
if (state === 'unavailable') return t('quotaUnavailable')
if (state === 'dead') return cooldown ? `${t('dead')} ${cooldown}` : t('dead')
if (state === 'auth_failed') return t('authFailed')
if (state === 'disabled') return t('disabled')
Expand Down Expand Up @@ -129,7 +131,7 @@ export function AccountCard({
? 'warning'
: state === 'quota_exhausted'
? 'danger'
: state === 'login' || state === 'dead' || state === 'auth_failed'
: state === 'login' || state === 'unavailable' || state === 'dead' || state === 'auth_failed'
? 'danger'
: undefined
const inFlight = account.in_flight ?? account.inFlight ?? 0
Expand Down Expand Up @@ -295,7 +297,7 @@ export function AccountCard({
resourcePackageLabel={t('quotaResourcePackage')}
exceededLabel={t('quotaExceeded')}
/>
) : <span className="text-[11px] text-foreground/65">{t('statsUnknown')}</span>}
) : <span className="text-[11px] text-foreground/65">{state === 'loading' ? t('quotaLoading') : t('quotaUnavailable')}</span>}
</div>

{account.provider === 'workbuddy' ? (
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ export const messages: Record<Lang, Dict> = {
stepBrowserDesc: 'Opens account selection. Finish in the browser, then this page polls until the session is ready.',
startBrowserLogin: 'Sign in with browser',
starting: 'Starting…',
quotaLoading: 'Getting quota…',
quotaUnavailable: 'Quota unavailable',
authUrl: 'Auth URL',
stepPatTitle: 'Personal access token',
stepPatDesc: 'Optional fallback when browser flow is inconvenient.',
Expand Down Expand Up @@ -906,6 +908,8 @@ export const messages: Record<Lang, Dict> = {
stepBrowserDesc: '打开账号选择页。浏览器完成授权后,本页会轮询直到会话就绪。',
startBrowserLogin: '用浏览器登录',
starting: '启动中…',
quotaLoading: '额度获取中…',
quotaUnavailable: '额度不可用',
authUrl: '授权链接',
stepPatTitle: '个人访问令牌',
stepPatDesc: '浏览器流程不方便时,可用 PAT 作为备选。',
Expand Down
16 changes: 11 additions & 5 deletions frontend/src/lib/account.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { AccountQuota, Overview } from '@/api/types'

export type AccountRow = NonNullable<Overview['accounts']>[number]
export type AccountState = 'disabled' | 'quota_exhausted' | 'cooling' | 'hot' | 'ready' | 'login' | 'starting' | 'dead' | 'auth_failed'
export type AccountState = 'disabled' | 'quota_exhausted' | 'cooling' | 'hot' | 'ready' | 'login' | 'loading' | 'starting' | 'unavailable' | 'dead' | 'auth_failed'
export type QuotaTone = 'ok' | 'warn' | 'danger'

export function cooldownLabel(until?: string | null) {
Expand All @@ -24,11 +24,15 @@ export function accountState(account: AccountRow): AccountState {
if (account.status === 'cooling' || cooldownLabel(account.down_until || account.cooldown_until)) return 'cooling'
if (account.status === 'dead' || account.runtime_state === 'dead') return 'dead'
if (account.status === 'auth_failed' || account.runtime_state === 'auth_failed') return 'auth_failed'
if (account.status === 'starting' && !account.hot && !account.ready) return 'starting'
if (account.status === 'login_required') return 'login'
if ((account.status === 'starting' || account.runtime_state === 'starting') && !account.hot && !account.ready) {
return account.quota ? 'starting' : 'loading'
}
if (account.status === 'ready') return account.hot ? 'hot' : 'ready'
if (account.hot) return 'hot'
if (account.ready) return 'ready'
return 'login'
if (account.status === 'error' || account.status === 'offline') return 'unavailable'
return 'unavailable'
}

export function isAvailable(account: AccountRow) {
Expand All @@ -41,7 +45,9 @@ export function runtimeSegments(state: AccountState) {
if (state === 'ready') return 9
if (state === 'cooling') return 5
if (state === 'login') return 3
if (state === 'loading') return 6
if (state === 'starting') return 6
if (state === 'unavailable') return 2
if (state === 'auth_failed') return 3
if (state === 'dead') return 2
return 1
Expand All @@ -50,8 +56,8 @@ export function runtimeSegments(state: AccountState) {
export function runtimeTone(state: AccountState): 'ok' | 'warn' | 'danger' | 'muted' {
if (state === 'hot' || state === 'ready') return 'ok'
if (state === 'cooling' || state === 'quota_exhausted') return 'warn'
if (state === 'starting') return 'warn'
if (state === 'auth_failed' || state === 'dead') return 'danger'
if (state === 'loading' || state === 'starting') return 'warn'
if (state === 'auth_failed' || state === 'unavailable' || state === 'dead') return 'danger'
if (state === 'login') return 'danger'
return 'muted'
}
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion internal/webui/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet" />
<script type="module" crossorigin src="/assets/index-B4cFaIkZ.js"></script>
<script type="module" crossorigin src="/assets/index-DzraaBh3.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D5rOrfco.css">
</head>
<body>
Expand Down
Loading