@design.estate/dees-catalog 6.1.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_bundle/bundle.js +3097 -1497
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +2 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.js +484 -8
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +10 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.d.ts +3 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.js +24 -4
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +26 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +398 -94
- package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.d.ts +32 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.demo.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.demo.js +31 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-overflow-text.js +297 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.js +1 -9
- package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.d.ts +10 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.js +181 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.d.ts +17 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.js +476 -36
- package/dist_ts_web/elements/00group-harness/dees-harness-tool-card/renderers.js +23 -20
- package/dist_ts_web/elements/00group-harness/harness.helpers.d.ts +5 -1
- package/dist_ts_web/elements/00group-harness/harness.helpers.js +66 -1
- package/dist_ts_web/elements/00group-harness/index.d.ts +1 -0
- package/dist_ts_web/elements/00group-harness/index.js +2 -1
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +31 -2
- package/dist_ts_web/elements/00group-harness/toolregistry.d.ts +4 -0
- package/dist_ts_web/elements/00group-harness/toolregistry.js +8 -1
- package/package.json +2 -2
- package/readme.hints.md +16 -1
- package/readme.md +41 -9
- package/readme.playbook.md +8 -4
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.demo.ts +490 -7
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +9 -0
- package/ts_web/elements/00group-harness/dees-harness-message/dees-harness-message.ts +24 -3
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +397 -84
- package/ts_web/elements/00group-harness/dees-harness-overflow-text.demo.ts +31 -0
- package/ts_web/elements/00group-harness/dees-harness-overflow-text.ts +236 -0
- package/ts_web/elements/00group-harness/dees-harness-session-list/dees-harness-session-list.ts +0 -8
- package/ts_web/elements/00group-harness/dees-harness-session-sidebar/dees-harness-session-sidebar.ts +193 -1
- package/ts_web/elements/00group-harness/dees-harness-tool-card/dees-harness-tool-card.ts +480 -34
- package/ts_web/elements/00group-harness/dees-harness-tool-card/renderers.ts +24 -21
- package/ts_web/elements/00group-harness/harness.helpers.ts +68 -1
- package/ts_web/elements/00group-harness/index.ts +1 -0
- package/ts_web/elements/00group-harness/interfaces.ts +36 -2
- package/ts_web/elements/00group-harness/toolregistry.ts +12 -0
package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts
CHANGED
|
@@ -11,9 +11,17 @@ import {
|
|
|
11
11
|
directives,
|
|
12
12
|
} from '@design.estate/dees-element';
|
|
13
13
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
14
|
-
import type {
|
|
14
|
+
import type {
|
|
15
|
+
IHarnessMessage,
|
|
16
|
+
IHarnessPermissionRequest,
|
|
17
|
+
IHarnessQuestionRequest,
|
|
18
|
+
IHarnessStatus,
|
|
19
|
+
THarnessMessageStreamDelta,
|
|
20
|
+
THarnessStreamDelta,
|
|
21
|
+
} from '../interfaces.js';
|
|
15
22
|
import { DeesHarnessMessage } from '../dees-harness-message/dees-harness-message.js';
|
|
16
|
-
import { DeesHarnessToolRegistry } from '../toolregistry.js';
|
|
23
|
+
import { DeesHarnessToolRegistry, type THarnessToolLayout } from '../toolregistry.js';
|
|
24
|
+
import { harnessApplyMessageDelta } from '../harness.helpers.js';
|
|
17
25
|
import '../dees-harness-status/dees-harness-status.js';
|
|
18
26
|
import '../dees-harness-permission-card/dees-harness-permission-card.js';
|
|
19
27
|
import '../dees-harness-question-card/dees-harness-question-card.js';
|
|
@@ -29,11 +37,28 @@ declare global {
|
|
|
29
37
|
|
|
30
38
|
const pinThresholdPx = 48;
|
|
31
39
|
|
|
40
|
+
type TMessageTimelineItem = {
|
|
41
|
+
kind: 'message';
|
|
42
|
+
time: number;
|
|
43
|
+
key: string;
|
|
44
|
+
message: IHarnessMessage;
|
|
45
|
+
toolLayout: THarnessToolLayout | null;
|
|
46
|
+
};
|
|
47
|
+
|
|
32
48
|
type TTimelineItem =
|
|
33
|
-
|
|
|
49
|
+
| TMessageTimelineItem
|
|
34
50
|
| { kind: 'permission'; time: number; key: string; request: IHarnessPermissionRequest }
|
|
35
51
|
| { kind: 'question'; time: number; key: string; question: IHarnessQuestionRequest };
|
|
36
52
|
|
|
53
|
+
interface IToolGroupTimelineItem {
|
|
54
|
+
kind: 'tool-group';
|
|
55
|
+
key: string;
|
|
56
|
+
layout: THarnessToolLayout;
|
|
57
|
+
messages: TMessageTimelineItem[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type TTimelineRenderItem = Exclude<TTimelineItem, TMessageTimelineItem> | TMessageTimelineItem | IToolGroupTimelineItem;
|
|
61
|
+
|
|
37
62
|
/**
|
|
38
63
|
* Scrollable, streaming-safe message log. Messages render through a keyed
|
|
39
64
|
* repeat (stable DOM per message id); `applyDelta()` routes incremental
|
|
@@ -67,9 +92,19 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
67
92
|
@property({ type: Boolean })
|
|
68
93
|
accessor autoFollow: boolean = true;
|
|
69
94
|
|
|
95
|
+
@property({ type: Boolean })
|
|
96
|
+
accessor hideScrollbar: boolean = false;
|
|
97
|
+
|
|
98
|
+
@property({ type: Boolean })
|
|
99
|
+
accessor fadeScrollEdges: boolean = false;
|
|
100
|
+
|
|
70
101
|
@property({ attribute: false })
|
|
71
102
|
accessor toolRegistry: DeesHarnessToolRegistry = DeesHarnessToolRegistry.default;
|
|
72
103
|
|
|
104
|
+
/** Nested transcripts set this false to prevent recursive inline streams. */
|
|
105
|
+
@property({ type: Boolean })
|
|
106
|
+
accessor allowSubtaskStreams: boolean = true;
|
|
107
|
+
|
|
73
108
|
@property({ type: Boolean })
|
|
74
109
|
accessor showRoles: boolean = true;
|
|
75
110
|
|
|
@@ -88,6 +123,16 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
88
123
|
@state()
|
|
89
124
|
accessor entryAnimationsEnabled: boolean = false;
|
|
90
125
|
|
|
126
|
+
@state()
|
|
127
|
+
accessor scrollFadeTop: boolean = false;
|
|
128
|
+
|
|
129
|
+
@state()
|
|
130
|
+
accessor scrollFadeBottom: boolean = false;
|
|
131
|
+
|
|
132
|
+
private readonly toolGroupIdByMessageId = new Map<string, string>();
|
|
133
|
+
private nextToolGroupId = 1;
|
|
134
|
+
private timelineRenderItems: TTimelineRenderItem[] = [];
|
|
135
|
+
|
|
91
136
|
public static styles = [
|
|
92
137
|
themeDefaultStyles,
|
|
93
138
|
cssManager.defaultStyles,
|
|
@@ -104,10 +149,47 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
104
149
|
.scroll {
|
|
105
150
|
height: 100%;
|
|
106
151
|
overflow-y: auto;
|
|
152
|
+
overscroll-behavior: contain;
|
|
107
153
|
scrollbar-width: thin;
|
|
108
154
|
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
|
109
155
|
padding: var(--dees-spacing-md);
|
|
110
156
|
box-sizing: border-box;
|
|
157
|
+
container: harness-transcript / inline-size;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.scroll.hideScrollbar {
|
|
161
|
+
scrollbar-width: none;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.scroll.hideScrollbar::-webkit-scrollbar {
|
|
165
|
+
display: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.scroll.fadeTop {
|
|
169
|
+
-webkit-mask-image: linear-gradient(to bottom, transparent 0, black 40px, black 100%);
|
|
170
|
+
mask-image: linear-gradient(to bottom, transparent 0, black 40px, black 100%);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.scroll.fadeBottom {
|
|
174
|
+
-webkit-mask-image: linear-gradient(to bottom, black 0, black calc(100% - 40px), transparent 100%);
|
|
175
|
+
mask-image: linear-gradient(to bottom, black 0, black calc(100% - 40px), transparent 100%);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.scroll.fadeTop.fadeBottom {
|
|
179
|
+
-webkit-mask-image: linear-gradient(
|
|
180
|
+
to bottom,
|
|
181
|
+
transparent 0,
|
|
182
|
+
black 40px,
|
|
183
|
+
black calc(100% - 40px),
|
|
184
|
+
transparent 100%
|
|
185
|
+
);
|
|
186
|
+
mask-image: linear-gradient(
|
|
187
|
+
to bottom,
|
|
188
|
+
transparent 0,
|
|
189
|
+
black 40px,
|
|
190
|
+
black calc(100% - 40px),
|
|
191
|
+
transparent 100%
|
|
192
|
+
);
|
|
111
193
|
}
|
|
112
194
|
|
|
113
195
|
/* observed by the follow ResizeObserver; fills the viewport so the
|
|
@@ -118,14 +200,62 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
118
200
|
flex-direction: column;
|
|
119
201
|
}
|
|
120
202
|
|
|
121
|
-
|
|
203
|
+
::slotted([slot='leading']) {
|
|
122
204
|
display: block;
|
|
205
|
+
min-width: 0;
|
|
206
|
+
margin-bottom: var(--dees-spacing-sm);
|
|
123
207
|
}
|
|
124
208
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
209
|
+
.stack {
|
|
210
|
+
display: grid;
|
|
211
|
+
grid-template-columns: minmax(0, 1fr);
|
|
212
|
+
gap: var(--dees-spacing-sm);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.stack > * {
|
|
216
|
+
min-width: 0;
|
|
217
|
+
grid-column: 1 / -1;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.toolGroup {
|
|
221
|
+
min-width: 0;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.toolGrid {
|
|
225
|
+
display: grid;
|
|
226
|
+
grid-template-columns: minmax(0, 1fr);
|
|
227
|
+
gap: var(--dees-spacing-sm);
|
|
228
|
+
min-width: 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.toolGrid > dees-harness-message {
|
|
232
|
+
min-width: 0;
|
|
233
|
+
align-self: start;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.toolGrid > dees-harness-message[data-tool-layout='full-row'] {
|
|
237
|
+
grid-column: 1 / -1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@container harness-transcript (min-width: 760px) {
|
|
241
|
+
.toolGrid.toolCount2,
|
|
242
|
+
.toolGrid.toolCount3,
|
|
243
|
+
.toolGrid.toolCount4 {
|
|
244
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@container harness-transcript (min-width: 1120px) {
|
|
249
|
+
.toolGrid.toolCount3,
|
|
250
|
+
.toolGrid.toolCount4 {
|
|
251
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
@container harness-transcript (min-width: 1480px) {
|
|
256
|
+
.toolGrid.toolCount4 {
|
|
257
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
258
|
+
}
|
|
129
259
|
}
|
|
130
260
|
|
|
131
261
|
.statusRow {
|
|
@@ -175,35 +305,19 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
175
305
|
.jumpChip:hover {
|
|
176
306
|
background: var(--dees-material-thick-bg);
|
|
177
307
|
}
|
|
308
|
+
|
|
178
309
|
`,
|
|
179
310
|
];
|
|
180
311
|
|
|
181
312
|
/** messages and permissions merged chronologically (stable ids as keys) */
|
|
182
313
|
private get timelineItems(): TTimelineItem[] {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const runningSubagentBase = Number.MAX_SAFE_INTEGER / 2;
|
|
191
|
-
const sessionActive = this.status?.type === 'busy';
|
|
192
|
-
const items: TTimelineItem[] = this.messages.map((message, index) => {
|
|
193
|
-
const call = message.toolCall;
|
|
194
|
-
const runningSubagent = Boolean(call)
|
|
195
|
-
&& (call!.name === 'task' || call!.name === 'subagent' || call!.kind === 'subtask')
|
|
196
|
-
&& (call!.status === 'pending' || call!.status === 'running');
|
|
197
|
-
const stale = !sessionActive || this.messages.length - 1 - index >= 20;
|
|
198
|
-
return {
|
|
199
|
-
kind: 'message' as const,
|
|
200
|
-
time: runningSubagent && !stale
|
|
201
|
-
? runningSubagentBase + (message.createdAt || 0)
|
|
202
|
-
: message.createdAt || 0,
|
|
203
|
-
key: message.id,
|
|
204
|
-
message,
|
|
205
|
-
};
|
|
206
|
-
});
|
|
314
|
+
const items: TTimelineItem[] = this.messages.map((message) => ({
|
|
315
|
+
kind: 'message' as const,
|
|
316
|
+
time: message.createdAt || 0,
|
|
317
|
+
key: `message:${message.id}`,
|
|
318
|
+
message,
|
|
319
|
+
toolLayout: message.toolCall ? this.toolRegistry.resolveLayout(message.toolCall) : null,
|
|
320
|
+
}));
|
|
207
321
|
for (const request of this.permissions) {
|
|
208
322
|
items.push({
|
|
209
323
|
kind: 'permission' as const,
|
|
@@ -212,7 +326,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
212
326
|
time: request.response
|
|
213
327
|
? (request.respondedAt ?? request.createdAt ?? 0)
|
|
214
328
|
: Number.MAX_SAFE_INTEGER,
|
|
215
|
-
key: `
|
|
329
|
+
key: `permission:${request.id}`,
|
|
216
330
|
request,
|
|
217
331
|
});
|
|
218
332
|
}
|
|
@@ -229,33 +343,93 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
229
343
|
return items.sort((left, right) => left.time - right.time);
|
|
230
344
|
}
|
|
231
345
|
|
|
232
|
-
|
|
346
|
+
private buildTimelineRenderItems(): TTimelineRenderItem[] {
|
|
233
347
|
const items = this.timelineItems;
|
|
348
|
+
const retainedToolIds = new Set(
|
|
349
|
+
items
|
|
350
|
+
.filter((item): item is TMessageTimelineItem => item.kind === 'message' && item.toolLayout !== null)
|
|
351
|
+
.map((item) => item.message.id),
|
|
352
|
+
);
|
|
353
|
+
for (const messageId of this.toolGroupIdByMessageId.keys()) {
|
|
354
|
+
if (!retainedToolIds.has(messageId)) this.toolGroupIdByMessageId.delete(messageId);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const result: TTimelineRenderItem[] = [];
|
|
358
|
+
const usedGroupIds = new Set<string>();
|
|
359
|
+
for (let index = 0; index < items.length;) {
|
|
360
|
+
const item = items[index];
|
|
361
|
+
if (item.kind !== 'message' || item.toolLayout === null) {
|
|
362
|
+
result.push(item);
|
|
363
|
+
index += 1;
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const layout = item.toolLayout;
|
|
368
|
+
const run: TMessageTimelineItem[] = [];
|
|
369
|
+
while (index < items.length) {
|
|
370
|
+
const candidate = items[index];
|
|
371
|
+
if (
|
|
372
|
+
candidate.kind !== 'message'
|
|
373
|
+
|| candidate.toolLayout !== layout
|
|
374
|
+
) break;
|
|
375
|
+
run.push(candidate);
|
|
376
|
+
index += 1;
|
|
377
|
+
}
|
|
378
|
+
this.appendToolRun(result, run, layout, usedGroupIds);
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
|
|
384
|
+
super.willUpdate(changedProperties);
|
|
385
|
+
this.timelineRenderItems = this.buildTimelineRenderItems();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private appendToolRun(
|
|
389
|
+
targetArg: TTimelineRenderItem[],
|
|
390
|
+
runArg: TMessageTimelineItem[],
|
|
391
|
+
layoutArg: THarnessToolLayout,
|
|
392
|
+
usedGroupIdsArg: Set<string>,
|
|
393
|
+
): void {
|
|
394
|
+
const groupId = runArg
|
|
395
|
+
.map((item) => this.toolGroupIdByMessageId.get(item.message.id))
|
|
396
|
+
.find((candidate): candidate is string => candidate !== undefined && !usedGroupIdsArg.has(candidate))
|
|
397
|
+
?? `tool-group-${this.nextToolGroupId++}`;
|
|
398
|
+
usedGroupIdsArg.add(groupId);
|
|
399
|
+
for (const item of runArg) {
|
|
400
|
+
this.toolGroupIdByMessageId.set(item.message.id, groupId);
|
|
401
|
+
}
|
|
402
|
+
targetArg.push({
|
|
403
|
+
kind: 'tool-group',
|
|
404
|
+
key: groupId,
|
|
405
|
+
layout: layoutArg,
|
|
406
|
+
messages: runArg,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public render(): TemplateResult {
|
|
411
|
+
const items = this.timelineRenderItems;
|
|
234
412
|
return html`
|
|
235
413
|
<div
|
|
236
|
-
class
|
|
414
|
+
class=${`scroll ${this.hideScrollbar ? 'hideScrollbar' : ''} ${this.scrollFadeTop ? 'fadeTop' : ''} ${this.scrollFadeBottom ? 'fadeBottom' : ''}`}
|
|
237
415
|
@scroll=${this.handleScroll}
|
|
238
416
|
@wheel=${this.handleUserScrollIntent}
|
|
239
417
|
@touchmove=${this.handleUserScrollIntent}
|
|
418
|
+
@pointerdown=${this.handleUserScrollIntent}
|
|
419
|
+
@keydown=${this.handleUserScrollIntent}
|
|
240
420
|
>
|
|
241
421
|
<div class="scrollContent">
|
|
422
|
+
<slot name="leading"></slot>
|
|
242
423
|
${items.length
|
|
243
424
|
? html`
|
|
244
425
|
<div class="stack">
|
|
245
426
|
${repeat(
|
|
246
427
|
items,
|
|
247
428
|
(item) => item.key,
|
|
248
|
-
(item) => item.kind === '
|
|
249
|
-
?
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
.message=${item.message}
|
|
253
|
-
.showRole=${this.showRoles}
|
|
254
|
-
.showTimestamp=${this.showTimestamps}
|
|
255
|
-
.toolRegistry=${this.toolRegistry}
|
|
256
|
-
.suppressEntry=${!this.entryAnimationsEnabled}
|
|
257
|
-
></dees-harness-message>
|
|
258
|
-
`
|
|
429
|
+
(item) => item.kind === 'tool-group'
|
|
430
|
+
? this.renderToolGroup(item)
|
|
431
|
+
: item.kind === 'message'
|
|
432
|
+
? this.renderMessage(item)
|
|
259
433
|
: item.kind === 'permission'
|
|
260
434
|
? html`
|
|
261
435
|
<dees-harness-permission-card
|
|
@@ -296,6 +470,41 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
296
470
|
`;
|
|
297
471
|
}
|
|
298
472
|
|
|
473
|
+
private renderMessage(itemArg: TMessageTimelineItem): TemplateResult {
|
|
474
|
+
return html`
|
|
475
|
+
<dees-harness-message
|
|
476
|
+
data-message-id=${itemArg.message.id}
|
|
477
|
+
data-tool-layout=${itemArg.toolLayout ?? 'message'}
|
|
478
|
+
.message=${itemArg.message}
|
|
479
|
+
.showRole=${this.showRoles}
|
|
480
|
+
.showTimestamp=${this.showTimestamps}
|
|
481
|
+
.toolRegistry=${this.toolRegistry}
|
|
482
|
+
.allowSubtaskStreams=${this.allowSubtaskStreams}
|
|
483
|
+
.suppressEntry=${!this.entryAnimationsEnabled}
|
|
484
|
+
></dees-harness-message>
|
|
485
|
+
`;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private renderToolGroup(groupArg: IToolGroupTimelineItem): TemplateResult {
|
|
489
|
+
const count = groupArg.messages.length;
|
|
490
|
+
const countClass = count >= 4 ? 'toolCount4' : `toolCount${count}`;
|
|
491
|
+
return html`
|
|
492
|
+
<section
|
|
493
|
+
class="toolGroup"
|
|
494
|
+
data-tool-group-id=${groupArg.key}
|
|
495
|
+
data-tool-layout=${groupArg.layout}
|
|
496
|
+
>
|
|
497
|
+
<div class=${`toolGrid ${countClass}`}>
|
|
498
|
+
${repeat(
|
|
499
|
+
groupArg.messages,
|
|
500
|
+
(item) => item.key,
|
|
501
|
+
(item) => this.renderMessage(item),
|
|
502
|
+
)}
|
|
503
|
+
</div>
|
|
504
|
+
</section>
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
507
|
+
|
|
299
508
|
// ------------------------------------------------------------------ deltas
|
|
300
509
|
|
|
301
510
|
/**
|
|
@@ -304,51 +513,50 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
304
513
|
* idempotent; only the target element re-renders.
|
|
305
514
|
*/
|
|
306
515
|
public applyDelta(delta: THarnessStreamDelta): void {
|
|
516
|
+
if (delta.parentMessageId) {
|
|
517
|
+
if (!this.allowSubtaskStreams) return;
|
|
518
|
+
const parentMessage = this.messages.find((candidate) => candidate.id === delta.parentMessageId);
|
|
519
|
+
if (!parentMessage) return;
|
|
520
|
+
const parentElement = this.messageElement(delta.parentMessageId);
|
|
521
|
+
const { parentMessageId: _parentMessageId, ...childDelta } = delta;
|
|
522
|
+
const localDelta = childDelta as THarnessMessageStreamDelta;
|
|
523
|
+
const appliedByElement = parentElement?.applySubtaskDelta(localDelta) ?? false;
|
|
524
|
+
if (!appliedByElement && parentMessage.toolCall?.subtask?.previewState === 'ready') {
|
|
525
|
+
if (harnessApplyMessageDelta(parentMessage.toolCall.subtask.messages, localDelta)) {
|
|
526
|
+
parentElement?.requestUpdate();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
this.followAfterContentChange();
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
307
532
|
const message = this.messages.find((candidate) => candidate.id === delta.messageId);
|
|
308
533
|
if (!message) return;
|
|
309
534
|
const element = this.messageElement(delta.messageId);
|
|
310
535
|
switch (delta.type) {
|
|
311
536
|
case 'text':
|
|
312
537
|
if (element) element.appendTextDelta(delta.delta);
|
|
313
|
-
else
|
|
314
|
-
message.text += delta.delta;
|
|
315
|
-
message.streaming = true;
|
|
316
|
-
}
|
|
538
|
+
else harnessApplyMessageDelta(this.messages, delta);
|
|
317
539
|
break;
|
|
318
540
|
case 'reasoning-start':
|
|
319
541
|
if (element) element.appendReasoningDelta(delta.partId, '', delta.startedAt);
|
|
320
|
-
else
|
|
321
|
-
message.reasoning ??= [];
|
|
322
|
-
message.reasoning.push({ id: delta.partId, text: '', startedAt: delta.startedAt ?? Date.now() });
|
|
323
|
-
}
|
|
542
|
+
else harnessApplyMessageDelta(this.messages, delta);
|
|
324
543
|
break;
|
|
325
544
|
case 'reasoning':
|
|
326
545
|
if (element) element.appendReasoningDelta(delta.partId, delta.delta);
|
|
327
|
-
else
|
|
328
|
-
message.reasoning ??= [];
|
|
329
|
-
const part = message.reasoning.find((candidate) => candidate.id === delta.partId);
|
|
330
|
-
if (part) part.text += delta.delta;
|
|
331
|
-
else message.reasoning.push({ id: delta.partId, text: delta.delta, startedAt: Date.now() });
|
|
332
|
-
}
|
|
546
|
+
else harnessApplyMessageDelta(this.messages, delta);
|
|
333
547
|
break;
|
|
334
548
|
case 'reasoning-end':
|
|
335
549
|
if (element) element.endReasoning(delta.partId, delta.endedAt);
|
|
336
|
-
else
|
|
337
|
-
const part = message.reasoning?.find((candidate) => candidate.id === delta.partId);
|
|
338
|
-
if (part) part.endedAt = delta.endedAt ?? Date.now();
|
|
339
|
-
}
|
|
550
|
+
else harnessApplyMessageDelta(this.messages, delta);
|
|
340
551
|
break;
|
|
341
552
|
case 'tool-update':
|
|
342
553
|
message.toolCall = { ...(message.toolCall ?? { id: delta.tool.id, name: '', status: 'pending' }), ...delta.tool };
|
|
343
554
|
element?.requestUpdate();
|
|
555
|
+
this.requestUpdate();
|
|
344
556
|
break;
|
|
345
557
|
case 'message-end':
|
|
346
558
|
if (element) element.endStream(delta.usage, delta.error);
|
|
347
|
-
else
|
|
348
|
-
message.streaming = false;
|
|
349
|
-
if (delta.usage) message.usage = delta.usage;
|
|
350
|
-
if (delta.error) message.error = delta.error;
|
|
351
|
-
}
|
|
559
|
+
else harnessApplyMessageDelta(this.messages, delta);
|
|
352
560
|
break;
|
|
353
561
|
}
|
|
354
562
|
this.followAfterContentChange();
|
|
@@ -372,8 +580,14 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
372
580
|
}
|
|
373
581
|
|
|
374
582
|
private contentResizeObserver: ResizeObserver | null = null;
|
|
583
|
+
private observedScrollContent: Element | null = null;
|
|
584
|
+
private observedContentBlockSize: number | null = null;
|
|
585
|
+
private followFrame: number | null = null;
|
|
586
|
+
private followSecondFrame: number | null = null;
|
|
587
|
+
private entryFrame: number | null = null;
|
|
588
|
+
private entrySecondFrame: number | null = null;
|
|
375
589
|
// Intent-based following: the list stays pinned until the user explicitly
|
|
376
|
-
// moves away from the bottom (wheel/touch
|
|
590
|
+
// moves away from the bottom (wheel/touch/keyboard or a scrollbar drag), and
|
|
377
591
|
// re-pins the moment they return. Late layout growth — async markdown
|
|
378
592
|
// parses, images, entry animations — re-pins through the content
|
|
379
593
|
// ResizeObserver for as long as the user has not left the bottom, so
|
|
@@ -395,13 +609,31 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
395
609
|
this.showJumpChip = true;
|
|
396
610
|
return;
|
|
397
611
|
}
|
|
398
|
-
this.
|
|
399
|
-
//
|
|
400
|
-
|
|
612
|
+
this.repinAfterContentGrowth();
|
|
613
|
+
// Catch post-render growth; later asynchronous growth uses the observer.
|
|
614
|
+
if (this.followFrame !== null || this.followSecondFrame !== null) return;
|
|
615
|
+
this.followFrame = requestAnimationFrame(() => {
|
|
616
|
+
this.followFrame = null;
|
|
617
|
+
if (!this.isConnected) return;
|
|
618
|
+
this.repinAfterContentGrowth();
|
|
619
|
+
this.followSecondFrame = requestAnimationFrame(() => {
|
|
620
|
+
this.followSecondFrame = null;
|
|
621
|
+
if (!this.isConnected) return;
|
|
622
|
+
this.repinAfterContentGrowth();
|
|
623
|
+
});
|
|
624
|
+
});
|
|
401
625
|
}
|
|
402
626
|
|
|
403
627
|
private handleUserScrollIntent = (event: Event): void => {
|
|
628
|
+
if (event.composedPath().some(
|
|
629
|
+
(candidate) => candidate instanceof DeesHarnessMessageList && candidate !== this,
|
|
630
|
+
)) return;
|
|
404
631
|
if (event.type === 'wheel' && (event as WheelEvent).deltaY >= 0) return;
|
|
632
|
+
if (
|
|
633
|
+
event.type === 'keydown'
|
|
634
|
+
&& !['ArrowUp', 'PageUp', 'Home'].includes((event as KeyboardEvent).key)
|
|
635
|
+
) return;
|
|
636
|
+
if (event.type === 'pointerdown' && event.target !== this.scrollContainer) return;
|
|
405
637
|
this.userUnpinned = true;
|
|
406
638
|
};
|
|
407
639
|
|
|
@@ -416,21 +648,20 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
416
648
|
this.lastObservedScrollTop = container.scrollTop;
|
|
417
649
|
this.showJumpChip = false;
|
|
418
650
|
}
|
|
651
|
+
this.syncScrollEdgeFades();
|
|
419
652
|
}
|
|
420
653
|
|
|
421
654
|
private handleScroll = (): void => {
|
|
422
655
|
const container = this.scrollContainer;
|
|
423
656
|
if (!container) return;
|
|
424
657
|
const top = container.scrollTop;
|
|
658
|
+
this.syncScrollEdgeFades();
|
|
425
659
|
if (this.isPinnedToBottom) {
|
|
426
660
|
// reaching the bottom — by any means — resumes following
|
|
427
661
|
this.userUnpinned = false;
|
|
428
662
|
if (this.showJumpChip) {
|
|
429
663
|
this.showJumpChip = false;
|
|
430
664
|
}
|
|
431
|
-
} else if (top < this.lastObservedScrollTop - 1) {
|
|
432
|
-
// moved up without our doing (scrollbar drag, keyboard): stop following
|
|
433
|
-
this.userUnpinned = true;
|
|
434
665
|
}
|
|
435
666
|
this.lastObservedScrollTop = top;
|
|
436
667
|
};
|
|
@@ -440,26 +671,108 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
440
671
|
if (changedProperties.has('messages') || changedProperties.has('status') || changedProperties.has('permissions') || changedProperties.has('questions')) {
|
|
441
672
|
this.followAfterContentChange();
|
|
442
673
|
}
|
|
674
|
+
if (changedProperties.has('autoFollow')) {
|
|
675
|
+
if (this.autoFollow) {
|
|
676
|
+
this.connectContentResizeObserver();
|
|
677
|
+
this.scrollToBottom(true);
|
|
678
|
+
}
|
|
679
|
+
else this.disconnectContentResizeObserver();
|
|
680
|
+
}
|
|
681
|
+
if (
|
|
682
|
+
changedProperties.has('fadeScrollEdges')
|
|
683
|
+
|| changedProperties.has('messages')
|
|
684
|
+
|| changedProperties.has('permissions')
|
|
685
|
+
|| changedProperties.has('questions')
|
|
686
|
+
) this.syncScrollEdgeFades();
|
|
443
687
|
}
|
|
444
688
|
|
|
445
689
|
public firstUpdated(changedProperties: Map<string, any>): void {
|
|
446
690
|
super.firstUpdated(changedProperties);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
691
|
+
if (!this.isConnected) return;
|
|
692
|
+
this.connectContentResizeObserver();
|
|
693
|
+
this.repinToBottom();
|
|
694
|
+
this.syncScrollEdgeFades();
|
|
452
695
|
// let the initial batch paint without entry animations, then enable them
|
|
453
|
-
requestAnimationFrame(() => {
|
|
454
|
-
|
|
696
|
+
this.entryFrame = requestAnimationFrame(() => {
|
|
697
|
+
this.entryFrame = null;
|
|
698
|
+
if (!this.isConnected) return;
|
|
699
|
+
this.entrySecondFrame = requestAnimationFrame(() => {
|
|
700
|
+
this.entrySecondFrame = null;
|
|
701
|
+
if (!this.isConnected) return;
|
|
455
702
|
this.entryAnimationsEnabled = true;
|
|
456
703
|
});
|
|
457
704
|
});
|
|
458
705
|
}
|
|
459
706
|
|
|
460
|
-
public async
|
|
707
|
+
public async connectedCallback(): Promise<void> {
|
|
708
|
+
const connected = super.connectedCallback();
|
|
709
|
+
if (this.hasUpdated) this.connectContentResizeObserver();
|
|
710
|
+
await connected;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
private connectContentResizeObserver(): void {
|
|
714
|
+
if (!this.isConnected || !this.autoFollow) return;
|
|
715
|
+
const content = this.shadowRoot?.querySelector('.scrollContent');
|
|
716
|
+
if (!content || (this.contentResizeObserver && content === this.observedScrollContent)) return;
|
|
717
|
+
this.contentResizeObserver?.disconnect();
|
|
718
|
+
this.observedContentBlockSize = content.getBoundingClientRect().height;
|
|
719
|
+
this.contentResizeObserver = new ResizeObserver((entries) => {
|
|
720
|
+
const entry = entries.find((candidate) => candidate.target === this.observedScrollContent);
|
|
721
|
+
if (!entry) return;
|
|
722
|
+
const blockSize = entry.borderBoxSize[0]?.blockSize ?? entry.contentRect.height;
|
|
723
|
+
this.handleContentBlockSize(blockSize);
|
|
724
|
+
});
|
|
725
|
+
this.contentResizeObserver.observe(content);
|
|
726
|
+
this.observedScrollContent = content;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private repinAfterContentGrowth(): void {
|
|
730
|
+
const content = this.observedScrollContent;
|
|
731
|
+
if (!content) return;
|
|
732
|
+
this.handleContentBlockSize(content.getBoundingClientRect().height);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private handleContentBlockSize(blockSizeArg: number): void {
|
|
736
|
+
const previousBlockSize = this.observedContentBlockSize;
|
|
737
|
+
this.observedContentBlockSize = blockSizeArg;
|
|
738
|
+
// Disclosure collapse and window eviction are not new streamed content.
|
|
739
|
+
// Let the browser handle shrink instead of fighting it with repins.
|
|
740
|
+
if (previousBlockSize !== null && blockSizeArg > previousBlockSize + 0.5) {
|
|
741
|
+
this.repinToBottom();
|
|
742
|
+
}
|
|
743
|
+
this.syncScrollEdgeFades();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private syncScrollEdgeFades(): void {
|
|
747
|
+
const container = this.scrollContainer;
|
|
748
|
+
const fadeTop = Boolean(this.fadeScrollEdges && container && container.scrollTop > 1);
|
|
749
|
+
const fadeBottom = Boolean(
|
|
750
|
+
this.fadeScrollEdges
|
|
751
|
+
&& container
|
|
752
|
+
&& container.scrollHeight - container.scrollTop - container.clientHeight > 1,
|
|
753
|
+
);
|
|
754
|
+
if (this.scrollFadeTop !== fadeTop) this.scrollFadeTop = fadeTop;
|
|
755
|
+
if (this.scrollFadeBottom !== fadeBottom) this.scrollFadeBottom = fadeBottom;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private disconnectContentResizeObserver(): void {
|
|
461
759
|
this.contentResizeObserver?.disconnect();
|
|
462
760
|
this.contentResizeObserver = null;
|
|
761
|
+
this.observedScrollContent = null;
|
|
762
|
+
this.observedContentBlockSize = null;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
public async disconnectedCallback(): Promise<void> {
|
|
766
|
+
if (this.followFrame !== null) cancelAnimationFrame(this.followFrame);
|
|
767
|
+
if (this.followSecondFrame !== null) cancelAnimationFrame(this.followSecondFrame);
|
|
768
|
+
if (this.entryFrame !== null) cancelAnimationFrame(this.entryFrame);
|
|
769
|
+
if (this.entrySecondFrame !== null) cancelAnimationFrame(this.entrySecondFrame);
|
|
770
|
+
this.followFrame = null;
|
|
771
|
+
this.followSecondFrame = null;
|
|
772
|
+
this.entryFrame = null;
|
|
773
|
+
this.entrySecondFrame = null;
|
|
774
|
+
this.entryAnimationsEnabled = true;
|
|
775
|
+
this.disconnectContentResizeObserver();
|
|
463
776
|
await super.disconnectedCallback();
|
|
464
777
|
}
|
|
465
778
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { html } from '@design.estate/dees-element';
|
|
2
|
+
|
|
3
|
+
export const demoFunc = () => html`
|
|
4
|
+
<style>
|
|
5
|
+
.overflowTextDemo {
|
|
6
|
+
display: grid;
|
|
7
|
+
gap: 16px;
|
|
8
|
+
max-width: 360px;
|
|
9
|
+
margin: 40px auto;
|
|
10
|
+
padding: 24px;
|
|
11
|
+
border-radius: var(--dees-radius-xl);
|
|
12
|
+
background: var(--dees-color-bg-secondary);
|
|
13
|
+
}
|
|
14
|
+
.overflowTextDemo .label {
|
|
15
|
+
color: var(--dees-color-text-muted);
|
|
16
|
+
font-size: 11px;
|
|
17
|
+
text-transform: uppercase;
|
|
18
|
+
letter-spacing: 0.05em;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
<div class="overflowTextDemo">
|
|
22
|
+
<div class="label">short text</div>
|
|
23
|
+
<dees-harness-overflow-text>
|
|
24
|
+
Fits without a fade
|
|
25
|
+
</dees-harness-overflow-text>
|
|
26
|
+
<div class="label">overflowing text</div>
|
|
27
|
+
<dees-harness-overflow-text>
|
|
28
|
+
This long activity description reveals its hidden ending after the initial delay or on hover
|
|
29
|
+
</dees-harness-overflow-text>
|
|
30
|
+
</div>
|
|
31
|
+
`;
|