fix(usage): show CLI-owned session recovery steps (#9069)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
64181fdd42
commit
4e232a030f
|
|
@ -241,6 +241,13 @@ describe('fetchGrokRateLimits', () => {
|
|||
const result = await fetchGrokRateLimits()
|
||||
expect(result.status).toBe('error')
|
||||
expect(result.error).toMatch(/expired/i)
|
||||
expect(result.error).toMatch(/run grok on the computer running Orca/i)
|
||||
expect(result.error).toMatch(/sign in if prompted/i)
|
||||
expect(result.error).toMatch(/no chat message is needed/i)
|
||||
expect(result.usageMetadata).toEqual({
|
||||
failureKind: 'delegated-refresh-required',
|
||||
source: 'oauth'
|
||||
})
|
||||
// Why: a stored-but-expired access token is refreshed by Grok CLI on next
|
||||
// use (a genuine sign-out returns 'missing'), so the message must not tell
|
||||
// users to re-run `grok login` (#8497).
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import { net } from 'electron'
|
||||
import type { ProviderRateLimits, RateLimitWindow } from '../../shared/rate-limit-types'
|
||||
import type {
|
||||
ProviderRateLimits,
|
||||
RateLimitWindow,
|
||||
UsageRateLimitMetadata
|
||||
} from '../../shared/rate-limit-types'
|
||||
import {
|
||||
isGrokAccessTokenFresh,
|
||||
readGrokAuthSession,
|
||||
|
|
@ -47,14 +51,19 @@ type GrokBillingResponse = GrokBillingConfig & {
|
|||
config?: GrokBillingConfig
|
||||
}
|
||||
|
||||
function result(status: ProviderRateLimits['status'], error: string | null): ProviderRateLimits {
|
||||
function result(
|
||||
status: ProviderRateLimits['status'],
|
||||
error: string | null,
|
||||
usageMetadata?: UsageRateLimitMetadata
|
||||
): ProviderRateLimits {
|
||||
return {
|
||||
provider: 'grok',
|
||||
session: null,
|
||||
weekly: null,
|
||||
updatedAt: Date.now(),
|
||||
error,
|
||||
status
|
||||
status,
|
||||
...(usageMetadata ? { usageMetadata } : {})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +234,11 @@ export async function fetchGrokRateLimits(
|
|||
// Why: a genuine sign-out returns 'missing' earlier, so reaching here always
|
||||
// means a stored, refreshable session — Grok CLI refreshes the access token
|
||||
// on its next run, so don't tell users to re-run `grok login` (#8497).
|
||||
return result('error', 'Grok access token expired — Grok CLI will refresh it on next use')
|
||||
return result(
|
||||
'error',
|
||||
'Grok sign-in expired — run grok on the computer running Orca; sign in if prompted. No chat message is needed.',
|
||||
{ failureKind: 'delegated-refresh-required', source: 'oauth' }
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,11 @@ describe('fetchKimiRateLimits', () => {
|
|||
const result = await fetchKimiRateLimits()
|
||||
expect(result.status).toBe('error')
|
||||
expect(result.error).toMatch(/expired/i)
|
||||
expect(result.error).toMatch(/run kimi on the computer running Orca/i)
|
||||
expect(result.usageMetadata).toEqual({
|
||||
failureKind: 'delegated-refresh-required',
|
||||
source: 'oauth'
|
||||
})
|
||||
expect(netFetchMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import { existsSync, readFileSync } from 'node:fs'
|
|||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { net } from 'electron'
|
||||
import type { ProviderRateLimits, RateLimitWindow } from '../../shared/rate-limit-types'
|
||||
import type {
|
||||
ProviderRateLimits,
|
||||
RateLimitWindow,
|
||||
UsageRateLimitMetadata
|
||||
} from '../../shared/rate-limit-types'
|
||||
|
||||
// Why: Kimi Code's managed coding plan exposes subscription usage at
|
||||
// `${base}/usages` (see packages/oauth/src/managed-usage.ts in the CLI bundle).
|
||||
|
|
@ -208,8 +212,20 @@ function mapUsageResponse(data: KimiUsageResponse): ProviderRateLimits {
|
|||
}
|
||||
}
|
||||
|
||||
function result(status: ProviderRateLimits['status'], error: string | null): ProviderRateLimits {
|
||||
return { provider: 'kimi', session: null, weekly: null, updatedAt: Date.now(), error, status }
|
||||
function result(
|
||||
status: ProviderRateLimits['status'],
|
||||
error: string | null,
|
||||
usageMetadata?: UsageRateLimitMetadata
|
||||
): ProviderRateLimits {
|
||||
return {
|
||||
provider: 'kimi',
|
||||
session: null,
|
||||
weekly: null,
|
||||
updatedAt: Date.now(),
|
||||
error,
|
||||
status,
|
||||
...(usageMetadata ? { usageMetadata } : {})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -239,7 +255,11 @@ export async function fetchKimiRateLimits(): Promise<ProviderRateLimits> {
|
|||
// Why: don't refresh — the CLI owns the token lifecycle. Report a transient
|
||||
// error so the rate-limit service keeps the last good snapshot (stale
|
||||
// policy) until the user next runs Kimi and the CLI refreshes the file.
|
||||
return result('error', 'Kimi token expired — open Kimi to refresh')
|
||||
return result(
|
||||
'error',
|
||||
'Kimi session expired — run kimi on the computer running Orca, then retry usage.',
|
||||
{ failureKind: 'delegated-refresh-required', source: 'oauth' }
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
// @vitest-environment happy-dom
|
||||
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
|
||||
import React from 'react'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getStatus: vi.fn(),
|
||||
refreshGrokRateLimits: vi.fn()
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/agent-catalog', () => ({
|
||||
AgentIcon: () => React.createElement('span', { 'data-testid': 'grok-icon' })
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n/i18n', () => ({
|
||||
translate: (_key: string, fallback: string, values?: Record<string, string>) => {
|
||||
let result = fallback
|
||||
for (const [key, value] of Object.entries(values ?? {})) {
|
||||
result = result.replace(`{{${key}}}`, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('../../store', () => ({
|
||||
useAppStore: (selector: (state: Record<string, unknown>) => unknown) =>
|
||||
selector({
|
||||
refreshGrokRateLimits: mocks.refreshGrokRateLimits,
|
||||
rateLimits: { grok: null }
|
||||
})
|
||||
}))
|
||||
|
||||
import { GrokAccountsSection } from './GrokAccountsSection'
|
||||
|
||||
describe('GrokAccountsSection', () => {
|
||||
beforeEach(() => {
|
||||
mocks.getStatus.mockResolvedValue({
|
||||
signedIn: true,
|
||||
email: 'dev@example.com',
|
||||
teamId: null,
|
||||
tokenFresh: false,
|
||||
error: null
|
||||
})
|
||||
mocks.refreshGrokRateLimits.mockResolvedValue(undefined)
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: { grokAccounts: { getStatus: mocks.getStatus } }
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('explains the host-scoped Grok recovery flow without requiring a chat message', async () => {
|
||||
render(<GrokAccountsSection />)
|
||||
|
||||
expect(
|
||||
await screen.findByText(
|
||||
'Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText(/grok login/i)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
|
@ -109,12 +109,12 @@ export function GrokAccountsSection(): React.JSX.Element {
|
|||
<p className="text-xs text-muted-foreground">
|
||||
{tokenFresh
|
||||
? translate(
|
||||
'auto.components.settings.GrokAccountsSection.c3d4e5f6a7',
|
||||
'Signed in. Orca only reads that file on disk — run grok login again if usage fails.'
|
||||
'auto.components.settings.GrokAccountsSection.b36fa2c908',
|
||||
'Signed in. Orca reads the Grok CLI session stored on disk.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.settings.GrokAccountsSection.d4e5f6a7b8',
|
||||
'Session expired — run grok login in a terminal to refresh.'
|
||||
'auto.components.settings.GrokAccountsSection.f08c41de73',
|
||||
'Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed.'
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -135,18 +135,36 @@ describe('provider usage error copy', () => {
|
|||
)
|
||||
})
|
||||
|
||||
it('keeps the reworded Grok expired-token error classified as an auth failure (#8497)', () => {
|
||||
// Why: the fix (grok-fetcher.ts) dropped the "run grok login" wording that
|
||||
// used to trigger auth classification; this pins that the new copy still
|
||||
// resolves to the softer refresh message instead of leaking the raw string.
|
||||
it('shows the exact Grok CLI recovery flow for an expired refreshable session (#8497)', () => {
|
||||
const grok = provider({
|
||||
provider: 'grok',
|
||||
error: 'Grok access token expired — Grok CLI will refresh it on next use'
|
||||
error:
|
||||
'Grok sign-in expired — run grok on the computer running Orca; sign in if prompted. No chat message is needed.',
|
||||
usageMetadata: {
|
||||
failureKind: 'delegated-refresh-required',
|
||||
source: 'oauth'
|
||||
}
|
||||
})
|
||||
|
||||
expect(getProviderUsageStatusLabel(grok)).toBe('Refresh failed')
|
||||
expect(getProviderUsageStatusLabel(grok)).toBe('Run Grok to refresh')
|
||||
expect(getProviderUsageErrorMessage(grok)).toBe(
|
||||
'Grok usage could not be refreshed. Agent sessions may still be signed in.'
|
||||
'Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.'
|
||||
)
|
||||
})
|
||||
|
||||
it('shows the exact Kimi CLI recovery flow for an expired read-only session', () => {
|
||||
const kimi = provider({
|
||||
provider: 'kimi',
|
||||
error: 'Kimi session expired — run kimi on the computer running Orca, then retry usage.',
|
||||
usageMetadata: {
|
||||
failureKind: 'delegated-refresh-required',
|
||||
source: 'oauth'
|
||||
}
|
||||
})
|
||||
|
||||
expect(getProviderUsageStatusLabel(kimi)).toBe('Run Kimi to refresh')
|
||||
expect(getProviderUsageErrorMessage(kimi)).toBe(
|
||||
'Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage.'
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,25 @@ function isUsageAuthError(message: string | null): boolean {
|
|||
return Boolean(message && USAGE_AUTH_ERROR_PATTERNS.some((pattern) => pattern.test(message)))
|
||||
}
|
||||
|
||||
function getDelegatedCliRefreshProvider(
|
||||
p: ProviderRateLimits
|
||||
): Extract<ProviderRateLimits['provider'], 'grok' | 'kimi'> | null {
|
||||
if (p.usageMetadata?.failureKind !== 'delegated-refresh-required') {
|
||||
return null
|
||||
}
|
||||
// Why: only these providers require a user-run CLI to rotate the read-only
|
||||
// session Orca consumes; Claude handles the same failure kind in-app.
|
||||
return p.provider === 'grok' || p.provider === 'kimi' ? p.provider : null
|
||||
}
|
||||
|
||||
export function getProviderUsageStatusLabel(p: ProviderRateLimits): string {
|
||||
const delegatedCliProvider = getDelegatedCliRefreshProvider(p)
|
||||
if (delegatedCliProvider === 'grok') {
|
||||
return translate('auto.components.status.bar.tooltip.e2c6a4f917', 'Run Grok to refresh')
|
||||
}
|
||||
if (delegatedCliProvider === 'kimi') {
|
||||
return translate('auto.components.status.bar.tooltip.f90b3d7a16', 'Run Kimi to refresh')
|
||||
}
|
||||
if (p.provider === 'claude') {
|
||||
switch (p.usageMetadata?.failureKind) {
|
||||
case 'deferred-by-live-session':
|
||||
|
|
@ -107,6 +125,19 @@ export function getProviderUsageErrorMessage(p: ProviderRateLimits): string {
|
|||
if (!p.error) {
|
||||
return fallback
|
||||
}
|
||||
const delegatedCliProvider = getDelegatedCliRefreshProvider(p)
|
||||
if (delegatedCliProvider === 'grok') {
|
||||
return translate(
|
||||
'auto.components.status.bar.tooltip.d1b7f509ac',
|
||||
'Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.'
|
||||
)
|
||||
}
|
||||
if (delegatedCliProvider === 'kimi') {
|
||||
return translate(
|
||||
'auto.components.status.bar.tooltip.a37e8c15d4',
|
||||
'Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage.'
|
||||
)
|
||||
}
|
||||
if (p.provider === 'claude') {
|
||||
switch (p.usageMetadata?.failureKind) {
|
||||
case 'deferred-by-live-session':
|
||||
|
|
|
|||
|
|
@ -3224,7 +3224,11 @@
|
|||
"42fdd4da1d": "Claude sign-in is being refreshed. Agent sessions may still be signed in.",
|
||||
"c06c1d215d": "Claude usage could not be refreshed because the network request failed.",
|
||||
"cabdc2a9e0": "Claude sign-in credentials could not be read.",
|
||||
"a7517cccb6": "Claude usage is unavailable right now."
|
||||
"a7517cccb6": "Claude usage is unavailable right now.",
|
||||
"e2c6a4f917": "Run Grok to refresh",
|
||||
"d1b7f509ac": "Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.",
|
||||
"f90b3d7a16": "Run Kimi to refresh",
|
||||
"a37e8c15d4": "Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage."
|
||||
},
|
||||
"SshTargetStatusRow": {
|
||||
"sshHost": "SSH Host"
|
||||
|
|
@ -9037,7 +9041,9 @@
|
|||
"b7e2d9f0a3": "Same weekly credit % as the grok /usage screen in the terminal.",
|
||||
"c6d1a8f4e2": "Resets {{when}}",
|
||||
"e6dadc1e2b": "Monthly usage",
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts."
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts.",
|
||||
"b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.",
|
||||
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed."
|
||||
},
|
||||
"AppearanceWindowSidebarSection": {
|
||||
"usagePercentageDisplayUsed": "Used",
|
||||
|
|
|
|||
|
|
@ -3224,7 +3224,11 @@
|
|||
"42fdd4da1d": "Se está actualizando el inicio de sesión de Claude. Las sesiones de agentes pueden seguir iniciadas.",
|
||||
"c06c1d215d": "No se pudo actualizar el uso de Claude porque falló la solicitud de red.",
|
||||
"cabdc2a9e0": "No se pudieron leer las credenciales de inicio de sesión de Claude.",
|
||||
"a7517cccb6": "El uso de Claude no está disponible ahora."
|
||||
"a7517cccb6": "El uso de Claude no está disponible ahora.",
|
||||
"e2c6a4f917": "Run Grok to refresh",
|
||||
"d1b7f509ac": "Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.",
|
||||
"f90b3d7a16": "Run Kimi to refresh",
|
||||
"a37e8c15d4": "Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage."
|
||||
},
|
||||
"SshTargetStatusRow": {
|
||||
"sshHost": "Host SSH"
|
||||
|
|
@ -9037,7 +9041,9 @@
|
|||
"b7e2d9f0a3": "El mismo porcentaje de créditos semanales que la pantalla grok /usage en la terminal.",
|
||||
"c6d1a8f4e2": "Se restablece {{when}}",
|
||||
"e6dadc1e2b": "Monthly usage",
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts."
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts.",
|
||||
"b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.",
|
||||
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed."
|
||||
},
|
||||
"AppearanceWindowSidebarSection": {
|
||||
"usagePercentageDisplayUsed": "Usado",
|
||||
|
|
|
|||
|
|
@ -3224,7 +3224,11 @@
|
|||
"42fdd4da1d": "Claude サインインを更新中です。エージェントセッションはまだサインイン済みの場合があります。",
|
||||
"c06c1d215d": "ネットワークリクエストに失敗したため、Claude 使用量を更新できませんでした。",
|
||||
"cabdc2a9e0": "Claude サインイン認証情報を読み取れませんでした。",
|
||||
"a7517cccb6": "Claude 使用量は現在利用できません。"
|
||||
"a7517cccb6": "Claude 使用量は現在利用できません。",
|
||||
"e2c6a4f917": "Run Grok to refresh",
|
||||
"d1b7f509ac": "Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.",
|
||||
"f90b3d7a16": "Run Kimi to refresh",
|
||||
"a37e8c15d4": "Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage."
|
||||
},
|
||||
"SshTargetStatusRow": {
|
||||
"sshHost": "SSH ホスト"
|
||||
|
|
@ -9037,7 +9041,9 @@
|
|||
"b7e2d9f0a3": "ターミナルの grok /usage 画面と同じ週次クレジット率です。",
|
||||
"c6d1a8f4e2": "{{when}} にリセット",
|
||||
"e6dadc1e2b": "Monthly usage",
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts."
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts.",
|
||||
"b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.",
|
||||
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed."
|
||||
},
|
||||
"AppearanceWindowSidebarSection": {
|
||||
"usagePercentageDisplayUsed": "使用済み",
|
||||
|
|
|
|||
|
|
@ -3224,7 +3224,11 @@
|
|||
"42fdd4da1d": "Claude 로그인을 새로 고치는 중입니다. 에이전트 세션은 여전히 로그인되어 있을 수 있습니다.",
|
||||
"c06c1d215d": "네트워크 요청 실패로 Claude 사용량을 새로 고칠 수 없습니다.",
|
||||
"cabdc2a9e0": "Claude 로그인 자격 증명을 읽을 수 없습니다.",
|
||||
"a7517cccb6": "현재 Claude 사용량을 사용할 수 없습니다."
|
||||
"a7517cccb6": "현재 Claude 사용량을 사용할 수 없습니다.",
|
||||
"e2c6a4f917": "Run Grok to refresh",
|
||||
"d1b7f509ac": "Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.",
|
||||
"f90b3d7a16": "Run Kimi to refresh",
|
||||
"a37e8c15d4": "Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage."
|
||||
},
|
||||
"SshTargetStatusRow": {
|
||||
"sshHost": "SSH 호스트"
|
||||
|
|
@ -9037,7 +9041,9 @@
|
|||
"b7e2d9f0a3": "터미널의 grok /usage 화면과 같은 주간 크레딧 비율입니다.",
|
||||
"c6d1a8f4e2": "{{when}}에 재설정",
|
||||
"e6dadc1e2b": "Monthly usage",
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts."
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts.",
|
||||
"b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.",
|
||||
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed."
|
||||
},
|
||||
"AppearanceWindowSidebarSection": {
|
||||
"usagePercentageDisplayUsed": "사용",
|
||||
|
|
|
|||
|
|
@ -3224,7 +3224,11 @@
|
|||
"42fdd4da1d": "正在刷新 Claude 登录。智能体会话可能仍处于登录状态。",
|
||||
"c06c1d215d": "由于网络请求失败,无法刷新 Claude 用量。",
|
||||
"cabdc2a9e0": "无法读取 Claude 登录凭据。",
|
||||
"a7517cccb6": "Claude 用量目前不可用。"
|
||||
"a7517cccb6": "Claude 用量目前不可用。",
|
||||
"e2c6a4f917": "Run Grok to refresh",
|
||||
"d1b7f509ac": "Run grok in a terminal on the computer running Orca and wait for it to start. If prompted, complete sign-in, then retry usage. You do not need to send a chat message.",
|
||||
"f90b3d7a16": "Run Kimi to refresh",
|
||||
"a37e8c15d4": "Run kimi in a terminal on the computer running Orca and wait for it to start, then retry usage."
|
||||
},
|
||||
"SshTargetStatusRow": {
|
||||
"sshHost": "SSH 主机"
|
||||
|
|
@ -9037,7 +9041,9 @@
|
|||
"b7e2d9f0a3": "与终端中 grok /usage 屏幕显示的每周额度百分比相同。",
|
||||
"c6d1a8f4e2": "{{when}} 重置",
|
||||
"e6dadc1e2b": "Monthly usage",
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts."
|
||||
"75e396bf42": "Included monthly usage for Grok unified-billing accounts.",
|
||||
"b36fa2c908": "Signed in. Orca reads the Grok CLI session stored on disk.",
|
||||
"f08c41de73": "Session expired — run grok on the computer running Orca and wait for it to start. If prompted, complete sign-in, then click Refresh usage. No chat message is needed."
|
||||
},
|
||||
"AppearanceWindowSidebarSection": {
|
||||
"usagePercentageDisplayUsed": "已用",
|
||||
|
|
|
|||
Loading…
Reference in New Issue