@adecore/agents-react 0.40.2 → 0.40.4
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/chat/chat-client.js +2 -1
- package/dist/chat/logic/scrubber.d.ts +4 -3
- package/dist/chat/logic/scrubber.js +52 -3
- package/dist/chat/ui/Timeline.js +26 -24
- package/dist/chat/ui/composer/ComposerControls.d.ts +1 -1
- package/dist/chat/ui/composer/ComposerControls.js +157 -148
- package/dist/chat/ui/rows/row-rhythm.js +1 -1
- package/dist/state/chats.d.ts +2 -0
- package/dist/state/chats.js +6 -2
- package/package.json +8 -8
- package/src/chat/chat-client.ts +3 -1
- package/src/chat/logic/scrubber.ts +44 -6
- package/src/chat/ui/Timeline.tsx +7 -4
- package/src/chat/ui/composer/ComposerControls.tsx +2 -3
- package/src/chat/ui/rows/row-rhythm.ts +1 -1
- package/src/state/chats.ts +12 -3
package/dist/chat/chat-client.js
CHANGED
|
@@ -398,7 +398,8 @@ export class ChatClient {
|
|
|
398
398
|
const page = result.history === undefined ? undefined : {
|
|
399
399
|
history: result.history,
|
|
400
400
|
pending: result.pending ?? [],
|
|
401
|
-
subagents: result.subagents ?? []
|
|
401
|
+
subagents: result.subagents ?? [],
|
|
402
|
+
outline: result.outline ?? []
|
|
402
403
|
};
|
|
403
404
|
this.sink.reset(chatId, result.info, result.items, page);
|
|
404
405
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChatBookmark } from '@adecore/agent-contracts';
|
|
2
|
-
import type
|
|
1
|
+
import type { ChatBookmark, ChatItem } from '@adecore/agent-contracts';
|
|
2
|
+
import { type TimelineRow } from './timeline.js';
|
|
3
3
|
export type TickKind = 'person' | 'wake' | 'bookmark';
|
|
4
4
|
export interface ScrubberTick {
|
|
5
5
|
id: string;
|
|
@@ -15,7 +15,8 @@ export declare const TICK_HEIGHT_PX = 2;
|
|
|
15
15
|
export declare const STRIP_WIDTH_PX = 24;
|
|
16
16
|
export declare const STRIP_INSET_PX = 8;
|
|
17
17
|
export declare const STRIP_CLEARANCE_PX: number;
|
|
18
|
-
export declare
|
|
18
|
+
export declare const EARLIER_ROW = -1;
|
|
19
|
+
export declare function ticksOf(rows: readonly TimelineRow[], marks?: ReadonlyMap<string, ChatBookmark>, earlier?: readonly ChatItem[], bookmarks?: readonly ChatBookmark[]): ScrubberTick[];
|
|
19
20
|
export interface TickSlot {
|
|
20
21
|
first: number;
|
|
21
22
|
last: number;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentTurnLabel } from './timeline.js';
|
|
1
2
|
import { DEFAULT_READING_WIDTH, readingMeasure } from '../reading-width.js';
|
|
2
3
|
/* With fewer messages than this the thread is short enough to scroll through, and the strip is noise. */
|
|
3
4
|
export const SCRUBBER_MIN_TICKS = 3;
|
|
@@ -15,9 +16,56 @@ const WIDTH_NEAR_PERSON_PX = 12;
|
|
|
15
16
|
const WIDTH_NEAR_WAKE_PX = 8;
|
|
16
17
|
const WIDTH_PERSON_PX = 8;
|
|
17
18
|
const WIDTH_WAKE_PX = 5;
|
|
18
|
-
/*
|
|
19
|
-
export
|
|
20
|
-
|
|
19
|
+
/* Below every row, so a tick before the rows this client holds sorts first and its stretch reaches the first row of the thread. */
|
|
20
|
+
export const EARLIER_ROW = -1;
|
|
21
|
+
/* A message before the rows this client holds, as the outline (`outlineItems`) has it; its row id is the one the thread will draw it with. */
|
|
22
|
+
function earlierTick(item, bookmarks) {
|
|
23
|
+
const bookmark = bookmarks.get(item.id) ?? null;
|
|
24
|
+
const rowIndex = EARLIER_ROW;
|
|
25
|
+
if (item.kind === 'user') {
|
|
26
|
+
const text = item.text !== '' ? item.text : (item.attachments ?? []).map(attachment => attachment.name).join(', ');
|
|
27
|
+
return {
|
|
28
|
+
id: item.id,
|
|
29
|
+
rowIndex,
|
|
30
|
+
kind: 'person',
|
|
31
|
+
text,
|
|
32
|
+
createdAt: item.createdAt,
|
|
33
|
+
turnId: item.turnId,
|
|
34
|
+
bookmark
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (item.kind === 'turn' && ((item.taskIds?.length ?? 0) > 0 || (item.messageFrom?.length ?? 0) > 0)) {
|
|
38
|
+
return {
|
|
39
|
+
id: `start-${item.id}`,
|
|
40
|
+
rowIndex,
|
|
41
|
+
kind: 'wake',
|
|
42
|
+
text: agentTurnLabel(item),
|
|
43
|
+
createdAt: item.createdAt,
|
|
44
|
+
turnId: item.id,
|
|
45
|
+
bookmark: null
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (item.kind === 'assistant' && bookmark !== null) {
|
|
49
|
+
return {
|
|
50
|
+
id: item.id,
|
|
51
|
+
rowIndex,
|
|
52
|
+
kind: 'bookmark',
|
|
53
|
+
text: item.text,
|
|
54
|
+
createdAt: item.createdAt,
|
|
55
|
+
turnId: item.turnId,
|
|
56
|
+
bookmark
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/*
|
|
62
|
+
* `marks` is what `bookmarkRows` answers: the bookmark drawn at a row, by row id. `earlier` is the
|
|
63
|
+
* outline of the thread before `rows`, and `bookmarks` the chat's, which mark the ticks of that part.
|
|
64
|
+
*/
|
|
65
|
+
export function ticksOf(rows, marks = new Map(), earlier = [], bookmarks = []) {
|
|
66
|
+
const byItem = new Map(bookmarks.map(bookmark => [bookmark.itemId, bookmark]));
|
|
67
|
+
const before = earlier.flatMap(item => earlierTick(item, byItem) ?? []);
|
|
68
|
+
const held = rows.flatMap((row, rowIndex) => {
|
|
21
69
|
const bookmark = marks.get(row.id) ?? null;
|
|
22
70
|
if (row.kind === 'user') {
|
|
23
71
|
// A message of only attachments still has a place in the thread; its names stand in for the text.
|
|
@@ -70,6 +118,7 @@ export function ticksOf(rows, marks = new Map()) {
|
|
|
70
118
|
bookmark
|
|
71
119
|
}];
|
|
72
120
|
});
|
|
121
|
+
return before.length === 0 ? held : [...before, ...held];
|
|
73
122
|
}
|
|
74
123
|
/* `marked` and `found` say per message whether it has a bookmark and whether a find hit falls under it; absent, none has. */
|
|
75
124
|
export function layoutTicks(kinds, height, marked = [], found = []) {
|
package/dist/chat/ui/Timeline.js
CHANGED
|
@@ -13,7 +13,7 @@ import { registerItemJumper, registerMessageStepper, registerTimeline, registerT
|
|
|
13
13
|
import { useTimelineFlash } from '../timeline-flash.js';
|
|
14
14
|
import { useChatPreferences } from '../preferences.js';
|
|
15
15
|
import { readingMeasure, readingWidthStyle } from '../reading-width.js';
|
|
16
|
-
import { SCRUBBER_MIN_TICKS, STRIP_INSET_PX, STRIP_WIDTH_PX, messagesInView, stepMessage, threadPaddingLeft, STRIP_CLEARANCE_PX, tickOfRow, ticksOf, ticksWithHits } from '../logic/scrubber.js';
|
|
16
|
+
import { SCRUBBER_MIN_TICKS, STRIP_INSET_PX, STRIP_WIDTH_PX, messagesInView, stepMessage, threadPaddingLeft, STRIP_CLEARANCE_PX, tickOfRow, ticksOf, ticksWithHits, EARLIER_ROW } from '../logic/scrubber.js';
|
|
17
17
|
import { EMPTY_TARGET, readTimelineTarget, revealUiReplyBlock, uiReplyJump, withCurrentText } from '../logic/timeline-target.js';
|
|
18
18
|
import { forkRefusal } from '../logic/fork.js';
|
|
19
19
|
import { UiReplyNavigationContext } from './reply-context.js';
|
|
@@ -410,11 +410,12 @@ export function Timeline({
|
|
|
410
410
|
const items = useChatRow(chatId, row_0 => row_0?.structure);
|
|
411
411
|
const activeTurnId = useChatRow(chatId, row_1 => row_1?.info.activeTurnId ?? null);
|
|
412
412
|
const bookmarks = useChatRow(chatId, row_2 => row_2?.bookmarks);
|
|
413
|
-
const
|
|
413
|
+
const outlineBefore = useChatRow(chatId, row_3 => row_3?.outlineBefore);
|
|
414
|
+
const visuals = useChatRow(chatId, row_4 => row_4?.visuals);
|
|
414
415
|
const forkedTurns = useForkedTurns(chatId);
|
|
415
|
-
const info = useChatRow(chatId,
|
|
416
|
-
const historyPending = useChatRow(chatId,
|
|
417
|
-
const cursor = useChatRow(chatId,
|
|
416
|
+
const info = useChatRow(chatId, row_5 => row_5?.info ?? null);
|
|
417
|
+
const historyPending = useChatRow(chatId, row_6 => row_6?.historyPending === true);
|
|
418
|
+
const cursor = useChatRow(chatId, row_7 => row_7?.history?.cursor ?? null);
|
|
418
419
|
const scope = useChatScope();
|
|
419
420
|
const {
|
|
420
421
|
id: scopeId,
|
|
@@ -499,7 +500,7 @@ export function Timeline({
|
|
|
499
500
|
// A chat that took a turn before is one whose thread has not come in yet, not one to greet.
|
|
500
501
|
const welcome = !loadingHistory && welcomes && empty && (info === null || info.usage.turns === 0 && info.activeTurnId === null);
|
|
501
502
|
const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
|
|
502
|
-
const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
|
|
503
|
+
const ticks = useMemo(() => ticksOf(rows, marks, outlineBefore, bookmarks), [rows, marks, outlineBefore, bookmarks]);
|
|
503
504
|
// A message a jump is on its way to; it may first have to open the turn its reply folded into.
|
|
504
505
|
const [seeking, setSeeking] = useState(null);
|
|
505
506
|
const [landing, setLanding] = useState(null);
|
|
@@ -601,7 +602,7 @@ export function Timeline({
|
|
|
601
602
|
}
|
|
602
603
|
// Ref measurements can change offsets after render. Commit those offsets before the first paint.
|
|
603
604
|
const measured = virtualizer.getVirtualItems();
|
|
604
|
-
if (virtualRows.length !== measured.length || virtualRows.some((
|
|
605
|
+
if (virtualRows.length !== measured.length || virtualRows.some((row_8, index_1) => row_8.key !== measured[index_1]?.key || row_8.start !== measured[index_1]?.start || row_8.size !== measured[index_1]?.size)) {
|
|
605
606
|
settleLayout();
|
|
606
607
|
return;
|
|
607
608
|
}
|
|
@@ -640,16 +641,16 @@ export function Timeline({
|
|
|
640
641
|
return;
|
|
641
642
|
}
|
|
642
643
|
const anchor = anchorRef.current;
|
|
643
|
-
const index_2 = moved && anchor !== null && !followRef.current ? rows.findIndex(
|
|
644
|
+
const index_2 = moved && anchor !== null && !followRef.current ? rows.findIndex(row_9 => row_9.id === anchor.id) : -1;
|
|
644
645
|
if (anchor !== null && index_2 !== -1) {
|
|
645
646
|
virtualizer.getTotalSize();
|
|
646
647
|
element_3.scrollTop = virtualizer.measurementsCache[index_2].start - anchor.offset;
|
|
647
648
|
return;
|
|
648
649
|
}
|
|
649
|
-
const
|
|
650
|
-
anchorRef.current =
|
|
651
|
-
id: String(
|
|
652
|
-
offset:
|
|
650
|
+
const row_10 = virtualizer.getVirtualItemForOffset(element_3.scrollTop);
|
|
651
|
+
anchorRef.current = row_10 === undefined ? null : {
|
|
652
|
+
id: String(row_10.key),
|
|
653
|
+
offset: row_10.start - element_3.scrollTop
|
|
653
654
|
};
|
|
654
655
|
});
|
|
655
656
|
// Close to the top, the page before goes in; again after each page that left too little above the reader.
|
|
@@ -759,10 +760,11 @@ export function Timeline({
|
|
|
759
760
|
if (!tick_0) {
|
|
760
761
|
return;
|
|
761
762
|
}
|
|
762
|
-
|
|
763
|
+
// A message before the rows this client holds comes in page by page until its row is there.
|
|
764
|
+
if (tick_0.bookmark !== null || tick_0.rowIndex === EARLIER_ROW) {
|
|
763
765
|
setLanding(null);
|
|
764
766
|
setSeeking({
|
|
765
|
-
itemId: tick_0.bookmark.
|
|
767
|
+
itemId: tick_0.bookmark?.itemId ?? tick_0.id,
|
|
766
768
|
key: keyOf(chatId)
|
|
767
769
|
});
|
|
768
770
|
return;
|
|
@@ -798,7 +800,7 @@ export function Timeline({
|
|
|
798
800
|
}
|
|
799
801
|
return;
|
|
800
802
|
}
|
|
801
|
-
const index_6 = rows.findIndex(
|
|
803
|
+
const index_6 = rows.findIndex(row_11 => row_11.id === seeking.itemId);
|
|
802
804
|
if (index_6 !== -1) {
|
|
803
805
|
setSeeking(null);
|
|
804
806
|
showRowAtTop(virtualizer, followRef, index_6);
|
|
@@ -872,9 +874,9 @@ export function Timeline({
|
|
|
872
874
|
turnId: turnId_0,
|
|
873
875
|
target: target_0
|
|
874
876
|
} = revealing;
|
|
875
|
-
const changes = target_0 === 'changes' ? rows.findIndex(
|
|
877
|
+
const changes = target_0 === 'changes' ? rows.findIndex(row_12 => row_12.kind === 'changed-files' && row_12.turnId === turnId_0) : -1;
|
|
876
878
|
const opening = order?.map(id_0 => items?.[id_0]).find(item_0 => item_0?.kind === 'user' && item_0.turnId === turnId_0);
|
|
877
|
-
const found_0 = changes !== -1 ? changes : rows.findIndex(
|
|
879
|
+
const found_0 = changes !== -1 ? changes : rows.findIndex(row_13 => row_13.id === opening?.id || row_13.id === `start-${turnId_0}` || row_13.id === `fold-${turnId_0}`);
|
|
878
880
|
if (found_0 !== -1) {
|
|
879
881
|
setRevealing(null);
|
|
880
882
|
showRowAtTop(virtualizer, followRef, found_0);
|
|
@@ -903,8 +905,8 @@ export function Timeline({
|
|
|
903
905
|
if (fork === null) {
|
|
904
906
|
return false;
|
|
905
907
|
}
|
|
906
|
-
const
|
|
907
|
-
return forkRefusal(
|
|
908
|
+
const row_14 = useChats.getState().byKey[keyOf(chatId)];
|
|
909
|
+
return forkRefusal(row_14?.info ?? null, row_14?.structure[turnId_2]) === null;
|
|
908
910
|
},
|
|
909
911
|
fork: turnId_3 => fork?.(chatId, turnId_3)
|
|
910
912
|
}), [keyOf, chatId, fork]);
|
|
@@ -1028,22 +1030,22 @@ export function Timeline({
|
|
|
1028
1030
|
children: /*#__PURE__*/_jsx(FindRevealContext.Provider, {
|
|
1029
1031
|
value: chatFind.reveal,
|
|
1030
1032
|
children: virtualRows.map(virtualRow_1 => {
|
|
1031
|
-
const
|
|
1033
|
+
const row_15 = rows[virtualRow_1.index];
|
|
1032
1034
|
return /*#__PURE__*/_jsx(ThreadRow, {
|
|
1033
|
-
row:
|
|
1035
|
+
row: row_15,
|
|
1034
1036
|
previous: virtualRow_1.index > 0 ? rows[virtualRow_1.index - 1] : null,
|
|
1035
1037
|
index: virtualRow_1.index,
|
|
1036
1038
|
top: virtualRow_1.start,
|
|
1037
1039
|
measure: virtualizer.measureElement,
|
|
1038
1040
|
chatId: chatId,
|
|
1039
|
-
flashNonce: flash !== null && flashedRow ===
|
|
1040
|
-
bookmark: marks.get(
|
|
1041
|
+
flashNonce: flash !== null && flashedRow === row_15.id ? flash.nonce : null,
|
|
1042
|
+
bookmark: marks.get(row_15.id) ?? null,
|
|
1041
1043
|
toggleGroup: groups.toggle,
|
|
1042
1044
|
toggleTurn: turns.toggle,
|
|
1043
1045
|
toggleSubagent: subagents.toggle,
|
|
1044
1046
|
openSubagent: openSubagent,
|
|
1045
1047
|
openConversation: openConversation
|
|
1046
|
-
},
|
|
1048
|
+
}, row_15.id);
|
|
1047
1049
|
})
|
|
1048
1050
|
})
|
|
1049
1051
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Ref } from 'react';
|
|
2
|
-
import type
|
|
2
|
+
import { type ChatInfo } from '@adecore/agent-contracts';
|
|
3
3
|
import { type Shortcut } from '@adecore/ui';
|
|
4
4
|
import { type ComposerSendAction, type ComposerSendDraft } from '../../../host.js';
|
|
5
5
|
import type { StashedPrompt } from '../../stash.js';
|
|
@@ -3,6 +3,7 @@ import { useRef, useState } from 'react';
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { ArrowUp, ChevronDown, Play, Plus, Square } from 'lucide-react';
|
|
6
|
+
import { holdsConversation } from '@adecore/agent-contracts';
|
|
6
7
|
import { Button, ErrorBoundary, Icon, Menu, Tooltip } from '@adecore/ui';
|
|
7
8
|
import { useChatHost } from '../../../host.js';
|
|
8
9
|
import { useChatScope } from '../../../scope.js';
|
|
@@ -219,7 +220,7 @@ function SendButton(t0) {
|
|
|
219
220
|
}
|
|
220
221
|
/* The row under the box: attach, the chat's settings, the stash, the app's own control, and stop and send. */
|
|
221
222
|
export function ComposerControls(t0) {
|
|
222
|
-
const $ = _c(
|
|
223
|
+
const $ = _c(78);
|
|
223
224
|
const {
|
|
224
225
|
chatId,
|
|
225
226
|
info,
|
|
@@ -253,22 +254,30 @@ export function ComposerControls(t0) {
|
|
|
253
254
|
t1 = $[3];
|
|
254
255
|
}
|
|
255
256
|
const model = t1;
|
|
256
|
-
const started = info.agentSessionId !== null || info.usage.turns > 0;
|
|
257
257
|
let t2;
|
|
258
|
-
if ($[4] !== info
|
|
259
|
-
t2 =
|
|
260
|
-
$[4] = info
|
|
261
|
-
$[5] =
|
|
262
|
-
$[6] = providers;
|
|
263
|
-
$[7] = started;
|
|
264
|
-
$[8] = t2;
|
|
258
|
+
if ($[4] !== info) {
|
|
259
|
+
t2 = holdsConversation(info);
|
|
260
|
+
$[4] = info;
|
|
261
|
+
$[5] = t2;
|
|
265
262
|
} else {
|
|
266
|
-
t2 = $[
|
|
263
|
+
t2 = $[5];
|
|
267
264
|
}
|
|
268
|
-
const
|
|
265
|
+
const started = t2;
|
|
269
266
|
let t3;
|
|
270
|
-
if ($[
|
|
271
|
-
t3 =
|
|
267
|
+
if ($[6] !== info.provider || $[7] !== providerFixed || $[8] !== providers || $[9] !== started) {
|
|
268
|
+
t3 = started || providerFixed ? providers.filter(entry_1 => entry_1.kind === info.provider) : providers.filter(_temp2);
|
|
269
|
+
$[6] = info.provider;
|
|
270
|
+
$[7] = providerFixed;
|
|
271
|
+
$[8] = providers;
|
|
272
|
+
$[9] = started;
|
|
273
|
+
$[10] = t3;
|
|
274
|
+
} else {
|
|
275
|
+
t3 = $[10];
|
|
276
|
+
}
|
|
277
|
+
const pickable = t3;
|
|
278
|
+
let t4;
|
|
279
|
+
if ($[11] !== attachable || $[12] !== t || $[13] !== writable) {
|
|
280
|
+
t4 = attachable && /*#__PURE__*/_jsx(Tooltip, {
|
|
272
281
|
label: t("composer.attach"),
|
|
273
282
|
name: true,
|
|
274
283
|
children: /*#__PURE__*/_jsx("button", {
|
|
@@ -282,16 +291,16 @@ export function ComposerControls(t0) {
|
|
|
282
291
|
})
|
|
283
292
|
})
|
|
284
293
|
});
|
|
285
|
-
$[
|
|
286
|
-
$[
|
|
287
|
-
$[
|
|
288
|
-
$[
|
|
294
|
+
$[11] = attachable;
|
|
295
|
+
$[12] = t;
|
|
296
|
+
$[13] = writable;
|
|
297
|
+
$[14] = t4;
|
|
289
298
|
} else {
|
|
290
|
-
|
|
299
|
+
t4 = $[14];
|
|
291
300
|
}
|
|
292
|
-
let
|
|
293
|
-
if ($[
|
|
294
|
-
|
|
301
|
+
let t5;
|
|
302
|
+
if ($[15] !== onFiles) {
|
|
303
|
+
t5 = /*#__PURE__*/_jsx("input", {
|
|
295
304
|
ref: fileInputRef,
|
|
296
305
|
type: "file",
|
|
297
306
|
multiple: true,
|
|
@@ -301,15 +310,15 @@ export function ComposerControls(t0) {
|
|
|
301
310
|
e.target.value = "";
|
|
302
311
|
}
|
|
303
312
|
});
|
|
304
|
-
$[
|
|
305
|
-
$[
|
|
313
|
+
$[15] = onFiles;
|
|
314
|
+
$[16] = t5;
|
|
306
315
|
} else {
|
|
307
|
-
|
|
316
|
+
t5 = $[16];
|
|
308
317
|
}
|
|
309
|
-
const
|
|
310
|
-
let
|
|
311
|
-
if ($[
|
|
312
|
-
|
|
318
|
+
const t6 = controls.configurationPending || !writable;
|
|
319
|
+
let t7;
|
|
320
|
+
if ($[17] !== controls || $[18] !== info.selection) {
|
|
321
|
+
t7 = (id, value) => controls.configure({
|
|
313
322
|
selection: {
|
|
314
323
|
...info.selection,
|
|
315
324
|
options: {
|
|
@@ -318,41 +327,41 @@ export function ComposerControls(t0) {
|
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
329
|
});
|
|
321
|
-
$[
|
|
322
|
-
$[
|
|
323
|
-
$[
|
|
330
|
+
$[17] = controls;
|
|
331
|
+
$[18] = info.selection;
|
|
332
|
+
$[19] = t7;
|
|
324
333
|
} else {
|
|
325
|
-
|
|
334
|
+
t7 = $[19];
|
|
326
335
|
}
|
|
327
|
-
let
|
|
328
|
-
if ($[
|
|
329
|
-
|
|
336
|
+
let t8;
|
|
337
|
+
if ($[20] !== controls) {
|
|
338
|
+
t8 = runtimeMode => controls.configure({
|
|
330
339
|
runtimeMode
|
|
331
340
|
});
|
|
332
|
-
$[
|
|
333
|
-
$[
|
|
341
|
+
$[20] = controls;
|
|
342
|
+
$[21] = t8;
|
|
334
343
|
} else {
|
|
335
|
-
|
|
344
|
+
t8 = $[21];
|
|
336
345
|
}
|
|
337
|
-
const
|
|
338
|
-
let
|
|
339
|
-
if ($[
|
|
340
|
-
|
|
346
|
+
const t9 = busy || !writable;
|
|
347
|
+
let t10;
|
|
348
|
+
if ($[22] !== accountChoice || $[23] !== controls.chooseAccount || $[24] !== started) {
|
|
349
|
+
t10 = accountChoice === null ? null : {
|
|
341
350
|
choice: accountChoice,
|
|
342
351
|
started,
|
|
343
352
|
onPick: controls.chooseAccount
|
|
344
353
|
};
|
|
345
|
-
$[
|
|
346
|
-
$[
|
|
347
|
-
$[
|
|
348
|
-
$[
|
|
354
|
+
$[22] = accountChoice;
|
|
355
|
+
$[23] = controls.chooseAccount;
|
|
356
|
+
$[24] = started;
|
|
357
|
+
$[25] = t10;
|
|
349
358
|
} else {
|
|
350
|
-
|
|
359
|
+
t10 = $[25];
|
|
351
360
|
}
|
|
352
|
-
let
|
|
353
|
-
if ($[
|
|
354
|
-
|
|
355
|
-
disabled:
|
|
361
|
+
let t11;
|
|
362
|
+
if ($[26] !== controls.chooseModel || $[27] !== controls.compact || $[28] !== controls.modelPickerOpen || $[29] !== controls.setModelPickerOpen || $[30] !== info.effectiveRuntimeMode || $[31] !== info.permissionMode || $[32] !== info.provider || $[33] !== info.runtimeMode || $[34] !== info.selection || $[35] !== info.usage || $[36] !== model || $[37] !== pickable || $[38] !== t10 || $[39] !== t6 || $[40] !== t7 || $[41] !== t8 || $[42] !== t9) {
|
|
363
|
+
t11 = /*#__PURE__*/_jsx(RunSettings, {
|
|
364
|
+
disabled: t6,
|
|
356
365
|
providers: pickable,
|
|
357
366
|
provider: info.provider,
|
|
358
367
|
selection: info.selection,
|
|
@@ -363,47 +372,47 @@ export function ComposerControls(t0) {
|
|
|
363
372
|
open: controls.modelPickerOpen,
|
|
364
373
|
onOpenChange: controls.setModelPickerOpen,
|
|
365
374
|
onModel: controls.chooseModel,
|
|
366
|
-
onOption:
|
|
367
|
-
onMode:
|
|
375
|
+
onOption: t7,
|
|
376
|
+
onMode: t8,
|
|
368
377
|
usage: info.usage,
|
|
369
|
-
compactDisabled:
|
|
378
|
+
compactDisabled: t9,
|
|
370
379
|
onCompact: controls.compact,
|
|
371
|
-
account:
|
|
372
|
-
});
|
|
373
|
-
$[24] = controls.chooseModel;
|
|
374
|
-
$[25] = controls.compact;
|
|
375
|
-
$[26] = controls.modelPickerOpen;
|
|
376
|
-
$[27] = controls.setModelPickerOpen;
|
|
377
|
-
$[28] = info.effectiveRuntimeMode;
|
|
378
|
-
$[29] = info.permissionMode;
|
|
379
|
-
$[30] = info.provider;
|
|
380
|
-
$[31] = info.runtimeMode;
|
|
381
|
-
$[32] = info.selection;
|
|
382
|
-
$[33] = info.usage;
|
|
383
|
-
$[34] = model;
|
|
384
|
-
$[35] = pickable;
|
|
385
|
-
$[36] = t5;
|
|
386
|
-
$[37] = t6;
|
|
387
|
-
$[38] = t7;
|
|
388
|
-
$[39] = t8;
|
|
389
|
-
$[40] = t9;
|
|
390
|
-
$[41] = t10;
|
|
391
|
-
} else {
|
|
392
|
-
t10 = $[41];
|
|
393
|
-
}
|
|
394
|
-
let t11;
|
|
395
|
-
if ($[42] !== onRestore) {
|
|
396
|
-
t11 = /*#__PURE__*/_jsx(StashPicker, {
|
|
397
|
-
onRestore: onRestore
|
|
380
|
+
account: t10
|
|
398
381
|
});
|
|
399
|
-
$[
|
|
382
|
+
$[26] = controls.chooseModel;
|
|
383
|
+
$[27] = controls.compact;
|
|
384
|
+
$[28] = controls.modelPickerOpen;
|
|
385
|
+
$[29] = controls.setModelPickerOpen;
|
|
386
|
+
$[30] = info.effectiveRuntimeMode;
|
|
387
|
+
$[31] = info.permissionMode;
|
|
388
|
+
$[32] = info.provider;
|
|
389
|
+
$[33] = info.runtimeMode;
|
|
390
|
+
$[34] = info.selection;
|
|
391
|
+
$[35] = info.usage;
|
|
392
|
+
$[36] = model;
|
|
393
|
+
$[37] = pickable;
|
|
394
|
+
$[38] = t10;
|
|
395
|
+
$[39] = t6;
|
|
396
|
+
$[40] = t7;
|
|
397
|
+
$[41] = t8;
|
|
398
|
+
$[42] = t9;
|
|
400
399
|
$[43] = t11;
|
|
401
400
|
} else {
|
|
402
401
|
t11 = $[43];
|
|
403
402
|
}
|
|
404
403
|
let t12;
|
|
405
|
-
if ($[44] !==
|
|
406
|
-
t12 =
|
|
404
|
+
if ($[44] !== onRestore) {
|
|
405
|
+
t12 = /*#__PURE__*/_jsx(StashPicker, {
|
|
406
|
+
onRestore: onRestore
|
|
407
|
+
});
|
|
408
|
+
$[44] = onRestore;
|
|
409
|
+
$[45] = t12;
|
|
410
|
+
} else {
|
|
411
|
+
t12 = $[45];
|
|
412
|
+
}
|
|
413
|
+
let t13;
|
|
414
|
+
if ($[46] !== Slot || $[47] !== chatId || $[48] !== insert || $[49] !== scope || $[50] !== t || $[51] !== writable) {
|
|
415
|
+
t13 = Slot !== null && /*#__PURE__*/_jsx(ErrorBoundary, {
|
|
407
416
|
label: t("composer.slotFailed"),
|
|
408
417
|
resetKeys: [chatId],
|
|
409
418
|
compact: true,
|
|
@@ -415,39 +424,39 @@ export function ComposerControls(t0) {
|
|
|
415
424
|
insert: insert
|
|
416
425
|
})
|
|
417
426
|
});
|
|
418
|
-
$[
|
|
419
|
-
$[
|
|
420
|
-
$[
|
|
421
|
-
$[
|
|
422
|
-
$[
|
|
423
|
-
$[
|
|
424
|
-
$[
|
|
427
|
+
$[46] = Slot;
|
|
428
|
+
$[47] = chatId;
|
|
429
|
+
$[48] = insert;
|
|
430
|
+
$[49] = scope;
|
|
431
|
+
$[50] = t;
|
|
432
|
+
$[51] = writable;
|
|
433
|
+
$[52] = t13;
|
|
425
434
|
} else {
|
|
426
|
-
|
|
435
|
+
t13 = $[52];
|
|
427
436
|
}
|
|
428
|
-
let
|
|
429
|
-
if ($[
|
|
430
|
-
|
|
437
|
+
let t14;
|
|
438
|
+
if ($[53] === Symbol.for("react.memo_cache_sentinel")) {
|
|
439
|
+
t14 = /*#__PURE__*/_jsx("span", {
|
|
431
440
|
className: "grow"
|
|
432
441
|
});
|
|
433
|
-
$[
|
|
442
|
+
$[53] = t14;
|
|
434
443
|
} else {
|
|
435
|
-
|
|
444
|
+
t14 = $[53];
|
|
436
445
|
}
|
|
437
|
-
let
|
|
438
|
-
if ($[
|
|
439
|
-
|
|
446
|
+
let t15;
|
|
447
|
+
if ($[54] !== dictationToolbar) {
|
|
448
|
+
t15 = /*#__PURE__*/_jsx("div", {
|
|
440
449
|
ref: dictationToolbar,
|
|
441
450
|
className: "flex items-center pl-1 empty:hidden [&_.icon-btn]:rounded-full"
|
|
442
451
|
});
|
|
443
|
-
$[
|
|
444
|
-
$[
|
|
452
|
+
$[54] = dictationToolbar;
|
|
453
|
+
$[55] = t15;
|
|
445
454
|
} else {
|
|
446
|
-
|
|
455
|
+
t15 = $[55];
|
|
447
456
|
}
|
|
448
|
-
let
|
|
449
|
-
if ($[
|
|
450
|
-
|
|
457
|
+
let t16;
|
|
458
|
+
if ($[56] !== busy || $[57] !== controls) {
|
|
459
|
+
t16 = busy && /*#__PURE__*/_jsx(Tooltip, {
|
|
451
460
|
label: composerStopLabel(),
|
|
452
461
|
name: true,
|
|
453
462
|
children: /*#__PURE__*/_jsx("button", {
|
|
@@ -459,15 +468,15 @@ export function ComposerControls(t0) {
|
|
|
459
468
|
})
|
|
460
469
|
})
|
|
461
470
|
});
|
|
462
|
-
$[
|
|
463
|
-
$[
|
|
464
|
-
$[
|
|
471
|
+
$[56] = busy;
|
|
472
|
+
$[57] = controls;
|
|
473
|
+
$[58] = t16;
|
|
465
474
|
} else {
|
|
466
|
-
|
|
475
|
+
t16 = $[58];
|
|
467
476
|
}
|
|
468
|
-
let
|
|
469
|
-
if ($[
|
|
470
|
-
|
|
477
|
+
let t17;
|
|
478
|
+
if ($[59] !== controls.configurationPending || $[60] !== send.onContinue || $[61] !== t) {
|
|
479
|
+
t17 = send.onContinue !== null && /*#__PURE__*/_jsx(Tooltip, {
|
|
471
480
|
label: t("composer.continueHint"),
|
|
472
481
|
children: /*#__PURE__*/_jsxs(Button, {
|
|
473
482
|
variant: "primary",
|
|
@@ -480,56 +489,56 @@ export function ComposerControls(t0) {
|
|
|
480
489
|
}), t("composer.continue")]
|
|
481
490
|
})
|
|
482
491
|
});
|
|
483
|
-
$[
|
|
484
|
-
$[
|
|
485
|
-
$[
|
|
486
|
-
$[
|
|
492
|
+
$[59] = controls.configurationPending;
|
|
493
|
+
$[60] = send.onContinue;
|
|
494
|
+
$[61] = t;
|
|
495
|
+
$[62] = t17;
|
|
487
496
|
} else {
|
|
488
|
-
|
|
497
|
+
t17 = $[62];
|
|
489
498
|
}
|
|
490
|
-
let
|
|
491
|
-
if ($[
|
|
492
|
-
|
|
499
|
+
let t18;
|
|
500
|
+
if ($[63] !== send || $[64] !== writable) {
|
|
501
|
+
t18 = send.onContinue === null && send.visible && /*#__PURE__*/_jsx(SendButton, {
|
|
493
502
|
send: send,
|
|
494
503
|
writable: writable
|
|
495
504
|
});
|
|
496
|
-
$[
|
|
497
|
-
$[
|
|
498
|
-
$[
|
|
505
|
+
$[63] = send;
|
|
506
|
+
$[64] = writable;
|
|
507
|
+
$[65] = t18;
|
|
499
508
|
} else {
|
|
500
|
-
|
|
509
|
+
t18 = $[65];
|
|
501
510
|
}
|
|
502
|
-
let
|
|
503
|
-
if ($[
|
|
504
|
-
|
|
511
|
+
let t19;
|
|
512
|
+
if ($[66] !== t15 || $[67] !== t16 || $[68] !== t17 || $[69] !== t18) {
|
|
513
|
+
t19 = /*#__PURE__*/_jsxs("div", {
|
|
505
514
|
className: "flex h-9 shrink-0 items-center gap-0.5 rounded-full bg-surface-hover",
|
|
506
|
-
children: [
|
|
515
|
+
children: [t15, t16, t17, t18]
|
|
507
516
|
});
|
|
508
|
-
$[
|
|
509
|
-
$[
|
|
510
|
-
$[
|
|
511
|
-
$[
|
|
512
|
-
$[
|
|
517
|
+
$[66] = t15;
|
|
518
|
+
$[67] = t16;
|
|
519
|
+
$[68] = t17;
|
|
520
|
+
$[69] = t18;
|
|
521
|
+
$[70] = t19;
|
|
513
522
|
} else {
|
|
514
|
-
|
|
523
|
+
t19 = $[70];
|
|
515
524
|
}
|
|
516
|
-
let
|
|
517
|
-
if ($[
|
|
518
|
-
|
|
525
|
+
let t20;
|
|
526
|
+
if ($[71] !== t11 || $[72] !== t12 || $[73] !== t13 || $[74] !== t19 || $[75] !== t4 || $[76] !== t5) {
|
|
527
|
+
t20 = /*#__PURE__*/_jsxs("div", {
|
|
519
528
|
className: "flex items-center gap-2 pt-3 pr-3.5 pb-3.5 pl-5 @max-md/composer:gap-1.5 @max-md/composer:p-3 @max-md/composer:pt-2.5",
|
|
520
|
-
children: [
|
|
529
|
+
children: [t4, t5, t11, t12, t13, t14, t19]
|
|
521
530
|
});
|
|
522
|
-
$[
|
|
523
|
-
$[
|
|
524
|
-
$[
|
|
525
|
-
$[
|
|
526
|
-
$[
|
|
527
|
-
$[
|
|
528
|
-
$[
|
|
531
|
+
$[71] = t11;
|
|
532
|
+
$[72] = t12;
|
|
533
|
+
$[73] = t13;
|
|
534
|
+
$[74] = t19;
|
|
535
|
+
$[75] = t4;
|
|
536
|
+
$[76] = t5;
|
|
537
|
+
$[77] = t20;
|
|
529
538
|
} else {
|
|
530
|
-
|
|
539
|
+
t20 = $[77];
|
|
531
540
|
}
|
|
532
|
-
return
|
|
541
|
+
return t20;
|
|
533
542
|
}
|
|
534
543
|
function _temp2(entry_2) {
|
|
535
544
|
return entry_2.installed && entry_2.capabilities.chat;
|
|
@@ -40,7 +40,7 @@ export function replyHeader(row, previous) {
|
|
|
40
40
|
}
|
|
41
41
|
// A transform also shifts a descendant's sticky position after it was resolved against the scrollport.
|
|
42
42
|
export function rowPosition(kind, top) {
|
|
43
|
-
return kind === 'visual' ? {
|
|
43
|
+
return kind === 'visual' || kind === 'app-card' ? {
|
|
44
44
|
top
|
|
45
45
|
} : {
|
|
46
46
|
transform: `translateY(${top}px)`
|
package/dist/state/chats.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface ChatState {
|
|
|
8
8
|
history?: ChatHistoryPage;
|
|
9
9
|
waitingBefore?: string[];
|
|
10
10
|
subagentsBefore?: string[];
|
|
11
|
+
outlineBefore?: ChatItem[];
|
|
11
12
|
bookmarks?: ChatBookmark[];
|
|
12
13
|
visuals?: ChatVisual[];
|
|
13
14
|
}
|
|
@@ -16,6 +17,7 @@ export interface ChatPage {
|
|
|
16
17
|
history: ChatHistoryPage;
|
|
17
18
|
pending: ChatItem[];
|
|
18
19
|
subagents?: ChatSubagentItem[];
|
|
20
|
+
outline?: ChatItem[];
|
|
19
21
|
}
|
|
20
22
|
export type ChatsById = Record<string, ChatState>;
|
|
21
23
|
export type ChatStatuses = Record<string, Pick<ChatState, 'info'>>;
|
package/dist/state/chats.js
CHANGED
|
@@ -29,7 +29,8 @@ function stateOf(info, items, page) {
|
|
|
29
29
|
order,
|
|
30
30
|
history: page.history,
|
|
31
31
|
waitingBefore: before.map(item => item.id),
|
|
32
|
-
subagentsBefore: subagents.map(item => item.id)
|
|
32
|
+
subagentsBefore: subagents.map(item => item.id),
|
|
33
|
+
outlineBefore: (page.outline ?? []).filter(item => !(item.id in byId))
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
/* An older host names no place in the thread, and it always hands over the whole of it. */
|
|
@@ -83,6 +84,7 @@ export function prependPage(state, cursor, page) {
|
|
|
83
84
|
history: _history,
|
|
84
85
|
waitingBefore: _waitingBefore,
|
|
85
86
|
subagentsBefore: _subagentsBefore,
|
|
87
|
+
outlineBefore: _outlineBefore,
|
|
86
88
|
...rest
|
|
87
89
|
} = loaded;
|
|
88
90
|
return {
|
|
@@ -94,6 +96,7 @@ export function prependPage(state, cursor, page) {
|
|
|
94
96
|
}
|
|
95
97
|
const waitingBefore = (state.waitingBefore ?? []).filter(id => !olderIds.has(id));
|
|
96
98
|
const subagentsBefore = (state.subagentsBefore ?? []).filter(id => !olderIds.has(id));
|
|
99
|
+
const outlineBefore = (state.outlineBefore ?? []).filter(item => !olderIds.has(item.id));
|
|
97
100
|
return {
|
|
98
101
|
...loaded,
|
|
99
102
|
items,
|
|
@@ -101,7 +104,8 @@ export function prependPage(state, cursor, page) {
|
|
|
101
104
|
order,
|
|
102
105
|
history: page.history,
|
|
103
106
|
waitingBefore,
|
|
104
|
-
subagentsBefore
|
|
107
|
+
subagentsBefore,
|
|
108
|
+
outlineBefore
|
|
105
109
|
};
|
|
106
110
|
}
|
|
107
111
|
/* The requests that still wait, oldest first: those from before the page this client holds, then the thread's own. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents-react",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.4",
|
|
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.40.
|
|
71
|
-
"@adecore/editor": "0.40.
|
|
72
|
-
"@adecore/editor-react": "0.40.
|
|
73
|
-
"@adecore/intelligent-ui": "0.40.
|
|
74
|
-
"@adecore/merge": "0.40.
|
|
70
|
+
"@adecore/agent-contracts": "0.40.4",
|
|
71
|
+
"@adecore/editor": "0.40.4",
|
|
72
|
+
"@adecore/editor-react": "0.40.4",
|
|
73
|
+
"@adecore/intelligent-ui": "0.40.4",
|
|
74
|
+
"@adecore/merge": "0.40.4",
|
|
75
75
|
"@codemirror/commands": "^6.11.1",
|
|
76
76
|
"@codemirror/language": "^6.13.1",
|
|
77
77
|
"@codemirror/state": "^6.7.6",
|
|
@@ -94,14 +94,14 @@
|
|
|
94
94
|
"zustand": "^5.0.16"
|
|
95
95
|
},
|
|
96
96
|
"peerDependencies": {
|
|
97
|
-
"@adecore/ui": "^0.40.
|
|
97
|
+
"@adecore/ui": "^0.40.4",
|
|
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.40.
|
|
104
|
+
"@adecore/ui": "0.40.4",
|
|
105
105
|
"@types/bun": "^1.4.4",
|
|
106
106
|
"@types/react": "^19.3.0",
|
|
107
107
|
"@types/react-dom": "^19.3.0",
|
package/src/chat/chat-client.ts
CHANGED
|
@@ -443,7 +443,9 @@ export class ChatClient {
|
|
|
443
443
|
}
|
|
444
444
|
} else {
|
|
445
445
|
const page =
|
|
446
|
-
result.history === undefined
|
|
446
|
+
result.history === undefined
|
|
447
|
+
? undefined
|
|
448
|
+
: { history: result.history, pending: result.pending ?? [], subagents: result.subagents ?? [], outline: result.outline ?? [] };
|
|
447
449
|
|
|
448
450
|
this.sink.reset(chatId, result.info, result.items, page);
|
|
449
451
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ChatBookmark } from '@adecore/agent-contracts';
|
|
2
|
-
import type
|
|
1
|
+
import type { ChatBookmark, ChatItem } from '@adecore/agent-contracts';
|
|
2
|
+
import { agentTurnLabel, type TimelineRow } from './timeline';
|
|
3
3
|
import { DEFAULT_READING_WIDTH, readingMeasure } from '../reading-width';
|
|
4
4
|
|
|
5
5
|
// The strip of ticks at the left edge of a chat view, as numbers; the component only draws what comes out of here.
|
|
@@ -9,7 +9,7 @@ export type TickKind = 'person' | 'wake' | 'bookmark';
|
|
|
9
9
|
|
|
10
10
|
export interface ScrubberTick {
|
|
11
11
|
id: string;
|
|
12
|
-
/* The row of the timeline the tick jumps to. */
|
|
12
|
+
/* The row of the timeline the tick jumps to; `EARLIER_ROW` for a message before the part of the thread this client holds. */
|
|
13
13
|
rowIndex: number;
|
|
14
14
|
kind: TickKind;
|
|
15
15
|
text: string;
|
|
@@ -41,9 +41,45 @@ const WIDTH_NEAR_WAKE_PX = 8;
|
|
|
41
41
|
const WIDTH_PERSON_PX = 8;
|
|
42
42
|
const WIDTH_WAKE_PX = 5;
|
|
43
43
|
|
|
44
|
-
/*
|
|
45
|
-
export
|
|
46
|
-
|
|
44
|
+
/* Below every row, so a tick before the rows this client holds sorts first and its stretch reaches the first row of the thread. */
|
|
45
|
+
export const EARLIER_ROW = -1;
|
|
46
|
+
|
|
47
|
+
/* A message before the rows this client holds, as the outline (`outlineItems`) has it; its row id is the one the thread will draw it with. */
|
|
48
|
+
function earlierTick(item: ChatItem, bookmarks: ReadonlyMap<string, ChatBookmark>): ScrubberTick | null {
|
|
49
|
+
const bookmark = bookmarks.get(item.id) ?? null;
|
|
50
|
+
const rowIndex = EARLIER_ROW;
|
|
51
|
+
|
|
52
|
+
if (item.kind === 'user') {
|
|
53
|
+
const text = item.text !== '' ? item.text : (item.attachments ?? []).map((attachment) => attachment.name).join(', ');
|
|
54
|
+
|
|
55
|
+
return { id: item.id, rowIndex, kind: 'person', text, createdAt: item.createdAt, turnId: item.turnId, bookmark };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (item.kind === 'turn' && ((item.taskIds?.length ?? 0) > 0 || (item.messageFrom?.length ?? 0) > 0)) {
|
|
59
|
+
return { id: `start-${item.id}`, rowIndex, kind: 'wake', text: agentTurnLabel(item), createdAt: item.createdAt, turnId: item.id, bookmark: null };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (item.kind === 'assistant' && bookmark !== null) {
|
|
63
|
+
return { id: item.id, rowIndex, kind: 'bookmark', text: item.text, createdAt: item.createdAt, turnId: item.turnId, bookmark };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* `marks` is what `bookmarkRows` answers: the bookmark drawn at a row, by row id. `earlier` is the
|
|
71
|
+
* outline of the thread before `rows`, and `bookmarks` the chat's, which mark the ticks of that part.
|
|
72
|
+
*/
|
|
73
|
+
export function ticksOf(
|
|
74
|
+
rows: readonly TimelineRow[],
|
|
75
|
+
marks: ReadonlyMap<string, ChatBookmark> = new Map(),
|
|
76
|
+
earlier: readonly ChatItem[] = [],
|
|
77
|
+
bookmarks: readonly ChatBookmark[] = []
|
|
78
|
+
): ScrubberTick[] {
|
|
79
|
+
const byItem = new Map(bookmarks.map((bookmark) => [bookmark.itemId, bookmark]));
|
|
80
|
+
const before = earlier.flatMap((item) => earlierTick(item, byItem) ?? []);
|
|
81
|
+
|
|
82
|
+
const held = rows.flatMap((row, rowIndex): ScrubberTick[] => {
|
|
47
83
|
const bookmark = marks.get(row.id) ?? null;
|
|
48
84
|
|
|
49
85
|
if (row.kind === 'user') {
|
|
@@ -71,6 +107,8 @@ export function ticksOf(rows: readonly TimelineRow[], marks: ReadonlyMap<string,
|
|
|
71
107
|
|
|
72
108
|
return [{ id: row.id, rowIndex, kind: 'bookmark', text: bookmark.excerpt, createdAt: bookmark.createdAt, turnId, bookmark }];
|
|
73
109
|
});
|
|
110
|
+
|
|
111
|
+
return before.length === 0 ? held : [...before, ...held];
|
|
74
112
|
}
|
|
75
113
|
|
|
76
114
|
/* One drawn tick: a message, or a run of messages merged once the height has fewer pixels than they need. */
|
package/src/chat/ui/Timeline.tsx
CHANGED
|
@@ -45,7 +45,8 @@ import {
|
|
|
45
45
|
STRIP_CLEARANCE_PX,
|
|
46
46
|
tickOfRow,
|
|
47
47
|
ticksOf,
|
|
48
|
-
ticksWithHits
|
|
48
|
+
ticksWithHits,
|
|
49
|
+
EARLIER_ROW
|
|
49
50
|
} from '../logic/scrubber';
|
|
50
51
|
import {
|
|
51
52
|
EMPTY_TARGET,
|
|
@@ -206,6 +207,7 @@ export function Timeline({
|
|
|
206
207
|
const items = useChatRow(chatId, (row) => row?.structure);
|
|
207
208
|
const activeTurnId = useChatRow(chatId, (row) => row?.info.activeTurnId ?? null);
|
|
208
209
|
const bookmarks = useChatRow(chatId, (row) => row?.bookmarks);
|
|
210
|
+
const outlineBefore = useChatRow(chatId, (row) => row?.outlineBefore);
|
|
209
211
|
const visuals = useChatRow(chatId, (row) => row?.visuals);
|
|
210
212
|
const forkedTurns = useForkedTurns(chatId);
|
|
211
213
|
const info = useChatRow(chatId, (row) => row?.info ?? null);
|
|
@@ -306,7 +308,7 @@ export function Timeline({
|
|
|
306
308
|
// A chat that took a turn before is one whose thread has not come in yet, not one to greet.
|
|
307
309
|
const welcome = !loadingHistory && welcomes && empty && (info === null || (info.usage.turns === 0 && info.activeTurnId === null));
|
|
308
310
|
const marks = useMemo(() => bookmarkRows(rows, bookmarks ?? [], items ?? {}), [rows, bookmarks, items]);
|
|
309
|
-
const ticks = useMemo(() => ticksOf(rows, marks), [rows, marks]);
|
|
311
|
+
const ticks = useMemo(() => ticksOf(rows, marks, outlineBefore, bookmarks), [rows, marks, outlineBefore, bookmarks]);
|
|
310
312
|
// A message a jump is on its way to; it may first have to open the turn its reply folded into.
|
|
311
313
|
const [seeking, setSeeking] = useState<{ itemId: string; block?: UiReplyTarget; key: string; options?: ItemJumpOptions } | null>(null);
|
|
312
314
|
const [landing, setLanding] = useState<{ target: UiReplyTarget; key: string } | null>(null);
|
|
@@ -613,9 +615,10 @@ export function Timeline({
|
|
|
613
615
|
return;
|
|
614
616
|
}
|
|
615
617
|
|
|
616
|
-
|
|
618
|
+
// A message before the rows this client holds comes in page by page until its row is there.
|
|
619
|
+
if (tick.bookmark !== null || tick.rowIndex === EARLIER_ROW) {
|
|
617
620
|
setLanding(null);
|
|
618
|
-
setSeeking({ itemId: tick.bookmark.
|
|
621
|
+
setSeeking({ itemId: tick.bookmark?.itemId ?? tick.id, key: keyOf(chatId) });
|
|
619
622
|
|
|
620
623
|
return;
|
|
621
624
|
}
|
|
@@ -2,7 +2,7 @@ import { useRef, useState, type Ref } from 'react';
|
|
|
2
2
|
import { useTranslation } from 'react-i18next';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
4
|
import { ArrowUp, ChevronDown, Play, Plus, Square } from 'lucide-react';
|
|
5
|
-
import type
|
|
5
|
+
import { holdsConversation, type ChatInfo } from '@adecore/agent-contracts';
|
|
6
6
|
import { Button, ErrorBoundary, Icon, Menu, Tooltip, type Shortcut } from '@adecore/ui';
|
|
7
7
|
import { useChatHost, type ComposerSendAction, type ComposerSendDraft } from '../../../host';
|
|
8
8
|
import { useChatScope } from '../../../scope';
|
|
@@ -124,8 +124,7 @@ export function ComposerControls({
|
|
|
124
124
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
125
125
|
const busy = info.activeTurnId !== null;
|
|
126
126
|
const model = providers.find((entry) => entry.kind === info.provider)?.models.find((entry) => entry.slug === info.selection.model);
|
|
127
|
-
|
|
128
|
-
const started = info.agentSessionId !== null || info.usage.turns > 0;
|
|
127
|
+
const started = holdsConversation(info);
|
|
129
128
|
|
|
130
129
|
// A chat that has started keeps its provider (the CLI holds the thread), and so does one opened for a
|
|
131
130
|
// named CLI; its models stay switchable, since the CLI takes a new one on the restart the next send does.
|
|
@@ -44,5 +44,5 @@ export function replyHeader(row: TimelineRow, previous: TimelineRow | null): { a
|
|
|
44
44
|
|
|
45
45
|
// A transform also shifts a descendant's sticky position after it was resolved against the scrollport.
|
|
46
46
|
export function rowPosition(kind: TimelineRow['kind'], top: number): CSSProperties {
|
|
47
|
-
return kind === 'visual' ? { top } : { transform: `translateY(${top}px)` };
|
|
47
|
+
return kind === 'visual' || kind === 'app-card' ? { top } : { transform: `translateY(${top}px)` };
|
|
48
48
|
}
|
package/src/state/chats.ts
CHANGED
|
@@ -38,6 +38,11 @@ export interface ChatState {
|
|
|
38
38
|
* that a message woke again without a scroll reaching back to its row.
|
|
39
39
|
*/
|
|
40
40
|
subagentsBefore?: string[];
|
|
41
|
+
/*
|
|
42
|
+
* The outline of the thread before that part (`outlineItems`), cut down and kept apart from
|
|
43
|
+
* `items`, so the scrubber has a tick for every message before a scroll reaches it.
|
|
44
|
+
*/
|
|
45
|
+
outlineBefore?: ChatItem[];
|
|
41
46
|
/* The messages marked to come back to, as the host last said; absent until it said anything. */
|
|
42
47
|
bookmarks?: ChatBookmark[];
|
|
43
48
|
/* The pages agents published in the chat, as the host last said; absent until it said anything. */
|
|
@@ -53,6 +58,8 @@ export interface ChatPage {
|
|
|
53
58
|
pending: ChatItem[];
|
|
54
59
|
/* The active sub-agents from before the page; absent from a host that sends none. */
|
|
55
60
|
subagents?: ChatSubagentItem[];
|
|
61
|
+
/* The outline of the thread before the page; absent from a host that sends none. */
|
|
62
|
+
outline?: ChatItem[];
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
/* Rows keyed with the scope's `keyOf`, so a thread says which host it runs on. */
|
|
@@ -124,7 +131,8 @@ function stateOf(info: ChatInfo, items: ChatItem[], page?: ChatPage): ChatState
|
|
|
124
131
|
order,
|
|
125
132
|
history: page.history,
|
|
126
133
|
waitingBefore: before.map((item) => item.id),
|
|
127
|
-
subagentsBefore: subagents.map((item) => item.id)
|
|
134
|
+
subagentsBefore: subagents.map((item) => item.id),
|
|
135
|
+
outlineBefore: (page.outline ?? []).filter((item) => !(item.id in byId))
|
|
128
136
|
};
|
|
129
137
|
}
|
|
130
138
|
|
|
@@ -173,15 +181,16 @@ export function prependPage(state: ChatState, cursor: string, page: ChatHistoryR
|
|
|
173
181
|
const order = [...olderIds, ...state.order];
|
|
174
182
|
|
|
175
183
|
if (page.history.cursor === null) {
|
|
176
|
-
const { history: _history, waitingBefore: _waitingBefore, subagentsBefore: _subagentsBefore, ...rest } = loaded;
|
|
184
|
+
const { history: _history, waitingBefore: _waitingBefore, subagentsBefore: _subagentsBefore, outlineBefore: _outlineBefore, ...rest } = loaded;
|
|
177
185
|
|
|
178
186
|
return { ...rest, items, structure: items, order };
|
|
179
187
|
}
|
|
180
188
|
|
|
181
189
|
const waitingBefore = (state.waitingBefore ?? []).filter((id) => !olderIds.has(id));
|
|
182
190
|
const subagentsBefore = (state.subagentsBefore ?? []).filter((id) => !olderIds.has(id));
|
|
191
|
+
const outlineBefore = (state.outlineBefore ?? []).filter((item) => !olderIds.has(item.id));
|
|
183
192
|
|
|
184
|
-
return { ...loaded, items, structure: items, order, history: page.history, waitingBefore, subagentsBefore };
|
|
193
|
+
return { ...loaded, items, structure: items, order, history: page.history, waitingBefore, subagentsBefore, outlineBefore };
|
|
185
194
|
}
|
|
186
195
|
|
|
187
196
|
/* The requests that still wait, oldest first: those from before the page this client holds, then the thread's own. */
|