@adecore/agents-react 0.33.0 → 0.33.2
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/account-limits.d.ts +1 -0
- package/dist/agents/account-limits.js +9 -5
- package/dist/chat/ui/LimitState.js +305 -66
- package/dist/chat/ui/RunSettings.js +7 -172
- package/dist/chat/ui/SessionLine.d.ts +5 -0
- package/dist/chat/ui/SessionLine.js +172 -0
- package/dist/locales/en/agent-chat.d.ts +3 -0
- package/dist/locales/en/agent-chat.js +4 -1
- package/dist/locales/nl/agent-chat.d.ts +3 -0
- package/dist/locales/nl/agent-chat.js +4 -1
- package/package.json +8 -8
- package/src/agents/account-limits.ts +16 -6
- package/src/chat/ui/LimitState.tsx +82 -15
- package/src/chat/ui/RunSettings.tsx +3 -43
- package/src/chat/ui/SessionLine.tsx +46 -0
- package/src/locales/en/agent-chat.ts +4 -1
- package/src/locales/nl/agent-chat.ts +4 -1
|
@@ -3,5 +3,6 @@ import { type AccountEntry } from './accounts.js';
|
|
|
3
3
|
export declare function limitsAccountId(entry: UsageLimitsProvider): string;
|
|
4
4
|
export declare function limitsOfAccount(snapshot: UsageLimitsSnapshot | null, id: string): UsageLimitsProvider | null;
|
|
5
5
|
export declare function sessionWindow(entry: UsageLimitsProvider | null): UsageWindow | null;
|
|
6
|
+
export declare function continueTargets(accounts: ProviderAccounts | null, snapshot: UsageLimitsSnapshot | null, kind: AgentKind, current: string | undefined): AccountEntry[];
|
|
6
7
|
export declare function continueTarget(accounts: ProviderAccounts | null, snapshot: UsageLimitsSnapshot | null, kind: AgentKind, current: string | undefined): AccountEntry | null;
|
|
7
8
|
export declare function hasUnreadAccount(accounts: ProviderAccounts | null, snapshot: UsageLimitsSnapshot | null, kind: AgentKind, current: string | undefined): boolean;
|
|
@@ -22,11 +22,11 @@ function others(accounts, kind, current) {
|
|
|
22
22
|
return accountsOfKind(accounts, kind).filter(entry => entry.id !== (current ?? kind) && entry.account.enabled !== false && entry.status?.state === 'ready');
|
|
23
23
|
}
|
|
24
24
|
/*
|
|
25
|
-
* The
|
|
26
|
-
* signed in and
|
|
27
|
-
* none was read yet; the choice is only ever offered, never made.
|
|
25
|
+
* The accounts a chat that stopped on a limit could go on under: the others of its CLI that are on,
|
|
26
|
+
* signed in and have room, the one with the least of its session spent first. Empty when none has
|
|
27
|
+
* room, or none was read yet; the choice is only ever offered, never made.
|
|
28
28
|
*/
|
|
29
|
-
export function
|
|
29
|
+
export function continueTargets(accounts, snapshot, kind, current) {
|
|
30
30
|
const roomy = others(accounts, kind, current).flatMap(entry => {
|
|
31
31
|
const limits = limitsOfAccount(snapshot, entry.id);
|
|
32
32
|
return hasRoom(limits) ? [{
|
|
@@ -35,7 +35,11 @@ export function continueTarget(accounts, snapshot, kind, current) {
|
|
|
35
35
|
}] : [];
|
|
36
36
|
});
|
|
37
37
|
roomy.sort((a, b) => a.pressure - b.pressure);
|
|
38
|
-
return roomy
|
|
38
|
+
return roomy.map(target => target.entry);
|
|
39
|
+
}
|
|
40
|
+
/* The one `continueTargets` suggests: the account with the most room. */
|
|
41
|
+
export function continueTarget(accounts, snapshot, kind, current) {
|
|
42
|
+
return continueTargets(accounts, snapshot, kind, current)[0] ?? null;
|
|
39
43
|
}
|
|
40
44
|
/* Whether another account a chat could go on under has no numbers yet, since the machine only reads one in use by itself. */
|
|
41
45
|
export function hasUnreadAccount(accounts, snapshot, kind, current) {
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { c as _c } from "react/compiler-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { ChevronDown, Hourglass } from 'lucide-react';
|
|
5
6
|
import { AccountDot } from '../../agents/AccountDot.js';
|
|
6
|
-
import {
|
|
7
|
+
import { continueTargets, hasUnreadAccount, limitsOfAccount, sessionWindow } from '../../agents/account-limits.js';
|
|
8
|
+
import { canContinueOn } from '../../agents/accounts.js';
|
|
7
9
|
import { useAccountChoice } from '../account-choice.js';
|
|
8
10
|
import { limitView } from '../logic/limit.js';
|
|
9
11
|
import { useFormatLocale } from '@adecore/ui/format';
|
|
10
|
-
import { Switch, Button, Icon, Pill, Tooltip, useNow } from '@adecore/ui';
|
|
12
|
+
import { Switch, Button, ButtonGroup, Icon, Menu, Pill, Tooltip, useNow } from '@adecore/ui';
|
|
11
13
|
import { useUsageLimits } from '../../usage/limits.js';
|
|
12
14
|
import { useChats } from '../../state/chats.js';
|
|
13
15
|
import { useChatHost } from '../../host.js';
|
|
14
16
|
import { useChatScope } from '../../scope.js';
|
|
15
17
|
import { useChatActions } from '../actions.js';
|
|
16
18
|
import { useUsageStore } from '../../state/usage.js';
|
|
19
|
+
import { SessionLine } from './SessionLine.js';
|
|
17
20
|
import { useHostResumeAtReset } from '../../host-hooks.js';
|
|
18
21
|
// A time a limit names reads as a clock today and with its day after that, so the words follow the minute.
|
|
19
22
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -118,13 +121,15 @@ function ResumeAtResetToggle(t0) {
|
|
|
118
121
|
return t7;
|
|
119
122
|
}
|
|
120
123
|
/*
|
|
121
|
-
* Another account of the chat's CLI that has room: the one with the least of its session spent
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
+
* Another account of the chat's CLI that has room: the one with the least of its session spent, and
|
|
125
|
+
* beside it a menu of every account with room, for a person whose companies each pay for their own
|
|
126
|
+
* work. Only ever offered, since going on there spends someone's other plan, and nothing is
|
|
127
|
+
* remembered: the next limit suggests the one with the most room again. An account that reads the
|
|
128
|
+
* chat's conversation takes it over; any other goes on in a fork, which then opens the way a fork does.
|
|
124
129
|
*/
|
|
125
130
|
function _temp() {}
|
|
126
131
|
function ContinueOn(t0) {
|
|
127
|
-
const $ = _c(
|
|
132
|
+
const $ = _c(65);
|
|
128
133
|
const {
|
|
129
134
|
chatId,
|
|
130
135
|
info,
|
|
@@ -138,7 +143,7 @@ function ContinueOn(t0) {
|
|
|
138
143
|
const actions = useChatActions();
|
|
139
144
|
const limits = useUsageLimits();
|
|
140
145
|
const [busy, setBusy] = useState(false);
|
|
141
|
-
const
|
|
146
|
+
const targets = continueTargets(choice.accounts, limits, info.provider, info.account);
|
|
142
147
|
let t1;
|
|
143
148
|
if ($[0] !== choice.accounts || $[1] !== info.account || $[2] !== info.provider || $[3] !== limits) {
|
|
144
149
|
t1 = limits !== null && hasUnreadAccount(choice.accounts, limits, info.provider, info.account);
|
|
@@ -169,86 +174,320 @@ function ContinueOn(t0) {
|
|
|
169
174
|
t3 = $[8];
|
|
170
175
|
}
|
|
171
176
|
useEffect(t2, t3);
|
|
172
|
-
|
|
177
|
+
const target = targets[0];
|
|
178
|
+
if (target === undefined) {
|
|
173
179
|
return null;
|
|
174
180
|
}
|
|
181
|
+
let t4;
|
|
182
|
+
if ($[9] !== actions || $[10] !== chatId || $[11] !== choice || $[12] !== host || $[13] !== t) {
|
|
183
|
+
t4 = entry => {
|
|
184
|
+
setBusy(true);
|
|
185
|
+
actions.continueOn(chatId, entry.id).catch(e => host.notify({
|
|
186
|
+
kind: "error",
|
|
187
|
+
title: t("limit.continueFailed", {
|
|
188
|
+
account: choice.nameOf(entry)
|
|
189
|
+
}),
|
|
190
|
+
description: e instanceof Error ? e.message : String(e)
|
|
191
|
+
})).finally(() => setBusy(false));
|
|
192
|
+
};
|
|
193
|
+
$[9] = actions;
|
|
194
|
+
$[10] = chatId;
|
|
195
|
+
$[11] = choice;
|
|
196
|
+
$[12] = host;
|
|
197
|
+
$[13] = t;
|
|
198
|
+
$[14] = t4;
|
|
199
|
+
} else {
|
|
200
|
+
t4 = $[14];
|
|
201
|
+
}
|
|
202
|
+
const go = t4;
|
|
175
203
|
const name = choice.nameOf(target);
|
|
176
|
-
const
|
|
177
|
-
setBusy(true);
|
|
178
|
-
actions.continueOn(chatId, target.id).catch(e => host.notify({
|
|
179
|
-
kind: "error",
|
|
180
|
-
title: t("limit.continueFailed", {
|
|
181
|
-
account: name
|
|
182
|
-
}),
|
|
183
|
-
description: e instanceof Error ? e.message : String(e)
|
|
184
|
-
})).finally(() => setBusy(false));
|
|
185
|
-
};
|
|
204
|
+
const split = targets.length > 1;
|
|
186
205
|
const T0 = Tooltip;
|
|
187
|
-
const
|
|
206
|
+
const t5 = t("limit.continueOnTip", {
|
|
188
207
|
account: name
|
|
189
208
|
});
|
|
190
209
|
const T1 = Button;
|
|
191
|
-
const
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
|
|
210
|
+
const t6 = "secondary";
|
|
211
|
+
const t7 = "sm";
|
|
212
|
+
const t8 = split && "rounded-r-none";
|
|
213
|
+
let t9;
|
|
214
|
+
if ($[15] !== t8) {
|
|
215
|
+
t9 = clsx("shrink-0 gap-1.5 whitespace-nowrap", t8);
|
|
216
|
+
$[15] = t8;
|
|
217
|
+
$[16] = t9;
|
|
218
|
+
} else {
|
|
219
|
+
t9 = $[16];
|
|
220
|
+
}
|
|
221
|
+
const t10 = t("limit.continueOnAria", {
|
|
195
222
|
account: name
|
|
196
223
|
});
|
|
197
|
-
let
|
|
198
|
-
if ($[
|
|
199
|
-
|
|
200
|
-
$[
|
|
201
|
-
$[
|
|
224
|
+
let t11;
|
|
225
|
+
if ($[17] !== go || $[18] !== target) {
|
|
226
|
+
t11 = () => go(target);
|
|
227
|
+
$[17] = go;
|
|
228
|
+
$[18] = target;
|
|
229
|
+
$[19] = t11;
|
|
202
230
|
} else {
|
|
203
|
-
|
|
231
|
+
t11 = $[19];
|
|
204
232
|
}
|
|
205
|
-
let
|
|
206
|
-
if ($[
|
|
207
|
-
|
|
233
|
+
let t12;
|
|
234
|
+
if ($[20] !== t) {
|
|
235
|
+
t12 = t("limit.continueOn");
|
|
236
|
+
$[20] = t;
|
|
237
|
+
$[21] = t12;
|
|
238
|
+
} else {
|
|
239
|
+
t12 = $[21];
|
|
240
|
+
}
|
|
241
|
+
let t13;
|
|
242
|
+
if ($[22] !== target.account.color) {
|
|
243
|
+
t13 = /*#__PURE__*/_jsx(AccountDot, {
|
|
208
244
|
color: target.account.color,
|
|
209
245
|
className: "size-1.75"
|
|
210
246
|
});
|
|
211
|
-
$[
|
|
212
|
-
$[
|
|
247
|
+
$[22] = target.account.color;
|
|
248
|
+
$[23] = t13;
|
|
213
249
|
} else {
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
let
|
|
217
|
-
if ($[
|
|
218
|
-
|
|
219
|
-
variant:
|
|
220
|
-
size:
|
|
221
|
-
className:
|
|
250
|
+
t13 = $[23];
|
|
251
|
+
}
|
|
252
|
+
let t14;
|
|
253
|
+
if ($[24] !== T1 || $[25] !== busy || $[26] !== name || $[27] !== t10 || $[28] !== t11 || $[29] !== t12 || $[30] !== t13 || $[31] !== t9) {
|
|
254
|
+
t14 = /*#__PURE__*/_jsxs(T1, {
|
|
255
|
+
variant: t6,
|
|
256
|
+
size: t7,
|
|
257
|
+
className: t9,
|
|
222
258
|
disabled: busy,
|
|
223
|
-
"aria-label":
|
|
224
|
-
onClick:
|
|
225
|
-
children: [
|
|
259
|
+
"aria-label": t10,
|
|
260
|
+
onClick: t11,
|
|
261
|
+
children: [t12, t13, name]
|
|
226
262
|
});
|
|
227
|
-
$[
|
|
228
|
-
$[
|
|
229
|
-
$[
|
|
230
|
-
$[
|
|
231
|
-
$[
|
|
232
|
-
$[
|
|
233
|
-
$[
|
|
234
|
-
$[
|
|
263
|
+
$[24] = T1;
|
|
264
|
+
$[25] = busy;
|
|
265
|
+
$[26] = name;
|
|
266
|
+
$[27] = t10;
|
|
267
|
+
$[28] = t11;
|
|
268
|
+
$[29] = t12;
|
|
269
|
+
$[30] = t13;
|
|
270
|
+
$[31] = t9;
|
|
271
|
+
$[32] = t14;
|
|
235
272
|
} else {
|
|
236
|
-
|
|
273
|
+
t14 = $[32];
|
|
237
274
|
}
|
|
238
|
-
let
|
|
239
|
-
if ($[
|
|
240
|
-
|
|
241
|
-
label:
|
|
242
|
-
children:
|
|
275
|
+
let t15;
|
|
276
|
+
if ($[33] !== T0 || $[34] !== t14 || $[35] !== t5) {
|
|
277
|
+
t15 = /*#__PURE__*/_jsx(T0, {
|
|
278
|
+
label: t5,
|
|
279
|
+
children: t14
|
|
280
|
+
});
|
|
281
|
+
$[33] = T0;
|
|
282
|
+
$[34] = t14;
|
|
283
|
+
$[35] = t5;
|
|
284
|
+
$[36] = t15;
|
|
285
|
+
} else {
|
|
286
|
+
t15 = $[36];
|
|
287
|
+
}
|
|
288
|
+
const suggestion = t15;
|
|
289
|
+
if (!split) {
|
|
290
|
+
return suggestion;
|
|
291
|
+
}
|
|
292
|
+
let t16;
|
|
293
|
+
if ($[37] !== t) {
|
|
294
|
+
t16 = t("limit.chooseAccount");
|
|
295
|
+
$[37] = t;
|
|
296
|
+
$[38] = t16;
|
|
297
|
+
} else {
|
|
298
|
+
t16 = $[38];
|
|
299
|
+
}
|
|
300
|
+
let t17;
|
|
301
|
+
if ($[39] !== busy) {
|
|
302
|
+
t17 = /*#__PURE__*/_jsx(Button, {
|
|
303
|
+
variant: "secondary",
|
|
304
|
+
size: "sm",
|
|
305
|
+
className: "-ml-px rounded-l-none border-l-0 px-1.5",
|
|
306
|
+
disabled: busy
|
|
307
|
+
});
|
|
308
|
+
$[39] = busy;
|
|
309
|
+
$[40] = t17;
|
|
310
|
+
} else {
|
|
311
|
+
t17 = $[40];
|
|
312
|
+
}
|
|
313
|
+
let t18;
|
|
314
|
+
if ($[41] === Symbol.for("react.memo_cache_sentinel")) {
|
|
315
|
+
t18 = /*#__PURE__*/_jsx(Icon, {
|
|
316
|
+
icon: ChevronDown,
|
|
317
|
+
size: 14
|
|
318
|
+
});
|
|
319
|
+
$[41] = t18;
|
|
320
|
+
} else {
|
|
321
|
+
t18 = $[41];
|
|
322
|
+
}
|
|
323
|
+
let t19;
|
|
324
|
+
if ($[42] !== t17) {
|
|
325
|
+
t19 = /*#__PURE__*/_jsx(Menu.Trigger, {
|
|
326
|
+
render: t17,
|
|
327
|
+
children: t18
|
|
328
|
+
});
|
|
329
|
+
$[42] = t17;
|
|
330
|
+
$[43] = t19;
|
|
331
|
+
} else {
|
|
332
|
+
t19 = $[43];
|
|
333
|
+
}
|
|
334
|
+
let t20;
|
|
335
|
+
if ($[44] !== t16 || $[45] !== t19) {
|
|
336
|
+
t20 = /*#__PURE__*/_jsx(Tooltip, {
|
|
337
|
+
label: t16,
|
|
338
|
+
name: true,
|
|
339
|
+
children: t19
|
|
340
|
+
});
|
|
341
|
+
$[44] = t16;
|
|
342
|
+
$[45] = t19;
|
|
343
|
+
$[46] = t20;
|
|
344
|
+
} else {
|
|
345
|
+
t20 = $[46];
|
|
346
|
+
}
|
|
347
|
+
let t21;
|
|
348
|
+
if ($[47] !== t) {
|
|
349
|
+
t21 = t("limit.continueOn");
|
|
350
|
+
$[47] = t;
|
|
351
|
+
$[48] = t21;
|
|
352
|
+
} else {
|
|
353
|
+
t21 = $[48];
|
|
354
|
+
}
|
|
355
|
+
let t22;
|
|
356
|
+
if ($[49] !== t21) {
|
|
357
|
+
t22 = /*#__PURE__*/_jsx(Menu.GroupLabel, {
|
|
358
|
+
children: t21
|
|
359
|
+
});
|
|
360
|
+
$[49] = t21;
|
|
361
|
+
$[50] = t22;
|
|
362
|
+
} else {
|
|
363
|
+
t22 = $[50];
|
|
364
|
+
}
|
|
365
|
+
let t23;
|
|
366
|
+
if ($[51] !== choice || $[52] !== go || $[53] !== info || $[54] !== targets) {
|
|
367
|
+
t23 = /*#__PURE__*/_jsx(ContinueOnRows, {
|
|
368
|
+
targets: targets,
|
|
369
|
+
info: info,
|
|
370
|
+
choice: choice,
|
|
371
|
+
onPick: go
|
|
372
|
+
});
|
|
373
|
+
$[51] = choice;
|
|
374
|
+
$[52] = go;
|
|
375
|
+
$[53] = info;
|
|
376
|
+
$[54] = targets;
|
|
377
|
+
$[55] = t23;
|
|
378
|
+
} else {
|
|
379
|
+
t23 = $[55];
|
|
380
|
+
}
|
|
381
|
+
let t24;
|
|
382
|
+
if ($[56] !== t22 || $[57] !== t23) {
|
|
383
|
+
t24 = /*#__PURE__*/_jsx(Menu.Popup, {
|
|
384
|
+
align: "end",
|
|
385
|
+
className: "min-w-64",
|
|
386
|
+
children: /*#__PURE__*/_jsxs(Menu.Group, {
|
|
387
|
+
children: [t22, t23]
|
|
388
|
+
})
|
|
389
|
+
});
|
|
390
|
+
$[56] = t22;
|
|
391
|
+
$[57] = t23;
|
|
392
|
+
$[58] = t24;
|
|
393
|
+
} else {
|
|
394
|
+
t24 = $[58];
|
|
395
|
+
}
|
|
396
|
+
let t25;
|
|
397
|
+
if ($[59] !== t20 || $[60] !== t24) {
|
|
398
|
+
t25 = /*#__PURE__*/_jsxs(Menu.Root, {
|
|
399
|
+
children: [t20, t24]
|
|
400
|
+
});
|
|
401
|
+
$[59] = t20;
|
|
402
|
+
$[60] = t24;
|
|
403
|
+
$[61] = t25;
|
|
404
|
+
} else {
|
|
405
|
+
t25 = $[61];
|
|
406
|
+
}
|
|
407
|
+
let t26;
|
|
408
|
+
if ($[62] !== suggestion || $[63] !== t25) {
|
|
409
|
+
t26 = /*#__PURE__*/_jsxs(ButtonGroup, {
|
|
410
|
+
className: "shrink-0",
|
|
411
|
+
children: [suggestion, t25]
|
|
243
412
|
});
|
|
244
|
-
$[
|
|
245
|
-
$[
|
|
246
|
-
$[
|
|
247
|
-
|
|
413
|
+
$[62] = suggestion;
|
|
414
|
+
$[63] = t25;
|
|
415
|
+
$[64] = t26;
|
|
416
|
+
} else {
|
|
417
|
+
t26 = $[64];
|
|
418
|
+
}
|
|
419
|
+
return t26;
|
|
420
|
+
}
|
|
421
|
+
/* Mounted only while the menu is open, so the clock of the session lines only ticks while someone reads them. */
|
|
422
|
+
function ContinueOnRows(t0) {
|
|
423
|
+
const $ = _c(15);
|
|
424
|
+
const {
|
|
425
|
+
targets,
|
|
426
|
+
info,
|
|
427
|
+
choice,
|
|
428
|
+
onPick
|
|
429
|
+
} = t0;
|
|
430
|
+
const {
|
|
431
|
+
t
|
|
432
|
+
} = useTranslation("agent-chat");
|
|
433
|
+
const limits = useUsageLimits();
|
|
434
|
+
const now = useNow(MINUTE_MS);
|
|
435
|
+
let t1;
|
|
436
|
+
if ($[0] !== choice || $[1] !== info || $[2] !== limits || $[3] !== now || $[4] !== onPick || $[5] !== t || $[6] !== targets) {
|
|
437
|
+
let t2;
|
|
438
|
+
if ($[8] !== choice || $[9] !== info || $[10] !== limits || $[11] !== now || $[12] !== onPick || $[13] !== t) {
|
|
439
|
+
t2 = entry => {
|
|
440
|
+
const name = choice.nameOf(entry);
|
|
441
|
+
const session = sessionWindow(limitsOfAccount(limits, entry.id));
|
|
442
|
+
const fork = !canContinueOn(choice.accounts, info.provider, info.account, entry.id);
|
|
443
|
+
return /*#__PURE__*/_jsx(Menu.Item, {
|
|
444
|
+
className: "items-start",
|
|
445
|
+
"aria-label": t(fork ? "limit.continueInForkAria" : "limit.continueOnAria", {
|
|
446
|
+
account: name
|
|
447
|
+
}),
|
|
448
|
+
onClick: () => onPick(entry),
|
|
449
|
+
children: /*#__PURE__*/_jsxs("span", {
|
|
450
|
+
className: "flex min-w-0 grow flex-col",
|
|
451
|
+
children: [/*#__PURE__*/_jsxs("span", {
|
|
452
|
+
className: "flex min-w-0 items-center gap-2",
|
|
453
|
+
children: [/*#__PURE__*/_jsx(AccountDot, {
|
|
454
|
+
color: entry.account.color
|
|
455
|
+
}), /*#__PURE__*/_jsx("span", {
|
|
456
|
+
className: "truncate",
|
|
457
|
+
children: name
|
|
458
|
+
}), fork && /*#__PURE__*/_jsx(Menu.Hint, {
|
|
459
|
+
children: t("limit.inFork")
|
|
460
|
+
})]
|
|
461
|
+
}), session !== null && /*#__PURE__*/_jsx(SessionLine, {
|
|
462
|
+
window: session,
|
|
463
|
+
now: now
|
|
464
|
+
})]
|
|
465
|
+
})
|
|
466
|
+
}, entry.id);
|
|
467
|
+
};
|
|
468
|
+
$[8] = choice;
|
|
469
|
+
$[9] = info;
|
|
470
|
+
$[10] = limits;
|
|
471
|
+
$[11] = now;
|
|
472
|
+
$[12] = onPick;
|
|
473
|
+
$[13] = t;
|
|
474
|
+
$[14] = t2;
|
|
475
|
+
} else {
|
|
476
|
+
t2 = $[14];
|
|
477
|
+
}
|
|
478
|
+
t1 = targets.map(t2);
|
|
479
|
+
$[0] = choice;
|
|
480
|
+
$[1] = info;
|
|
481
|
+
$[2] = limits;
|
|
482
|
+
$[3] = now;
|
|
483
|
+
$[4] = onPick;
|
|
484
|
+
$[5] = t;
|
|
485
|
+
$[6] = targets;
|
|
486
|
+
$[7] = t1;
|
|
248
487
|
} else {
|
|
249
|
-
|
|
488
|
+
t1 = $[7];
|
|
250
489
|
}
|
|
251
|
-
return
|
|
490
|
+
return t1;
|
|
252
491
|
}
|
|
253
492
|
/* At the top of the composer while the last turn stopped on a limit: what stopped it, when it goes on, and the switch. */
|
|
254
493
|
export function LimitDock(t0) {
|
|
@@ -9,15 +9,15 @@ import { canContinueOn } from '../../agents/accounts.js';
|
|
|
9
9
|
import { AgentIcon } from '../../agents/AgentIcon.js';
|
|
10
10
|
import { modelName, shortModelName } from '../../agents/model-name.js';
|
|
11
11
|
import { ModelMarks } from './ModelMarks.js';
|
|
12
|
+
import { SessionLine } from './SessionLine.js';
|
|
12
13
|
import { favoritesFirst, searchModels, shortlistOf, wantsModelSearch } from '../logic/picker-entries.js';
|
|
13
14
|
import { favoriteModelsOf, toggleFavoriteModel, useChatPreferences } from '../preferences.js';
|
|
14
15
|
import { CONTEXT_OPTION, contextFraction, contextSegments, orderOptions } from '../logic/context-usage.js';
|
|
15
16
|
import { RUNTIME_MODES } from '../runtime-modes.js';
|
|
16
17
|
import { localizedOption } from '../../agents/option-words.js';
|
|
17
|
-
import {
|
|
18
|
+
import { formatPercent, formatTokens } from '@adecore/ui/format';
|
|
18
19
|
import { Icon, Menu, Tooltip, useNow } from '@adecore/ui';
|
|
19
20
|
import { useChatHost } from '../../host.js';
|
|
20
|
-
import { evenPace, paceText } from '../../usage/limit-groups.js';
|
|
21
21
|
import { useUsageLimits } from '../../usage/limits.js';
|
|
22
22
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
23
23
|
const MINUTE_MS = 60_000;
|
|
@@ -803,7 +803,11 @@ export function RunSettings(t0) {
|
|
|
803
803
|
}), t49]
|
|
804
804
|
});
|
|
805
805
|
}
|
|
806
|
-
/*
|
|
806
|
+
/*
|
|
807
|
+
* The account the next turn runs under, for the chat's own CLI. Before the first turn any of them;
|
|
808
|
+
* after it one that reads the conversation, since the others would start it over, and a fork is how
|
|
809
|
+
* the conversation goes along to those. A switch applies from the next turn.
|
|
810
|
+
*/
|
|
807
811
|
function _temp3(count, group) {
|
|
808
812
|
return count + group.models.length;
|
|
809
813
|
}
|
|
@@ -813,23 +817,6 @@ function _temp2(preferences) {
|
|
|
813
817
|
function _temp(option_0) {
|
|
814
818
|
return option_0.id === CONTEXT_OPTION;
|
|
815
819
|
}
|
|
816
|
-
function sessionTone(used) {
|
|
817
|
-
return used >= 0.9 ? {
|
|
818
|
-
bar: 'bg-status-error',
|
|
819
|
-
text: 'text-status-error'
|
|
820
|
-
} : used >= 0.7 ? {
|
|
821
|
-
bar: 'bg-status-needs-you',
|
|
822
|
-
text: 'text-status-needs-you'
|
|
823
|
-
} : {
|
|
824
|
-
bar: 'bg-text-muted',
|
|
825
|
-
text: 'text-text-muted'
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
/*
|
|
829
|
-
* The account the next turn runs under, for the chat's own CLI. Before the first turn any of them;
|
|
830
|
-
* after it one that reads the conversation, since the others would start it over, and a fork is how
|
|
831
|
-
* the conversation goes along to those. A switch applies from the next turn.
|
|
832
|
-
*/
|
|
833
820
|
function AccountSubmenu(t0) {
|
|
834
821
|
const $ = _c(51);
|
|
835
822
|
const {
|
|
@@ -1119,158 +1106,6 @@ function AccountRows(t0) {
|
|
|
1119
1106
|
}
|
|
1120
1107
|
return t1;
|
|
1121
1108
|
}
|
|
1122
|
-
function SessionLine(t0) {
|
|
1123
|
-
const $ = _c(37);
|
|
1124
|
-
const {
|
|
1125
|
-
window,
|
|
1126
|
-
now
|
|
1127
|
-
} = t0;
|
|
1128
|
-
const {
|
|
1129
|
-
t
|
|
1130
|
-
} = useTranslation("agent-chat");
|
|
1131
|
-
let resetsAt;
|
|
1132
|
-
let t1;
|
|
1133
|
-
let t2;
|
|
1134
|
-
let t3;
|
|
1135
|
-
let t4;
|
|
1136
|
-
let t5;
|
|
1137
|
-
if ($[0] !== now || $[1] !== window) {
|
|
1138
|
-
const percent = Math.round(window.used * 100);
|
|
1139
|
-
const tone = sessionTone(window.used);
|
|
1140
|
-
const mark = evenPace(window, now)?.percent ?? null;
|
|
1141
|
-
resetsAt = window.resetsAt;
|
|
1142
|
-
t4 = "flex items-center gap-2 text-xs text-text-faint tabular-nums";
|
|
1143
|
-
const t6 = true;
|
|
1144
|
-
const t7 = "relative h-1 w-14 shrink-0 rounded-full bg-surface-sunken";
|
|
1145
|
-
const t8 = clsx("absolute inset-y-0 left-0 rounded-full", tone.bar);
|
|
1146
|
-
const t9 = `${Math.min(100, percent)}%`;
|
|
1147
|
-
let t10;
|
|
1148
|
-
if ($[8] !== t9) {
|
|
1149
|
-
t10 = {
|
|
1150
|
-
width: t9
|
|
1151
|
-
};
|
|
1152
|
-
$[8] = t9;
|
|
1153
|
-
$[9] = t10;
|
|
1154
|
-
} else {
|
|
1155
|
-
t10 = $[9];
|
|
1156
|
-
}
|
|
1157
|
-
let t11;
|
|
1158
|
-
if ($[10] !== t10 || $[11] !== t8) {
|
|
1159
|
-
t11 = /*#__PURE__*/_jsx("span", {
|
|
1160
|
-
className: t8,
|
|
1161
|
-
style: t10
|
|
1162
|
-
});
|
|
1163
|
-
$[10] = t10;
|
|
1164
|
-
$[11] = t8;
|
|
1165
|
-
$[12] = t11;
|
|
1166
|
-
} else {
|
|
1167
|
-
t11 = $[12];
|
|
1168
|
-
}
|
|
1169
|
-
const t12 = mark !== null && /*#__PURE__*/_jsx("span", {
|
|
1170
|
-
className: clsx("absolute -inset-y-0.5 w-px -translate-x-1/2", mark <= percent ? "bg-surface-raised" : "bg-text-muted"),
|
|
1171
|
-
style: {
|
|
1172
|
-
left: `${mark}%`
|
|
1173
|
-
}
|
|
1174
|
-
});
|
|
1175
|
-
if ($[13] !== t11 || $[14] !== t12 || $[15] !== t6 || $[16] !== t7) {
|
|
1176
|
-
t5 = /*#__PURE__*/_jsxs("span", {
|
|
1177
|
-
"aria-hidden": t6,
|
|
1178
|
-
className: t7,
|
|
1179
|
-
children: [t11, t12]
|
|
1180
|
-
});
|
|
1181
|
-
$[13] = t11;
|
|
1182
|
-
$[14] = t12;
|
|
1183
|
-
$[15] = t6;
|
|
1184
|
-
$[16] = t7;
|
|
1185
|
-
$[17] = t5;
|
|
1186
|
-
} else {
|
|
1187
|
-
t5 = $[17];
|
|
1188
|
-
}
|
|
1189
|
-
t1 = true;
|
|
1190
|
-
t2 = tone.text;
|
|
1191
|
-
t3 = formatPercent(percent);
|
|
1192
|
-
$[0] = now;
|
|
1193
|
-
$[1] = window;
|
|
1194
|
-
$[2] = resetsAt;
|
|
1195
|
-
$[3] = t1;
|
|
1196
|
-
$[4] = t2;
|
|
1197
|
-
$[5] = t3;
|
|
1198
|
-
$[6] = t4;
|
|
1199
|
-
$[7] = t5;
|
|
1200
|
-
} else {
|
|
1201
|
-
resetsAt = $[2];
|
|
1202
|
-
t1 = $[3];
|
|
1203
|
-
t2 = $[4];
|
|
1204
|
-
t3 = $[5];
|
|
1205
|
-
t4 = $[6];
|
|
1206
|
-
t5 = $[7];
|
|
1207
|
-
}
|
|
1208
|
-
let t6;
|
|
1209
|
-
if ($[18] !== t1 || $[19] !== t2 || $[20] !== t3) {
|
|
1210
|
-
t6 = /*#__PURE__*/_jsx("span", {
|
|
1211
|
-
"aria-hidden": t1,
|
|
1212
|
-
className: t2,
|
|
1213
|
-
children: t3
|
|
1214
|
-
});
|
|
1215
|
-
$[18] = t1;
|
|
1216
|
-
$[19] = t2;
|
|
1217
|
-
$[20] = t3;
|
|
1218
|
-
$[21] = t6;
|
|
1219
|
-
} else {
|
|
1220
|
-
t6 = $[21];
|
|
1221
|
-
}
|
|
1222
|
-
let t7;
|
|
1223
|
-
if ($[22] !== now || $[23] !== window) {
|
|
1224
|
-
t7 = paceText(window, now);
|
|
1225
|
-
$[22] = now;
|
|
1226
|
-
$[23] = window;
|
|
1227
|
-
$[24] = t7;
|
|
1228
|
-
} else {
|
|
1229
|
-
t7 = $[24];
|
|
1230
|
-
}
|
|
1231
|
-
let t8;
|
|
1232
|
-
if ($[25] !== t7) {
|
|
1233
|
-
t8 = /*#__PURE__*/_jsx("span", {
|
|
1234
|
-
className: "sr-only",
|
|
1235
|
-
children: t7
|
|
1236
|
-
});
|
|
1237
|
-
$[25] = t7;
|
|
1238
|
-
$[26] = t8;
|
|
1239
|
-
} else {
|
|
1240
|
-
t8 = $[26];
|
|
1241
|
-
}
|
|
1242
|
-
let t9;
|
|
1243
|
-
if ($[27] !== now || $[28] !== resetsAt || $[29] !== t) {
|
|
1244
|
-
t9 = resetsAt !== null && /*#__PURE__*/_jsxs("span", {
|
|
1245
|
-
className: "whitespace-nowrap",
|
|
1246
|
-
children: ["\xB7 ", t("pickers.account.resets", {
|
|
1247
|
-
time: isSameDay(resetsAt, now) ? formatClock(resetsAt) : formatWeekdayClock(resetsAt)
|
|
1248
|
-
})]
|
|
1249
|
-
});
|
|
1250
|
-
$[27] = now;
|
|
1251
|
-
$[28] = resetsAt;
|
|
1252
|
-
$[29] = t;
|
|
1253
|
-
$[30] = t9;
|
|
1254
|
-
} else {
|
|
1255
|
-
t9 = $[30];
|
|
1256
|
-
}
|
|
1257
|
-
let t10;
|
|
1258
|
-
if ($[31] !== t4 || $[32] !== t5 || $[33] !== t6 || $[34] !== t8 || $[35] !== t9) {
|
|
1259
|
-
t10 = /*#__PURE__*/_jsxs("span", {
|
|
1260
|
-
className: t4,
|
|
1261
|
-
children: [t5, t6, t8, t9]
|
|
1262
|
-
});
|
|
1263
|
-
$[31] = t4;
|
|
1264
|
-
$[32] = t5;
|
|
1265
|
-
$[33] = t6;
|
|
1266
|
-
$[34] = t8;
|
|
1267
|
-
$[35] = t9;
|
|
1268
|
-
$[36] = t10;
|
|
1269
|
-
} else {
|
|
1270
|
-
t10 = $[36];
|
|
1271
|
-
}
|
|
1272
|
-
return t10;
|
|
1273
|
-
}
|
|
1274
1109
|
/* How full the context is, drawn small enough to sit in the pill beside the window's size. */
|
|
1275
1110
|
function ContextRing(t0) {
|
|
1276
1111
|
const $ = _c(15);
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { formatClock, formatPercent, formatWeekdayClock, isSameDay } from '@adecore/ui/format';
|
|
5
|
+
import { evenPace, paceText } from '../../usage/limit-groups.js';
|
|
6
|
+
/* Red where a window is nearly spent, amber where it is worth knowing, as the usage bars have it. */
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
function sessionTone(used) {
|
|
9
|
+
return used >= 0.9 ? {
|
|
10
|
+
bar: 'bg-status-error',
|
|
11
|
+
text: 'text-status-error'
|
|
12
|
+
} : used >= 0.7 ? {
|
|
13
|
+
bar: 'bg-status-needs-you',
|
|
14
|
+
text: 'text-status-needs-you'
|
|
15
|
+
} : {
|
|
16
|
+
bar: 'bg-text-muted',
|
|
17
|
+
text: 'text-text-muted'
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/* How much of an account's session is spent and when it resets, on the second line of a row that names the account. */
|
|
21
|
+
export function SessionLine(t0) {
|
|
22
|
+
const $ = _c(37);
|
|
23
|
+
const {
|
|
24
|
+
window,
|
|
25
|
+
now
|
|
26
|
+
} = t0;
|
|
27
|
+
const {
|
|
28
|
+
t
|
|
29
|
+
} = useTranslation("agent-chat");
|
|
30
|
+
let resetsAt;
|
|
31
|
+
let t1;
|
|
32
|
+
let t2;
|
|
33
|
+
let t3;
|
|
34
|
+
let t4;
|
|
35
|
+
let t5;
|
|
36
|
+
if ($[0] !== now || $[1] !== window) {
|
|
37
|
+
const percent = Math.round(window.used * 100);
|
|
38
|
+
const tone = sessionTone(window.used);
|
|
39
|
+
const mark = evenPace(window, now)?.percent ?? null;
|
|
40
|
+
resetsAt = window.resetsAt;
|
|
41
|
+
t4 = "flex items-center gap-2 text-xs text-text-faint tabular-nums";
|
|
42
|
+
const t6 = true;
|
|
43
|
+
const t7 = "relative h-1 w-14 shrink-0 rounded-full bg-surface-sunken";
|
|
44
|
+
const t8 = clsx("absolute inset-y-0 left-0 rounded-full", tone.bar);
|
|
45
|
+
const t9 = `${Math.min(100, percent)}%`;
|
|
46
|
+
let t10;
|
|
47
|
+
if ($[8] !== t9) {
|
|
48
|
+
t10 = {
|
|
49
|
+
width: t9
|
|
50
|
+
};
|
|
51
|
+
$[8] = t9;
|
|
52
|
+
$[9] = t10;
|
|
53
|
+
} else {
|
|
54
|
+
t10 = $[9];
|
|
55
|
+
}
|
|
56
|
+
let t11;
|
|
57
|
+
if ($[10] !== t10 || $[11] !== t8) {
|
|
58
|
+
t11 = /*#__PURE__*/_jsx("span", {
|
|
59
|
+
className: t8,
|
|
60
|
+
style: t10
|
|
61
|
+
});
|
|
62
|
+
$[10] = t10;
|
|
63
|
+
$[11] = t8;
|
|
64
|
+
$[12] = t11;
|
|
65
|
+
} else {
|
|
66
|
+
t11 = $[12];
|
|
67
|
+
}
|
|
68
|
+
const t12 = mark !== null && /*#__PURE__*/_jsx("span", {
|
|
69
|
+
className: clsx("absolute -inset-y-0.5 w-px -translate-x-1/2", mark <= percent ? "bg-surface-raised" : "bg-text-muted"),
|
|
70
|
+
style: {
|
|
71
|
+
left: `${mark}%`
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if ($[13] !== t11 || $[14] !== t12 || $[15] !== t6 || $[16] !== t7) {
|
|
75
|
+
t5 = /*#__PURE__*/_jsxs("span", {
|
|
76
|
+
"aria-hidden": t6,
|
|
77
|
+
className: t7,
|
|
78
|
+
children: [t11, t12]
|
|
79
|
+
});
|
|
80
|
+
$[13] = t11;
|
|
81
|
+
$[14] = t12;
|
|
82
|
+
$[15] = t6;
|
|
83
|
+
$[16] = t7;
|
|
84
|
+
$[17] = t5;
|
|
85
|
+
} else {
|
|
86
|
+
t5 = $[17];
|
|
87
|
+
}
|
|
88
|
+
t1 = true;
|
|
89
|
+
t2 = tone.text;
|
|
90
|
+
t3 = formatPercent(percent);
|
|
91
|
+
$[0] = now;
|
|
92
|
+
$[1] = window;
|
|
93
|
+
$[2] = resetsAt;
|
|
94
|
+
$[3] = t1;
|
|
95
|
+
$[4] = t2;
|
|
96
|
+
$[5] = t3;
|
|
97
|
+
$[6] = t4;
|
|
98
|
+
$[7] = t5;
|
|
99
|
+
} else {
|
|
100
|
+
resetsAt = $[2];
|
|
101
|
+
t1 = $[3];
|
|
102
|
+
t2 = $[4];
|
|
103
|
+
t3 = $[5];
|
|
104
|
+
t4 = $[6];
|
|
105
|
+
t5 = $[7];
|
|
106
|
+
}
|
|
107
|
+
let t6;
|
|
108
|
+
if ($[18] !== t1 || $[19] !== t2 || $[20] !== t3) {
|
|
109
|
+
t6 = /*#__PURE__*/_jsx("span", {
|
|
110
|
+
"aria-hidden": t1,
|
|
111
|
+
className: t2,
|
|
112
|
+
children: t3
|
|
113
|
+
});
|
|
114
|
+
$[18] = t1;
|
|
115
|
+
$[19] = t2;
|
|
116
|
+
$[20] = t3;
|
|
117
|
+
$[21] = t6;
|
|
118
|
+
} else {
|
|
119
|
+
t6 = $[21];
|
|
120
|
+
}
|
|
121
|
+
let t7;
|
|
122
|
+
if ($[22] !== now || $[23] !== window) {
|
|
123
|
+
t7 = paceText(window, now);
|
|
124
|
+
$[22] = now;
|
|
125
|
+
$[23] = window;
|
|
126
|
+
$[24] = t7;
|
|
127
|
+
} else {
|
|
128
|
+
t7 = $[24];
|
|
129
|
+
}
|
|
130
|
+
let t8;
|
|
131
|
+
if ($[25] !== t7) {
|
|
132
|
+
t8 = /*#__PURE__*/_jsx("span", {
|
|
133
|
+
className: "sr-only",
|
|
134
|
+
children: t7
|
|
135
|
+
});
|
|
136
|
+
$[25] = t7;
|
|
137
|
+
$[26] = t8;
|
|
138
|
+
} else {
|
|
139
|
+
t8 = $[26];
|
|
140
|
+
}
|
|
141
|
+
let t9;
|
|
142
|
+
if ($[27] !== now || $[28] !== resetsAt || $[29] !== t) {
|
|
143
|
+
t9 = resetsAt !== null && /*#__PURE__*/_jsxs("span", {
|
|
144
|
+
className: "whitespace-nowrap",
|
|
145
|
+
children: ["\xB7 ", t("pickers.account.resets", {
|
|
146
|
+
time: isSameDay(resetsAt, now) ? formatClock(resetsAt) : formatWeekdayClock(resetsAt)
|
|
147
|
+
})]
|
|
148
|
+
});
|
|
149
|
+
$[27] = now;
|
|
150
|
+
$[28] = resetsAt;
|
|
151
|
+
$[29] = t;
|
|
152
|
+
$[30] = t9;
|
|
153
|
+
} else {
|
|
154
|
+
t9 = $[30];
|
|
155
|
+
}
|
|
156
|
+
let t10;
|
|
157
|
+
if ($[31] !== t4 || $[32] !== t5 || $[33] !== t6 || $[34] !== t8 || $[35] !== t9) {
|
|
158
|
+
t10 = /*#__PURE__*/_jsxs("span", {
|
|
159
|
+
className: t4,
|
|
160
|
+
children: [t5, t6, t8, t9]
|
|
161
|
+
});
|
|
162
|
+
$[31] = t4;
|
|
163
|
+
$[32] = t5;
|
|
164
|
+
$[33] = t6;
|
|
165
|
+
$[34] = t8;
|
|
166
|
+
$[35] = t9;
|
|
167
|
+
$[36] = t10;
|
|
168
|
+
} else {
|
|
169
|
+
t10 = $[36];
|
|
170
|
+
}
|
|
171
|
+
return t10;
|
|
172
|
+
}
|
|
@@ -94,7 +94,10 @@ export default {
|
|
|
94
94
|
continueOn: 'Continue on',
|
|
95
95
|
continueOnAria: 'Continue on {{account}}',
|
|
96
96
|
continueOnTip: 'Switches to {{account}}, which has the most room left, and resends the turn.',
|
|
97
|
-
continueFailed: 'Could not continue on {{account}}'
|
|
97
|
+
continueFailed: 'Could not continue on {{account}}',
|
|
98
|
+
chooseAccount: 'Choose another account',
|
|
99
|
+
continueInForkAria: 'Continue on {{account}} in a fork',
|
|
100
|
+
inFork: 'in a fork'
|
|
98
101
|
},
|
|
99
102
|
contextMeter: {
|
|
100
103
|
aria: '{{percent}}% of context used',
|
|
@@ -94,7 +94,10 @@ export default {
|
|
|
94
94
|
continueOn: 'Verder op',
|
|
95
95
|
continueOnAria: 'Verder op {{account}}',
|
|
96
96
|
continueOnTip: 'Wisselt naar {{account}}, dat de meeste ruimte over heeft, en verstuurt de turn opnieuw.',
|
|
97
|
-
continueFailed: 'Kon niet verder op {{account}}'
|
|
97
|
+
continueFailed: 'Kon niet verder op {{account}}',
|
|
98
|
+
chooseAccount: 'Kies een ander account',
|
|
99
|
+
continueInForkAria: 'Verder op {{account}} in een fork',
|
|
100
|
+
inFork: 'in een fork'
|
|
98
101
|
},
|
|
99
102
|
contextMeter: {
|
|
100
103
|
aria: '{{percent}}% van de context gebruikt',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.2",
|
|
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.33.
|
|
71
|
-
"@adecore/editor": "0.33.
|
|
72
|
-
"@adecore/editor-react": "0.33.
|
|
73
|
-
"@adecore/intelligent-ui": "0.33.
|
|
74
|
-
"@adecore/merge": "0.33.
|
|
70
|
+
"@adecore/agent-contracts": "0.33.2",
|
|
71
|
+
"@adecore/editor": "0.33.2",
|
|
72
|
+
"@adecore/editor-react": "0.33.2",
|
|
73
|
+
"@adecore/intelligent-ui": "0.33.2",
|
|
74
|
+
"@adecore/merge": "0.33.2",
|
|
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.33.
|
|
97
|
+
"@adecore/ui": "^0.33.2",
|
|
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.33.
|
|
104
|
+
"@adecore/ui": "0.33.2",
|
|
105
105
|
"@types/bun": "^1.4.2",
|
|
106
106
|
"@types/react": "^19.3.0",
|
|
107
107
|
"@types/react-dom": "^19.3.0",
|
|
@@ -32,22 +32,32 @@ function others(accounts: ProviderAccounts | null, kind: AgentKind, current: str
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/*
|
|
35
|
-
* The
|
|
36
|
-
* signed in and
|
|
37
|
-
* none was read yet; the choice is only ever offered, never made.
|
|
35
|
+
* The accounts a chat that stopped on a limit could go on under: the others of its CLI that are on,
|
|
36
|
+
* signed in and have room, the one with the least of its session spent first. Empty when none has
|
|
37
|
+
* room, or none was read yet; the choice is only ever offered, never made.
|
|
38
38
|
*/
|
|
39
|
-
export function
|
|
39
|
+
export function continueTargets(
|
|
40
40
|
accounts: ProviderAccounts | null,
|
|
41
41
|
snapshot: UsageLimitsSnapshot | null,
|
|
42
42
|
kind: AgentKind,
|
|
43
43
|
current: string | undefined
|
|
44
|
-
): AccountEntry
|
|
44
|
+
): AccountEntry[] {
|
|
45
45
|
const roomy = others(accounts, kind, current).flatMap((entry) => {
|
|
46
46
|
const limits = limitsOfAccount(snapshot, entry.id);
|
|
47
47
|
return hasRoom(limits) ? [{ entry, pressure: pressure(limits) }] : [];
|
|
48
48
|
});
|
|
49
49
|
roomy.sort((a, b) => a.pressure - b.pressure);
|
|
50
|
-
return roomy
|
|
50
|
+
return roomy.map((target) => target.entry);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* The one `continueTargets` suggests: the account with the most room. */
|
|
54
|
+
export function continueTarget(
|
|
55
|
+
accounts: ProviderAccounts | null,
|
|
56
|
+
snapshot: UsageLimitsSnapshot | null,
|
|
57
|
+
kind: AgentKind,
|
|
58
|
+
current: string | undefined
|
|
59
|
+
): AccountEntry | null {
|
|
60
|
+
return continueTargets(accounts, snapshot, kind, current)[0] ?? null;
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
/* Whether another account a chat could go on under has no numbers yet, since the machine only reads one in use by itself. */
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
|
-
import
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { ChevronDown, Hourglass } from 'lucide-react';
|
|
4
5
|
import type { ChatInfo } from '@adecore/agent-contracts';
|
|
5
6
|
import { AccountDot } from '../../agents/AccountDot';
|
|
6
|
-
import {
|
|
7
|
+
import { continueTargets, hasUnreadAccount, limitsOfAccount, sessionWindow } from '../../agents/account-limits';
|
|
8
|
+
import { canContinueOn, type AccountEntry } from '../../agents/accounts';
|
|
7
9
|
import { useAccountChoice, type AccountChoice } from '../account-choice';
|
|
8
10
|
import { limitView } from '../logic/limit';
|
|
9
11
|
import { useFormatLocale } from '@adecore/ui/format';
|
|
10
|
-
import { Switch, Button, Icon, Pill, Tooltip, useNow } from '@adecore/ui';
|
|
12
|
+
import { Switch, Button, ButtonGroup, Icon, Menu, Pill, Tooltip, useNow } from '@adecore/ui';
|
|
11
13
|
import { useUsageLimits } from '../../usage/limits';
|
|
12
14
|
import { useChats } from '../../state/chats';
|
|
13
15
|
import { useChatHost } from '../../host';
|
|
14
16
|
import { useChatScope, type ChatScope } from '../../scope';
|
|
15
17
|
import { useChatActions } from '../actions';
|
|
16
18
|
import { useUsageStore } from '../../state/usage';
|
|
19
|
+
import { SessionLine } from './SessionLine';
|
|
17
20
|
import { useHostResumeAtReset } from '../../host-hooks';
|
|
18
21
|
|
|
19
22
|
// A time a limit names reads as a clock today and with its day after that, so the words follow the minute.
|
|
@@ -62,9 +65,11 @@ function ResumeAtResetToggle({ chatId, info }: { chatId: string; info: ChatInfo
|
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
/*
|
|
65
|
-
* Another account of the chat's CLI that has room: the one with the least of its session spent
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
+
* Another account of the chat's CLI that has room: the one with the least of its session spent, and
|
|
69
|
+
* beside it a menu of every account with room, for a person whose companies each pay for their own
|
|
70
|
+
* work. Only ever offered, since going on there spends someone's other plan, and nothing is
|
|
71
|
+
* remembered: the next limit suggests the one with the most room again. An account that reads the
|
|
72
|
+
* chat's conversation takes it over; any other goes on in a fork, which then opens the way a fork does.
|
|
68
73
|
*/
|
|
69
74
|
function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo; choice: AccountChoice }) {
|
|
70
75
|
const host = useChatHost();
|
|
@@ -73,7 +78,7 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
|
|
|
73
78
|
const actions = useChatActions();
|
|
74
79
|
const limits = useUsageLimits();
|
|
75
80
|
const [busy, setBusy] = useState(false);
|
|
76
|
-
const
|
|
81
|
+
const targets = continueTargets(choice.accounts, limits, info.provider, info.account);
|
|
77
82
|
const unread = limits !== null && hasUnreadAccount(choice.accounts, limits, info.provider, info.account);
|
|
78
83
|
|
|
79
84
|
useEffect(() => {
|
|
@@ -82,32 +87,34 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
|
|
|
82
87
|
}
|
|
83
88
|
}, [scope, unread]);
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
const target = targets[0];
|
|
91
|
+
if (target === undefined) {
|
|
86
92
|
return null;
|
|
87
93
|
}
|
|
88
|
-
const
|
|
89
|
-
const go = (): void => {
|
|
94
|
+
const go = (entry: AccountEntry): void => {
|
|
90
95
|
setBusy(true);
|
|
91
96
|
actions
|
|
92
|
-
.continueOn(chatId,
|
|
97
|
+
.continueOn(chatId, entry.id)
|
|
93
98
|
.catch((e: unknown) =>
|
|
94
99
|
host.notify({
|
|
95
100
|
kind: 'error',
|
|
96
|
-
title: t('limit.continueFailed', { account:
|
|
101
|
+
title: t('limit.continueFailed', { account: choice.nameOf(entry) }),
|
|
97
102
|
description: e instanceof Error ? e.message : String(e)
|
|
98
103
|
})
|
|
99
104
|
)
|
|
100
105
|
.finally(() => setBusy(false));
|
|
101
106
|
};
|
|
102
|
-
|
|
107
|
+
const name = choice.nameOf(target);
|
|
108
|
+
const split = targets.length > 1;
|
|
109
|
+
const suggestion = (
|
|
103
110
|
<Tooltip label={t('limit.continueOnTip', { account: name })}>
|
|
104
111
|
<Button
|
|
105
112
|
variant="secondary"
|
|
106
113
|
size="sm"
|
|
107
|
-
className=
|
|
114
|
+
className={clsx('shrink-0 gap-1.5 whitespace-nowrap', split && 'rounded-r-none')}
|
|
108
115
|
disabled={busy}
|
|
109
116
|
aria-label={t('limit.continueOnAria', { account: name })}
|
|
110
|
-
onClick={go}
|
|
117
|
+
onClick={() => go(target)}
|
|
111
118
|
>
|
|
112
119
|
{t('limit.continueOn')}
|
|
113
120
|
<AccountDot color={target.account.color} className="size-1.75" />
|
|
@@ -115,6 +122,66 @@ function ContinueOn({ chatId, info, choice }: { chatId: string; info: ChatInfo;
|
|
|
115
122
|
</Button>
|
|
116
123
|
</Tooltip>
|
|
117
124
|
);
|
|
125
|
+
if (!split) {
|
|
126
|
+
return suggestion;
|
|
127
|
+
}
|
|
128
|
+
return (
|
|
129
|
+
<ButtonGroup className="shrink-0">
|
|
130
|
+
{suggestion}
|
|
131
|
+
<Menu.Root>
|
|
132
|
+
<Tooltip label={t('limit.chooseAccount')} name>
|
|
133
|
+
<Menu.Trigger render={<Button variant="secondary" size="sm" className="-ml-px rounded-l-none border-l-0 px-1.5" disabled={busy} />}>
|
|
134
|
+
<Icon icon={ChevronDown} size={14} />
|
|
135
|
+
</Menu.Trigger>
|
|
136
|
+
</Tooltip>
|
|
137
|
+
<Menu.Popup align="end" className="min-w-64">
|
|
138
|
+
<Menu.Group>
|
|
139
|
+
<Menu.GroupLabel>{t('limit.continueOn')}</Menu.GroupLabel>
|
|
140
|
+
<ContinueOnRows targets={targets} info={info} choice={choice} onPick={go} />
|
|
141
|
+
</Menu.Group>
|
|
142
|
+
</Menu.Popup>
|
|
143
|
+
</Menu.Root>
|
|
144
|
+
</ButtonGroup>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Mounted only while the menu is open, so the clock of the session lines only ticks while someone reads them. */
|
|
149
|
+
function ContinueOnRows({
|
|
150
|
+
targets,
|
|
151
|
+
info,
|
|
152
|
+
choice,
|
|
153
|
+
onPick
|
|
154
|
+
}: {
|
|
155
|
+
targets: AccountEntry[];
|
|
156
|
+
info: ChatInfo;
|
|
157
|
+
choice: AccountChoice;
|
|
158
|
+
onPick(entry: AccountEntry): void;
|
|
159
|
+
}) {
|
|
160
|
+
const { t } = useTranslation('agent-chat');
|
|
161
|
+
const limits = useUsageLimits();
|
|
162
|
+
const now = useNow(MINUTE_MS);
|
|
163
|
+
return targets.map((entry) => {
|
|
164
|
+
const name = choice.nameOf(entry);
|
|
165
|
+
const session = sessionWindow(limitsOfAccount(limits, entry.id));
|
|
166
|
+
const fork = !canContinueOn(choice.accounts, info.provider, info.account, entry.id);
|
|
167
|
+
return (
|
|
168
|
+
<Menu.Item
|
|
169
|
+
key={entry.id}
|
|
170
|
+
className="items-start"
|
|
171
|
+
aria-label={t(fork ? 'limit.continueInForkAria' : 'limit.continueOnAria', { account: name })}
|
|
172
|
+
onClick={() => onPick(entry)}
|
|
173
|
+
>
|
|
174
|
+
<span className="flex min-w-0 grow flex-col">
|
|
175
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
176
|
+
<AccountDot color={entry.account.color} />
|
|
177
|
+
<span className="truncate">{name}</span>
|
|
178
|
+
{fork && <Menu.Hint>{t('limit.inFork')}</Menu.Hint>}
|
|
179
|
+
</span>
|
|
180
|
+
{session !== null && <SessionLine window={session} now={now} />}
|
|
181
|
+
</span>
|
|
182
|
+
</Menu.Item>
|
|
183
|
+
);
|
|
184
|
+
});
|
|
118
185
|
}
|
|
119
186
|
|
|
120
187
|
/* At the top of the composer while the last turn stopped on a limit: what stopped it, when it goes on, and the switch. */
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
Star,
|
|
19
19
|
type LucideIcon
|
|
20
20
|
} from 'lucide-react';
|
|
21
|
-
import type { AgentKind, ChatUsage, ModelInfo, ModelOptionDescriptor, ModelSelection, ProviderInfo, RuntimeMode
|
|
21
|
+
import type { AgentKind, ChatUsage, ModelInfo, ModelOptionDescriptor, ModelSelection, ProviderInfo, RuntimeMode } from '@adecore/agent-contracts';
|
|
22
22
|
import { AccountDot } from '../../agents/AccountDot';
|
|
23
23
|
import { limitsOfAccount, sessionWindow } from '../../agents/account-limits';
|
|
24
24
|
import { canContinueOn } from '../../agents/accounts';
|
|
@@ -26,15 +26,15 @@ import { AgentIcon } from '../../agents/AgentIcon';
|
|
|
26
26
|
import type { AccountChoice } from '../account-choice';
|
|
27
27
|
import { modelName, shortModelName } from '../../agents/model-name';
|
|
28
28
|
import { ModelMarks } from './ModelMarks';
|
|
29
|
+
import { SessionLine } from './SessionLine';
|
|
29
30
|
import { favoritesFirst, searchModels, shortlistOf, wantsModelSearch } from '../logic/picker-entries';
|
|
30
31
|
import { favoriteModelsOf, toggleFavoriteModel, useChatPreferences } from '../preferences';
|
|
31
32
|
import { CONTEXT_OPTION, contextFraction, contextSegments, orderOptions, type ContextPart } from '../logic/context-usage';
|
|
32
33
|
import { RUNTIME_MODES } from '../runtime-modes';
|
|
33
34
|
import { localizedOption } from '../../agents/option-words';
|
|
34
|
-
import {
|
|
35
|
+
import { formatPercent, formatTokens } from '@adecore/ui/format';
|
|
35
36
|
import { Icon, Menu, Tooltip, useNow } from '@adecore/ui';
|
|
36
37
|
import { useChatHost } from '../../host';
|
|
37
|
-
import { evenPace, paceText } from '../../usage/limit-groups';
|
|
38
38
|
import { useUsageLimits } from '../../usage/limits';
|
|
39
39
|
|
|
40
40
|
const MINUTE_MS = 60_000;
|
|
@@ -453,15 +453,6 @@ export function RunSettings({
|
|
|
453
453
|
);
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
-
/* Red where a window is nearly spent, amber where it is worth knowing, as the usage bars have it. */
|
|
457
|
-
function sessionTone(used: number): { bar: string; text: string } {
|
|
458
|
-
return used >= 0.9
|
|
459
|
-
? { bar: 'bg-status-error', text: 'text-status-error' }
|
|
460
|
-
: used >= 0.7
|
|
461
|
-
? { bar: 'bg-status-needs-you', text: 'text-status-needs-you' }
|
|
462
|
-
: { bar: 'bg-text-muted', text: 'text-text-muted' };
|
|
463
|
-
}
|
|
464
|
-
|
|
465
456
|
/*
|
|
466
457
|
* The account the next turn runs under, for the chat's own CLI. Before the first turn any of them;
|
|
467
458
|
* after it one that reads the conversation, since the others would start it over, and a fork is how
|
|
@@ -529,37 +520,6 @@ function AccountRows({ choice, locked }: { choice: AccountChoice; locked(id: str
|
|
|
529
520
|
});
|
|
530
521
|
}
|
|
531
522
|
|
|
532
|
-
function SessionLine({ window, now }: { window: UsageWindow; now: number }) {
|
|
533
|
-
const { t } = useTranslation('agent-chat');
|
|
534
|
-
const percent = Math.round(window.used * 100);
|
|
535
|
-
const tone = sessionTone(window.used);
|
|
536
|
-
const mark = evenPace(window, now)?.percent ?? null;
|
|
537
|
-
const resetsAt = window.resetsAt;
|
|
538
|
-
return (
|
|
539
|
-
<span className="flex items-center gap-2 text-xs text-text-faint tabular-nums">
|
|
540
|
-
<span aria-hidden className="relative h-1 w-14 shrink-0 rounded-full bg-surface-sunken">
|
|
541
|
-
<span className={clsx('absolute inset-y-0 left-0 rounded-full', tone.bar)} style={{ width: `${Math.min(100, percent)}%` }} />
|
|
542
|
-
{/* Where an even spread over the window would stand, as the usage bars mark it. */}
|
|
543
|
-
{mark !== null && (
|
|
544
|
-
<span
|
|
545
|
-
className={clsx('absolute -inset-y-0.5 w-px -translate-x-1/2', mark <= percent ? 'bg-surface-raised' : 'bg-text-muted')}
|
|
546
|
-
style={{ left: `${mark}%` }}
|
|
547
|
-
/>
|
|
548
|
-
)}
|
|
549
|
-
</span>
|
|
550
|
-
<span aria-hidden className={tone.text}>
|
|
551
|
-
{formatPercent(percent)}
|
|
552
|
-
</span>
|
|
553
|
-
<span className="sr-only">{paceText(window, now)}</span>
|
|
554
|
-
{resetsAt !== null && (
|
|
555
|
-
<span className="whitespace-nowrap">
|
|
556
|
-
· {t('pickers.account.resets', { time: isSameDay(resetsAt, now) ? formatClock(resetsAt) : formatWeekdayClock(resetsAt) })}
|
|
557
|
-
</span>
|
|
558
|
-
)}
|
|
559
|
-
</span>
|
|
560
|
-
);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
523
|
/* How full the context is, drawn small enough to sit in the pill beside the window's size. */
|
|
564
524
|
function ContextRing({ usage }: { usage: ChatUsage }) {
|
|
565
525
|
const fraction = contextFraction(usage);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import type { UsageWindow } from '@adecore/agent-contracts';
|
|
4
|
+
import { formatClock, formatPercent, formatWeekdayClock, isSameDay } from '@adecore/ui/format';
|
|
5
|
+
import { evenPace, paceText } from '../../usage/limit-groups';
|
|
6
|
+
|
|
7
|
+
/* Red where a window is nearly spent, amber where it is worth knowing, as the usage bars have it. */
|
|
8
|
+
function sessionTone(used: number): { bar: string; text: string } {
|
|
9
|
+
return used >= 0.9
|
|
10
|
+
? { bar: 'bg-status-error', text: 'text-status-error' }
|
|
11
|
+
: used >= 0.7
|
|
12
|
+
? { bar: 'bg-status-needs-you', text: 'text-status-needs-you' }
|
|
13
|
+
: { bar: 'bg-text-muted', text: 'text-text-muted' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* How much of an account's session is spent and when it resets, on the second line of a row that names the account. */
|
|
17
|
+
export function SessionLine({ window, now }: { window: UsageWindow; now: number }) {
|
|
18
|
+
const { t } = useTranslation('agent-chat');
|
|
19
|
+
const percent = Math.round(window.used * 100);
|
|
20
|
+
const tone = sessionTone(window.used);
|
|
21
|
+
const mark = evenPace(window, now)?.percent ?? null;
|
|
22
|
+
const resetsAt = window.resetsAt;
|
|
23
|
+
return (
|
|
24
|
+
<span className="flex items-center gap-2 text-xs text-text-faint tabular-nums">
|
|
25
|
+
<span aria-hidden className="relative h-1 w-14 shrink-0 rounded-full bg-surface-sunken">
|
|
26
|
+
<span className={clsx('absolute inset-y-0 left-0 rounded-full', tone.bar)} style={{ width: `${Math.min(100, percent)}%` }} />
|
|
27
|
+
{/* Where an even spread over the window would stand, as the usage bars mark it. */}
|
|
28
|
+
{mark !== null && (
|
|
29
|
+
<span
|
|
30
|
+
className={clsx('absolute -inset-y-0.5 w-px -translate-x-1/2', mark <= percent ? 'bg-surface-raised' : 'bg-text-muted')}
|
|
31
|
+
style={{ left: `${mark}%` }}
|
|
32
|
+
/>
|
|
33
|
+
)}
|
|
34
|
+
</span>
|
|
35
|
+
<span aria-hidden className={tone.text}>
|
|
36
|
+
{formatPercent(percent)}
|
|
37
|
+
</span>
|
|
38
|
+
<span className="sr-only">{paceText(window, now)}</span>
|
|
39
|
+
{resetsAt !== null && (
|
|
40
|
+
<span className="whitespace-nowrap">
|
|
41
|
+
· {t('pickers.account.resets', { time: isSameDay(resetsAt, now) ? formatClock(resetsAt) : formatWeekdayClock(resetsAt) })}
|
|
42
|
+
</span>
|
|
43
|
+
)}
|
|
44
|
+
</span>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -94,7 +94,10 @@ export default {
|
|
|
94
94
|
continueOn: 'Continue on',
|
|
95
95
|
continueOnAria: 'Continue on {{account}}',
|
|
96
96
|
continueOnTip: 'Switches to {{account}}, which has the most room left, and resends the turn.',
|
|
97
|
-
continueFailed: 'Could not continue on {{account}}'
|
|
97
|
+
continueFailed: 'Could not continue on {{account}}',
|
|
98
|
+
chooseAccount: 'Choose another account',
|
|
99
|
+
continueInForkAria: 'Continue on {{account}} in a fork',
|
|
100
|
+
inFork: 'in a fork'
|
|
98
101
|
},
|
|
99
102
|
contextMeter: {
|
|
100
103
|
aria: '{{percent}}% of context used',
|
|
@@ -94,7 +94,10 @@ export default {
|
|
|
94
94
|
continueOn: 'Verder op',
|
|
95
95
|
continueOnAria: 'Verder op {{account}}',
|
|
96
96
|
continueOnTip: 'Wisselt naar {{account}}, dat de meeste ruimte over heeft, en verstuurt de turn opnieuw.',
|
|
97
|
-
continueFailed: 'Kon niet verder op {{account}}'
|
|
97
|
+
continueFailed: 'Kon niet verder op {{account}}',
|
|
98
|
+
chooseAccount: 'Kies een ander account',
|
|
99
|
+
continueInForkAria: 'Verder op {{account}} in een fork',
|
|
100
|
+
inFork: 'in een fork'
|
|
98
101
|
},
|
|
99
102
|
contextMeter: {
|
|
100
103
|
aria: '{{percent}}% van de context gebruikt',
|