@adecore/agents-react 0.30.0 → 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/index.d.ts +1 -0
- package/dist/index.js +1 -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-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 +8 -8
- package/dist/providers/parts.js +2 -1
- 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/index.ts +1 -0
- package/src/locales/en/agent-providers.ts +4 -1
- 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 +9 -7
- package/src/providers/parts.tsx +2 -1
- package/src/theme.css +3 -0
- package/src/usage/format.ts +6 -2
- package/src/usage/limit-groups.ts +2 -1
package/dist/agents/AgentIcon.js
CHANGED
|
@@ -3,18 +3,19 @@ import clsx from 'clsx';
|
|
|
3
3
|
import { Bot } from 'lucide-react';
|
|
4
4
|
import { siClaude, siGithubcopilot, siGooglegemini } from 'simple-icons';
|
|
5
5
|
import { Icon } from '@adecore/ui';
|
|
6
|
+
import { hostAgentMark } from './agent-marks.js';
|
|
6
7
|
import { PROVIDER_PATHS } from './provider-paths.js';
|
|
7
8
|
// simple-icons removed OpenAI's mark at its request, so Codex reuses the usage page's path.
|
|
8
|
-
const
|
|
9
|
+
const BUILT_IN_MARKS = {
|
|
9
10
|
claude: siClaude,
|
|
10
11
|
codex: { title: 'Codex', path: PROVIDER_PATHS.codex },
|
|
11
12
|
gemini: siGooglegemini,
|
|
12
13
|
copilot: siGithubcopilot
|
|
13
14
|
};
|
|
14
15
|
export function AgentIcon({ kind, size = 14, className }) {
|
|
15
|
-
const mark =
|
|
16
|
+
const mark = hostAgentMark(kind) ?? BUILT_IN_MARKS[kind];
|
|
16
17
|
if (!mark) {
|
|
17
18
|
return _jsx(Icon, { icon: Bot, size: size, className: className });
|
|
18
19
|
}
|
|
19
|
-
return (_jsx("svg", { role: "img", "aria-label": mark.title, viewBox:
|
|
20
|
+
return (_jsx("svg", { role: "img", "aria-label": mark.title, viewBox: mark.viewBox ?? '0 0 24 24', width: size, height: size, fill: "currentColor", className: clsx('align-middle', className), children: _jsx("path", { d: mark.path }) }));
|
|
20
21
|
}
|
|
@@ -10,6 +10,8 @@ export interface AccountEntry {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function accountsOfKind(accounts: ProviderAccounts | null, kind: AgentKind): AccountEntry[];
|
|
12
12
|
export declare function accountName(entry: Pick<AccountEntry, 'id' | 'account' | 'isDefault'>, providerName: string): string;
|
|
13
|
+
export type AccountLogin = 'terminal' | 'chat' | null;
|
|
14
|
+
export declare function accountLoginOf(accounts: ProviderAccounts | null | undefined, kind: AgentKind): AccountLogin;
|
|
13
15
|
export type AccountTone = 'muted' | 'needs-you' | 'error';
|
|
14
16
|
export declare const ACCOUNT_TONE_CLASSES: Record<AccountTone, string>;
|
|
15
17
|
export declare function accountStatusLine(status: ProviderAccountStatus | null, canLogIn: boolean): {
|
package/dist/agents/accounts.js
CHANGED
|
@@ -26,6 +26,12 @@ export function accountsOfKind(accounts, kind) {
|
|
|
26
26
|
export function accountName(entry, providerName) {
|
|
27
27
|
return entry.account.label ?? (entry.isDefault ? providerName : entry.id);
|
|
28
28
|
}
|
|
29
|
+
export function accountLoginOf(accounts, kind) {
|
|
30
|
+
if (accounts?.loginCommands?.[kind] !== undefined) {
|
|
31
|
+
return 'terminal';
|
|
32
|
+
}
|
|
33
|
+
return accounts?.chatLogins?.includes(kind) === true ? 'chat' : null;
|
|
34
|
+
}
|
|
29
35
|
export const ACCOUNT_TONE_CLASSES = {
|
|
30
36
|
muted: 'text-text-muted',
|
|
31
37
|
'needs-you': 'text-status-needs-you',
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentKind } from '@adecore/agent-contracts';
|
|
2
|
+
export interface AgentMark {
|
|
3
|
+
title: string;
|
|
4
|
+
path: string;
|
|
5
|
+
viewBox?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function configureAgentMarks(marks: Readonly<Partial<Record<AgentKind, AgentMark>>>): void;
|
|
8
|
+
export declare function hostAgentMark(kind: AgentKind): AgentMark | undefined;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
let hostMarks = {};
|
|
2
|
+
/*
|
|
3
|
+
* The marks of the kinds a host adds, and any it draws differently, set once before the first render. Every
|
|
4
|
+
* place the shared UI draws an agent's mark reads them; a kind with none keeps the built-in mark or the robot.
|
|
5
|
+
* A later call replaces the whole set.
|
|
6
|
+
*/
|
|
7
|
+
export function configureAgentMarks(marks) {
|
|
8
|
+
hostMarks = { ...marks };
|
|
9
|
+
}
|
|
10
|
+
export function hostAgentMark(kind) {
|
|
11
|
+
return hostMarks[kind];
|
|
12
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* The marks of Anthropic and OpenAI. Lucide has no brand marks, so these are the icon rule's
|
|
2
|
+
* The marks of Anthropic and OpenAI, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
|
|
3
3
|
* exception for provider marks. One path each, in `currentColor`, so a row decides how loud the logo is.
|
|
4
4
|
*/
|
|
5
5
|
export const PROVIDER_PATHS = {
|
|
6
6
|
claude: 'M4.709 15.955l4.72-2.647.079-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.377-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.048.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.607.212-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.914-1.316-.012.01z',
|
|
7
|
-
codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z'
|
|
7
|
+
codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z',
|
|
8
|
+
nanogpt: 'M5 20V4h2.6l8.8 11.2V4H19v16h-2.6L7.6 8.8V20z'
|
|
8
9
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { type ChatActions, type FileRef, type ShellCodeBlockContext, type ChatPl
|
|
|
2
2
|
export { type ChatScope, ChatScopeContext, useChatScope } from './scope.js';
|
|
3
3
|
export { type ChatRequestMap, type ChatEventMap, type ChatTransportStatus, type ChatTransport, ChatTransportError, errorCode, isConnectionError } from './transport.js';
|
|
4
4
|
export { portTransport, type PortTransport } from './port-transport.js';
|
|
5
|
+
export { configureAgentMarks, type AgentMark } from './agents/agent-marks.js';
|
|
5
6
|
export { configureChatStorage, type ChatStorageRecord, type ChatStorageAdapter, type ChatStorageOptions } from './storage.js';
|
|
6
7
|
export { setLazyPrefetch, onLazyOpenError } from './lazy.js';
|
|
7
8
|
export { type MountedEntry, MountedRegistry } from './mounted-registry.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { ChatHostProvider, useChatHost } from './host.js';
|
|
|
3
3
|
export { ChatScopeContext, useChatScope } from './scope.js';
|
|
4
4
|
export { ChatTransportError, errorCode, isConnectionError } from './transport.js';
|
|
5
5
|
export { portTransport } from './port-transport.js';
|
|
6
|
+
export { configureAgentMarks } from './agents/agent-marks.js';
|
|
6
7
|
export { configureChatStorage } from './storage.js';
|
|
7
8
|
export { setLazyPrefetch, onLazyOpenError } from './lazy.js';
|
|
8
9
|
export { MountedRegistry } from './mounted-registry.js';
|
|
@@ -66,6 +66,8 @@ declare const _default: {
|
|
|
66
66
|
signedOutDetail: string;
|
|
67
67
|
logIn: string;
|
|
68
68
|
logInAgain: string;
|
|
69
|
+
inChat: string;
|
|
70
|
+
inChatDetail: string;
|
|
69
71
|
};
|
|
70
72
|
folder: {
|
|
71
73
|
label: string;
|
|
@@ -106,6 +108,7 @@ declare const _default: {
|
|
|
106
108
|
add: {
|
|
107
109
|
title: string;
|
|
108
110
|
description: string;
|
|
111
|
+
chatDescription: string;
|
|
109
112
|
name: string;
|
|
110
113
|
namePlaceholder: string;
|
|
111
114
|
color: string;
|
|
@@ -65,7 +65,9 @@ export default {
|
|
|
65
65
|
signedOut: 'Not logged in',
|
|
66
66
|
signedOutDetail: "Opens a terminal that runs the CLI's login with this folder.",
|
|
67
67
|
logIn: 'Log in',
|
|
68
|
-
logInAgain: 'Log in again'
|
|
68
|
+
logInAgain: 'Log in again',
|
|
69
|
+
inChat: 'Logs in from a chat',
|
|
70
|
+
inChatDetail: 'The first chat on this account asks you to log in, and the CLI keeps that login in this folder.'
|
|
69
71
|
},
|
|
70
72
|
folder: {
|
|
71
73
|
label: 'Folder',
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
add: {
|
|
107
109
|
title: 'Add a {{provider}} account',
|
|
108
110
|
description: 'Ruimte makes a folder for it on the machine, then opens a terminal where you log in.',
|
|
111
|
+
chatDescription: 'A folder is made for it on the machine. The first chat on it asks you to log in.',
|
|
109
112
|
name: 'Name',
|
|
110
113
|
namePlaceholder: 'Work',
|
|
111
114
|
color: 'Color',
|
|
@@ -66,6 +66,8 @@ declare const _default: {
|
|
|
66
66
|
signedOutDetail: string;
|
|
67
67
|
logIn: string;
|
|
68
68
|
logInAgain: string;
|
|
69
|
+
inChat: string;
|
|
70
|
+
inChatDetail: string;
|
|
69
71
|
};
|
|
70
72
|
folder: {
|
|
71
73
|
label: string;
|
|
@@ -106,6 +108,7 @@ declare const _default: {
|
|
|
106
108
|
add: {
|
|
107
109
|
title: string;
|
|
108
110
|
description: string;
|
|
111
|
+
chatDescription: string;
|
|
109
112
|
name: string;
|
|
110
113
|
namePlaceholder: string;
|
|
111
114
|
color: string;
|
|
@@ -65,7 +65,9 @@ export default {
|
|
|
65
65
|
signedOut: 'Niet ingelogd',
|
|
66
66
|
signedOutDetail: 'Opent een terminal die de login van de CLI met deze map draait.',
|
|
67
67
|
logIn: 'Inloggen',
|
|
68
|
-
logInAgain: 'Opnieuw inloggen'
|
|
68
|
+
logInAgain: 'Opnieuw inloggen',
|
|
69
|
+
inChat: 'Logt in vanuit een chat',
|
|
70
|
+
inChatDetail: 'De eerste chat op dit account vraagt je in te loggen, en de CLI bewaart die login in deze map.'
|
|
69
71
|
},
|
|
70
72
|
folder: {
|
|
71
73
|
label: 'Map',
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
add: {
|
|
107
109
|
title: 'Een {{provider}}-account toevoegen',
|
|
108
110
|
description: 'Ruimte maakt er een map voor op de machine en opent dan een terminal waarin je inlogt.',
|
|
111
|
+
chatDescription: 'Er komt een map voor op de machine. De eerste chat erop vraagt je in te loggen.',
|
|
109
112
|
name: 'Naam',
|
|
110
113
|
namePlaceholder: 'Werk',
|
|
111
114
|
color: 'Kleur',
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ProviderInfo } from '@adecore/agent-contracts';
|
|
2
|
-
import { type AccountEntry } from '../agents/accounts.js';
|
|
2
|
+
import { type AccountEntry, type AccountLogin } from '../agents/accounts.js';
|
|
3
3
|
interface AccountDetailProps {
|
|
4
4
|
provider: ProviderInfo;
|
|
5
5
|
entry: AccountEntry;
|
|
6
|
-
|
|
6
|
+
login: AccountLogin;
|
|
7
|
+
canOpenLogin: boolean;
|
|
7
8
|
loginBlocked: string | null;
|
|
8
9
|
secretsAvailable: boolean;
|
|
9
10
|
onRemoved(): void;
|
|
10
11
|
}
|
|
11
|
-
export declare function AccountDetail({ provider, entry,
|
|
12
|
+
export declare function AccountDetail({ provider, entry, login, canOpenLogin, loginBlocked, secretsAvailable, onRemoved }: AccountDetailProps): import("react").JSX.Element;
|
|
12
13
|
export {};
|
|
@@ -32,7 +32,7 @@ function NameField({ value, label, onSave }) {
|
|
|
32
32
|
} }));
|
|
33
33
|
}
|
|
34
34
|
/* One account: how it looks in a picker, who is signed in, where its folder is, its variables. */
|
|
35
|
-
export function AccountDetail({ provider, entry,
|
|
35
|
+
export function AccountDetail({ provider, entry, login, canOpenLogin, loginBlocked, secretsAvailable, onRemoved }) {
|
|
36
36
|
const { t } = useTranslation('agent-providers');
|
|
37
37
|
const scope = useChatScope();
|
|
38
38
|
const host = useChatHost();
|
|
@@ -46,9 +46,10 @@ export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secrets
|
|
|
46
46
|
copyText(folder);
|
|
47
47
|
host.notify({ kind: 'success', title: t('account.folderCopied'), description: folder });
|
|
48
48
|
};
|
|
49
|
+
const trouble = status !== null && TROUBLE.has(status.state) && status.message ? status.message : null;
|
|
49
50
|
const loginDetail = () => {
|
|
50
|
-
if (
|
|
51
|
-
return
|
|
51
|
+
if (trouble !== null) {
|
|
52
|
+
return trouble;
|
|
52
53
|
}
|
|
53
54
|
if (!signedIn) {
|
|
54
55
|
return t('account.login.signedOutDetail');
|
|
@@ -59,11 +60,11 @@ export function AccountDetail({ provider, entry, canLogIn, loginBlocked, secrets
|
|
|
59
60
|
return (_jsxs(_Fragment, { children: [_jsx(DetailHeader, { mark: _jsx("span", { className: "grid h-6 shrink-0 place-items-center", children: _jsx(AccountDot, { color: account.color, className: "size-3" }) }), title: name, subtitle: _jsxs(_Fragment, { children: [_jsx(CliMark, { kind: provider.kind, size: 12, className: "text-text-muted" }), t('account.kind', { provider: provider.name })] }), actions: folder !== '' && (_jsx(Button, { variant: "secondary", onClick: showFolder, children: t('account.showFolder') })) }), _jsxs(SettingsSection, { children: [_jsx(SettingsRow, { label: t('account.enabled.label'), description: t('account.enabled.description'), control: _jsx(Switch, { checked: account.enabled !== false, label: t('account.enabled.label'), onCheckedChange: (checked) => {
|
|
60
61
|
const { enabled: _enabled, ...rest } = account;
|
|
61
62
|
void saveAccount(scope, host, id, checked ? rest : { ...rest, enabled: false });
|
|
62
|
-
} }) }), _jsx(SettingsRow, { label: t('account.name'), control: _jsx(NameField, { value: name, label: t('account.name'), onSave: (label) => void saveAccount(scope, host, id, { ...account, label }) }, name) }), _jsx(SettingsRow, { label: t('account.color'), control: _jsx(AccountColors, { value: account.color, label: t('account.color'), onChange: (color) => void saveAccount(scope, host, id, { ...account, color }) }) })] }),
|
|
63
|
+
} }) }), _jsx(SettingsRow, { label: t('account.name'), control: _jsx(NameField, { value: name, label: t('account.name'), onSave: (label) => void saveAccount(scope, host, id, { ...account, label }) }, name) }), _jsx(SettingsRow, { label: t('account.color'), control: _jsx(AccountColors, { value: account.color, label: t('account.color'), onChange: (color) => void saveAccount(scope, host, id, { ...account, color }) }) })] }), (login === 'chat' || (login === 'terminal' && canOpenLogin)) && (_jsxs(SettingsSection, { title: t('account.login.title'), children: [login === 'chat' ? (_jsx(SettingsRow, { label: t('account.login.inChat'), description: trouble ?? t('account.login.inChatDetail') })) : (_jsx(SettingsRow, { label: signedIn
|
|
63
64
|
? status?.email
|
|
64
65
|
? t('account.login.signedInAs', { email: status.email })
|
|
65
66
|
: t('account.login.signedIn')
|
|
66
|
-
: t('account.login.signedOut'), description: loginBlocked === null ? loginDetail() : `${loginDetail()} ${loginBlocked}`, control: loginBlocked === null ? (loginButton) : (_jsx(Tooltip, { label: loginBlocked, children: _jsx("span", { className: "inline-flex", children: loginButton }) })) }), _jsxs(SettingsRow, { label: t('account.folder.label'), children: [_jsx(Input, { mono: true, className: "h-8.5", value: folder, readOnly: true, "aria-label": t('account.folder.label') }), _jsx("p", { className: "-mt-1 text-xs text-text-muted", children: variable ? (_jsx(Trans, { t: t, i18nKey: "account.folder.detail", values: { variable }, components: { code: _jsx("code", { className: "font-mono text-code" }) } })) : (t('account.folder.detailPlain')) })] })] })), _jsx(AccountVariables, { id: id, account: account, secretsAvailable: secretsAvailable }, `${id}:${JSON.stringify(account.env ?? [])}`), _jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('account.remove.label'), description: isDefault ? t('account.remove.defaultNote') : t('account.remove.description'), control: _jsx(Button, { variant: "danger-outline", disabled: isDefault, onClick: () => setConfirming(true), children: t('account.remove.button') }) }) }), _jsx(ConfirmDialog, { open: confirming, onOpenChange: setConfirming, title: t('account.remove.confirmTitle', { account: name }), description: t('account.remove.confirmDescription', { folder }), confirmLabel: t('account.remove.confirm'), onConfirm: async () => {
|
|
67
|
+
: t('account.login.signedOut'), description: loginBlocked === null ? loginDetail() : `${loginDetail()} ${loginBlocked}`, control: loginBlocked === null ? (loginButton) : (_jsx(Tooltip, { label: loginBlocked, children: _jsx("span", { className: "inline-flex", children: loginButton }) })) })), _jsxs(SettingsRow, { label: t('account.folder.label'), children: [_jsx(Input, { mono: true, className: "h-8.5", value: folder, readOnly: true, "aria-label": t('account.folder.label') }), _jsx("p", { className: "-mt-1 text-xs text-text-muted", children: variable ? (_jsx(Trans, { t: t, i18nKey: "account.folder.detail", values: { variable }, components: { code: _jsx("code", { className: "font-mono text-code" }) } })) : (t('account.folder.detailPlain')) })] })] })), _jsx(AccountVariables, { id: id, account: account, secretsAvailable: secretsAvailable }, `${id}:${JSON.stringify(account.env ?? [])}`), _jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('account.remove.label'), description: isDefault ? t('account.remove.defaultNote') : t('account.remove.description'), control: _jsx(Button, { variant: "danger-outline", disabled: isDefault, onClick: () => setConfirming(true), children: t('account.remove.button') }) }) }), _jsx(ConfirmDialog, { open: confirming, onOpenChange: setConfirming, title: t('account.remove.confirmTitle', { account: name }), description: t('account.remove.confirmDescription', { folder }), confirmLabel: t('account.remove.confirm'), onConfirm: async () => {
|
|
67
68
|
await removeAccount(scope, host, id, provider.kind);
|
|
68
69
|
onRemoved();
|
|
69
70
|
} })] }));
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { ProviderInfo } from '@adecore/agent-contracts';
|
|
2
|
-
import { type AccountEntry } from '../agents/accounts.js';
|
|
2
|
+
import { type AccountEntry, type AccountLogin } from '../agents/accounts.js';
|
|
3
3
|
interface AddAccountFormProps {
|
|
4
4
|
provider: ProviderInfo;
|
|
5
5
|
entries: AccountEntry[];
|
|
6
|
+
login: AccountLogin;
|
|
6
7
|
onCancel(): void;
|
|
7
8
|
onAdded(id: string, name: string, made: boolean): void;
|
|
8
9
|
}
|
|
9
|
-
export declare function AddAccountForm({ provider, entries, onCancel, onAdded }: AddAccountFormProps): import("react").JSX.Element;
|
|
10
|
+
export declare function AddAccountForm({ provider, entries, login, onCancel, onAdded }: AddAccountFormProps): import("react").JSX.Element;
|
|
10
11
|
export {};
|
|
@@ -16,7 +16,7 @@ import { providerAccountsOf } from '../state/provider-accounts.js';
|
|
|
16
16
|
* A name and a color, asked before anything exists, so the account is made once with both. By default
|
|
17
17
|
* the machine makes the folder; under Advanced a person points at a folder of their own instead.
|
|
18
18
|
*/
|
|
19
|
-
export function AddAccountForm({ provider, entries, onCancel, onAdded }) {
|
|
19
|
+
export function AddAccountForm({ provider, entries, login, onCancel, onAdded }) {
|
|
20
20
|
const { t } = useTranslation('agent-providers');
|
|
21
21
|
const scope = useChatScope();
|
|
22
22
|
const { accents } = useChatHost();
|
|
@@ -51,5 +51,5 @@ export function AddAccountForm({ provider, entries, onCancel, onAdded }) {
|
|
|
51
51
|
setBusy(false);
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
-
return (_jsxs("form", { className: "contents", onSubmit: (event) => void submit(event), children: [_jsx(DetailHeader, { mark: _jsx(CliTile, { kind: provider.kind }), title: t('add.title', { provider: provider.name }), subtitle: t('add.description') }), _jsxs(SettingsSection, { children: [_jsx(SettingsRow, { label: t('add.name'), control: _jsx(Input, { className: "w-55 max-w-full", value: name, maxLength: 80, autoFocus: true, placeholder: t('add.namePlaceholder'), "aria-label": t('add.name'), onChange: (event) => setName(event.target.value) }) }), _jsx(SettingsRow, { label: t('add.color'), control: _jsx(AccountColors, { value: color, label: t('add.color'), onChange: setColor }) })] }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2.5", children: [_jsxs("button", { type: "button", "aria-expanded": advanced, className: "flex items-center gap-1.5 self-start text-sm font-medium text-text-muted hover:text-text", onClick: () => setAdvanced((open) => !open), children: [_jsx(Icon, { icon: ChevronRight, size: 14, className: clsx('transition-transform', advanced && 'rotate-90') }), t('add.advanced')] }), advanced && (_jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('add.link.label'), description: t('add.link.description'), children: _jsx(Input, { mono: true, value: folder, spellCheck: false, autoComplete: "off", placeholder: t('add.link.placeholder'), "aria-label": t('add.link.label'), onChange: (event) => setFolder(event.target.value) }) }) }))] }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2", children: [error !== null && (_jsx(FormError, { render: _jsx("span", {}), className: "mr-auto break-words", children: error || t('add.failed') })), _jsx(Button, { onClick: onCancel, children: t('common.cancel') }), _jsx(Button, { type: "submit", variant: "primary", disabled: label === '' || busy, children: linking ? t('add.linkButton') : t('add.create') })] })] }));
|
|
54
|
+
return (_jsxs("form", { className: "contents", onSubmit: (event) => void submit(event), children: [_jsx(DetailHeader, { mark: _jsx(CliTile, { kind: provider.kind }), title: t('add.title', { provider: provider.name }), subtitle: login === 'chat' ? t('add.chatDescription') : t('add.description') }), _jsxs(SettingsSection, { children: [_jsx(SettingsRow, { label: t('add.name'), control: _jsx(Input, { className: "w-55 max-w-full", value: name, maxLength: 80, autoFocus: true, placeholder: t('add.namePlaceholder'), "aria-label": t('add.name'), onChange: (event) => setName(event.target.value) }) }), _jsx(SettingsRow, { label: t('add.color'), control: _jsx(AccountColors, { value: color, label: t('add.color'), onChange: setColor }) })] }), _jsxs("div", { className: "flex min-w-0 flex-col gap-2.5", children: [_jsxs("button", { type: "button", "aria-expanded": advanced, className: "flex items-center gap-1.5 self-start text-sm font-medium text-text-muted hover:text-text", onClick: () => setAdvanced((open) => !open), children: [_jsx(Icon, { icon: ChevronRight, size: 14, className: clsx('transition-transform', advanced && 'rotate-90') }), t('add.advanced')] }), advanced && (_jsx(SettingsSection, { children: _jsx(SettingsRow, { label: t('add.link.label'), description: t('add.link.description'), children: _jsx(Input, { mono: true, value: folder, spellCheck: false, autoComplete: "off", placeholder: t('add.link.placeholder'), "aria-label": t('add.link.label'), onChange: (event) => setFolder(event.target.value) }) }) }))] }), _jsxs("div", { className: "flex min-w-0 flex-wrap items-center justify-end gap-2", children: [error !== null && (_jsx(FormError, { render: _jsx("span", {}), className: "mr-auto break-words", children: error || t('add.failed') })), _jsx(Button, { onClick: onCancel, children: t('common.cancel') }), _jsx(Button, { type: "submit", variant: "primary", disabled: label === '' || busy, children: linking ? t('add.linkButton') : t('add.create') })] })] }));
|
|
55
55
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ProviderInfo } from '@adecore/agent-contracts';
|
|
2
|
-
import { type AccountEntry } from '../agents/accounts.js';
|
|
2
|
+
import { type AccountEntry, type AccountLogin } from '../agents/accounts.js';
|
|
3
3
|
interface CliDetailProps {
|
|
4
4
|
provider: ProviderInfo;
|
|
5
5
|
entries: AccountEntry[] | null;
|
|
6
6
|
defaults: boolean;
|
|
7
|
-
|
|
7
|
+
login: AccountLogin;
|
|
8
8
|
onAddAccount(): void;
|
|
9
9
|
onPickAccount(id: string): void;
|
|
10
10
|
}
|
|
11
|
-
export declare function CliDetail({ provider, entries, defaults,
|
|
11
|
+
export declare function CliDetail({ provider, entries, defaults, login, onAddAccount, onPickAccount }: CliDetailProps): import("react").JSX.Element;
|
|
12
12
|
export {};
|
|
@@ -21,7 +21,7 @@ function ModelOptionRows({ provider, model, options }) {
|
|
|
21
21
|
return (_jsx(_Fragment, { children: model.options.map((option) => (_jsx(SettingsRow, { indent: true, muted: true, label: option.label, control: _jsx(ModelOptionControl, { option: option, options: options, layout: "row", onChange: (value) => setOption(option.id, value) }) }, option.id))) }));
|
|
22
22
|
}
|
|
23
23
|
/* One CLI: what it is, what a new chat of it starts with unless `defaults` is off, and its accounts. */
|
|
24
|
-
export function CliDetail({ provider, entries, defaults,
|
|
24
|
+
export function CliDetail({ provider, entries, defaults, login, onAddAccount, onPickAccount }) {
|
|
25
25
|
const { t } = useTranslation('agent-providers');
|
|
26
26
|
const { id: scopeId } = useChatScope();
|
|
27
27
|
const preferences = useChatPreferences();
|
|
@@ -31,7 +31,7 @@ export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAcc
|
|
|
31
31
|
const picked = accountFor(preferences, scopeId, provider.kind, machineAccounts) ?? provider.kind;
|
|
32
32
|
// An account that is off is out of every picker, unless it is the one picked already.
|
|
33
33
|
const choosable = (entries ?? []).filter((entry) => entry.account.enabled !== false || entry.id === picked);
|
|
34
|
-
const
|
|
34
|
+
const canAddAccount = login !== null;
|
|
35
35
|
return (_jsxs(_Fragment, { children: [_jsx(DetailHeader, { mark: _jsx(CliTile, { kind: provider.kind }), title: provider.name, subtitle: `${provider.version ? `${t('abilities.version', { version: provider.version })} ` : ''}${providerAbilities(provider)}`, actions: canAddAccount && (_jsxs(Button, { variant: "secondary", className: "whitespace-nowrap", onClick: onAddAccount, children: [_jsx(Icon, { icon: Plus, size: 14 }), t('cli.addAccount')] })) }), defaults && (_jsxs(SettingsSection, { title: t('cli.defaults.title'), description: t('cli.defaults.description'), children: [choosable.length > 0 && (_jsx(SettingsRow, { searchId: "providers.defaults.account", label: t('cli.defaults.account'), control: _jsx(Select, { value: picked, label: t('cli.defaults.accountFor', { provider: provider.name }), align: "end", items: choosable.map((entry) => ({
|
|
36
36
|
value: entry.id,
|
|
37
37
|
label: accountName(entry, provider.name),
|
|
@@ -48,7 +48,7 @@ export function CliDetail({ provider, entries, defaults, canAddAccount, onAddAcc
|
|
|
48
48
|
model: value,
|
|
49
49
|
options: carryOptions(selection?.options ?? {}, provider.models.find((entry) => entry.slug === value))
|
|
50
50
|
}) }) })), model && _jsx(ModelOptionRows, { provider: provider.kind, model: model, options: selection?.options ?? {} }), choosable.length === 0 && provider.models.length === 0 && _jsx(SettingsRow, { muted: true, label: t('cli.defaults.usesCliDefault') })] })), entries === null ? (_jsx("p", { className: "text-xs text-text-faint", children: t('list.noAccounts') })) : (_jsx(SettingsSection, { title: t('cli.accounts'), footer: canAddAccount ? undefined : t('cli.onlyDefault', { provider: provider.name }), children: entries.map((entry) => {
|
|
51
|
-
const status = accountStatusLine(entry.status,
|
|
51
|
+
const status = accountStatusLine(entry.status, login === 'terminal');
|
|
52
52
|
return (_jsxs("button", { type: "button", className: "flex w-full min-w-0 items-center gap-3 px-4.5 py-3 text-left first:rounded-t-xl last:rounded-b-xl hover:bg-surface-hover", onClick: () => onPickAccount(entry.id), children: [_jsx(AccountDot, { color: entry.account.color }), _jsx("span", { className: "min-w-0 grow truncate text-sm text-text", children: accountName(entry, provider.name) }), _jsx("span", { className: `shrink-0 text-xs ${ACCOUNT_TONE_CLASSES[status.tone]}`, children: status.text }), _jsx(Icon, { icon: ChevronRight, size: 14, className: "shrink-0 text-text-faint" })] }, entry.id));
|
|
53
53
|
}) }))] }));
|
|
54
54
|
}
|
|
@@ -4,7 +4,7 @@ import clsx from 'clsx';
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { Plus, Terminal } from 'lucide-react';
|
|
6
6
|
import { AccountDot } from '../agents/AccountDot.js';
|
|
7
|
-
import { ACCOUNT_TONE_CLASSES, accountName, accountsOfKind, accountStatusLine } from '../agents/accounts.js';
|
|
7
|
+
import { ACCOUNT_TONE_CLASSES, accountLoginOf, accountName, accountsOfKind, accountStatusLine } from '../agents/accounts.js';
|
|
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.js';
|
|
@@ -34,7 +34,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }) {
|
|
|
34
34
|
const installed = useMemo(() => providers.filter((provider) => provider.installed || !provider.capabilities.terminal), [providers]);
|
|
35
35
|
const missing = useMemo(() => providers.filter((provider) => !provider.installed && provider.capabilities.terminal), [providers]);
|
|
36
36
|
const entriesOf = (kind) => accountsOfKind(accounts, kind);
|
|
37
|
-
const
|
|
37
|
+
const loginOf = (kind) => accountLoginOf(accounts, kind);
|
|
38
38
|
// What was picked may be gone since (an account removed elsewhere); the first CLI stands in for it.
|
|
39
39
|
const pickedEntry = picked?.kind === 'account' ? (Object.hasOwn(accounts?.accounts ?? {}, picked.id) ? picked.id : null) : null;
|
|
40
40
|
const current = picked !== null && (picked.kind !== 'account' || pickedEntry !== null) ? picked : installed[0] ? { kind: 'cli', cli: installed[0].kind } : null;
|
|
@@ -54,15 +54,15 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }) {
|
|
|
54
54
|
}
|
|
55
55
|
const added = (id, name, made) => {
|
|
56
56
|
setPicked({ kind: 'account', id });
|
|
57
|
-
if (made && provider !== null && openLogin !== null && loginBlocked === null) {
|
|
57
|
+
if (made && provider !== null && loginOf(provider.kind) === 'terminal' && openLogin !== null && loginBlocked === null) {
|
|
58
58
|
void openLogin(scopeId, provider.kind, id, name);
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
61
|
const group = (cli) => {
|
|
62
62
|
const entries = cli.installed ? entriesOf(cli.kind) : [];
|
|
63
|
-
return (_jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-0.5 pt-1", children: [_jsxs(ListRow, { selected: current?.kind === 'cli' && current.cli === cli.kind, onSelect: () => setPicked({ kind: 'cli', cli: cli.kind }), className: "pr-1", trailing:
|
|
63
|
+
return (_jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-0.5 pt-1", children: [_jsxs(ListRow, { selected: current?.kind === 'cli' && current.cli === cli.kind, onSelect: () => setPicked({ kind: 'cli', cli: cli.kind }), className: "pr-1", trailing: loginOf(cli.kind) !== null && (_jsx(IconButton, { icon: Plus, size: "sm", label: t('list.addAccount', { provider: cli.name }), onClick: () => setPicked({ kind: 'add', cli: cli.kind }) })), children: [_jsx(CliMark, { kind: cli.kind }), _jsx("span", { className: "min-w-0 grow truncate text-xs font-medium text-text", children: cli.name }), cli.capabilities.terminal && cli.version && _jsx("span", { className: "shrink-0 font-mono text-code text-text-faint", children: cli.version })] }), entries.map((entry) => {
|
|
64
64
|
const name = accountName(entry, cli.name);
|
|
65
|
-
const status = accountStatusLine(entry.status,
|
|
65
|
+
const status = accountStatusLine(entry.status, loginOf(cli.kind) === 'terminal');
|
|
66
66
|
return (_jsxs(ListRow, { selected: current?.kind === 'account' && current.id === entry.id, onSelect: () => setPicked({ kind: 'account', id: entry.id }), children: [_jsx("span", { className: "grid w-3.5 shrink-0 place-items-center", children: _jsx(AccountDot, { color: entry.account.color, className: "size-2.25" }) }), _jsxs("span", { className: "flex min-w-0 grow flex-col", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2 text-sm text-text", children: [_jsx("span", { className: "truncate", children: name }), entry.isDefault && (_jsx("span", { className: "shrink-0 rounded-md bg-surface-active px-1.5 text-xs text-text-muted", children: t('list.default') }))] }), _jsx("span", { className: clsx('truncate text-xs', ACCOUNT_TONE_CLASSES[status.tone]), children: status.text })] })] }, entry.id));
|
|
67
67
|
})] }, cli.kind));
|
|
68
68
|
};
|
|
@@ -76,13 +76,13 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }) {
|
|
|
76
76
|
return own;
|
|
77
77
|
}
|
|
78
78
|
if (current.kind === 'add') {
|
|
79
|
-
return (_jsx(AddAccountForm, { provider: provider, entries: entriesOf(provider.kind), onCancel: () => setPicked({ kind: 'cli', cli: provider.kind }), onAdded: added }, provider.kind));
|
|
79
|
+
return (_jsx(AddAccountForm, { provider: provider, entries: entriesOf(provider.kind), login: loginOf(provider.kind), onCancel: () => setPicked({ kind: 'cli', cli: provider.kind }), onAdded: added }, provider.kind));
|
|
80
80
|
}
|
|
81
81
|
if (current.kind === 'account') {
|
|
82
82
|
const entry = entriesOf(provider.kind).find((candidate) => candidate.id === current.id);
|
|
83
|
-
return (entry && (_jsx(AccountDetail, { provider: provider, entry: entry,
|
|
83
|
+
return (entry && (_jsx(AccountDetail, { provider: provider, entry: entry, login: loginOf(provider.kind), canOpenLogin: openLogin !== null, loginBlocked: loginBlocked, secretsAvailable: accounts?.secretsAvailable === true, onRemoved: () => setPicked({ kind: 'cli', cli: provider.kind }) }, entry.id)));
|
|
84
84
|
}
|
|
85
|
-
return (_jsx(CliDetail, { provider: provider, entries: accounts === null ? null : entriesOf(provider.kind), defaults: defaults,
|
|
85
|
+
return (_jsx(CliDetail, { provider: provider, entries: accounts === null ? null : entriesOf(provider.kind), defaults: defaults, login: loginOf(provider.kind), onAddAccount: () => setPicked({ kind: 'add', cli: provider.kind }), onPickAccount: (id) => setPicked({ kind: 'account', id }) }));
|
|
86
86
|
};
|
|
87
87
|
return (_jsx(MasterDetail, { listWidth: 340, listLabel: t('list.label'), list: list, detail: _jsx(ErrorBoundary, { label: t('failed'), resetKeys: [scopeId, current?.kind, currentCli], children: _jsx("div", { className: "flex min-w-0 flex-col gap-7", children: detail() }) }) }));
|
|
88
88
|
}
|
package/dist/providers/parts.js
CHANGED
|
@@ -7,7 +7,8 @@ const CLI_COLORS = {
|
|
|
7
7
|
claude: 'text-chart-claude',
|
|
8
8
|
codex: 'text-chart-codex',
|
|
9
9
|
gemini: 'text-chart-gemini',
|
|
10
|
-
copilot: 'text-chart-copilot'
|
|
10
|
+
copilot: 'text-chart-copilot',
|
|
11
|
+
nanogpt: 'text-chart-nanogpt'
|
|
11
12
|
};
|
|
12
13
|
export function CliMark({ kind, size = 14, className }) {
|
|
13
14
|
return _jsx(AgentIcon, { kind: kind, size: size, className: clsx('shrink-0', CLI_COLORS[kind] ?? 'text-text-muted', className) });
|
package/dist/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/dist/usage/format.js
CHANGED
|
@@ -20,8 +20,12 @@ export function moneyFormat(currency, rate) {
|
|
|
20
20
|
return formatMoney(value, code, value !== 0 && Math.abs(value) < 0.01 ? 4 : undefined);
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
|
-
export const PROVIDER_LABELS = { claude: 'Claude Code', codex: 'Codex' };
|
|
24
|
-
export const PROVIDER_COLORS = {
|
|
23
|
+
export const PROVIDER_LABELS = { claude: 'Claude Code', codex: 'Codex', nanogpt: 'NanoGPT' };
|
|
24
|
+
export const PROVIDER_COLORS = {
|
|
25
|
+
claude: 'var(--chart-claude)',
|
|
26
|
+
codex: 'var(--chart-codex)',
|
|
27
|
+
nanogpt: 'var(--chart-nanogpt)'
|
|
28
|
+
};
|
|
25
29
|
/*
|
|
26
30
|
* A slot is `2026-09-10` or `2026-09-10T14`, already in the viewer's own zone, so it is read as plain
|
|
27
31
|
* wall clock and never handed to a parser that would shift it back.
|
|
@@ -76,7 +76,8 @@ export function explain(provider) {
|
|
|
76
76
|
return i18next.t('agent-usage:limits.notInstalled');
|
|
77
77
|
}
|
|
78
78
|
if (provider.unavailable.reason === 'no-subscription') {
|
|
79
|
-
|
|
79
|
+
// A provider that knows why it has no plan says so; the rest are accounts on an API key.
|
|
80
|
+
return provider.unavailable.message ?? i18next.t('agent-usage:limits.noSubscription');
|
|
80
81
|
}
|
|
81
82
|
return provider.unavailable.message ?? i18next.t('agent-usage:limits.unreachable');
|
|
82
83
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "React agent chat threads, composers, approvals, provider accounts and usage views.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -67,11 +67,11 @@
|
|
|
67
67
|
"test:visuals": "bun --config=integration.bunfig.toml --conditions=source test ./src/chat/visual-viewport.integration.test.ts"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@adecore/agent-contracts": "0.
|
|
71
|
-
"@adecore/editor": "0.
|
|
72
|
-
"@adecore/editor-react": "0.
|
|
73
|
-
"@adecore/intelligent-ui": "0.
|
|
74
|
-
"@adecore/merge": "0.
|
|
70
|
+
"@adecore/agent-contracts": "0.31.0",
|
|
71
|
+
"@adecore/editor": "0.31.0",
|
|
72
|
+
"@adecore/editor-react": "0.31.0",
|
|
73
|
+
"@adecore/intelligent-ui": "0.31.0",
|
|
74
|
+
"@adecore/merge": "0.31.0",
|
|
75
75
|
"@codemirror/commands": "^6.11.1",
|
|
76
76
|
"@codemirror/language": "^6.12.4",
|
|
77
77
|
"@codemirror/state": "^6.7.6",
|
|
@@ -94,14 +94,14 @@
|
|
|
94
94
|
"zustand": "^5.0.15"
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
|
-
"@adecore/ui": "^0.
|
|
97
|
+
"@adecore/ui": "^0.31.0",
|
|
98
98
|
"i18next": "^26.4.2",
|
|
99
99
|
"react": "^19.3.0",
|
|
100
100
|
"react-dom": "^19.3.0",
|
|
101
101
|
"react-i18next": "^17.0.15"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@adecore/ui": "0.
|
|
104
|
+
"@adecore/ui": "0.31.0",
|
|
105
105
|
"@types/bun": "^1.4.2",
|
|
106
106
|
"@types/react": "^19.3.0",
|
|
107
107
|
"@types/react-dom": "^19.3.0",
|
package/src/agents/AgentIcon.tsx
CHANGED
|
@@ -3,10 +3,11 @@ import clsx from 'clsx';
|
|
|
3
3
|
import { Bot } from 'lucide-react';
|
|
4
4
|
import { siClaude, siGithubcopilot, siGooglegemini } from 'simple-icons';
|
|
5
5
|
import { Icon } from '@adecore/ui';
|
|
6
|
+
import { type AgentMark, hostAgentMark } from './agent-marks';
|
|
6
7
|
import { PROVIDER_PATHS } from './provider-paths';
|
|
7
8
|
|
|
8
9
|
// simple-icons removed OpenAI's mark at its request, so Codex reuses the usage page's path.
|
|
9
|
-
const
|
|
10
|
+
const BUILT_IN_MARKS: Partial<Record<AgentKind, AgentMark>> = {
|
|
10
11
|
claude: siClaude,
|
|
11
12
|
codex: { title: 'Codex', path: PROVIDER_PATHS.codex },
|
|
12
13
|
gemini: siGooglegemini,
|
|
@@ -14,12 +15,20 @@ const MARKS: Partial<Record<AgentKind, { title: string; path: string }>> = {
|
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
export function AgentIcon({ kind, size = 14, className }: { kind: AgentKind; size?: number; className?: string }) {
|
|
17
|
-
const mark =
|
|
18
|
+
const mark = hostAgentMark(kind) ?? BUILT_IN_MARKS[kind];
|
|
18
19
|
if (!mark) {
|
|
19
20
|
return <Icon icon={Bot} size={size} className={className} />;
|
|
20
21
|
}
|
|
21
22
|
return (
|
|
22
|
-
<svg
|
|
23
|
+
<svg
|
|
24
|
+
role="img"
|
|
25
|
+
aria-label={mark.title}
|
|
26
|
+
viewBox={mark.viewBox ?? '0 0 24 24'}
|
|
27
|
+
width={size}
|
|
28
|
+
height={size}
|
|
29
|
+
fill="currentColor"
|
|
30
|
+
className={clsx('align-middle', className)}
|
|
31
|
+
>
|
|
23
32
|
<path d={mark.path} />
|
|
24
33
|
</svg>
|
|
25
34
|
);
|
package/src/agents/accounts.ts
CHANGED
|
@@ -41,6 +41,19 @@ export function accountName(entry: Pick<AccountEntry, 'id' | 'account' | 'isDefa
|
|
|
41
41
|
return entry.account.label ?? (entry.isDefault ? providerName : entry.id);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/*
|
|
45
|
+
* Where an account of a kind signs in: a terminal runs the CLI's login, or its first chat asks. Null for a
|
|
46
|
+
* CLI without a config folder, which has only its default account; also null until the accounts arrived.
|
|
47
|
+
*/
|
|
48
|
+
export type AccountLogin = 'terminal' | 'chat' | null;
|
|
49
|
+
|
|
50
|
+
export function accountLoginOf(accounts: ProviderAccounts | null | undefined, kind: AgentKind): AccountLogin {
|
|
51
|
+
if (accounts?.loginCommands?.[kind] !== undefined) {
|
|
52
|
+
return 'terminal';
|
|
53
|
+
}
|
|
54
|
+
return accounts?.chatLogins?.includes(kind) === true ? 'chat' : null;
|
|
55
|
+
}
|
|
56
|
+
|
|
44
57
|
export type AccountTone = 'muted' | 'needs-you' | 'error';
|
|
45
58
|
|
|
46
59
|
export const ACCOUNT_TONE_CLASSES: Record<AccountTone, string> = {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentKind } from '@adecore/agent-contracts';
|
|
2
|
+
|
|
3
|
+
/* A CLI's mark, drawn in the current text color. */
|
|
4
|
+
export interface AgentMark {
|
|
5
|
+
/* Read by a screen reader. */
|
|
6
|
+
title: string;
|
|
7
|
+
/* The `d` of one SVG path; several shapes go in one path. */
|
|
8
|
+
path: string;
|
|
9
|
+
/* `0 0 24 24` when absent. */
|
|
10
|
+
viewBox?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let hostMarks: Readonly<Partial<Record<AgentKind, AgentMark>>> = {};
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The marks of the kinds a host adds, and any it draws differently, set once before the first render. Every
|
|
17
|
+
* place the shared UI draws an agent's mark reads them; a kind with none keeps the built-in mark or the robot.
|
|
18
|
+
* A later call replaces the whole set.
|
|
19
|
+
*/
|
|
20
|
+
export function configureAgentMarks(marks: Readonly<Partial<Record<AgentKind, AgentMark>>>): void {
|
|
21
|
+
hostMarks = { ...marks };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function hostAgentMark(kind: AgentKind): AgentMark | undefined {
|
|
25
|
+
return hostMarks[kind];
|
|
26
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { UsageProvider } from '@adecore/agent-contracts';
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
* The marks of Anthropic and OpenAI. Lucide has no brand marks, so these are the icon rule's
|
|
4
|
+
* The marks of Anthropic and OpenAI, and a monogram for a provider with no mark of its own. Lucide has no brand marks, so these are the icon rule's
|
|
5
5
|
* exception for provider marks. One path each, in `currentColor`, so a row decides how loud the logo is.
|
|
6
6
|
*/
|
|
7
7
|
export const PROVIDER_PATHS: Record<UsageProvider, string> = {
|
|
8
8
|
claude: 'M4.709 15.955l4.72-2.647.079-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.377-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.048.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.607.212-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.914-1.316-.012.01z',
|
|
9
|
-
codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z'
|
|
9
|
+
codex: 'M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.554 5.554 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.019.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.001zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z',
|
|
10
|
+
nanogpt: 'M5 20V4h2.6l8.8 11.2V4H19v16h-2.6L7.6 8.8V20z'
|
|
10
11
|
};
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ export {
|
|
|
37
37
|
isConnectionError
|
|
38
38
|
} from './transport';
|
|
39
39
|
export { portTransport, type PortTransport } from './port-transport';
|
|
40
|
+
export { configureAgentMarks, type AgentMark } from './agents/agent-marks';
|
|
40
41
|
export { configureChatStorage, type ChatStorageRecord, type ChatStorageAdapter, type ChatStorageOptions } from './storage';
|
|
41
42
|
export { setLazyPrefetch, onLazyOpenError } from './lazy';
|
|
42
43
|
export { type MountedEntry, MountedRegistry } from './mounted-registry';
|
|
@@ -65,7 +65,9 @@ export default {
|
|
|
65
65
|
signedOut: 'Not logged in',
|
|
66
66
|
signedOutDetail: "Opens a terminal that runs the CLI's login with this folder.",
|
|
67
67
|
logIn: 'Log in',
|
|
68
|
-
logInAgain: 'Log in again'
|
|
68
|
+
logInAgain: 'Log in again',
|
|
69
|
+
inChat: 'Logs in from a chat',
|
|
70
|
+
inChatDetail: 'The first chat on this account asks you to log in, and the CLI keeps that login in this folder.'
|
|
69
71
|
},
|
|
70
72
|
folder: {
|
|
71
73
|
label: 'Folder',
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
add: {
|
|
107
109
|
title: 'Add a {{provider}} account',
|
|
108
110
|
description: 'Ruimte makes a folder for it on the machine, then opens a terminal where you log in.',
|
|
111
|
+
chatDescription: 'A folder is made for it on the machine. The first chat on it asks you to log in.',
|
|
109
112
|
name: 'Name',
|
|
110
113
|
namePlaceholder: 'Work',
|
|
111
114
|
color: 'Color',
|
|
@@ -65,7 +65,9 @@ export default {
|
|
|
65
65
|
signedOut: 'Niet ingelogd',
|
|
66
66
|
signedOutDetail: 'Opent een terminal die de login van de CLI met deze map draait.',
|
|
67
67
|
logIn: 'Inloggen',
|
|
68
|
-
logInAgain: 'Opnieuw inloggen'
|
|
68
|
+
logInAgain: 'Opnieuw inloggen',
|
|
69
|
+
inChat: 'Logt in vanuit een chat',
|
|
70
|
+
inChatDetail: 'De eerste chat op dit account vraagt je in te loggen, en de CLI bewaart die login in deze map.'
|
|
69
71
|
},
|
|
70
72
|
folder: {
|
|
71
73
|
label: 'Map',
|
|
@@ -106,6 +108,7 @@ export default {
|
|
|
106
108
|
add: {
|
|
107
109
|
title: 'Een {{provider}}-account toevoegen',
|
|
108
110
|
description: 'Ruimte maakt er een map voor op de machine en opent dan een terminal waarin je inlogt.',
|
|
111
|
+
chatDescription: 'Er komt een map voor op de machine. De eerste chat erop vraagt je in te loggen.',
|
|
109
112
|
name: 'Naam',
|
|
110
113
|
namePlaceholder: 'Werk',
|
|
111
114
|
color: 'Kleur',
|
|
@@ -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';
|
|
@@ -72,7 +72,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
72
72
|
const installed = useMemo(() => providers.filter((provider) => provider.installed || !provider.capabilities.terminal), [providers]);
|
|
73
73
|
const missing = useMemo(() => providers.filter((provider) => !provider.installed && provider.capabilities.terminal), [providers]);
|
|
74
74
|
const entriesOf = (kind: AgentKind): AccountEntry[] => accountsOfKind(accounts, kind);
|
|
75
|
-
const
|
|
75
|
+
const loginOf = (kind: AgentKind) => accountLoginOf(accounts, kind);
|
|
76
76
|
|
|
77
77
|
// What was picked may be gone since (an account removed elsewhere); the first CLI stands in for it.
|
|
78
78
|
const pickedEntry = picked?.kind === 'account' ? (Object.hasOwn(accounts?.accounts ?? {}, picked.id) ? picked.id : null) : null;
|
|
@@ -95,7 +95,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
95
95
|
|
|
96
96
|
const added = (id: string, name: string, made: boolean): void => {
|
|
97
97
|
setPicked({ kind: 'account', id });
|
|
98
|
-
if (made && provider !== null && openLogin !== null && loginBlocked === null) {
|
|
98
|
+
if (made && provider !== null && loginOf(provider.kind) === 'terminal' && openLogin !== null && loginBlocked === null) {
|
|
99
99
|
void openLogin(scopeId, provider.kind, id, name);
|
|
100
100
|
}
|
|
101
101
|
};
|
|
@@ -109,7 +109,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
109
109
|
onSelect={() => setPicked({ kind: 'cli', cli: cli.kind })}
|
|
110
110
|
className="pr-1"
|
|
111
111
|
trailing={
|
|
112
|
-
|
|
112
|
+
loginOf(cli.kind) !== null && (
|
|
113
113
|
<IconButton
|
|
114
114
|
icon={Plus}
|
|
115
115
|
size="sm"
|
|
@@ -125,7 +125,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
125
125
|
</ListRow>
|
|
126
126
|
{entries.map((entry) => {
|
|
127
127
|
const name = accountName(entry, cli.name);
|
|
128
|
-
const status = accountStatusLine(entry.status,
|
|
128
|
+
const status = accountStatusLine(entry.status, loginOf(cli.kind) === 'terminal');
|
|
129
129
|
return (
|
|
130
130
|
<ListRow
|
|
131
131
|
key={entry.id}
|
|
@@ -190,6 +190,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
190
190
|
key={provider.kind}
|
|
191
191
|
provider={provider}
|
|
192
192
|
entries={entriesOf(provider.kind)}
|
|
193
|
+
login={loginOf(provider.kind)}
|
|
193
194
|
onCancel={() => setPicked({ kind: 'cli', cli: provider.kind })}
|
|
194
195
|
onAdded={added}
|
|
195
196
|
/>
|
|
@@ -203,7 +204,8 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
203
204
|
key={entry.id}
|
|
204
205
|
provider={provider}
|
|
205
206
|
entry={entry}
|
|
206
|
-
|
|
207
|
+
login={loginOf(provider.kind)}
|
|
208
|
+
canOpenLogin={openLogin !== null}
|
|
207
209
|
loginBlocked={loginBlocked}
|
|
208
210
|
secretsAvailable={accounts?.secretsAvailable === true}
|
|
209
211
|
onRemoved={() => setPicked({ kind: 'cli', cli: provider.kind })}
|
|
@@ -216,7 +218,7 @@ export function ProvidersPane({ target = null, detailOf, defaults = true }: Prov
|
|
|
216
218
|
provider={provider}
|
|
217
219
|
entries={accounts === null ? null : entriesOf(provider.kind)}
|
|
218
220
|
defaults={defaults}
|
|
219
|
-
|
|
221
|
+
login={loginOf(provider.kind)}
|
|
220
222
|
onAddAccount={() => setPicked({ kind: 'add', cli: provider.kind })}
|
|
221
223
|
onPickAccount={(id) => setPicked({ kind: 'account', id })}
|
|
222
224
|
/>
|
package/src/providers/parts.tsx
CHANGED
|
@@ -8,7 +8,8 @@ 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 }) {
|
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
|
}
|