@kodaris/krubble-app-components 1.0.70 → 1.0.72
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/chatbot.d.ts +137 -26
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +881 -206
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +878 -206
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +563 -175
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +878 -206
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +579 -191
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -10,7 +10,10 @@ import { classMap } from 'lit/directives/class-map.js';
|
|
|
10
10
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
11
11
|
import { repeat } from 'lit/directives/repeat.js';
|
|
12
12
|
import { marked } from 'marked';
|
|
13
|
+
// Importing these named exports also registers their custom elements
|
|
14
|
+
// (kr-dialog, kr-tab-group, kr-tab, kr-text-field) used by the browse dialog.
|
|
13
15
|
import { KRFilePreview } from '@kodaris/krubble-components';
|
|
16
|
+
import { KRHttp } from '@kodaris/krubble-http';
|
|
14
17
|
/** Image MIME types the chatbot accepts (matches what Bedrock can ingest). */
|
|
15
18
|
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
16
19
|
/**
|
|
@@ -33,7 +36,7 @@ const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/web
|
|
|
33
36
|
* ## Usage
|
|
34
37
|
* ```html
|
|
35
38
|
* <kr-chatbot
|
|
36
|
-
*
|
|
39
|
+
* label="AI Assistant"
|
|
37
40
|
* .send=${(params) => {
|
|
38
41
|
* return fetch('/api/ai/chat/stream', {
|
|
39
42
|
* method: 'POST',
|
|
@@ -76,16 +79,25 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
76
79
|
/**
|
|
77
80
|
* Callback function for sending messages. Receives the user's message and
|
|
78
81
|
* current conversation history. Must return a Response with a streaming SSE body.
|
|
82
|
+
*
|
|
83
|
+
* Optional — when not set, the chatbot uses its built-in default that streams
|
|
84
|
+
* from the Kodaris Bedrock conversation endpoints. Set this to override.
|
|
79
85
|
*/
|
|
80
86
|
this.send = null;
|
|
81
87
|
/**
|
|
82
88
|
* Callback function called when the user clears the conversation.
|
|
83
89
|
* Must return a Promise — messages are only cleared if it resolves.
|
|
90
|
+
*
|
|
91
|
+
* Optional — when not set, the chatbot uses its built-in default that clears
|
|
92
|
+
* the Kodaris Bedrock conversation state. Set this to override.
|
|
84
93
|
*/
|
|
85
94
|
this.clear = null;
|
|
86
95
|
/**
|
|
87
96
|
* Callback function called on init to load existing conversation messages.
|
|
88
97
|
* Must return a Promise that resolves with an array of KRChatbotMessage.
|
|
98
|
+
*
|
|
99
|
+
* Optional — when not set, the chatbot uses its built-in default that loads
|
|
100
|
+
* existing messages from the Kodaris Bedrock conversation. Set this to override.
|
|
89
101
|
*/
|
|
90
102
|
this.load = null;
|
|
91
103
|
/**
|
|
@@ -94,17 +106,19 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
94
106
|
*/
|
|
95
107
|
this.messages = [];
|
|
96
108
|
/**
|
|
97
|
-
* Header
|
|
109
|
+
* Header label text.
|
|
98
110
|
*/
|
|
99
|
-
this.
|
|
100
|
-
/**
|
|
101
|
-
* Header subtitle text (shown below title with a green status dot).
|
|
102
|
-
*/
|
|
103
|
-
this.subtitle = '';
|
|
111
|
+
this.label = 'Kat';
|
|
104
112
|
/**
|
|
105
113
|
* Input placeholder text.
|
|
106
114
|
*/
|
|
107
115
|
this.placeholder = 'Type a message...';
|
|
116
|
+
/**
|
|
117
|
+
* Skills the user can invoke by typing `/` in the composer. Selecting one
|
|
118
|
+
* inserts `/name ` into the message. The `/` menu shows the first 15 (filtered
|
|
119
|
+
* as the user types); "Browse all skills" opens a dialog with the full list.
|
|
120
|
+
*/
|
|
121
|
+
this.skills = [];
|
|
108
122
|
/**
|
|
109
123
|
* Whether the chat panel is open. Only relevant in floating mode.
|
|
110
124
|
*/
|
|
@@ -115,15 +129,39 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
115
129
|
* then driven via the `open()` / `close()` / `toggle()` methods.
|
|
116
130
|
*/
|
|
117
131
|
this.hideToggle = false;
|
|
132
|
+
/**
|
|
133
|
+
* Layout mode.
|
|
134
|
+
* - `'floating'` (default): the chatbot is a fixed bottom-right widget with a
|
|
135
|
+
* toggle button, and the panel can be dragged and resized. This is the
|
|
136
|
+
* traditional chat-widget appearance.
|
|
137
|
+
* - `'inline'`: the chatbot drops its fixed positioning and fills its parent
|
|
138
|
+
* container (100% width/height, no border radius/shadow, no toggle, no
|
|
139
|
+
* drag/resize). The panel is always open. Use this to embed the chat as a
|
|
140
|
+
* full page or panel region.
|
|
141
|
+
*/
|
|
142
|
+
this.variant = 'floating';
|
|
118
143
|
// --- @state (internal) ---
|
|
119
144
|
this._inputValue = '';
|
|
120
145
|
this._isStreaming = false;
|
|
121
|
-
this._statusText = '';
|
|
122
146
|
this._error = '';
|
|
123
147
|
/** Images staged in the composer, before the message is sent. */
|
|
124
|
-
this.
|
|
148
|
+
this._pendingInternalFiles = [];
|
|
125
149
|
/** True while a file is being dragged over the panel (shows the drop overlay). */
|
|
126
150
|
this._isDraggingFile = false;
|
|
151
|
+
/** Whether the `/` skill menu is open above the composer. */
|
|
152
|
+
this._skillMenuOpened = false;
|
|
153
|
+
/** Current `/` query (text after the slash), used to filter the skill menu. */
|
|
154
|
+
this._skillQuery = '';
|
|
155
|
+
/** Highlighted item in the skill menu for keyboard navigation (-1 = none). */
|
|
156
|
+
this._skillHighlighted = 0;
|
|
157
|
+
/** Index of the message whose copy button just flashed "Copied" (-1 = none). */
|
|
158
|
+
this._copiedIndex = -1;
|
|
159
|
+
/** Whether the "Browse all skills" dialog is open. */
|
|
160
|
+
this._skillBrowseOpened = false;
|
|
161
|
+
/** Active tab in the browse dialog. */
|
|
162
|
+
this._skillBrowseSource = 'org';
|
|
163
|
+
/** Search text in the browse dialog. */
|
|
164
|
+
this._skillBrowseQuery = '';
|
|
127
165
|
this._handleMouseMove = (e) => {
|
|
128
166
|
if (this._isDragging) {
|
|
129
167
|
let newLeft = this._dragStartLeft + (e.clientX - this._dragStartX);
|
|
@@ -185,12 +223,27 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
185
223
|
// --- Lifecycle ---
|
|
186
224
|
connectedCallback() {
|
|
187
225
|
super.connectedCallback();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
// Default to loading existing messages from the Kodaris Bedrock conversation
|
|
227
|
+
// when the consumer hasn't provided their own `load`. The endpoint returns
|
|
228
|
+
// messages already in KRChatbotMessage shape ({ type, content, images }), so
|
|
229
|
+
// they pass straight through.
|
|
230
|
+
if (!this.load) {
|
|
231
|
+
this.load = () => KRHttp.fetch({
|
|
232
|
+
url: '/api/system/integration/aws/bedrock/getConversationMessages',
|
|
233
|
+
})
|
|
234
|
+
.then((r) => r.json())
|
|
235
|
+
.then((res) => res.data || []);
|
|
236
|
+
}
|
|
237
|
+
this.load().then((messages) => {
|
|
238
|
+
if (messages && messages.length) {
|
|
239
|
+
this.messages = messages;
|
|
240
|
+
}
|
|
241
|
+
}).catch(() => { });
|
|
242
|
+
// Inline mode fills its container and is always open — the floating widget's
|
|
243
|
+
// toggle/drag/resize state (including any persisted layout) doesn't apply.
|
|
244
|
+
if (this.variant === 'inline') {
|
|
245
|
+
this.opened = true;
|
|
246
|
+
return;
|
|
194
247
|
}
|
|
195
248
|
this._restoreState();
|
|
196
249
|
}
|
|
@@ -203,10 +256,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
203
256
|
document.removeEventListener('mousemove', this._handleMouseMove);
|
|
204
257
|
document.removeEventListener('mouseup', this._handleMouseUp);
|
|
205
258
|
// Release any object URLs still held by staged attachments.
|
|
206
|
-
this.
|
|
259
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
207
260
|
}
|
|
208
261
|
updated(changed) {
|
|
209
|
-
if (changed.has('messages')
|
|
262
|
+
if (changed.has('messages')) {
|
|
210
263
|
if (!this._userHasScrolledUp && !this._isStreaming && this._messagesEl) {
|
|
211
264
|
requestAnimationFrame(() => {
|
|
212
265
|
if (this._messagesEl) {
|
|
@@ -222,6 +275,40 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
222
275
|
}
|
|
223
276
|
}, 200);
|
|
224
277
|
}
|
|
278
|
+
// Position the fixed skill menu above the composer, following the krubble
|
|
279
|
+
// select-field pattern (fixed element positioned from the trigger's rect).
|
|
280
|
+
if (this._skillMenuOpened) {
|
|
281
|
+
this._positionSkillMenu();
|
|
282
|
+
}
|
|
283
|
+
// Keep the keyboard-highlighted skill scrolled into view.
|
|
284
|
+
if (changed.has('_skillHighlighted') && this._skillMenuOpened) {
|
|
285
|
+
this._skillMenuEl
|
|
286
|
+
?.querySelector('.skill-menu__item--highlighted')
|
|
287
|
+
?.scrollIntoView({ block: 'nearest' });
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/** Anchor the fixed skill menu to the composer, opening upward above it. */
|
|
291
|
+
_positionSkillMenu() {
|
|
292
|
+
requestAnimationFrame(() => {
|
|
293
|
+
if (!this._skillMenuEl || !this._inputWrapperEl) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const inputRect = this._inputWrapperEl.getBoundingClientRect();
|
|
297
|
+
this._skillMenuEl.style.left = inputRect.left + 'px';
|
|
298
|
+
this._skillMenuEl.style.width = inputRect.width + 'px';
|
|
299
|
+
// Bound the menu by the chat panel's own top edge so it stays within the
|
|
300
|
+
// widget. In floating mode that keeps it inside the card; in inline mode the
|
|
301
|
+
// panel top sits just below the app header/breadcrumb, so it stays clear of
|
|
302
|
+
// those too. An 8px gap keeps it off both edges.
|
|
303
|
+
const topLimit = this._panelEl.getBoundingClientRect().top + 8;
|
|
304
|
+
const spaceAbove = inputRect.top - 8 - topLimit;
|
|
305
|
+
this._skillMenuEl.style.maxHeight = spaceAbove + 'px';
|
|
306
|
+
// Measure after the cap is applied so the upward anchor uses the real
|
|
307
|
+
// height, then clamp the top edge to the panel bound.
|
|
308
|
+
const menuRect = this._skillMenuEl.getBoundingClientRect();
|
|
309
|
+
const top = Math.max(topLimit, inputRect.top - menuRect.height - 8);
|
|
310
|
+
this._skillMenuEl.style.top = top + 'px';
|
|
311
|
+
});
|
|
225
312
|
}
|
|
226
313
|
// --- Public methods ---
|
|
227
314
|
/**
|
|
@@ -280,6 +367,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
280
367
|
}
|
|
281
368
|
}
|
|
282
369
|
_handleHeaderMouseDown(e) {
|
|
370
|
+
// Inline mode fills its parent — dragging the panel doesn't apply.
|
|
371
|
+
if (this.variant === 'inline') {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
283
374
|
// Ignore clicks on buttons inside the header
|
|
284
375
|
if (e.target.closest('button')) {
|
|
285
376
|
return;
|
|
@@ -305,6 +396,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
305
396
|
document.addEventListener('mouseup', this._handleMouseUp);
|
|
306
397
|
}
|
|
307
398
|
_handleResizeMouseDown(e) {
|
|
399
|
+
// Inline mode fills its parent — resizing the panel doesn't apply.
|
|
400
|
+
if (this.variant === 'inline') {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
308
403
|
if (!this._panelEl) {
|
|
309
404
|
return;
|
|
310
405
|
}
|
|
@@ -407,13 +502,129 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
407
502
|
const textarea = e.target;
|
|
408
503
|
textarea.style.height = 'auto';
|
|
409
504
|
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
|
|
505
|
+
// Open the skill menu while the message is a `/` command being typed (a
|
|
506
|
+
// leading slash with no space yet). The text after the slash filters it.
|
|
507
|
+
const slashMatch = this._inputValue.match(/^\/(\S*)$/);
|
|
508
|
+
if (slashMatch && this.skills.length) {
|
|
509
|
+
this._skillMenuOpened = true;
|
|
510
|
+
this._skillQuery = slashMatch[1];
|
|
511
|
+
this._skillHighlighted = 0;
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
this._skillMenuOpened = false;
|
|
515
|
+
}
|
|
410
516
|
}
|
|
411
517
|
_handleKeyDown(e) {
|
|
518
|
+
// When the skill menu is open, arrow keys / Enter / Escape drive it. The two
|
|
519
|
+
// top actions (Attach Files, Browse All Skills) are index 0 and 1, then the
|
|
520
|
+
// filtered skills follow — all navigable as one list.
|
|
521
|
+
if (this._skillMenuOpened) {
|
|
522
|
+
const count = 2 + this._filteredSkills().length;
|
|
523
|
+
if (e.key === 'ArrowDown') {
|
|
524
|
+
e.preventDefault();
|
|
525
|
+
this._skillHighlighted = (this._skillHighlighted + 1) % count;
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (e.key === 'ArrowUp') {
|
|
529
|
+
e.preventDefault();
|
|
530
|
+
this._skillHighlighted = (this._skillHighlighted - 1 + count) % count;
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
534
|
+
e.preventDefault();
|
|
535
|
+
this._activateSkillMenuItem(this._skillHighlighted);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (e.key === 'Escape') {
|
|
539
|
+
e.preventDefault();
|
|
540
|
+
this._skillMenuOpened = false;
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
412
544
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
413
545
|
e.preventDefault();
|
|
414
546
|
this._handleSubmit();
|
|
415
547
|
}
|
|
416
548
|
}
|
|
549
|
+
/** Run the skill-menu item at the given flat index (0/1 = actions, then skills). */
|
|
550
|
+
_activateSkillMenuItem(index) {
|
|
551
|
+
if (index === 0) {
|
|
552
|
+
this._openFilePicker();
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (index === 1) {
|
|
556
|
+
this._handleSkillBrowseOpen();
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const skill = this._filteredSkills()[index - 2];
|
|
560
|
+
if (skill) {
|
|
561
|
+
this._handleSkillSelect(skill);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// --- Skills ---
|
|
565
|
+
/** Skills matching the current `/` query, capped at the first 15. */
|
|
566
|
+
_filteredSkills() {
|
|
567
|
+
const query = this._skillQuery.toLowerCase();
|
|
568
|
+
return this.skills
|
|
569
|
+
.filter((skill) => skill.name.toLowerCase().includes(query) ||
|
|
570
|
+
skill.label.toLowerCase().includes(query))
|
|
571
|
+
.slice(0, 15);
|
|
572
|
+
}
|
|
573
|
+
/** The `+` button toggles the skill/attachment menu open (showing all skills). */
|
|
574
|
+
_handlePlusClick() {
|
|
575
|
+
if (this._skillMenuOpened) {
|
|
576
|
+
this._skillMenuOpened = false;
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
this._skillQuery = '';
|
|
580
|
+
this._skillHighlighted = 0;
|
|
581
|
+
this._skillMenuOpened = true;
|
|
582
|
+
}
|
|
583
|
+
/** Insert `/name ` into the composer and focus it, ready for the message. */
|
|
584
|
+
_handleSkillSelect(skill) {
|
|
585
|
+
this._inputValue = `/${skill.name} `;
|
|
586
|
+
this._skillMenuOpened = false;
|
|
587
|
+
this._skillBrowseOpened = false;
|
|
588
|
+
this._inputEl?.focus();
|
|
589
|
+
}
|
|
590
|
+
/** Copy a message's text to the clipboard and briefly flash "Copied". */
|
|
591
|
+
_handleCopyMessage(content, index) {
|
|
592
|
+
navigator.clipboard.writeText(content).then(() => {
|
|
593
|
+
this._copiedIndex = index;
|
|
594
|
+
setTimeout(() => {
|
|
595
|
+
if (this._copiedIndex === index) {
|
|
596
|
+
this._copiedIndex = -1;
|
|
597
|
+
}
|
|
598
|
+
}, 2000);
|
|
599
|
+
}).catch(() => { });
|
|
600
|
+
}
|
|
601
|
+
_handleSkillBrowseOpen() {
|
|
602
|
+
this._skillMenuOpened = false;
|
|
603
|
+
this._skillBrowseQuery = '';
|
|
604
|
+
this._skillBrowseOpened = true;
|
|
605
|
+
}
|
|
606
|
+
_handleSkillBrowseClose() {
|
|
607
|
+
this._skillBrowseOpened = false;
|
|
608
|
+
}
|
|
609
|
+
_handleSkillBrowseSearch(e) {
|
|
610
|
+
this._skillBrowseQuery = e.target.value;
|
|
611
|
+
}
|
|
612
|
+
_handleSkillBrowseTabChange(e) {
|
|
613
|
+
this._skillBrowseSource = e.detail.activeTabId;
|
|
614
|
+
}
|
|
615
|
+
/** Skills for the browse dialog's active tab, filtered by its search box. */
|
|
616
|
+
_browseSkills() {
|
|
617
|
+
const query = this._skillBrowseQuery.toLowerCase();
|
|
618
|
+
return this.skills.filter((skill) => {
|
|
619
|
+
const source = skill.source || 'org';
|
|
620
|
+
if (source !== this._skillBrowseSource) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
return skill.name.toLowerCase().includes(query) ||
|
|
624
|
+
skill.label.toLowerCase().includes(query) ||
|
|
625
|
+
(skill.description || '').toLowerCase().includes(query);
|
|
626
|
+
});
|
|
627
|
+
}
|
|
417
628
|
// --- Attachments ---
|
|
418
629
|
_openFilePicker() {
|
|
419
630
|
this._fileInputEl?.click();
|
|
@@ -497,54 +708,49 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
497
708
|
name: file.name,
|
|
498
709
|
previewUrl: URL.createObjectURL(file),
|
|
499
710
|
};
|
|
500
|
-
this.
|
|
711
|
+
this._pendingInternalFiles = [...this._pendingInternalFiles, pending];
|
|
501
712
|
});
|
|
502
713
|
}
|
|
503
714
|
_removePending(localId) {
|
|
504
|
-
const target = this.
|
|
715
|
+
const target = this._pendingInternalFiles.find((p) => p.localId === localId);
|
|
505
716
|
if (target) {
|
|
506
717
|
URL.revokeObjectURL(target.previewUrl);
|
|
507
718
|
}
|
|
508
|
-
this.
|
|
719
|
+
this._pendingInternalFiles = this._pendingInternalFiles.filter((p) => p.localId !== localId);
|
|
509
720
|
}
|
|
510
721
|
_clearPending() {
|
|
511
|
-
this.
|
|
512
|
-
this.
|
|
722
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
723
|
+
this._pendingInternalFiles = [];
|
|
513
724
|
}
|
|
514
|
-
/** Open the shared full-screen previewer for
|
|
515
|
-
|
|
725
|
+
/** Open the shared full-screen previewer for a message image. */
|
|
726
|
+
_openImage(img) {
|
|
516
727
|
KRFilePreview.open({
|
|
517
|
-
src:
|
|
518
|
-
name:
|
|
728
|
+
src: `/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view`,
|
|
729
|
+
name: img.niceName || 'image',
|
|
519
730
|
});
|
|
520
731
|
}
|
|
521
732
|
/** Whether the send button is active: needs text or at least one image. */
|
|
522
733
|
_canSend() {
|
|
523
|
-
return Boolean(this._inputValue.trim()) || this.
|
|
734
|
+
return Boolean(this._inputValue.trim()) || this._pendingInternalFiles.length > 0;
|
|
524
735
|
}
|
|
525
736
|
_handleSubmit() {
|
|
526
|
-
if (
|
|
737
|
+
if (this._isStreaming) {
|
|
527
738
|
return;
|
|
528
739
|
}
|
|
529
740
|
const messageText = this._inputValue.trim();
|
|
530
|
-
const pending = this.
|
|
741
|
+
const pending = this._pendingInternalFiles;
|
|
531
742
|
// Allow sending an image with no text, but require at least one of them.
|
|
532
743
|
if (!messageText && !pending.length) {
|
|
533
744
|
return;
|
|
534
745
|
}
|
|
535
|
-
// The raw files sent to the backend
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
const
|
|
539
|
-
const attachments = pending.map((p) => ({
|
|
540
|
-
url: p.previewUrl,
|
|
541
|
-
name: p.name,
|
|
542
|
-
mimeType: p.file.type,
|
|
543
|
-
}));
|
|
746
|
+
// The raw files sent to the backend. Sent images are shown once the
|
|
747
|
+
// conversation reloads and they come back as stored internal files; the
|
|
748
|
+
// just-sent message itself carries text only.
|
|
749
|
+
const internalFiles = pending.map((p) => p.file);
|
|
544
750
|
const submitEvent = new CustomEvent('message-submit', {
|
|
545
751
|
detail: {
|
|
546
752
|
message: messageText,
|
|
547
|
-
|
|
753
|
+
internalFiles,
|
|
548
754
|
},
|
|
549
755
|
bubbles: true,
|
|
550
756
|
composed: true,
|
|
@@ -555,30 +761,26 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
555
761
|
return;
|
|
556
762
|
}
|
|
557
763
|
const userMessage = {
|
|
558
|
-
|
|
764
|
+
type: 'user',
|
|
559
765
|
content: messageText,
|
|
560
766
|
};
|
|
561
|
-
if (attachments.length) {
|
|
562
|
-
userMessage.attachments = attachments;
|
|
563
|
-
}
|
|
564
767
|
this.messages = [...this.messages, userMessage];
|
|
565
768
|
this._inputValue = '';
|
|
566
769
|
this._error = '';
|
|
567
770
|
this._userHasScrolledUp = false;
|
|
568
|
-
// Clear the composer
|
|
569
|
-
|
|
570
|
-
this.
|
|
771
|
+
// Clear the composer and release its preview object URLs.
|
|
772
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
773
|
+
this._pendingInternalFiles = [];
|
|
571
774
|
// Reset textarea height
|
|
572
775
|
if (this._inputEl) {
|
|
573
776
|
this._inputEl.style.height = 'auto';
|
|
574
777
|
}
|
|
575
778
|
// Add empty assistant message to stream into
|
|
576
779
|
this.messages = [...this.messages, {
|
|
577
|
-
|
|
780
|
+
type: 'assistant',
|
|
578
781
|
content: '',
|
|
579
782
|
}];
|
|
580
783
|
this._isStreaming = true;
|
|
581
|
-
this._statusText = '';
|
|
582
784
|
// Scroll user's message to the top (Gemini-style).
|
|
583
785
|
// Set min-height on the assistant message = container height so there's enough
|
|
584
786
|
// scroll room below the user message to push it to the top.
|
|
@@ -600,10 +802,34 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
600
802
|
this._messagesEl.scrollTop = userMsgEl.offsetTop - this._messagesEl.offsetTop - 24;
|
|
601
803
|
}
|
|
602
804
|
});
|
|
805
|
+
// Default to streaming from the Kodaris Bedrock conversation endpoints when
|
|
806
|
+
// the consumer hasn't provided their own `send`.
|
|
807
|
+
if (!this.send) {
|
|
808
|
+
this.send = (params) => {
|
|
809
|
+
// With images, post text + files as multipart; without, post plain JSON.
|
|
810
|
+
if (params.internalFiles.length) {
|
|
811
|
+
const form = new FormData();
|
|
812
|
+
form.append('userText', params.message);
|
|
813
|
+
params.internalFiles.forEach((file) => form.append('images', file));
|
|
814
|
+
return KRHttp.fetch({
|
|
815
|
+
url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithImages',
|
|
816
|
+
method: 'POST',
|
|
817
|
+
headers: { Accept: 'text/event-stream' },
|
|
818
|
+
body: form,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
return KRHttp.fetch({
|
|
822
|
+
url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithState',
|
|
823
|
+
method: 'POST',
|
|
824
|
+
headers: { Accept: 'text/event-stream' },
|
|
825
|
+
body: { userText: params.message },
|
|
826
|
+
});
|
|
827
|
+
};
|
|
828
|
+
}
|
|
603
829
|
this.send({
|
|
604
830
|
message: messageText,
|
|
605
831
|
messages: this.messages.slice(0, -1),
|
|
606
|
-
|
|
832
|
+
internalFiles,
|
|
607
833
|
}).then((response) => {
|
|
608
834
|
if (!response.ok) {
|
|
609
835
|
this._handleStreamError('Request failed: ' + response.status);
|
|
@@ -629,11 +855,15 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
629
855
|
this._reader = null;
|
|
630
856
|
}
|
|
631
857
|
this._isStreaming = false;
|
|
632
|
-
this._statusText = '';
|
|
633
858
|
}
|
|
634
859
|
_handleClear() {
|
|
860
|
+
// Default to clearing the Kodaris Bedrock conversation state when the consumer
|
|
861
|
+
// hasn't provided their own `clear`.
|
|
635
862
|
if (!this.clear) {
|
|
636
|
-
|
|
863
|
+
this.clear = () => KRHttp.fetch({
|
|
864
|
+
url: '/api/system/integration/aws/bedrock/clearConversationWithState',
|
|
865
|
+
method: 'POST',
|
|
866
|
+
});
|
|
637
867
|
}
|
|
638
868
|
this.clear().then(() => {
|
|
639
869
|
if (this._reader) {
|
|
@@ -642,7 +872,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
642
872
|
}
|
|
643
873
|
this.messages = [];
|
|
644
874
|
this._isStreaming = false;
|
|
645
|
-
this._statusText = '';
|
|
646
875
|
this._error = '';
|
|
647
876
|
this._inputValue = '';
|
|
648
877
|
this._userHasScrolledUp = false;
|
|
@@ -668,7 +897,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
668
897
|
this._reader.read().then((result) => {
|
|
669
898
|
if (result.done) {
|
|
670
899
|
this._isStreaming = false;
|
|
671
|
-
this._statusText = '';
|
|
672
900
|
this._reader = null;
|
|
673
901
|
return;
|
|
674
902
|
}
|
|
@@ -701,15 +929,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
701
929
|
bubbles: true,
|
|
702
930
|
composed: true,
|
|
703
931
|
}));
|
|
704
|
-
if (data.type === '
|
|
705
|
-
if (data.message) {
|
|
706
|
-
this._statusText = String(data.message);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
else if (data.type === 'REASONING_PART') {
|
|
932
|
+
if (data.type === 'REASONING_PART') {
|
|
710
933
|
if (data.message) {
|
|
711
934
|
const lastIndex = this.messages.length - 1;
|
|
712
|
-
if (lastIndex >= 0 && this.messages[lastIndex].
|
|
935
|
+
if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
|
|
713
936
|
if (!this.messages[lastIndex].reasoning) {
|
|
714
937
|
this.messages[lastIndex].reasoning = '';
|
|
715
938
|
}
|
|
@@ -720,9 +943,8 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
720
943
|
}
|
|
721
944
|
else if (data.type === 'RESPONSE_PART') {
|
|
722
945
|
if (data.message) {
|
|
723
|
-
this._statusText = '';
|
|
724
946
|
const lastIndex = this.messages.length - 1;
|
|
725
|
-
if (lastIndex >= 0 && this.messages[lastIndex].
|
|
947
|
+
if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
|
|
726
948
|
this.messages[lastIndex].content += String(data.message);
|
|
727
949
|
this.requestUpdate();
|
|
728
950
|
}
|
|
@@ -733,7 +955,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
733
955
|
}
|
|
734
956
|
if (data.finished) {
|
|
735
957
|
this._isStreaming = false;
|
|
736
|
-
this._statusText = '';
|
|
737
958
|
this._reader = null;
|
|
738
959
|
}
|
|
739
960
|
}
|
|
@@ -747,7 +968,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
747
968
|
}
|
|
748
969
|
_handleStreamError(message) {
|
|
749
970
|
this._isStreaming = false;
|
|
750
|
-
this._statusText = '';
|
|
751
971
|
this._error = message;
|
|
752
972
|
if (this._reader) {
|
|
753
973
|
this._reader.cancel();
|
|
@@ -756,15 +976,128 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
756
976
|
// Remove the empty assistant message if it has no content
|
|
757
977
|
if (this.messages.length > 0) {
|
|
758
978
|
const lastMessage = this.messages[this.messages.length - 1];
|
|
759
|
-
if (lastMessage.
|
|
979
|
+
if (lastMessage.type === 'assistant' && !lastMessage.content) {
|
|
760
980
|
this.messages = this.messages.slice(0, -1);
|
|
761
981
|
}
|
|
762
982
|
}
|
|
763
983
|
}
|
|
764
984
|
// --- Render ---
|
|
985
|
+
/** The `/` skill menu shown above the composer while typing a slash command. */
|
|
986
|
+
_renderSkillMenu() {
|
|
987
|
+
const skills = this._filteredSkills();
|
|
988
|
+
return html `
|
|
989
|
+
<div class="skill-menu">
|
|
990
|
+
<div class="skill-menu__actions">
|
|
991
|
+
<button
|
|
992
|
+
class=${classMap({
|
|
993
|
+
'skill-menu__action': true,
|
|
994
|
+
'skill-menu__item--highlighted': this._skillHighlighted === 0,
|
|
995
|
+
})}
|
|
996
|
+
@click=${this._openFilePicker}
|
|
997
|
+
>
|
|
998
|
+
Attach Files
|
|
999
|
+
</button>
|
|
1000
|
+
<button
|
|
1001
|
+
class=${classMap({
|
|
1002
|
+
'skill-menu__action': true,
|
|
1003
|
+
'skill-menu__item--highlighted': this._skillHighlighted === 1,
|
|
1004
|
+
})}
|
|
1005
|
+
@click=${this._handleSkillBrowseOpen}
|
|
1006
|
+
>
|
|
1007
|
+
Browse All Skills
|
|
1008
|
+
</button>
|
|
1009
|
+
</div>
|
|
1010
|
+
${skills.length ? html `
|
|
1011
|
+
<ul class="skill-menu__list">
|
|
1012
|
+
${skills.map((skill, index) => html `
|
|
1013
|
+
<li>
|
|
1014
|
+
<button
|
|
1015
|
+
class=${classMap({
|
|
1016
|
+
'skill-menu__item': true,
|
|
1017
|
+
'skill-menu__item--highlighted': index + 2 === this._skillHighlighted,
|
|
1018
|
+
})}
|
|
1019
|
+
@click=${() => this._handleSkillSelect(skill)}
|
|
1020
|
+
>
|
|
1021
|
+
<span class="skill-menu__name">${skill.label}</span>
|
|
1022
|
+
${skill.description ? html `
|
|
1023
|
+
<span class="skill-menu__desc">${skill.description}</span>
|
|
1024
|
+
` : nothing}
|
|
1025
|
+
</button>
|
|
1026
|
+
</li>
|
|
1027
|
+
`)}
|
|
1028
|
+
</ul>
|
|
1029
|
+
` : html `
|
|
1030
|
+
<div class="skill-menu__empty">No matching skills</div>
|
|
1031
|
+
`}
|
|
1032
|
+
</div>
|
|
1033
|
+
`;
|
|
1034
|
+
}
|
|
1035
|
+
/** The "Browse all skills" dialog — a searchable grid of Org / Kodaris cards. */
|
|
1036
|
+
_renderSkillBrowse() {
|
|
1037
|
+
return html `
|
|
1038
|
+
<kr-dialog
|
|
1039
|
+
opened
|
|
1040
|
+
label="Browse Skills"
|
|
1041
|
+
width="720px"
|
|
1042
|
+
@close=${this._handleSkillBrowseClose}
|
|
1043
|
+
>
|
|
1044
|
+
<div class="skill-browse">
|
|
1045
|
+
<kr-text-field
|
|
1046
|
+
placeholder="Search skills..."
|
|
1047
|
+
.value=${this._skillBrowseQuery}
|
|
1048
|
+
@input=${this._handleSkillBrowseSearch}
|
|
1049
|
+
></kr-text-field>
|
|
1050
|
+
<kr-tab-group
|
|
1051
|
+
active-tab-id=${this._skillBrowseSource}
|
|
1052
|
+
@tab-change=${this._handleSkillBrowseTabChange}
|
|
1053
|
+
>
|
|
1054
|
+
<kr-tab id="org" label="Your Org">
|
|
1055
|
+
${this._renderSkillGrid()}
|
|
1056
|
+
</kr-tab>
|
|
1057
|
+
<kr-tab id="kodaris" label="Kodaris">
|
|
1058
|
+
${this._renderSkillGrid()}
|
|
1059
|
+
</kr-tab>
|
|
1060
|
+
</kr-tab-group>
|
|
1061
|
+
</div>
|
|
1062
|
+
</kr-dialog>
|
|
1063
|
+
`;
|
|
1064
|
+
}
|
|
1065
|
+
/** The card grid for the browse dialog's active tab. */
|
|
1066
|
+
_renderSkillGrid() {
|
|
1067
|
+
const skills = this._browseSkills();
|
|
1068
|
+
if (!skills.length) {
|
|
1069
|
+
return html `<div class="skill-browse__empty">No matching skills</div>`;
|
|
1070
|
+
}
|
|
1071
|
+
return html `
|
|
1072
|
+
<div class="skill-browse__grid">
|
|
1073
|
+
${skills.map((skill) => html `
|
|
1074
|
+
<button
|
|
1075
|
+
class="skill-card"
|
|
1076
|
+
@click=${() => this._handleSkillSelect(skill)}
|
|
1077
|
+
>
|
|
1078
|
+
<span class="skill-card__name">${skill.label}</span>
|
|
1079
|
+
${skill.description ? html `
|
|
1080
|
+
<span class="skill-card__desc">${skill.description}</span>
|
|
1081
|
+
` : nothing}
|
|
1082
|
+
</button>
|
|
1083
|
+
`)}
|
|
1084
|
+
</div>
|
|
1085
|
+
`;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Render a user message, highlighting a leading `/skill-name` token when it
|
|
1089
|
+
* matches a known skill (like the way Claude highlights slash commands).
|
|
1090
|
+
*/
|
|
1091
|
+
_renderUserContent(content) {
|
|
1092
|
+
const match = content.match(/^\/(\S+)(\s[\s\S]*)?$/);
|
|
1093
|
+
if (match && this.skills.some((skill) => skill.name === match[1])) {
|
|
1094
|
+
return html `<span class="message-skill">/${match[1]}</span>${match[2] || ''}`;
|
|
1095
|
+
}
|
|
1096
|
+
return content;
|
|
1097
|
+
}
|
|
765
1098
|
render() {
|
|
766
1099
|
return html `
|
|
767
|
-
${this.hideToggle ? nothing : html `
|
|
1100
|
+
${(this.hideToggle || this.variant === 'inline') ? nothing : html `
|
|
768
1101
|
<button
|
|
769
1102
|
class=${classMap({
|
|
770
1103
|
'toggle': true,
|
|
@@ -815,53 +1148,52 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
815
1148
|
<span>Drop images to attach</span>
|
|
816
1149
|
</div>
|
|
817
1150
|
` : nothing}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1151
|
+
${this.variant === 'inline' ? nothing : html `
|
|
1152
|
+
<div
|
|
1153
|
+
class="resize resize--n"
|
|
1154
|
+
data-dir="n"
|
|
1155
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1156
|
+
></div>
|
|
1157
|
+
<div
|
|
1158
|
+
class="resize resize--s"
|
|
1159
|
+
data-dir="s"
|
|
1160
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1161
|
+
></div>
|
|
1162
|
+
<div
|
|
1163
|
+
class="resize resize--w"
|
|
1164
|
+
data-dir="w"
|
|
1165
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1166
|
+
></div>
|
|
1167
|
+
<div
|
|
1168
|
+
class="resize resize--e"
|
|
1169
|
+
data-dir="e"
|
|
1170
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1171
|
+
></div>
|
|
1172
|
+
<div
|
|
1173
|
+
class="resize resize--nw"
|
|
1174
|
+
data-dir="nw"
|
|
1175
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1176
|
+
></div>
|
|
1177
|
+
<div
|
|
1178
|
+
class="resize resize--ne"
|
|
1179
|
+
data-dir="ne"
|
|
1180
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1181
|
+
></div>
|
|
1182
|
+
<div
|
|
1183
|
+
class="resize resize--sw"
|
|
1184
|
+
data-dir="sw"
|
|
1185
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1186
|
+
></div>
|
|
1187
|
+
<div
|
|
1188
|
+
class="resize resize--se"
|
|
1189
|
+
data-dir="se"
|
|
1190
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
1191
|
+
></div>
|
|
1192
|
+
`}
|
|
858
1193
|
<div class="header" @mousedown=${this._handleHeaderMouseDown}>
|
|
859
1194
|
<div class="header-left">
|
|
860
1195
|
<div class="header-info">
|
|
861
|
-
<span class="header-title">${this.
|
|
862
|
-
${this.subtitle ? html `
|
|
863
|
-
<span class="header-subtitle">${this.subtitle}</span>
|
|
864
|
-
` : nothing}
|
|
1196
|
+
<span class="header-title">${this.label}</span>
|
|
865
1197
|
</div>
|
|
866
1198
|
</div>
|
|
867
1199
|
<div class="header-actions">
|
|
@@ -887,77 +1219,124 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
887
1219
|
</svg>
|
|
888
1220
|
</button>
|
|
889
1221
|
` : nothing}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
<svg
|
|
896
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
897
|
-
fill="none"
|
|
898
|
-
viewBox="0 0 24 24"
|
|
899
|
-
stroke-width="2"
|
|
900
|
-
stroke="currentColor"
|
|
901
|
-
class="header-action-icon"
|
|
1222
|
+
${this.variant === 'inline' ? nothing : html `
|
|
1223
|
+
<button
|
|
1224
|
+
class="header-action"
|
|
1225
|
+
@click=${this._handleToggle}
|
|
1226
|
+
title="Close"
|
|
902
1227
|
>
|
|
903
|
-
<
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1228
|
+
<svg
|
|
1229
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1230
|
+
fill="none"
|
|
1231
|
+
viewBox="0 0 24 24"
|
|
1232
|
+
stroke-width="2"
|
|
1233
|
+
stroke="currentColor"
|
|
1234
|
+
class="header-action-icon"
|
|
1235
|
+
>
|
|
1236
|
+
<path
|
|
1237
|
+
stroke-linecap="round"
|
|
1238
|
+
stroke-linejoin="round"
|
|
1239
|
+
d="M6 18 18 6M6 6l12 12"
|
|
1240
|
+
/>
|
|
1241
|
+
</svg>
|
|
1242
|
+
</button>
|
|
1243
|
+
`}
|
|
910
1244
|
</div>
|
|
911
1245
|
</div>
|
|
912
1246
|
|
|
913
1247
|
<div class="messages" @scroll=${this._handleMessagesScroll}>
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
<
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1248
|
+
<div class="messages-inner">
|
|
1249
|
+
${this.messages.length === 0 ? html `
|
|
1250
|
+
<div class="empty">
|
|
1251
|
+
<span class="empty-text">What can I help you with?</span>
|
|
1252
|
+
</div>
|
|
1253
|
+
` : nothing}
|
|
1254
|
+
${this.messages.map((msg, index) => html `
|
|
1255
|
+
<div class=${classMap({
|
|
921
1256
|
'message': true,
|
|
922
|
-
'message--user': msg.
|
|
923
|
-
'message--assistant': msg.
|
|
1257
|
+
'message--user': msg.type === 'user',
|
|
1258
|
+
'message--assistant': msg.type === 'assistant',
|
|
924
1259
|
})}>
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1260
|
+
${msg.reasoning ? html `
|
|
1261
|
+
<details class="reasoning">
|
|
1262
|
+
<summary>Thinking</summary>
|
|
1263
|
+
<div class="reasoning-content">${msg.reasoning}</div>
|
|
1264
|
+
</details>
|
|
1265
|
+
` : nothing}
|
|
1266
|
+
${msg.internalFiles && msg.internalFiles.length ? html `
|
|
1267
|
+
<div class="attachments">
|
|
1268
|
+
${msg.internalFiles.map((img) => html `
|
|
1269
|
+
<button
|
|
1270
|
+
class="attachment"
|
|
1271
|
+
type="button"
|
|
1272
|
+
title=${img.niceName || 'View image'}
|
|
1273
|
+
@click=${() => this._openImage(img)}
|
|
1274
|
+
>
|
|
1275
|
+
<img
|
|
1276
|
+
src="/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view"
|
|
1277
|
+
alt=${img.niceName || ''}
|
|
1278
|
+
/>
|
|
1279
|
+
</button>
|
|
1280
|
+
`)}
|
|
1281
|
+
</div>
|
|
1282
|
+
` : nothing}
|
|
1283
|
+
${msg.content ? html `
|
|
1284
|
+
<div class="message-content">
|
|
1285
|
+
${msg.type === 'user'
|
|
1286
|
+
? this._renderUserContent(msg.content)
|
|
949
1287
|
: unsafeHTML(marked.parse(msg.content, { breaks: true }))}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1288
|
+
</div>
|
|
1289
|
+
` : nothing}
|
|
1290
|
+
${msg.type === 'assistant' && this._isStreaming && msg === this.messages[this.messages.length - 1] ? html `
|
|
1291
|
+
<div class="typing">
|
|
1292
|
+
<span></span>
|
|
1293
|
+
<span></span>
|
|
1294
|
+
<span></span>
|
|
1295
|
+
</div>
|
|
1296
|
+
` : nothing}
|
|
1297
|
+
${msg.content ? html `
|
|
1298
|
+
<button
|
|
1299
|
+
class="message-copy"
|
|
1300
|
+
title="Copy"
|
|
1301
|
+
@click=${() => this._handleCopyMessage(msg.content, index)}
|
|
1302
|
+
>
|
|
1303
|
+
${this._copiedIndex === index ? html `
|
|
1304
|
+
<svg
|
|
1305
|
+
viewBox="0 0 24 24"
|
|
1306
|
+
fill="none"
|
|
1307
|
+
stroke="currentColor"
|
|
1308
|
+
stroke-width="1.5"
|
|
1309
|
+
stroke-linecap="round"
|
|
1310
|
+
stroke-linejoin="round"
|
|
1311
|
+
>
|
|
1312
|
+
<path d="M20 6 9 17l-5-5" />
|
|
1313
|
+
</svg>
|
|
1314
|
+
Copied
|
|
1315
|
+
` : html `
|
|
1316
|
+
<svg
|
|
1317
|
+
viewBox="0 0 24 24"
|
|
1318
|
+
fill="none"
|
|
1319
|
+
stroke="currentColor"
|
|
1320
|
+
stroke-width="1.5"
|
|
1321
|
+
stroke-linecap="round"
|
|
1322
|
+
stroke-linejoin="round"
|
|
1323
|
+
>
|
|
1324
|
+
<rect
|
|
1325
|
+
x="9"
|
|
1326
|
+
y="9"
|
|
1327
|
+
width="13"
|
|
1328
|
+
height="13"
|
|
1329
|
+
rx="2"
|
|
1330
|
+
/>
|
|
1331
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
1332
|
+
</svg>
|
|
1333
|
+
Copy
|
|
1334
|
+
`}
|
|
1335
|
+
</button>
|
|
1336
|
+
` : nothing}
|
|
1337
|
+
</div>
|
|
1338
|
+
`)}
|
|
1339
|
+
</div>
|
|
961
1340
|
</div>
|
|
962
1341
|
|
|
963
1342
|
${this._error ? html `
|
|
@@ -983,9 +1362,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
983
1362
|
` : nothing}
|
|
984
1363
|
|
|
985
1364
|
<div class="footer">
|
|
986
|
-
${this.
|
|
1365
|
+
${this._pendingInternalFiles.length ? html `
|
|
987
1366
|
<div class="composer-attachments">
|
|
988
|
-
${repeat(this.
|
|
1367
|
+
${repeat(this._pendingInternalFiles, (p) => p.localId, (p) => html `
|
|
989
1368
|
<div class="chip">
|
|
990
1369
|
<img src=${p.previewUrl} alt=${p.name} />
|
|
991
1370
|
<button
|
|
@@ -1019,11 +1398,11 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1019
1398
|
hidden
|
|
1020
1399
|
@change=${this._handleFileInputChange}
|
|
1021
1400
|
/>
|
|
1401
|
+
${this._skillMenuOpened ? this._renderSkillMenu() : nothing}
|
|
1022
1402
|
<div class="input-wrapper">
|
|
1023
1403
|
<button
|
|
1024
1404
|
class="input-plus"
|
|
1025
|
-
|
|
1026
|
-
@click=${this._openFilePicker}
|
|
1405
|
+
@click=${this._handlePlusClick}
|
|
1027
1406
|
>+</button>
|
|
1028
1407
|
<textarea
|
|
1029
1408
|
class="input"
|
|
@@ -1080,6 +1459,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1080
1459
|
`}
|
|
1081
1460
|
</div>
|
|
1082
1461
|
</div>
|
|
1462
|
+
${this._skillBrowseOpened ? this._renderSkillBrowse() : nothing}
|
|
1083
1463
|
</div>
|
|
1084
1464
|
`;
|
|
1085
1465
|
}
|
|
@@ -1174,6 +1554,28 @@ KRChatbot.styles = css `
|
|
|
1174
1554
|
display: flex;
|
|
1175
1555
|
}
|
|
1176
1556
|
|
|
1557
|
+
/* Inline mode: fill the parent container instead of floating bottom-right. */
|
|
1558
|
+
:host([variant='inline']) {
|
|
1559
|
+
position: static;
|
|
1560
|
+
bottom: auto;
|
|
1561
|
+
right: auto;
|
|
1562
|
+
z-index: auto;
|
|
1563
|
+
height: 100%;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
:host([variant='inline']) .panel {
|
|
1567
|
+
position: relative;
|
|
1568
|
+
bottom: auto;
|
|
1569
|
+
right: auto;
|
|
1570
|
+
width: 100%;
|
|
1571
|
+
height: 100%;
|
|
1572
|
+
max-height: none;
|
|
1573
|
+
border-radius: 0;
|
|
1574
|
+
box-shadow: none;
|
|
1575
|
+
border: none;
|
|
1576
|
+
animation: none;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1177
1579
|
@keyframes chatbot-panel-in {
|
|
1178
1580
|
from {
|
|
1179
1581
|
opacity: 0;
|
|
@@ -1204,7 +1606,7 @@ KRChatbot.styles = css `
|
|
|
1204
1606
|
gap: 10px;
|
|
1205
1607
|
}
|
|
1206
1608
|
|
|
1207
|
-
.header-info {
|
|
1609
|
+
.header-info {
|
|
1208
1610
|
display: flex;
|
|
1209
1611
|
flex-direction: column;
|
|
1210
1612
|
}
|
|
@@ -1214,22 +1616,6 @@ KRChatbot.styles = css `
|
|
|
1214
1616
|
font-weight: 600;
|
|
1215
1617
|
}
|
|
1216
1618
|
|
|
1217
|
-
.header-subtitle {
|
|
1218
|
-
color: rgba(255, 255, 255, 0.5);
|
|
1219
|
-
font-size: 11px;
|
|
1220
|
-
display: flex;
|
|
1221
|
-
align-items: center;
|
|
1222
|
-
gap: 4px;
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
.header-subtitle::before {
|
|
1226
|
-
content: '';
|
|
1227
|
-
width: 5px;
|
|
1228
|
-
height: 5px;
|
|
1229
|
-
background: var(--kr-chatbot-success);
|
|
1230
|
-
border-radius: 50%;
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
1619
|
.header-actions {
|
|
1234
1620
|
display: flex;
|
|
1235
1621
|
align-items: center;
|
|
@@ -1261,17 +1647,29 @@ KRChatbot.styles = css `
|
|
|
1261
1647
|
}
|
|
1262
1648
|
|
|
1263
1649
|
/* Messages */
|
|
1650
|
+
/* Full-width scroll container — keeps the scrollbar at the panel edge. */
|
|
1264
1651
|
.messages {
|
|
1265
1652
|
flex: 1;
|
|
1266
1653
|
overflow-y: auto;
|
|
1267
|
-
padding: 32px;
|
|
1268
1654
|
display: flex;
|
|
1269
1655
|
flex-direction: column;
|
|
1270
|
-
gap: 24px;
|
|
1271
1656
|
scroll-behavior: smooth;
|
|
1272
1657
|
background: white;
|
|
1273
1658
|
}
|
|
1274
1659
|
|
|
1660
|
+
/* Centered, width-capped column that actually holds the messages. */
|
|
1661
|
+
.messages-inner {
|
|
1662
|
+
flex: 1;
|
|
1663
|
+
display: flex;
|
|
1664
|
+
flex-direction: column;
|
|
1665
|
+
gap: 24px;
|
|
1666
|
+
padding: 32px;
|
|
1667
|
+
width: 100%;
|
|
1668
|
+
max-width: 768px;
|
|
1669
|
+
margin-left: auto;
|
|
1670
|
+
margin-right: auto;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1275
1673
|
.messages::-webkit-scrollbar {
|
|
1276
1674
|
width: 4px;
|
|
1277
1675
|
}
|
|
@@ -1295,12 +1693,13 @@ KRChatbot.styles = css `
|
|
|
1295
1693
|
gap: 8px;
|
|
1296
1694
|
}
|
|
1297
1695
|
|
|
1298
|
-
.empty-text {
|
|
1696
|
+
.empty-text {
|
|
1299
1697
|
font-size: 14px;
|
|
1300
1698
|
color: rgb(0 0 0 / 80%);
|
|
1301
1699
|
}
|
|
1302
1700
|
|
|
1303
1701
|
.message {
|
|
1702
|
+
position: relative;
|
|
1304
1703
|
display: flex;
|
|
1305
1704
|
flex-direction: column;
|
|
1306
1705
|
max-width: 100%;
|
|
@@ -1345,6 +1744,58 @@ KRChatbot.styles = css `
|
|
|
1345
1744
|
font-size: 14px;
|
|
1346
1745
|
}
|
|
1347
1746
|
|
|
1747
|
+
/* Highlighted leading /skill token in a sent user message. Monospace + a
|
|
1748
|
+
stronger color makes it read as a distinct command without a pill. */
|
|
1749
|
+
.message-skill {
|
|
1750
|
+
font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace;
|
|
1751
|
+
font-weight: 600;
|
|
1752
|
+
color: #b45309;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/* Per-message copy button. Kept in flow with a fixed height that is always
|
|
1756
|
+
reserved (via a negative bottom margin that cancels it) so message spacing
|
|
1757
|
+
is identical whether or not it's shown; only its opacity toggles on hover. */
|
|
1758
|
+
.message-copy {
|
|
1759
|
+
align-self: flex-start;
|
|
1760
|
+
margin-top: 4px;
|
|
1761
|
+
margin-bottom: -27px;
|
|
1762
|
+
display: inline-flex;
|
|
1763
|
+
align-items: center;
|
|
1764
|
+
gap: 4px;
|
|
1765
|
+
height: 23px;
|
|
1766
|
+
padding: 4px 8px;
|
|
1767
|
+
background: none;
|
|
1768
|
+
border: none;
|
|
1769
|
+
border-radius: 6px;
|
|
1770
|
+
font: inherit;
|
|
1771
|
+
font-size: 12px;
|
|
1772
|
+
color: #64668b;
|
|
1773
|
+
cursor: pointer;
|
|
1774
|
+
opacity: 0;
|
|
1775
|
+
pointer-events: none;
|
|
1776
|
+
transition: opacity 0.15s, background-color 0.15s, color 0.15s;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
.message--user .message-copy {
|
|
1780
|
+
align-self: flex-end;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
.message:hover .message-copy,
|
|
1784
|
+
.message-copy:focus-visible {
|
|
1785
|
+
opacity: 1;
|
|
1786
|
+
pointer-events: auto;
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
.message-copy:hover {
|
|
1790
|
+
background: rgba(0, 0, 0, 0.06);
|
|
1791
|
+
color: #1a1a2e;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
.message-copy svg {
|
|
1795
|
+
width: 14px;
|
|
1796
|
+
height: 14px;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1348
1799
|
.message--assistant .message-content {
|
|
1349
1800
|
background: transparent;
|
|
1350
1801
|
color: #000000;
|
|
@@ -1407,7 +1858,7 @@ KRChatbot.styles = css `
|
|
|
1407
1858
|
word-wrap: break-word;
|
|
1408
1859
|
}
|
|
1409
1860
|
|
|
1410
|
-
.typing {
|
|
1861
|
+
.typing {
|
|
1411
1862
|
display: flex;
|
|
1412
1863
|
gap: 4px;
|
|
1413
1864
|
padding: 4px 0;
|
|
@@ -1475,6 +1926,15 @@ KRChatbot.styles = css `
|
|
|
1475
1926
|
padding: 16px 16px 16px;
|
|
1476
1927
|
flex-shrink: 0;
|
|
1477
1928
|
background: white;
|
|
1929
|
+
width: 100%;
|
|
1930
|
+
max-width: 768px;
|
|
1931
|
+
margin-left: auto;
|
|
1932
|
+
margin-right: auto;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/* Inline mode: keep the composer clear of the bottom edge. */
|
|
1936
|
+
:host([variant='inline']) .footer {
|
|
1937
|
+
padding-bottom: 40px;
|
|
1478
1938
|
}
|
|
1479
1939
|
|
|
1480
1940
|
.input-wrapper {
|
|
@@ -1493,6 +1953,15 @@ KRChatbot.styles = css `
|
|
|
1493
1953
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
1494
1954
|
}
|
|
1495
1955
|
|
|
1956
|
+
/* Inline mode: keep the border and add a soft floating shadow (ChatGPT/Gemini style). */
|
|
1957
|
+
:host([variant='inline']) .input-wrapper {
|
|
1958
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
:host([variant='inline']) .input-wrapper:focus-within {
|
|
1962
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 12px 32px rgba(0, 0, 0, 0.14);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1496
1965
|
.input-plus {
|
|
1497
1966
|
width: 34px;
|
|
1498
1967
|
height: 34px;
|
|
@@ -1600,6 +2069,12 @@ KRChatbot.styles = css `
|
|
|
1600
2069
|
cursor: grabbing;
|
|
1601
2070
|
}
|
|
1602
2071
|
|
|
2072
|
+
/* Inline mode isn't draggable — don't show the grab/hand cursor. */
|
|
2073
|
+
:host([variant='inline']) .header,
|
|
2074
|
+
:host([variant='inline']) .header:active {
|
|
2075
|
+
cursor: default;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
1603
2078
|
/* Drag / Resize states */
|
|
1604
2079
|
.panel--dragging,
|
|
1605
2080
|
.panel--resizing {
|
|
@@ -1656,6 +2131,176 @@ KRChatbot.styles = css `
|
|
|
1656
2131
|
display: block;
|
|
1657
2132
|
}
|
|
1658
2133
|
|
|
2134
|
+
/* Skill menu (opened by typing '/'). Fixed + JS-positioned above the
|
|
2135
|
+
composer, following the krubble select-field dropdown pattern. */
|
|
2136
|
+
.skill-menu {
|
|
2137
|
+
position: fixed;
|
|
2138
|
+
z-index: 10000;
|
|
2139
|
+
background: white;
|
|
2140
|
+
border: 1px solid #9ba7b6;
|
|
2141
|
+
border-radius: 8px;
|
|
2142
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
2143
|
+
overflow: hidden;
|
|
2144
|
+
display: flex;
|
|
2145
|
+
flex-direction: column;
|
|
2146
|
+
/* max-height is set dynamically to the space above the composer. */
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/* Floating mode is a compact card — use a lighter shadow and softer border. */
|
|
2150
|
+
:host(:not([variant='inline'])) .skill-menu {
|
|
2151
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
2152
|
+
border-color: #b0b0b0;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
.skill-menu__list {
|
|
2156
|
+
list-style: none;
|
|
2157
|
+
margin: 0;
|
|
2158
|
+
padding: 8px 0;
|
|
2159
|
+
overflow-y: auto;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
.skill-menu__item {
|
|
2163
|
+
display: flex;
|
|
2164
|
+
flex-direction: column;
|
|
2165
|
+
gap: 2px;
|
|
2166
|
+
width: 100%;
|
|
2167
|
+
padding: 8px 16px;
|
|
2168
|
+
background: none;
|
|
2169
|
+
border: none;
|
|
2170
|
+
text-align: left;
|
|
2171
|
+
cursor: pointer;
|
|
2172
|
+
font: inherit;
|
|
2173
|
+
color: #000000;
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
/* Floating mode is a compact card — tighten name/description spacing. */
|
|
2178
|
+
:host(:not([variant='inline'])) .skill-menu__item {
|
|
2179
|
+
gap: 1px;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
.skill-menu__name {
|
|
2183
|
+
font-weight: 500;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
/* Floating mode is a compact card — shrink the skill name a touch. */
|
|
2187
|
+
:host(:not([variant='inline'])) .skill-menu__name {
|
|
2188
|
+
font-size: 13px;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
.skill-menu__desc {
|
|
2192
|
+
font-size: 12px;
|
|
2193
|
+
color: #000000;
|
|
2194
|
+
overflow: hidden;
|
|
2195
|
+
text-overflow: ellipsis;
|
|
2196
|
+
white-space: nowrap;
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
.skill-menu__empty {
|
|
2200
|
+
padding: 16px;
|
|
2201
|
+
text-align: center;
|
|
2202
|
+
color: #000000;
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
.skill-menu__actions {
|
|
2206
|
+
display: flex;
|
|
2207
|
+
flex-direction: column;
|
|
2208
|
+
padding: 8px 0;
|
|
2209
|
+
border-bottom: 1px solid #eee;
|
|
2210
|
+
flex-shrink: 0;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
.skill-menu__action {
|
|
2214
|
+
width: 100%;
|
|
2215
|
+
padding: 6px 16px;
|
|
2216
|
+
background: transparent;
|
|
2217
|
+
border: none;
|
|
2218
|
+
text-align: left;
|
|
2219
|
+
cursor: pointer;
|
|
2220
|
+
font: inherit;
|
|
2221
|
+
font-size: 13px;
|
|
2222
|
+
color: var(--kr-chatbot-primary);
|
|
2223
|
+
font-weight: 500;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
/* Highlight (keyboard) and hover (mouse) are independent — either shades the
|
|
2227
|
+
row. Declared after the base rules so it wins the cascade for both the
|
|
2228
|
+
action buttons and the skill items. */
|
|
2229
|
+
.skill-menu__action.skill-menu__item--highlighted,
|
|
2230
|
+
.skill-menu__item.skill-menu__item--highlighted,
|
|
2231
|
+
.skill-menu__action:hover,
|
|
2232
|
+
.skill-menu__item:hover {
|
|
2233
|
+
background: #f3f4f6;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
/* Browse-all dialog body. Owns the height so the dialog is the same size on
|
|
2237
|
+
every tab; the search is fixed and the card grid flexes/scrolls to fill. */
|
|
2238
|
+
.skill-browse {
|
|
2239
|
+
display: flex;
|
|
2240
|
+
flex-direction: column;
|
|
2241
|
+
height: 70vh;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
.skill-browse kr-text-field {
|
|
2245
|
+
display: block;
|
|
2246
|
+
margin: 20px 20px 16px;
|
|
2247
|
+
flex-shrink: 0;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
.skill-browse kr-tab-group {
|
|
2251
|
+
flex: 1;
|
|
2252
|
+
min-height: 0;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
/* The grid is the scroller and spans the full width, so its scrollbar sits
|
|
2256
|
+
flush against the dialog edge; padding-right insets the cards from it. */
|
|
2257
|
+
.skill-browse__grid {
|
|
2258
|
+
height: 100%;
|
|
2259
|
+
overflow-y: auto;
|
|
2260
|
+
display: grid;
|
|
2261
|
+
grid-template-columns: repeat(3, 1fr);
|
|
2262
|
+
grid-auto-rows: min-content;
|
|
2263
|
+
align-content: start;
|
|
2264
|
+
gap: 12px;
|
|
2265
|
+
padding: 16px 20px 20px;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
.skill-card {
|
|
2269
|
+
display: flex;
|
|
2270
|
+
flex-direction: column;
|
|
2271
|
+
gap: 6px;
|
|
2272
|
+
padding: 14px;
|
|
2273
|
+
background: white;
|
|
2274
|
+
border: 1px solid #e5e7eb;
|
|
2275
|
+
border-radius: 10px;
|
|
2276
|
+
text-align: left;
|
|
2277
|
+
cursor: pointer;
|
|
2278
|
+
font: inherit;
|
|
2279
|
+
color: #000000;
|
|
2280
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
.skill-card:hover {
|
|
2284
|
+
border-color: var(--kr-chatbot-primary);
|
|
2285
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
.skill-card__name {
|
|
2289
|
+
font-weight: 600;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.skill-card__desc {
|
|
2293
|
+
font-size: 12px;
|
|
2294
|
+
color: #000000;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
.skill-browse__empty {
|
|
2298
|
+
height: 100%;
|
|
2299
|
+
padding: 32px;
|
|
2300
|
+
text-align: center;
|
|
2301
|
+
color: #000000;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
1659
2304
|
/* Composer attachment chips (pre-send) */
|
|
1660
2305
|
.composer-attachments {
|
|
1661
2306
|
display: flex;
|
|
@@ -1751,13 +2396,13 @@ __decorate([
|
|
|
1751
2396
|
], KRChatbot.prototype, "messages", void 0);
|
|
1752
2397
|
__decorate([
|
|
1753
2398
|
property({ type: String })
|
|
1754
|
-
], KRChatbot.prototype, "
|
|
1755
|
-
__decorate([
|
|
1756
|
-
property({ type: String })
|
|
1757
|
-
], KRChatbot.prototype, "subtitle", void 0);
|
|
2399
|
+
], KRChatbot.prototype, "label", void 0);
|
|
1758
2400
|
__decorate([
|
|
1759
2401
|
property({ type: String })
|
|
1760
2402
|
], KRChatbot.prototype, "placeholder", void 0);
|
|
2403
|
+
__decorate([
|
|
2404
|
+
property({ attribute: false })
|
|
2405
|
+
], KRChatbot.prototype, "skills", void 0);
|
|
1761
2406
|
__decorate([
|
|
1762
2407
|
property({
|
|
1763
2408
|
type: Boolean,
|
|
@@ -1771,24 +2416,48 @@ __decorate([
|
|
|
1771
2416
|
reflect: true
|
|
1772
2417
|
})
|
|
1773
2418
|
], KRChatbot.prototype, "hideToggle", void 0);
|
|
2419
|
+
__decorate([
|
|
2420
|
+
property({
|
|
2421
|
+
type: String,
|
|
2422
|
+
reflect: true
|
|
2423
|
+
})
|
|
2424
|
+
], KRChatbot.prototype, "variant", void 0);
|
|
1774
2425
|
__decorate([
|
|
1775
2426
|
state()
|
|
1776
2427
|
], KRChatbot.prototype, "_inputValue", void 0);
|
|
1777
2428
|
__decorate([
|
|
1778
2429
|
state()
|
|
1779
2430
|
], KRChatbot.prototype, "_isStreaming", void 0);
|
|
1780
|
-
__decorate([
|
|
1781
|
-
state()
|
|
1782
|
-
], KRChatbot.prototype, "_statusText", void 0);
|
|
1783
2431
|
__decorate([
|
|
1784
2432
|
state()
|
|
1785
2433
|
], KRChatbot.prototype, "_error", void 0);
|
|
1786
2434
|
__decorate([
|
|
1787
2435
|
state()
|
|
1788
|
-
], KRChatbot.prototype, "
|
|
2436
|
+
], KRChatbot.prototype, "_pendingInternalFiles", void 0);
|
|
1789
2437
|
__decorate([
|
|
1790
2438
|
state()
|
|
1791
2439
|
], KRChatbot.prototype, "_isDraggingFile", void 0);
|
|
2440
|
+
__decorate([
|
|
2441
|
+
state()
|
|
2442
|
+
], KRChatbot.prototype, "_skillMenuOpened", void 0);
|
|
2443
|
+
__decorate([
|
|
2444
|
+
state()
|
|
2445
|
+
], KRChatbot.prototype, "_skillQuery", void 0);
|
|
2446
|
+
__decorate([
|
|
2447
|
+
state()
|
|
2448
|
+
], KRChatbot.prototype, "_skillHighlighted", void 0);
|
|
2449
|
+
__decorate([
|
|
2450
|
+
state()
|
|
2451
|
+
], KRChatbot.prototype, "_copiedIndex", void 0);
|
|
2452
|
+
__decorate([
|
|
2453
|
+
state()
|
|
2454
|
+
], KRChatbot.prototype, "_skillBrowseOpened", void 0);
|
|
2455
|
+
__decorate([
|
|
2456
|
+
state()
|
|
2457
|
+
], KRChatbot.prototype, "_skillBrowseSource", void 0);
|
|
2458
|
+
__decorate([
|
|
2459
|
+
state()
|
|
2460
|
+
], KRChatbot.prototype, "_skillBrowseQuery", void 0);
|
|
1792
2461
|
__decorate([
|
|
1793
2462
|
query('.panel')
|
|
1794
2463
|
], KRChatbot.prototype, "_panelEl", void 0);
|
|
@@ -1801,6 +2470,12 @@ __decorate([
|
|
|
1801
2470
|
__decorate([
|
|
1802
2471
|
query('.file-input')
|
|
1803
2472
|
], KRChatbot.prototype, "_fileInputEl", void 0);
|
|
2473
|
+
__decorate([
|
|
2474
|
+
query('.input-wrapper')
|
|
2475
|
+
], KRChatbot.prototype, "_inputWrapperEl", void 0);
|
|
2476
|
+
__decorate([
|
|
2477
|
+
query('.skill-menu')
|
|
2478
|
+
], KRChatbot.prototype, "_skillMenuEl", void 0);
|
|
1804
2479
|
KRChatbot = __decorate([
|
|
1805
2480
|
customElement('kr-chatbot')
|
|
1806
2481
|
], KRChatbot);
|