@design.estate/dees-catalog 6.8.2 → 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 +271 -35
- 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/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/interfaces.ts +6 -0
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
IHarnessAccountChangeDetail,
|
|
15
15
|
IHarnessAttachment,
|
|
16
16
|
IHarnessComposerOption,
|
|
17
|
+
IHarnessComposerSuggestion,
|
|
17
18
|
IHarnessMessage,
|
|
18
19
|
IHarnessPermissionRequest,
|
|
19
20
|
IHarnessQuestionRequest,
|
|
@@ -154,6 +155,9 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
154
155
|
@property({ attribute: false })
|
|
155
156
|
accessor attachments: IHarnessAttachment[] = [];
|
|
156
157
|
|
|
158
|
+
@property({ attribute: false })
|
|
159
|
+
accessor suggestions: IHarnessComposerSuggestion[] = [];
|
|
160
|
+
|
|
157
161
|
@property({ type: Number })
|
|
158
162
|
accessor maxAttachmentCount: number = Number.POSITIVE_INFINITY;
|
|
159
163
|
|
|
@@ -659,6 +663,7 @@ export class DeesHarnessChat extends DeesElement {
|
|
|
659
663
|
docked
|
|
660
664
|
.value=${this.composerValue}
|
|
661
665
|
.attachments=${this.attachments}
|
|
666
|
+
.suggestions=${this.suggestions}
|
|
662
667
|
.maxAttachmentCount=${this.maxAttachmentCount}
|
|
663
668
|
.maxAttachmentBytes=${this.maxAttachmentBytes}
|
|
664
669
|
.maxTotalAttachmentBytes=${this.maxTotalAttachmentBytes}
|
|
@@ -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;
|
|
@@ -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
|
}
|