@adecore/agents-react 0.29.3 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/AgentIcon.js +4 -3
- package/dist/agents/accounts.d.ts +2 -0
- package/dist/agents/accounts.js +6 -0
- package/dist/agents/agent-marks.d.ts +8 -0
- package/dist/agents/agent-marks.js +12 -0
- package/dist/agents/provider-paths.js +3 -2
- package/dist/chat/logic/picker-entries.d.ts +12 -2
- package/dist/chat/logic/picker-entries.js +41 -7
- package/dist/chat/ui/Pickers.js +31 -3
- package/dist/chat/ui/rows/row-rhythm.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/locales/en/agent-chat.d.ts +2 -0
- package/dist/locales/en/agent-chat.js +2 -0
- package/dist/locales/en/agent-providers.d.ts +3 -0
- package/dist/locales/en/agent-providers.js +4 -1
- package/dist/locales/nl/agent-chat.d.ts +2 -0
- package/dist/locales/nl/agent-chat.js +2 -0
- package/dist/locales/nl/agent-providers.d.ts +3 -0
- package/dist/locales/nl/agent-providers.js +4 -1
- package/dist/providers/AccountDetail.d.ts +4 -3
- package/dist/providers/AccountDetail.js +6 -5
- package/dist/providers/AddAccountForm.d.ts +3 -2
- package/dist/providers/AddAccountForm.js +2 -2
- package/dist/providers/CliDetail.d.ts +3 -3
- package/dist/providers/CliDetail.js +3 -3
- package/dist/providers/ProvidersPane.js +11 -10
- package/dist/providers/parts.js +5 -4
- package/dist/theme.css +3 -0
- package/dist/usage/format.js +6 -2
- package/dist/usage/limit-groups.js +2 -1
- package/package.json +8 -8
- package/src/agents/AgentIcon.tsx +12 -3
- package/src/agents/accounts.ts +13 -0
- package/src/agents/agent-marks.ts +26 -0
- package/src/agents/provider-paths.ts +3 -2
- package/src/chat/logic/picker-entries.ts +70 -10
- package/src/chat/ui/Pickers.tsx +54 -2
- package/src/chat/ui/rows/row-rhythm.ts +3 -2
- package/src/index.ts +1 -0
- package/src/locales/en/agent-chat.ts +2 -0
- package/src/locales/en/agent-providers.ts +4 -1
- package/src/locales/nl/agent-chat.ts +2 -0
- package/src/locales/nl/agent-providers.ts +4 -1
- package/src/providers/AccountDetail.tsx +34 -26
- package/src/providers/AddAccountForm.tsx +8 -3
- package/src/providers/CliDetail.tsx +5 -5
- package/src/providers/ProvidersPane.tsx +13 -10
- package/src/providers/parts.tsx +6 -5
- package/src/theme.css +3 -0
- package/src/usage/format.ts +6 -2
- package/src/usage/limit-groups.ts +2 -1
|
@@ -3,7 +3,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
|
|
3
3
|
import { LogIn } from 'lucide-react';
|
|
4
4
|
import type { ProviderInfo } from '@adecore/agent-contracts';
|
|
5
5
|
import { AccountDot } from '../agents/AccountDot';
|
|
6
|
-
import { accountName, FOLDER_VARIABLES, type AccountEntry } from '../agents/accounts';
|
|
6
|
+
import { accountName, FOLDER_VARIABLES, type AccountEntry, type AccountLogin } from '../agents/accounts';
|
|
7
7
|
import { ConfirmDialog, DetailHeader, SettingsRow, SettingsSection } from '@adecore/ui/settings';
|
|
8
8
|
import { Button, copyText, Icon, Input, Switch, Tooltip } from '@adecore/ui';
|
|
9
9
|
import { useChatHost } from '../host';
|
|
@@ -19,8 +19,10 @@ const TROUBLE = new Set(['folder-missing', 'unavailable', 'failed']);
|
|
|
19
19
|
interface AccountDetailProps {
|
|
20
20
|
provider: ProviderInfo;
|
|
21
21
|
entry: AccountEntry;
|
|
22
|
-
/*
|
|
23
|
-
|
|
22
|
+
/* Null for a CLI without a config folder: it has only its own login, and nothing to sign in here. */
|
|
23
|
+
login: AccountLogin;
|
|
24
|
+
/* Whether the host can open a terminal for a login at all. */
|
|
25
|
+
canOpenLogin: boolean;
|
|
24
26
|
/* Why a login cannot open a terminal right now, or null when it can. */
|
|
25
27
|
loginBlocked: string | null;
|
|
26
28
|
secretsAvailable: boolean;
|
|
@@ -56,7 +58,7 @@ function NameField({ value, label, onSave }: { value: string; label: string; onS
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
/* One account: how it looks in a picker, who is signed in, where its folder is, its variables. */
|
|
59
|
-
export function AccountDetail({ provider, entry,
|
|
61
|
+
export function AccountDetail({ provider, entry, login, canOpenLogin, loginBlocked, secretsAvailable, onRemoved }: AccountDetailProps) {
|
|
60
62
|
const { t } = useTranslation('agent-providers');
|
|
61
63
|
const scope = useChatScope();
|
|
62
64
|
const host = useChatHost();
|
|
@@ -72,9 +74,11 @@ export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secrets
|
|
|
72
74
|
host.notify({ kind: 'success', title: t('account.folderCopied'), description: folder });
|
|
73
75
|
};
|
|
74
76
|
|
|
77
|
+
const trouble = status !== null && TROUBLE.has(status.state) && status.message ? status.message : null;
|
|
78
|
+
|
|
75
79
|
const loginDetail = (): string => {
|
|
76
|
-
if (
|
|
77
|
-
return
|
|
80
|
+
if (trouble !== null) {
|
|
81
|
+
return trouble;
|
|
78
82
|
}
|
|
79
83
|
if (!signedIn) {
|
|
80
84
|
return t('account.login.signedOutDetail');
|
|
@@ -149,27 +153,31 @@ export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secrets
|
|
|
149
153
|
}
|
|
150
154
|
/>
|
|
151
155
|
</SettingsSection>
|
|
152
|
-
{
|
|
156
|
+
{(login === 'chat' || (login === 'terminal' && canOpenLogin)) && (
|
|
153
157
|
<SettingsSection title={t('account.login.title')}>
|
|
154
|
-
|
|
155
|
-
label={
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
158
|
+
{login === 'chat' ? (
|
|
159
|
+
<SettingsRow label={t('account.login.inChat')} description={trouble ?? t('account.login.inChatDetail')} />
|
|
160
|
+
) : (
|
|
161
|
+
<SettingsRow
|
|
162
|
+
label={
|
|
163
|
+
signedIn
|
|
164
|
+
? status?.email
|
|
165
|
+
? t('account.login.signedInAs', { email: status.email })
|
|
166
|
+
: t('account.login.signedIn')
|
|
167
|
+
: t('account.login.signedOut')
|
|
168
|
+
}
|
|
169
|
+
description={loginBlocked === null ? loginDetail() : `${loginDetail()} ${loginBlocked}`}
|
|
170
|
+
control={
|
|
171
|
+
loginBlocked === null ? (
|
|
172
|
+
loginButton
|
|
173
|
+
) : (
|
|
174
|
+
<Tooltip label={loginBlocked}>
|
|
175
|
+
<span className="inline-flex">{loginButton}</span>
|
|
176
|
+
</Tooltip>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
/>
|
|
180
|
+
)}
|
|
173
181
|
<SettingsRow label={t('account.folder.label')}>
|
|
174
182
|
<Input mono className="h-8.5" value={folder} readOnly aria-label={t('account.folder.label')} />
|
|
175
183
|
<p className="-mt-1 text-xs text-text-muted">
|
|
@@ -3,7 +3,7 @@ import clsx from 'clsx';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { ChevronRight } from 'lucide-react';
|
|
5
5
|
import type { ProviderInfo } from '@adecore/agent-contracts';
|
|
6
|
-
import { freeAccountColor, mintAccountId, type AccountEntry } from '../agents/accounts';
|
|
6
|
+
import { freeAccountColor, mintAccountId, type AccountEntry, type AccountLogin } from '../agents/accounts';
|
|
7
7
|
import { useChatHost } from '../host';
|
|
8
8
|
import { useChatScope } from '../scope';
|
|
9
9
|
import { createAccount, linkAccount } from './account-actions';
|
|
@@ -16,6 +16,7 @@ import { providerAccountsOf } from '../state/provider-accounts';
|
|
|
16
16
|
interface AddAccountFormProps {
|
|
17
17
|
provider: ProviderInfo;
|
|
18
18
|
entries: AccountEntry[];
|
|
19
|
+
login: AccountLogin;
|
|
19
20
|
onCancel(): void;
|
|
20
21
|
/* The account exists; `made` says the machine made its folder, so nobody signed in there yet. */
|
|
21
22
|
onAdded(id: string, name: string, made: boolean): void;
|
|
@@ -25,7 +26,7 @@ interface AddAccountFormProps {
|
|
|
25
26
|
* A name and a color, asked before anything exists, so the account is made once with both. By default
|
|
26
27
|
* the machine makes the folder; under Advanced a person points at a folder of their own instead.
|
|
27
28
|
*/
|
|
28
|
-
export function AddAccountForm({ provider, entries, onCancel, onAdded }: AddAccountFormProps) {
|
|
29
|
+
export function AddAccountForm({ provider, entries, login, onCancel, onAdded }: AddAccountFormProps) {
|
|
29
30
|
const { t } = useTranslation('agent-providers');
|
|
30
31
|
const scope = useChatScope();
|
|
31
32
|
const { accents } = useChatHost();
|
|
@@ -62,7 +63,11 @@ export function AddAccountForm({ provider, entries, onCancel, onAdded }: AddAcco
|
|
|
62
63
|
|
|
63
64
|
return (
|
|
64
65
|
<form className="contents" onSubmit={(event) => void submit(event)}>
|
|
65
|
-
<DetailHeader
|
|
66
|
+
<DetailHeader
|
|
67
|
+
mark={<CliTile kind={provider.kind} />}
|
|
68
|
+
title={t('add.title', { provider: provider.name })}
|
|
69
|
+
subtitle={login === 'chat' ? t('add.chatDescription') : t('add.description')}
|
|
70
|
+
/>
|
|
66
71
|
<SettingsSection>
|
|
67
72
|
<SettingsRow
|
|
68
73
|
label={t('add.name')}
|
|
@@ -4,7 +4,7 @@ import type { AgentKind, ModelInfo, ProviderInfo } from '@adecore/agent-contract
|
|
|
4
4
|
import { AccountDot } from '../agents/AccountDot';
|
|
5
5
|
import { ModelOptionControl } from '../agents/ModelOptionControl';
|
|
6
6
|
import { carryOptions } from '../agents/model-options';
|
|
7
|
-
import { ACCOUNT_TONE_CLASSES, accountName, accountStatusLine, type AccountEntry } from '../agents/accounts';
|
|
7
|
+
import { ACCOUNT_TONE_CLASSES, accountName, accountStatusLine, type AccountEntry, type AccountLogin } from '../agents/accounts';
|
|
8
8
|
import { accountFor, forgetChatSelection, rememberChatAccount, rememberChatSelection, selectionFor, useChatPreferences } from '../chat/preferences';
|
|
9
9
|
import { useChatScope } from '../scope';
|
|
10
10
|
import { providerAbilities } from './provider-abilities';
|
|
@@ -40,13 +40,13 @@ interface CliDetailProps {
|
|
|
40
40
|
/* Null for a machine that keeps no accounts. */
|
|
41
41
|
entries: AccountEntry[] | null;
|
|
42
42
|
defaults: boolean;
|
|
43
|
-
|
|
43
|
+
login: AccountLogin;
|
|
44
44
|
onAddAccount(): void;
|
|
45
45
|
onPickAccount(id: string): void;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/* One CLI: what it is, what a new chat of it starts with unless `defaults` is off, and its accounts. */
|
|
49
|
-
export function CliDetail({ provider, entries, defaults,
|
|
49
|
+
export function CliDetail({ provider, entries, defaults, login, onAddAccount, onPickAccount }: CliDetailProps) {
|
|
50
50
|
const { t } = useTranslation('agent-providers');
|
|
51
51
|
const { id: scopeId } = useChatScope();
|
|
52
52
|
const preferences = useChatPreferences();
|
|
@@ -56,7 +56,7 @@ export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAcc
|
|
|
56
56
|
const picked = accountFor(preferences, scopeId, provider.kind, machineAccounts) ?? provider.kind;
|
|
57
57
|
// An account that is off is out of every picker, unless it is the one picked already.
|
|
58
58
|
const choosable = (entries ?? []).filter((entry) => entry.account.enabled !== false || entry.id === picked);
|
|
59
|
-
const
|
|
59
|
+
const canAddAccount = login !== null;
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<>
|
|
@@ -135,7 +135,7 @@ export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAcc
|
|
|
135
135
|
) : (
|
|
136
136
|
<SettingsSection title={t('cli.accounts')} footer={canAddAccount ? undefined : t('cli.onlyDefault', { provider: provider.name })}>
|
|
137
137
|
{entries.map((entry) => {
|
|
138
|
-
const status = accountStatusLine(entry.status,
|
|
138
|
+
const status = accountStatusLine(entry.status, login === 'terminal');
|
|
139
139
|
return (
|
|
140
140
|
<button
|
|
141
141
|
key={entry.id}
|
|
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
4
4
|
import { Plus, Terminal } from 'lucide-react';
|
|
5
5
|
import type { AgentKind, ProviderInfo } from '@adecore/agent-contracts';
|
|
6
6
|
import { AccountDot } from '../agents/AccountDot';
|
|
7
|
-
import { ACCOUNT_TONE_CLASSES, accountName, accountsOfKind, accountStatusLine, type AccountEntry } from '../agents/accounts';
|
|
7
|
+
import { ACCOUNT_TONE_CLASSES, accountLoginOf, accountName, accountsOfKind, accountStatusLine, type AccountEntry } from '../agents/accounts';
|
|
8
8
|
import { ErrorBoundary, Icon, IconButton, SectionLabel, Skeleton } from '@adecore/ui';
|
|
9
9
|
import { MasterDetail } from '@adecore/ui/settings';
|
|
10
10
|
import { useChatHost } from '../host';
|
|
@@ -68,10 +68,11 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
68
68
|
const loginBlocked = host.useLoginBlocked(scopeId);
|
|
69
69
|
const [picked, setPicked] = useState<Picked | null>(() => (target === 'providers.apple' ? { kind: 'cli', cli: 'apple' } : null));
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
const
|
|
71
|
+
// A provider that never runs in a terminal is no CLI to install: it is turned on in its own detail, so it is always listed.
|
|
72
|
+
const installed = useMemo(() => providers.filter((provider) => provider.installed || !provider.capabilities.terminal), [providers]);
|
|
73
|
+
const missing = useMemo(() => providers.filter((provider) => !provider.installed && provider.capabilities.terminal), [providers]);
|
|
73
74
|
const entriesOf = (kind: AgentKind): AccountEntry[] => accountsOfKind(accounts, kind);
|
|
74
|
-
const
|
|
75
|
+
const loginOf = (kind: AgentKind) => accountLoginOf(accounts, kind);
|
|
75
76
|
|
|
76
77
|
// What was picked may be gone since (an account removed elsewhere); the first CLI stands in for it.
|
|
77
78
|
const pickedEntry = picked?.kind === 'account' ? (Object.hasOwn(accounts?.accounts ?? {}, picked.id) ? picked.id : null) : null;
|
|
@@ -94,7 +95,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
94
95
|
|
|
95
96
|
const added = (id: string, name: string, made: boolean): void => {
|
|
96
97
|
setPicked({ kind: 'account', id });
|
|
97
|
-
if (made && provider !== null && openLogin !== null && loginBlocked === null) {
|
|
98
|
+
if (made && provider !== null && loginOf(provider.kind) === 'terminal' && openLogin !== null && loginBlocked === null) {
|
|
98
99
|
void openLogin(scopeId, provider.kind, id, name);
|
|
99
100
|
}
|
|
100
101
|
};
|
|
@@ -108,7 +109,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
108
109
|
onSelect={() => setPicked({ kind: 'cli', cli: cli.kind })}
|
|
109
110
|
className="pr-1"
|
|
110
111
|
trailing={
|
|
111
|
-
|
|
112
|
+
loginOf(cli.kind) !== null && (
|
|
112
113
|
<IconButton
|
|
113
114
|
icon={Plus}
|
|
114
115
|
size="sm"
|
|
@@ -120,11 +121,11 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
120
121
|
>
|
|
121
122
|
<CliMark kind={cli.kind} />
|
|
122
123
|
<span className="min-w-0 grow truncate text-xs font-medium text-text">{cli.name}</span>
|
|
123
|
-
{cli.
|
|
124
|
+
{cli.capabilities.terminal && cli.version && <span className="shrink-0 font-mono text-code text-text-faint">{cli.version}</span>}
|
|
124
125
|
</ListRow>
|
|
125
126
|
{entries.map((entry) => {
|
|
126
127
|
const name = accountName(entry, cli.name);
|
|
127
|
-
const status = accountStatusLine(entry.status,
|
|
128
|
+
const status = accountStatusLine(entry.status, loginOf(cli.kind) === 'terminal');
|
|
128
129
|
return (
|
|
129
130
|
<ListRow
|
|
130
131
|
key={entry.id}
|
|
@@ -189,6 +190,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
189
190
|
key={provider.kind}
|
|
190
191
|
provider={provider}
|
|
191
192
|
entries={entriesOf(provider.kind)}
|
|
193
|
+
login={loginOf(provider.kind)}
|
|
192
194
|
onCancel={() => setPicked({ kind: 'cli', cli: provider.kind })}
|
|
193
195
|
onAdded={added}
|
|
194
196
|
/>
|
|
@@ -202,7 +204,8 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
202
204
|
key={entry.id}
|
|
203
205
|
provider={provider}
|
|
204
206
|
entry={entry}
|
|
205
|
-
|
|
207
|
+
login={loginOf(provider.kind)}
|
|
208
|
+
canOpenLogin={openLogin !== null}
|
|
206
209
|
loginBlocked={loginBlocked}
|
|
207
210
|
secretsAvailable={accounts?.secretsAvailable === true}
|
|
208
211
|
onRemoved={() => setPicked({ kind: 'cli', cli: provider.kind })}
|
|
@@ -215,7 +218,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
215
218
|
provider={provider}
|
|
216
219
|
entries={accounts === null ? null : entriesOf(provider.kind)}
|
|
217
220
|
defaults={defaults}
|
|
218
|
-
|
|
221
|
+
login={loginOf(provider.kind)}
|
|
219
222
|
onAddAccount={() => setPicked({ kind: 'add', cli: provider.kind })}
|
|
220
223
|
onPickAccount={(id) => setPicked({ kind: 'account', id })}
|
|
221
224
|
/>
|
package/src/providers/parts.tsx
CHANGED
|
@@ -2,17 +2,18 @@ import clsx from 'clsx';
|
|
|
2
2
|
import type { AgentKind } from '@adecore/agent-contracts';
|
|
3
3
|
import { AgentIcon } from '../agents/AgentIcon';
|
|
4
4
|
|
|
5
|
-
// Each CLI in its color of the usage charts, so a mark here reads as the same CLI there.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// Each CLI in its color of the usage charts, so a mark here reads as the same CLI there. A CLI without
|
|
6
|
+
// a chart color of its own is muted.
|
|
7
|
+
const CLI_COLORS: Partial<Record<AgentKind, string>> = {
|
|
8
8
|
claude: 'text-chart-claude',
|
|
9
9
|
codex: 'text-chart-codex',
|
|
10
10
|
gemini: 'text-chart-gemini',
|
|
11
|
-
copilot: 'text-chart-copilot'
|
|
11
|
+
copilot: 'text-chart-copilot',
|
|
12
|
+
nanogpt: 'text-chart-nanogpt'
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export function CliMark({ kind, size = 14, className }: { kind: AgentKind; size?: number; className?: string }) {
|
|
15
|
-
return <AgentIcon kind={kind} size={size} className={clsx('shrink-0', CLI_COLORS[kind], className)} />;
|
|
16
|
+
return <AgentIcon kind={kind} size={size} className={clsx('shrink-0', CLI_COLORS[kind] ?? 'text-text-muted', className)} />;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/* As tall as the title's first line, so the mark sits beside the name rather than between the two lines. */
|
package/src/theme.css
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
--chart-codex: #30b0c7;
|
|
14
14
|
--chart-gemini: #3b82f6;
|
|
15
15
|
--chart-copilot: #64748b;
|
|
16
|
+
--chart-nanogpt: #34c759;
|
|
16
17
|
/* What a chat's context holds, one color per part; the rest is the system's and stays gray. */
|
|
17
18
|
--chart-context-tools: #4d7ce6;
|
|
18
19
|
--chart-context-files: #9a63e0;
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
--chart-codex: #40c8e0;
|
|
46
47
|
--chart-gemini: #60a5fa;
|
|
47
48
|
--chart-copilot: #94a3b8;
|
|
49
|
+
--chart-nanogpt: #30d158;
|
|
48
50
|
--chart-context-tools: #8aa7f5;
|
|
49
51
|
--chart-context-files: #b98af3;
|
|
50
52
|
--chart-context-conversation: #6dd08b;
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
--color-chart-codex: var(--chart-codex);
|
|
83
85
|
--color-chart-gemini: var(--chart-gemini);
|
|
84
86
|
--color-chart-copilot: var(--chart-copilot);
|
|
87
|
+
--color-chart-nanogpt: var(--chart-nanogpt);
|
|
85
88
|
--color-chart-context-tools: var(--chart-context-tools);
|
|
86
89
|
--color-chart-context-files: var(--chart-context-files);
|
|
87
90
|
--color-chart-context-conversation: var(--chart-context-conversation);
|
package/src/usage/format.ts
CHANGED
|
@@ -28,9 +28,13 @@ export function moneyFormat(currency: UsageCurrency, rate: UsageRate | null): (u
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export const PROVIDER_LABELS: Record<UsageProvider, string> = { claude: 'Claude Code', codex: 'Codex' };
|
|
31
|
+
export const PROVIDER_LABELS: Record<UsageProvider, string> = { claude: 'Claude Code', codex: 'Codex', nanogpt: 'NanoGPT' };
|
|
32
32
|
|
|
33
|
-
export const PROVIDER_COLORS: Record<UsageProvider, string> = {
|
|
33
|
+
export const PROVIDER_COLORS: Record<UsageProvider, string> = {
|
|
34
|
+
claude: 'var(--chart-claude)',
|
|
35
|
+
codex: 'var(--chart-codex)',
|
|
36
|
+
nanogpt: 'var(--chart-nanogpt)'
|
|
37
|
+
};
|
|
34
38
|
|
|
35
39
|
/*
|
|
36
40
|
* A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as plain
|
|
@@ -108,7 +108,8 @@ export function explain(provider: UsageLimitsProvider): string | null {
|
|
|
108
108
|
return i18next.t('agent-usage:limits.notInstalled');
|
|
109
109
|
}
|
|
110
110
|
if (provider.unavailable.reason === 'no-subscription') {
|
|
111
|
-
|
|
111
|
+
// A provider that knows why it has no plan says so; the rest are accounts on an API key.
|
|
112
|
+
return provider.unavailable.message ?? i18next.t('agent-usage:limits.noSubscription');
|
|
112
113
|
}
|
|
113
114
|
return provider.unavailable.message ?? i18next.t('agent-usage:limits.unreachable');
|
|
114
115
|
}
|