@design.estate/dees-catalog 6.8.1 → 6.9.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 +334 -45
- 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 -1
- package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +11 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +23 -2
- package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +278 -10
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.d.ts +8 -0
- package/dist_ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.js +80 -12
- package/dist_ts_web/elements/00group-harness/interfaces.d.ts +5 -0
- package/package.json +1 -1
- package/readme.md +16 -2
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +5 -0
- package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +269 -7
- package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts +83 -10
- package/ts_web/elements/00group-harness/interfaces.ts +6 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
customElement,
|
|
7
7
|
type TemplateResult,
|
|
8
8
|
property,
|
|
9
|
+
state,
|
|
9
10
|
cssManager,
|
|
10
11
|
} from '@design.estate/dees-element';
|
|
11
12
|
import { themeDefaultStyles } from '../../00theme.js';
|
|
@@ -13,6 +14,7 @@ import type {
|
|
|
13
14
|
IHarnessAttachment,
|
|
14
15
|
IHarnessComposerOption,
|
|
15
16
|
IHarnessComposerSendDetail,
|
|
17
|
+
IHarnessComposerSuggestion,
|
|
16
18
|
} from '../interfaces.js';
|
|
17
19
|
import {
|
|
18
20
|
harnessFileToBase64,
|
|
@@ -40,6 +42,8 @@ const toDropdownOptions = (values: string[]): TDropdownOption[] =>
|
|
|
40
42
|
const toLabeledDropdownOptions = (values: IHarnessComposerOption[]): TDropdownOption[] =>
|
|
41
43
|
values.map((value) => ({ option: value.label, key: value.value }));
|
|
42
44
|
|
|
45
|
+
let nextSuggestionListId = 0;
|
|
46
|
+
|
|
43
47
|
/**
|
|
44
48
|
* Prompt composer: autogrow textarea, attachments (picker / paste /
|
|
45
49
|
* drag-drop with per-file and total size limits), optional account, model,
|
|
@@ -47,7 +51,7 @@ const toLabeledDropdownOptions = (values: IHarnessComposerOption[]): TDropdownOp
|
|
|
47
51
|
* additional button and Send stays available for queueing; an empty prompt
|
|
48
52
|
* with queued messages offers "Steer now".
|
|
49
53
|
*
|
|
50
|
-
* Controlled component: `value` and `
|
|
54
|
+
* Controlled component: `value`, `attachments`, and `suggestions` are owned by the host;
|
|
51
55
|
* the composer emits `harness-input`, `harness-attachments-change`,
|
|
52
56
|
* `harness-attachment-error`, `harness-account-change`, `harness-model-change`, `harness-send`,
|
|
53
57
|
* `harness-steer`, and `harness-abort` (all bubbling + composed).
|
|
@@ -91,6 +95,21 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
91
95
|
@property({ attribute: false })
|
|
92
96
|
accessor attachments: IHarnessAttachment[] = [];
|
|
93
97
|
|
|
98
|
+
@property({ attribute: false })
|
|
99
|
+
accessor suggestions: IHarnessComposerSuggestion[] = [];
|
|
100
|
+
|
|
101
|
+
@state()
|
|
102
|
+
private accessor activeSuggestionIndex: number = -1;
|
|
103
|
+
|
|
104
|
+
@state()
|
|
105
|
+
private accessor suggestionsDismissed: boolean = false;
|
|
106
|
+
|
|
107
|
+
@state()
|
|
108
|
+
private accessor promptFocused: boolean = false;
|
|
109
|
+
|
|
110
|
+
private composing: boolean = false;
|
|
111
|
+
private readonly suggestionListId = `dees-harness-composer-suggestions-${++nextSuggestionListId}`;
|
|
112
|
+
|
|
94
113
|
@property({ type: Number })
|
|
95
114
|
accessor maxAttachmentBytes: number = harnessMaxAttachmentBytes;
|
|
96
115
|
|
|
@@ -141,6 +160,69 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
141
160
|
container-type: inline-size;
|
|
142
161
|
}
|
|
143
162
|
|
|
163
|
+
.composerShell {
|
|
164
|
+
position: relative;
|
|
165
|
+
min-width: 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.suggestionList {
|
|
169
|
+
position: absolute;
|
|
170
|
+
z-index: 2;
|
|
171
|
+
inset-inline: 0;
|
|
172
|
+
bottom: calc(100% + var(--dees-spacing-xs));
|
|
173
|
+
box-sizing: border-box;
|
|
174
|
+
max-height: min(240px, 35dvh);
|
|
175
|
+
margin: 0;
|
|
176
|
+
padding: var(--dees-spacing-xs);
|
|
177
|
+
overflow-x: hidden;
|
|
178
|
+
overflow-y: auto;
|
|
179
|
+
overscroll-behavior: contain;
|
|
180
|
+
list-style: none;
|
|
181
|
+
border: 1px solid var(--dees-color-border-subtle);
|
|
182
|
+
border-radius: var(--dees-radius-xl);
|
|
183
|
+
corner-shape: var(--dees-corner-shape);
|
|
184
|
+
background: var(--dees-color-bg-primary);
|
|
185
|
+
box-shadow: var(--dees-shadow-up-sm);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.suggestionOption {
|
|
189
|
+
display: grid;
|
|
190
|
+
align-content: center;
|
|
191
|
+
box-sizing: border-box;
|
|
192
|
+
min-width: 0;
|
|
193
|
+
min-height: 44px;
|
|
194
|
+
padding: var(--dees-spacing-xs) var(--dees-spacing-sm);
|
|
195
|
+
border-radius: var(--dees-radius-lg);
|
|
196
|
+
corner-shape: var(--dees-corner-shape);
|
|
197
|
+
color: var(--dees-color-text-primary);
|
|
198
|
+
cursor: pointer;
|
|
199
|
+
touch-action: manipulation;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.suggestionOption:hover,
|
|
203
|
+
.suggestionOption.active {
|
|
204
|
+
background: var(--dees-color-fill-secondary);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.suggestionLabel {
|
|
208
|
+
min-width: 0;
|
|
209
|
+
overflow: hidden;
|
|
210
|
+
font-size: var(--dees-font-control-size, 13px);
|
|
211
|
+
font-weight: 600;
|
|
212
|
+
line-height: 1.35;
|
|
213
|
+
text-overflow: ellipsis;
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.suggestionDescription {
|
|
218
|
+
min-width: 0;
|
|
219
|
+
margin-top: 2px;
|
|
220
|
+
color: var(--dees-color-text-secondary);
|
|
221
|
+
font-size: var(--dees-font-caption1-size, 11px);
|
|
222
|
+
line-height: 1.35;
|
|
223
|
+
overflow-wrap: anywhere;
|
|
224
|
+
}
|
|
225
|
+
|
|
144
226
|
.composer {
|
|
145
227
|
display: grid;
|
|
146
228
|
gap: var(--dees-spacing-sm);
|
|
@@ -365,18 +447,31 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
365
447
|
|
|
366
448
|
public clear(): void {
|
|
367
449
|
this.value = '';
|
|
450
|
+
this.resetSuggestionInteraction();
|
|
368
451
|
this.emitInput('');
|
|
369
452
|
this.autogrow();
|
|
370
453
|
}
|
|
371
454
|
|
|
455
|
+
public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
|
|
456
|
+
super.willUpdate(changedProperties);
|
|
457
|
+
if (changedProperties.has('suggestions')) this.resetSuggestionInteraction();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
public updated(changedProperties: Map<PropertyKey, unknown>): void {
|
|
461
|
+
super.updated(changedProperties);
|
|
462
|
+
this.syncTextareaSuggestionAria();
|
|
463
|
+
}
|
|
464
|
+
|
|
372
465
|
public render(): TemplateResult {
|
|
373
466
|
return html`
|
|
374
|
-
<div
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
467
|
+
<div class="composerShell">
|
|
468
|
+
${this.suggestionsVisible ? this.renderSuggestions() : ''}
|
|
469
|
+
<div
|
|
470
|
+
class="composer"
|
|
471
|
+
@dragover=${this.handleDragOver}
|
|
472
|
+
@dragleave=${this.handleDragLeave}
|
|
473
|
+
@drop=${this.handleDrop}
|
|
474
|
+
>
|
|
380
475
|
<input
|
|
381
476
|
class="fileInput"
|
|
382
477
|
type="file"
|
|
@@ -392,11 +487,16 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
392
487
|
: ''}
|
|
393
488
|
<textarea
|
|
394
489
|
.value=${this.value}
|
|
490
|
+
aria-label="Message"
|
|
395
491
|
placeholder=${this.placeholder}
|
|
396
492
|
?disabled=${this.disabled}
|
|
397
493
|
rows="1"
|
|
398
494
|
@input=${this.handleTextInput}
|
|
399
495
|
@keydown=${this.handleKeydown}
|
|
496
|
+
@focus=${this.handlePromptFocus}
|
|
497
|
+
@blur=${this.handlePromptBlur}
|
|
498
|
+
@compositionstart=${this.handleCompositionStart}
|
|
499
|
+
@compositionend=${this.handleCompositionEnd}
|
|
400
500
|
@paste=${this.handlePaste}
|
|
401
501
|
></textarea>
|
|
402
502
|
<div class="controlsRow">
|
|
@@ -507,11 +607,42 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
507
607
|
Send
|
|
508
608
|
</dees-button>
|
|
509
609
|
`}
|
|
610
|
+
</div>
|
|
510
611
|
</div>
|
|
511
612
|
</div>
|
|
512
613
|
`;
|
|
513
614
|
}
|
|
514
615
|
|
|
616
|
+
private renderSuggestions(): TemplateResult {
|
|
617
|
+
return html`
|
|
618
|
+
<ul
|
|
619
|
+
id=${this.suggestionListId}
|
|
620
|
+
class="suggestionList"
|
|
621
|
+
role="listbox"
|
|
622
|
+
aria-label="Suggestions"
|
|
623
|
+
@pointerdown=${this.handleSuggestionPointerDown}
|
|
624
|
+
>
|
|
625
|
+
${this.suggestions.map((suggestion, index) => html`
|
|
626
|
+
<li
|
|
627
|
+
id=${this.suggestionOptionId(index)}
|
|
628
|
+
class=${`suggestionOption ${index === this.activeSuggestionIndex ? 'active' : ''}`}
|
|
629
|
+
role="option"
|
|
630
|
+
aria-selected=${String(index === this.activeSuggestionIndex)}
|
|
631
|
+
@pointerenter=${() => {
|
|
632
|
+
this.activeSuggestionIndex = index;
|
|
633
|
+
}}
|
|
634
|
+
@click=${() => void this.selectSuggestion(index)}
|
|
635
|
+
>
|
|
636
|
+
<span class="suggestionLabel">${suggestion.label}</span>
|
|
637
|
+
${suggestion.description
|
|
638
|
+
? html`<span class="suggestionDescription">${suggestion.description}</span>`
|
|
639
|
+
: ''}
|
|
640
|
+
</li>
|
|
641
|
+
`)}
|
|
642
|
+
</ul>
|
|
643
|
+
`;
|
|
644
|
+
}
|
|
645
|
+
|
|
515
646
|
private renderChip(attachment: IHarnessAttachment): TemplateResult {
|
|
516
647
|
const imageUrl = attachment.kind === 'image' && attachment.dataBase64
|
|
517
648
|
? `data:${attachment.mediaType};base64,${attachment.dataBase64}`
|
|
@@ -541,16 +672,42 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
541
672
|
|
|
542
673
|
private handleTextInput = (event: Event): void => {
|
|
543
674
|
this.value = (event.currentTarget as HTMLTextAreaElement).value;
|
|
675
|
+
this.resetSuggestionInteraction();
|
|
544
676
|
this.emitInput(this.value);
|
|
545
677
|
this.autogrow();
|
|
546
678
|
};
|
|
547
679
|
|
|
548
680
|
private handleKeydown = (event: KeyboardEvent): void => {
|
|
681
|
+
if (event.isComposing || this.composing) return;
|
|
549
682
|
if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
|
|
550
683
|
event.preventDefault();
|
|
551
684
|
this.send();
|
|
552
685
|
return;
|
|
553
686
|
}
|
|
687
|
+
if (this.suggestionsVisible) {
|
|
688
|
+
if (event.key === 'Escape') {
|
|
689
|
+
event.preventDefault();
|
|
690
|
+
this.suggestionsDismissed = true;
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (!event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) {
|
|
694
|
+
if (event.key === 'ArrowDown') {
|
|
695
|
+
event.preventDefault();
|
|
696
|
+
this.moveActiveSuggestion(1);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (event.key === 'ArrowUp') {
|
|
700
|
+
event.preventDefault();
|
|
701
|
+
this.moveActiveSuggestion(-1);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (event.key === 'Enter') {
|
|
705
|
+
event.preventDefault();
|
|
706
|
+
void this.selectSuggestion(this.activeSuggestionIndex);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
554
711
|
// Shift+Tab cycles the agent mode without leaving the prompt
|
|
555
712
|
if (event.key === 'Tab' && event.shiftKey && this.modeOptions.length) {
|
|
556
713
|
event.preventDefault();
|
|
@@ -561,6 +718,111 @@ export class DeesHarnessComposer extends DeesElement {
|
|
|
561
718
|
}
|
|
562
719
|
};
|
|
563
720
|
|
|
721
|
+
private handleCompositionStart = (): void => {
|
|
722
|
+
this.composing = true;
|
|
723
|
+
};
|
|
724
|
+
|
|
725
|
+
private handleCompositionEnd = (): void => {
|
|
726
|
+
this.composing = false;
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
private handlePromptFocus = (): void => {
|
|
730
|
+
this.promptFocused = true;
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
private handlePromptBlur = (): void => {
|
|
734
|
+
this.promptFocused = false;
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
private handleSuggestionPointerDown = (event: PointerEvent): void => {
|
|
738
|
+
event.preventDefault();
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
private resetSuggestionInteraction(): void {
|
|
742
|
+
this.activeSuggestionIndex = this.suggestions.length ? 0 : -1;
|
|
743
|
+
this.suggestionsDismissed = false;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private get suggestionsVisible(): boolean {
|
|
747
|
+
return this.promptFocused
|
|
748
|
+
&& !this.disabled
|
|
749
|
+
&& !this.suggestionsDismissed
|
|
750
|
+
&& this.suggestions.length > 0;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
private suggestionOptionId(indexArg: number): string {
|
|
754
|
+
return `${this.suggestionListId}-option-${indexArg}`;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
private syncTextareaSuggestionAria(): void {
|
|
758
|
+
const textarea = this.shadowRoot?.querySelector<HTMLTextAreaElement>('textarea');
|
|
759
|
+
if (!textarea) return;
|
|
760
|
+
const suggestionsAvailable = !this.disabled && this.suggestions.length > 0;
|
|
761
|
+
if (suggestionsAvailable) {
|
|
762
|
+
textarea.setAttribute('aria-autocomplete', 'list');
|
|
763
|
+
textarea.setAttribute('aria-haspopup', 'listbox');
|
|
764
|
+
} else {
|
|
765
|
+
textarea.removeAttribute('aria-autocomplete');
|
|
766
|
+
textarea.removeAttribute('aria-haspopup');
|
|
767
|
+
}
|
|
768
|
+
if (this.suggestionsVisible) {
|
|
769
|
+
textarea.setAttribute('aria-controls', this.suggestionListId);
|
|
770
|
+
} else {
|
|
771
|
+
textarea.removeAttribute('aria-controls');
|
|
772
|
+
}
|
|
773
|
+
if (
|
|
774
|
+
this.suggestionsVisible
|
|
775
|
+
&& this.activeSuggestionIndex >= 0
|
|
776
|
+
&& this.activeSuggestionIndex < this.suggestions.length
|
|
777
|
+
) {
|
|
778
|
+
textarea.setAttribute(
|
|
779
|
+
'aria-activedescendant',
|
|
780
|
+
this.suggestionOptionId(this.activeSuggestionIndex),
|
|
781
|
+
);
|
|
782
|
+
} else {
|
|
783
|
+
textarea.removeAttribute('aria-activedescendant');
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
private moveActiveSuggestion(offsetArg: -1 | 1): void {
|
|
788
|
+
if (!this.suggestions.length) return;
|
|
789
|
+
this.activeSuggestionIndex = this.activeSuggestionIndex < 0
|
|
790
|
+
? (offsetArg === 1 ? 0 : this.suggestions.length - 1)
|
|
791
|
+
: (this.activeSuggestionIndex + offsetArg + this.suggestions.length) % this.suggestions.length;
|
|
792
|
+
void this.scrollActiveSuggestionIntoView();
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private async scrollActiveSuggestionIntoView(): Promise<void> {
|
|
796
|
+
await this.updateComplete;
|
|
797
|
+
const list = this.shadowRoot?.getElementById(this.suggestionListId);
|
|
798
|
+
const option = this.shadowRoot?.getElementById(
|
|
799
|
+
this.suggestionOptionId(this.activeSuggestionIndex),
|
|
800
|
+
);
|
|
801
|
+
if (!list || !option) return;
|
|
802
|
+
const optionTop = option.offsetTop;
|
|
803
|
+
const optionBottom = optionTop + option.offsetHeight;
|
|
804
|
+
if (optionTop < list.scrollTop) {
|
|
805
|
+
list.scrollTop = optionTop;
|
|
806
|
+
} else if (optionBottom > list.scrollTop + list.clientHeight) {
|
|
807
|
+
list.scrollTop = optionBottom - list.clientHeight;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
private async selectSuggestion(indexArg: number): Promise<void> {
|
|
812
|
+
if (this.disabled) return;
|
|
813
|
+
const suggestion = this.suggestions[indexArg];
|
|
814
|
+
if (!suggestion) return;
|
|
815
|
+
this.value = suggestion.value;
|
|
816
|
+
this.suggestionsDismissed = true;
|
|
817
|
+
this.emitInput(this.value);
|
|
818
|
+
await this.updateComplete;
|
|
819
|
+
const textarea = this.shadowRoot?.querySelector<HTMLTextAreaElement>('textarea');
|
|
820
|
+
if (!textarea) return;
|
|
821
|
+
textarea.focus();
|
|
822
|
+
textarea.setSelectionRange(this.value.length, this.value.length);
|
|
823
|
+
this.autogrow();
|
|
824
|
+
}
|
|
825
|
+
|
|
564
826
|
private autogrow(): void {
|
|
565
827
|
const textarea = this.shadowRoot?.querySelector<HTMLTextAreaElement>('textarea');
|
|
566
828
|
if (!textarea) return;
|
package/ts_web/elements/00group-harness/dees-harness-message-list/dees-harness-message-list.ts
CHANGED
|
@@ -189,6 +189,10 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
189
189
|
private lastVirtualViewportWidth = 0;
|
|
190
190
|
private pendingVirtualAnchor: { key: string; offset: number } | undefined;
|
|
191
191
|
private pendingFullAnchor: { key: string; offset: number } | undefined;
|
|
192
|
+
private activeFullAnchor: { key: string; offset: number } | undefined;
|
|
193
|
+
private fullAnchorRestoreGeneration = 0;
|
|
194
|
+
private fullAnchorRestoreFrame: number | null = null;
|
|
195
|
+
private fullAnchorRestoreSecondFrame: number | null = null;
|
|
192
196
|
private measurementAnchor: { key: string; offset: number } | undefined;
|
|
193
197
|
private pendingVirtualRepin = false;
|
|
194
198
|
private virtualizationEnabled = false;
|
|
@@ -534,17 +538,23 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
534
538
|
const container = this.scrollContainer;
|
|
535
539
|
const preserveAnchor = this.userUnpinned || !this.autoFollow;
|
|
536
540
|
const anchor = container && preserveAnchor
|
|
537
|
-
? this.captureCurrentAnchor(container)
|
|
541
|
+
? (this.activeFullAnchor ?? this.captureCurrentAnchor(container))
|
|
538
542
|
: undefined;
|
|
543
|
+
const anchorContentBoundaryKey = anchor
|
|
544
|
+
? this.lastContentKeyForTimelineItem(anchor.key)
|
|
545
|
+
: undefined;
|
|
546
|
+
this.cancelFullAnchorRestore();
|
|
539
547
|
const nextItems = this.buildTimelineRenderItems();
|
|
540
548
|
const retainedKeys = new Set(nextItems.map((item) => item.key));
|
|
541
|
-
const contentKeys =
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
];
|
|
549
|
+
const contentKeys = nextItems.flatMap((item) => this.contentKeysForTimelineItem(item));
|
|
550
|
+
const anchorContentBoundaryIndex = anchorContentBoundaryKey
|
|
551
|
+
? contentKeys.indexOf(anchorContentBoundaryKey)
|
|
552
|
+
: -1;
|
|
546
553
|
this.currentNewContentKeys = new Set(
|
|
547
|
-
contentKeys.filter((key) =>
|
|
554
|
+
contentKeys.filter((key, index) => (
|
|
555
|
+
!this.knownContentKeys.has(key)
|
|
556
|
+
&& (anchorContentBoundaryIndex < 0 || index > anchorContentBoundaryIndex)
|
|
557
|
+
)),
|
|
548
558
|
);
|
|
549
559
|
this.knownContentKeys.clear();
|
|
550
560
|
for (const key of contentKeys) this.knownContentKeys.add(key);
|
|
@@ -622,6 +632,18 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
622
632
|
}
|
|
623
633
|
}
|
|
624
634
|
|
|
635
|
+
private contentKeysForTimelineItem(itemArg: TTimelineRenderItem): string[] {
|
|
636
|
+
return itemArg.kind === 'tool-group'
|
|
637
|
+
? itemArg.messages.map((item) => item.key)
|
|
638
|
+
: [itemArg.key];
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
private lastContentKeyForTimelineItem(keyArg: string): string | undefined {
|
|
642
|
+
const item = this.timelineRenderItems.find((candidate) => candidate.key === keyArg);
|
|
643
|
+
if (!item) return undefined;
|
|
644
|
+
return item.kind === 'tool-group' ? item.messages.at(-1)?.key : item.key;
|
|
645
|
+
}
|
|
646
|
+
|
|
625
647
|
private pruneViewState(
|
|
626
648
|
stateArg: IHarnessTranscriptViewState,
|
|
627
649
|
messageIdsArg: string[],
|
|
@@ -1049,12 +1071,21 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1049
1071
|
if (event.composedPath().some(
|
|
1050
1072
|
(candidate) => candidate instanceof DeesHarnessMessageList && candidate !== this,
|
|
1051
1073
|
)) return;
|
|
1052
|
-
if (event.type === 'wheel'
|
|
1074
|
+
if (event.type === 'wheel') {
|
|
1075
|
+
this.cancelFullAnchorRestore();
|
|
1076
|
+
if ((event as WheelEvent).deltaY >= 0) return;
|
|
1077
|
+
}
|
|
1053
1078
|
if (
|
|
1054
1079
|
event.type === 'keydown'
|
|
1055
1080
|
&& !['ArrowUp', 'PageUp', 'Home'].includes((event as KeyboardEvent).key)
|
|
1056
|
-
)
|
|
1081
|
+
) {
|
|
1082
|
+
if (['ArrowDown', 'PageDown', 'End', ' '].includes((event as KeyboardEvent).key)) {
|
|
1083
|
+
this.cancelFullAnchorRestore();
|
|
1084
|
+
}
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1057
1087
|
if (event.type === 'pointerdown' && event.target !== this.scrollContainer) return;
|
|
1088
|
+
this.cancelFullAnchorRestore();
|
|
1058
1089
|
this.userUnpinned = true;
|
|
1059
1090
|
this.measurementAnchor = undefined;
|
|
1060
1091
|
};
|
|
@@ -1063,6 +1094,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1063
1094
|
const container = this.scrollContainer;
|
|
1064
1095
|
if (!container) return;
|
|
1065
1096
|
if (force) {
|
|
1097
|
+
this.cancelFullAnchorRestore();
|
|
1066
1098
|
this.userUnpinned = false;
|
|
1067
1099
|
this.measurementAnchor = undefined;
|
|
1068
1100
|
}
|
|
@@ -1225,7 +1257,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1225
1257
|
} else {
|
|
1226
1258
|
this.disconnectVirtualObservers();
|
|
1227
1259
|
if (this.pendingFullAnchor) {
|
|
1228
|
-
this.
|
|
1260
|
+
this.scheduleFullAnchorRestore(this.scrollContainer!, this.pendingFullAnchor);
|
|
1229
1261
|
this.pendingFullAnchor = undefined;
|
|
1230
1262
|
}
|
|
1231
1263
|
}
|
|
@@ -1408,6 +1440,46 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1408
1440
|
+ anchorArg.offset;
|
|
1409
1441
|
}
|
|
1410
1442
|
|
|
1443
|
+
private scheduleFullAnchorRestore(
|
|
1444
|
+
containerArg: HTMLElement,
|
|
1445
|
+
anchorArg: { key: string; offset: number },
|
|
1446
|
+
): void {
|
|
1447
|
+
this.cancelFullAnchorRestore();
|
|
1448
|
+
const generation = this.fullAnchorRestoreGeneration;
|
|
1449
|
+
this.activeFullAnchor = anchorArg;
|
|
1450
|
+
const restore = (): boolean => {
|
|
1451
|
+
if (
|
|
1452
|
+
generation !== this.fullAnchorRestoreGeneration
|
|
1453
|
+
|| !this.isConnected
|
|
1454
|
+
|| this.scrollContainer !== containerArg
|
|
1455
|
+
) return false;
|
|
1456
|
+
this.restoreFullAnchor(containerArg, anchorArg);
|
|
1457
|
+
return true;
|
|
1458
|
+
};
|
|
1459
|
+
restore();
|
|
1460
|
+
queueMicrotask(() => {
|
|
1461
|
+
if (!restore()) return;
|
|
1462
|
+
this.fullAnchorRestoreFrame = requestAnimationFrame(() => {
|
|
1463
|
+
this.fullAnchorRestoreFrame = null;
|
|
1464
|
+
if (!restore()) return;
|
|
1465
|
+
this.fullAnchorRestoreSecondFrame = requestAnimationFrame(() => {
|
|
1466
|
+
this.fullAnchorRestoreSecondFrame = null;
|
|
1467
|
+
if (!restore()) return;
|
|
1468
|
+
this.activeFullAnchor = undefined;
|
|
1469
|
+
});
|
|
1470
|
+
});
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
private cancelFullAnchorRestore(): void {
|
|
1475
|
+
this.fullAnchorRestoreGeneration += 1;
|
|
1476
|
+
if (this.fullAnchorRestoreFrame !== null) cancelAnimationFrame(this.fullAnchorRestoreFrame);
|
|
1477
|
+
if (this.fullAnchorRestoreSecondFrame !== null) cancelAnimationFrame(this.fullAnchorRestoreSecondFrame);
|
|
1478
|
+
this.fullAnchorRestoreFrame = null;
|
|
1479
|
+
this.fullAnchorRestoreSecondFrame = null;
|
|
1480
|
+
this.activeFullAnchor = undefined;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1411
1483
|
private virtualLocalScrollTop(containerArg: HTMLElement): number {
|
|
1412
1484
|
const stack = this.shadowRoot?.querySelector<HTMLElement>('.virtualStack');
|
|
1413
1485
|
return Math.max(0, containerArg.scrollTop - (stack?.offsetTop ?? 0));
|
|
@@ -1680,6 +1752,7 @@ export class DeesHarnessMessageList extends DeesElement {
|
|
|
1680
1752
|
|
|
1681
1753
|
public async disconnectedCallback(): Promise<void> {
|
|
1682
1754
|
this.disconnectSelectionChangeListener();
|
|
1755
|
+
this.cancelFullAnchorRestore();
|
|
1683
1756
|
if (this.followFrame !== null) cancelAnimationFrame(this.followFrame);
|
|
1684
1757
|
if (this.followSecondFrame !== null) cancelAnimationFrame(this.followSecondFrame);
|
|
1685
1758
|
if (this.entryFrame !== null) cancelAnimationFrame(this.entryFrame);
|
|
@@ -464,6 +464,12 @@ export interface IHarnessComposerOption {
|
|
|
464
464
|
value: string;
|
|
465
465
|
}
|
|
466
466
|
|
|
467
|
+
export interface IHarnessComposerSuggestion {
|
|
468
|
+
label: string;
|
|
469
|
+
value: string;
|
|
470
|
+
description?: string;
|
|
471
|
+
}
|
|
472
|
+
|
|
467
473
|
export interface IHarnessAccountChangeDetail {
|
|
468
474
|
account: string;
|
|
469
475
|
}
|