@kodaris/krubble-app-components 1.0.70 → 1.0.71
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 +72 -26
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +299 -203
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +298 -203
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +195 -147
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +298 -203
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +226 -178
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -11,6 +11,7 @@ import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
|
11
11
|
import { repeat } from 'lit/directives/repeat.js';
|
|
12
12
|
import { marked } from 'marked';
|
|
13
13
|
import { KRFilePreview } from '@kodaris/krubble-components';
|
|
14
|
+
import { KRHttp } from '@kodaris/krubble-http';
|
|
14
15
|
/** Image MIME types the chatbot accepts (matches what Bedrock can ingest). */
|
|
15
16
|
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
16
17
|
/**
|
|
@@ -33,7 +34,7 @@ const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/web
|
|
|
33
34
|
* ## Usage
|
|
34
35
|
* ```html
|
|
35
36
|
* <kr-chatbot
|
|
36
|
-
*
|
|
37
|
+
* label="AI Assistant"
|
|
37
38
|
* .send=${(params) => {
|
|
38
39
|
* return fetch('/api/ai/chat/stream', {
|
|
39
40
|
* method: 'POST',
|
|
@@ -76,16 +77,25 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
76
77
|
/**
|
|
77
78
|
* Callback function for sending messages. Receives the user's message and
|
|
78
79
|
* current conversation history. Must return a Response with a streaming SSE body.
|
|
80
|
+
*
|
|
81
|
+
* Optional — when not set, the chatbot uses its built-in default that streams
|
|
82
|
+
* from the Kodaris Bedrock conversation endpoints. Set this to override.
|
|
79
83
|
*/
|
|
80
84
|
this.send = null;
|
|
81
85
|
/**
|
|
82
86
|
* Callback function called when the user clears the conversation.
|
|
83
87
|
* Must return a Promise — messages are only cleared if it resolves.
|
|
88
|
+
*
|
|
89
|
+
* Optional — when not set, the chatbot uses its built-in default that clears
|
|
90
|
+
* the Kodaris Bedrock conversation state. Set this to override.
|
|
84
91
|
*/
|
|
85
92
|
this.clear = null;
|
|
86
93
|
/**
|
|
87
94
|
* Callback function called on init to load existing conversation messages.
|
|
88
95
|
* Must return a Promise that resolves with an array of KRChatbotMessage.
|
|
96
|
+
*
|
|
97
|
+
* Optional — when not set, the chatbot uses its built-in default that loads
|
|
98
|
+
* existing messages from the Kodaris Bedrock conversation. Set this to override.
|
|
89
99
|
*/
|
|
90
100
|
this.load = null;
|
|
91
101
|
/**
|
|
@@ -94,13 +104,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
94
104
|
*/
|
|
95
105
|
this.messages = [];
|
|
96
106
|
/**
|
|
97
|
-
* Header
|
|
107
|
+
* Header label text.
|
|
98
108
|
*/
|
|
99
|
-
this.
|
|
100
|
-
/**
|
|
101
|
-
* Header subtitle text (shown below title with a green status dot).
|
|
102
|
-
*/
|
|
103
|
-
this.subtitle = '';
|
|
109
|
+
this.label = 'Kat';
|
|
104
110
|
/**
|
|
105
111
|
* Input placeholder text.
|
|
106
112
|
*/
|
|
@@ -115,13 +121,23 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
115
121
|
* then driven via the `open()` / `close()` / `toggle()` methods.
|
|
116
122
|
*/
|
|
117
123
|
this.hideToggle = false;
|
|
124
|
+
/**
|
|
125
|
+
* Layout mode.
|
|
126
|
+
* - `'floating'` (default): the chatbot is a fixed bottom-right widget with a
|
|
127
|
+
* toggle button, and the panel can be dragged and resized. This is the
|
|
128
|
+
* traditional chat-widget appearance.
|
|
129
|
+
* - `'inline'`: the chatbot drops its fixed positioning and fills its parent
|
|
130
|
+
* container (100% width/height, no border radius/shadow, no toggle, no
|
|
131
|
+
* drag/resize). The panel is always open. Use this to embed the chat as a
|
|
132
|
+
* full page or panel region.
|
|
133
|
+
*/
|
|
134
|
+
this.variant = 'floating';
|
|
118
135
|
// --- @state (internal) ---
|
|
119
136
|
this._inputValue = '';
|
|
120
137
|
this._isStreaming = false;
|
|
121
|
-
this._statusText = '';
|
|
122
138
|
this._error = '';
|
|
123
139
|
/** Images staged in the composer, before the message is sent. */
|
|
124
|
-
this.
|
|
140
|
+
this._pendingInternalFiles = [];
|
|
125
141
|
/** True while a file is being dragged over the panel (shows the drop overlay). */
|
|
126
142
|
this._isDraggingFile = false;
|
|
127
143
|
this._handleMouseMove = (e) => {
|
|
@@ -185,12 +201,27 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
185
201
|
// --- Lifecycle ---
|
|
186
202
|
connectedCallback() {
|
|
187
203
|
super.connectedCallback();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
204
|
+
// Default to loading existing messages from the Kodaris Bedrock conversation
|
|
205
|
+
// when the consumer hasn't provided their own `load`. The endpoint returns
|
|
206
|
+
// messages already in KRChatbotMessage shape ({ type, content, images }), so
|
|
207
|
+
// they pass straight through.
|
|
208
|
+
if (!this.load) {
|
|
209
|
+
this.load = () => KRHttp.fetch({
|
|
210
|
+
url: '/api/system/integration/aws/bedrock/getConversationMessages',
|
|
211
|
+
})
|
|
212
|
+
.then((r) => r.json())
|
|
213
|
+
.then((res) => res.data || []);
|
|
214
|
+
}
|
|
215
|
+
this.load().then((messages) => {
|
|
216
|
+
if (messages && messages.length) {
|
|
217
|
+
this.messages = messages;
|
|
218
|
+
}
|
|
219
|
+
}).catch(() => { });
|
|
220
|
+
// Inline mode fills its container and is always open — the floating widget's
|
|
221
|
+
// toggle/drag/resize state (including any persisted layout) doesn't apply.
|
|
222
|
+
if (this.variant === 'inline') {
|
|
223
|
+
this.opened = true;
|
|
224
|
+
return;
|
|
194
225
|
}
|
|
195
226
|
this._restoreState();
|
|
196
227
|
}
|
|
@@ -203,10 +234,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
203
234
|
document.removeEventListener('mousemove', this._handleMouseMove);
|
|
204
235
|
document.removeEventListener('mouseup', this._handleMouseUp);
|
|
205
236
|
// Release any object URLs still held by staged attachments.
|
|
206
|
-
this.
|
|
237
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
207
238
|
}
|
|
208
239
|
updated(changed) {
|
|
209
|
-
if (changed.has('messages')
|
|
240
|
+
if (changed.has('messages')) {
|
|
210
241
|
if (!this._userHasScrolledUp && !this._isStreaming && this._messagesEl) {
|
|
211
242
|
requestAnimationFrame(() => {
|
|
212
243
|
if (this._messagesEl) {
|
|
@@ -280,6 +311,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
280
311
|
}
|
|
281
312
|
}
|
|
282
313
|
_handleHeaderMouseDown(e) {
|
|
314
|
+
// Inline mode fills its parent — dragging the panel doesn't apply.
|
|
315
|
+
if (this.variant === 'inline') {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
283
318
|
// Ignore clicks on buttons inside the header
|
|
284
319
|
if (e.target.closest('button')) {
|
|
285
320
|
return;
|
|
@@ -305,6 +340,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
305
340
|
document.addEventListener('mouseup', this._handleMouseUp);
|
|
306
341
|
}
|
|
307
342
|
_handleResizeMouseDown(e) {
|
|
343
|
+
// Inline mode fills its parent — resizing the panel doesn't apply.
|
|
344
|
+
if (this.variant === 'inline') {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
308
347
|
if (!this._panelEl) {
|
|
309
348
|
return;
|
|
310
349
|
}
|
|
@@ -497,54 +536,49 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
497
536
|
name: file.name,
|
|
498
537
|
previewUrl: URL.createObjectURL(file),
|
|
499
538
|
};
|
|
500
|
-
this.
|
|
539
|
+
this._pendingInternalFiles = [...this._pendingInternalFiles, pending];
|
|
501
540
|
});
|
|
502
541
|
}
|
|
503
542
|
_removePending(localId) {
|
|
504
|
-
const target = this.
|
|
543
|
+
const target = this._pendingInternalFiles.find((p) => p.localId === localId);
|
|
505
544
|
if (target) {
|
|
506
545
|
URL.revokeObjectURL(target.previewUrl);
|
|
507
546
|
}
|
|
508
|
-
this.
|
|
547
|
+
this._pendingInternalFiles = this._pendingInternalFiles.filter((p) => p.localId !== localId);
|
|
509
548
|
}
|
|
510
549
|
_clearPending() {
|
|
511
|
-
this.
|
|
512
|
-
this.
|
|
550
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
551
|
+
this._pendingInternalFiles = [];
|
|
513
552
|
}
|
|
514
|
-
/** Open the shared full-screen previewer for
|
|
515
|
-
|
|
553
|
+
/** Open the shared full-screen previewer for a message image. */
|
|
554
|
+
_openImage(img) {
|
|
516
555
|
KRFilePreview.open({
|
|
517
|
-
src:
|
|
518
|
-
name:
|
|
556
|
+
src: `/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view`,
|
|
557
|
+
name: img.niceName || 'image',
|
|
519
558
|
});
|
|
520
559
|
}
|
|
521
560
|
/** Whether the send button is active: needs text or at least one image. */
|
|
522
561
|
_canSend() {
|
|
523
|
-
return Boolean(this._inputValue.trim()) || this.
|
|
562
|
+
return Boolean(this._inputValue.trim()) || this._pendingInternalFiles.length > 0;
|
|
524
563
|
}
|
|
525
564
|
_handleSubmit() {
|
|
526
|
-
if (
|
|
565
|
+
if (this._isStreaming) {
|
|
527
566
|
return;
|
|
528
567
|
}
|
|
529
568
|
const messageText = this._inputValue.trim();
|
|
530
|
-
const pending = this.
|
|
569
|
+
const pending = this._pendingInternalFiles;
|
|
531
570
|
// Allow sending an image with no text, but require at least one of them.
|
|
532
571
|
if (!messageText && !pending.length) {
|
|
533
572
|
return;
|
|
534
573
|
}
|
|
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
|
-
}));
|
|
574
|
+
// The raw files sent to the backend. Sent images are shown once the
|
|
575
|
+
// conversation reloads and they come back as stored internal files; the
|
|
576
|
+
// just-sent message itself carries text only.
|
|
577
|
+
const internalFiles = pending.map((p) => p.file);
|
|
544
578
|
const submitEvent = new CustomEvent('message-submit', {
|
|
545
579
|
detail: {
|
|
546
580
|
message: messageText,
|
|
547
|
-
|
|
581
|
+
internalFiles,
|
|
548
582
|
},
|
|
549
583
|
bubbles: true,
|
|
550
584
|
composed: true,
|
|
@@ -555,30 +589,26 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
555
589
|
return;
|
|
556
590
|
}
|
|
557
591
|
const userMessage = {
|
|
558
|
-
|
|
592
|
+
type: 'user',
|
|
559
593
|
content: messageText,
|
|
560
594
|
};
|
|
561
|
-
if (attachments.length) {
|
|
562
|
-
userMessage.attachments = attachments;
|
|
563
|
-
}
|
|
564
595
|
this.messages = [...this.messages, userMessage];
|
|
565
596
|
this._inputValue = '';
|
|
566
597
|
this._error = '';
|
|
567
598
|
this._userHasScrolledUp = false;
|
|
568
|
-
// Clear the composer
|
|
569
|
-
|
|
570
|
-
this.
|
|
599
|
+
// Clear the composer and release its preview object URLs.
|
|
600
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
601
|
+
this._pendingInternalFiles = [];
|
|
571
602
|
// Reset textarea height
|
|
572
603
|
if (this._inputEl) {
|
|
573
604
|
this._inputEl.style.height = 'auto';
|
|
574
605
|
}
|
|
575
606
|
// Add empty assistant message to stream into
|
|
576
607
|
this.messages = [...this.messages, {
|
|
577
|
-
|
|
608
|
+
type: 'assistant',
|
|
578
609
|
content: '',
|
|
579
610
|
}];
|
|
580
611
|
this._isStreaming = true;
|
|
581
|
-
this._statusText = '';
|
|
582
612
|
// Scroll user's message to the top (Gemini-style).
|
|
583
613
|
// Set min-height on the assistant message = container height so there's enough
|
|
584
614
|
// scroll room below the user message to push it to the top.
|
|
@@ -600,10 +630,34 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
600
630
|
this._messagesEl.scrollTop = userMsgEl.offsetTop - this._messagesEl.offsetTop - 24;
|
|
601
631
|
}
|
|
602
632
|
});
|
|
633
|
+
// Default to streaming from the Kodaris Bedrock conversation endpoints when
|
|
634
|
+
// the consumer hasn't provided their own `send`.
|
|
635
|
+
if (!this.send) {
|
|
636
|
+
this.send = (params) => {
|
|
637
|
+
// With images, post text + files as multipart; without, post plain JSON.
|
|
638
|
+
if (params.internalFiles.length) {
|
|
639
|
+
const form = new FormData();
|
|
640
|
+
form.append('userText', params.message);
|
|
641
|
+
params.internalFiles.forEach((file) => form.append('images', file));
|
|
642
|
+
return KRHttp.fetch({
|
|
643
|
+
url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithImages',
|
|
644
|
+
method: 'POST',
|
|
645
|
+
headers: { Accept: 'text/event-stream' },
|
|
646
|
+
body: form,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return KRHttp.fetch({
|
|
650
|
+
url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithState',
|
|
651
|
+
method: 'POST',
|
|
652
|
+
headers: { Accept: 'text/event-stream' },
|
|
653
|
+
body: { userText: params.message },
|
|
654
|
+
});
|
|
655
|
+
};
|
|
656
|
+
}
|
|
603
657
|
this.send({
|
|
604
658
|
message: messageText,
|
|
605
659
|
messages: this.messages.slice(0, -1),
|
|
606
|
-
|
|
660
|
+
internalFiles,
|
|
607
661
|
}).then((response) => {
|
|
608
662
|
if (!response.ok) {
|
|
609
663
|
this._handleStreamError('Request failed: ' + response.status);
|
|
@@ -629,11 +683,15 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
629
683
|
this._reader = null;
|
|
630
684
|
}
|
|
631
685
|
this._isStreaming = false;
|
|
632
|
-
this._statusText = '';
|
|
633
686
|
}
|
|
634
687
|
_handleClear() {
|
|
688
|
+
// Default to clearing the Kodaris Bedrock conversation state when the consumer
|
|
689
|
+
// hasn't provided their own `clear`.
|
|
635
690
|
if (!this.clear) {
|
|
636
|
-
|
|
691
|
+
this.clear = () => KRHttp.fetch({
|
|
692
|
+
url: '/api/system/integration/aws/bedrock/clearConversationWithState',
|
|
693
|
+
method: 'POST',
|
|
694
|
+
});
|
|
637
695
|
}
|
|
638
696
|
this.clear().then(() => {
|
|
639
697
|
if (this._reader) {
|
|
@@ -642,7 +700,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
642
700
|
}
|
|
643
701
|
this.messages = [];
|
|
644
702
|
this._isStreaming = false;
|
|
645
|
-
this._statusText = '';
|
|
646
703
|
this._error = '';
|
|
647
704
|
this._inputValue = '';
|
|
648
705
|
this._userHasScrolledUp = false;
|
|
@@ -668,7 +725,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
668
725
|
this._reader.read().then((result) => {
|
|
669
726
|
if (result.done) {
|
|
670
727
|
this._isStreaming = false;
|
|
671
|
-
this._statusText = '';
|
|
672
728
|
this._reader = null;
|
|
673
729
|
return;
|
|
674
730
|
}
|
|
@@ -701,15 +757,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
701
757
|
bubbles: true,
|
|
702
758
|
composed: true,
|
|
703
759
|
}));
|
|
704
|
-
if (data.type === '
|
|
705
|
-
if (data.message) {
|
|
706
|
-
this._statusText = String(data.message);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
else if (data.type === 'REASONING_PART') {
|
|
760
|
+
if (data.type === 'REASONING_PART') {
|
|
710
761
|
if (data.message) {
|
|
711
762
|
const lastIndex = this.messages.length - 1;
|
|
712
|
-
if (lastIndex >= 0 && this.messages[lastIndex].
|
|
763
|
+
if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
|
|
713
764
|
if (!this.messages[lastIndex].reasoning) {
|
|
714
765
|
this.messages[lastIndex].reasoning = '';
|
|
715
766
|
}
|
|
@@ -720,9 +771,8 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
720
771
|
}
|
|
721
772
|
else if (data.type === 'RESPONSE_PART') {
|
|
722
773
|
if (data.message) {
|
|
723
|
-
this._statusText = '';
|
|
724
774
|
const lastIndex = this.messages.length - 1;
|
|
725
|
-
if (lastIndex >= 0 && this.messages[lastIndex].
|
|
775
|
+
if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
|
|
726
776
|
this.messages[lastIndex].content += String(data.message);
|
|
727
777
|
this.requestUpdate();
|
|
728
778
|
}
|
|
@@ -733,7 +783,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
733
783
|
}
|
|
734
784
|
if (data.finished) {
|
|
735
785
|
this._isStreaming = false;
|
|
736
|
-
this._statusText = '';
|
|
737
786
|
this._reader = null;
|
|
738
787
|
}
|
|
739
788
|
}
|
|
@@ -747,7 +796,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
747
796
|
}
|
|
748
797
|
_handleStreamError(message) {
|
|
749
798
|
this._isStreaming = false;
|
|
750
|
-
this._statusText = '';
|
|
751
799
|
this._error = message;
|
|
752
800
|
if (this._reader) {
|
|
753
801
|
this._reader.cancel();
|
|
@@ -756,7 +804,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
756
804
|
// Remove the empty assistant message if it has no content
|
|
757
805
|
if (this.messages.length > 0) {
|
|
758
806
|
const lastMessage = this.messages[this.messages.length - 1];
|
|
759
|
-
if (lastMessage.
|
|
807
|
+
if (lastMessage.type === 'assistant' && !lastMessage.content) {
|
|
760
808
|
this.messages = this.messages.slice(0, -1);
|
|
761
809
|
}
|
|
762
810
|
}
|
|
@@ -764,7 +812,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
764
812
|
// --- Render ---
|
|
765
813
|
render() {
|
|
766
814
|
return html `
|
|
767
|
-
${this.hideToggle ? nothing : html `
|
|
815
|
+
${(this.hideToggle || this.variant === 'inline') ? nothing : html `
|
|
768
816
|
<button
|
|
769
817
|
class=${classMap({
|
|
770
818
|
'toggle': true,
|
|
@@ -815,53 +863,52 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
815
863
|
<span>Drop images to attach</span>
|
|
816
864
|
</div>
|
|
817
865
|
` : 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
|
-
|
|
866
|
+
${this.variant === 'inline' ? nothing : html `
|
|
867
|
+
<div
|
|
868
|
+
class="resize resize--n"
|
|
869
|
+
data-dir="n"
|
|
870
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
871
|
+
></div>
|
|
872
|
+
<div
|
|
873
|
+
class="resize resize--s"
|
|
874
|
+
data-dir="s"
|
|
875
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
876
|
+
></div>
|
|
877
|
+
<div
|
|
878
|
+
class="resize resize--w"
|
|
879
|
+
data-dir="w"
|
|
880
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
881
|
+
></div>
|
|
882
|
+
<div
|
|
883
|
+
class="resize resize--e"
|
|
884
|
+
data-dir="e"
|
|
885
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
886
|
+
></div>
|
|
887
|
+
<div
|
|
888
|
+
class="resize resize--nw"
|
|
889
|
+
data-dir="nw"
|
|
890
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
891
|
+
></div>
|
|
892
|
+
<div
|
|
893
|
+
class="resize resize--ne"
|
|
894
|
+
data-dir="ne"
|
|
895
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
896
|
+
></div>
|
|
897
|
+
<div
|
|
898
|
+
class="resize resize--sw"
|
|
899
|
+
data-dir="sw"
|
|
900
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
901
|
+
></div>
|
|
902
|
+
<div
|
|
903
|
+
class="resize resize--se"
|
|
904
|
+
data-dir="se"
|
|
905
|
+
@mousedown=${this._handleResizeMouseDown}
|
|
906
|
+
></div>
|
|
907
|
+
`}
|
|
858
908
|
<div class="header" @mousedown=${this._handleHeaderMouseDown}>
|
|
859
909
|
<div class="header-left">
|
|
860
910
|
<div class="header-info">
|
|
861
|
-
<span class="header-title">${this.
|
|
862
|
-
${this.subtitle ? html `
|
|
863
|
-
<span class="header-subtitle">${this.subtitle}</span>
|
|
864
|
-
` : nothing}
|
|
911
|
+
<span class="header-title">${this.label}</span>
|
|
865
912
|
</div>
|
|
866
913
|
</div>
|
|
867
914
|
<div class="header-actions">
|
|
@@ -887,77 +934,84 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
887
934
|
</svg>
|
|
888
935
|
</button>
|
|
889
936
|
` : 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"
|
|
937
|
+
${this.variant === 'inline' ? nothing : html `
|
|
938
|
+
<button
|
|
939
|
+
class="header-action"
|
|
940
|
+
@click=${this._handleToggle}
|
|
941
|
+
title="Close"
|
|
902
942
|
>
|
|
903
|
-
<
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
943
|
+
<svg
|
|
944
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
945
|
+
fill="none"
|
|
946
|
+
viewBox="0 0 24 24"
|
|
947
|
+
stroke-width="2"
|
|
948
|
+
stroke="currentColor"
|
|
949
|
+
class="header-action-icon"
|
|
950
|
+
>
|
|
951
|
+
<path
|
|
952
|
+
stroke-linecap="round"
|
|
953
|
+
stroke-linejoin="round"
|
|
954
|
+
d="M6 18 18 6M6 6l12 12"
|
|
955
|
+
/>
|
|
956
|
+
</svg>
|
|
957
|
+
</button>
|
|
958
|
+
`}
|
|
910
959
|
</div>
|
|
911
960
|
</div>
|
|
912
961
|
|
|
913
962
|
<div class="messages" @scroll=${this._handleMessagesScroll}>
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
<
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
963
|
+
<div class="messages-inner">
|
|
964
|
+
${this.messages.length === 0 ? html `
|
|
965
|
+
<div class="empty">
|
|
966
|
+
<span class="empty-text">What can I help you with?</span>
|
|
967
|
+
</div>
|
|
968
|
+
` : nothing}
|
|
969
|
+
${this.messages.map((msg) => html `
|
|
970
|
+
<div class=${classMap({
|
|
921
971
|
'message': true,
|
|
922
|
-
'message--user': msg.
|
|
923
|
-
'message--assistant': msg.
|
|
972
|
+
'message--user': msg.type === 'user',
|
|
973
|
+
'message--assistant': msg.type === 'assistant',
|
|
924
974
|
})}>
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
975
|
+
${msg.reasoning ? html `
|
|
976
|
+
<details class="reasoning">
|
|
977
|
+
<summary>Thinking</summary>
|
|
978
|
+
<div class="reasoning-content">${msg.reasoning}</div>
|
|
979
|
+
</details>
|
|
980
|
+
` : nothing}
|
|
981
|
+
${msg.internalFiles && msg.internalFiles.length ? html `
|
|
982
|
+
<div class="attachments">
|
|
983
|
+
${msg.internalFiles.map((img) => html `
|
|
984
|
+
<button
|
|
985
|
+
class="attachment"
|
|
986
|
+
type="button"
|
|
987
|
+
title=${img.niceName || 'View image'}
|
|
988
|
+
@click=${() => this._openImage(img)}
|
|
989
|
+
>
|
|
990
|
+
<img
|
|
991
|
+
src="/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view"
|
|
992
|
+
alt=${img.niceName || ''}
|
|
993
|
+
/>
|
|
994
|
+
</button>
|
|
995
|
+
`)}
|
|
996
|
+
</div>
|
|
997
|
+
` : nothing}
|
|
998
|
+
${msg.content ? html `
|
|
999
|
+
<div class="message-content">
|
|
1000
|
+
${msg.type === 'user'
|
|
948
1001
|
? msg.content
|
|
949
1002
|
: unsafeHTML(marked.parse(msg.content, { breaks: true }))}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1003
|
+
</div>
|
|
1004
|
+
` : nothing}
|
|
1005
|
+
${msg.type === 'assistant' && this._isStreaming && msg === this.messages[this.messages.length - 1] ? html `
|
|
1006
|
+
<div class="typing">
|
|
1007
|
+
<span></span>
|
|
1008
|
+
<span></span>
|
|
1009
|
+
<span></span>
|
|
1010
|
+
</div>
|
|
1011
|
+
` : nothing}
|
|
1012
|
+
</div>
|
|
1013
|
+
`)}
|
|
1014
|
+
</div>
|
|
961
1015
|
</div>
|
|
962
1016
|
|
|
963
1017
|
${this._error ? html `
|
|
@@ -983,9 +1037,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
983
1037
|
` : nothing}
|
|
984
1038
|
|
|
985
1039
|
<div class="footer">
|
|
986
|
-
${this.
|
|
1040
|
+
${this._pendingInternalFiles.length ? html `
|
|
987
1041
|
<div class="composer-attachments">
|
|
988
|
-
${repeat(this.
|
|
1042
|
+
${repeat(this._pendingInternalFiles, (p) => p.localId, (p) => html `
|
|
989
1043
|
<div class="chip">
|
|
990
1044
|
<img src=${p.previewUrl} alt=${p.name} />
|
|
991
1045
|
<button
|
|
@@ -1174,6 +1228,28 @@ KRChatbot.styles = css `
|
|
|
1174
1228
|
display: flex;
|
|
1175
1229
|
}
|
|
1176
1230
|
|
|
1231
|
+
/* Inline mode: fill the parent container instead of floating bottom-right. */
|
|
1232
|
+
:host([variant='inline']) {
|
|
1233
|
+
position: static;
|
|
1234
|
+
bottom: auto;
|
|
1235
|
+
right: auto;
|
|
1236
|
+
z-index: auto;
|
|
1237
|
+
height: 100%;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
:host([variant='inline']) .panel {
|
|
1241
|
+
position: relative;
|
|
1242
|
+
bottom: auto;
|
|
1243
|
+
right: auto;
|
|
1244
|
+
width: 100%;
|
|
1245
|
+
height: 100%;
|
|
1246
|
+
max-height: none;
|
|
1247
|
+
border-radius: 0;
|
|
1248
|
+
box-shadow: none;
|
|
1249
|
+
border: none;
|
|
1250
|
+
animation: none;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1177
1253
|
@keyframes chatbot-panel-in {
|
|
1178
1254
|
from {
|
|
1179
1255
|
opacity: 0;
|
|
@@ -1204,7 +1280,7 @@ KRChatbot.styles = css `
|
|
|
1204
1280
|
gap: 10px;
|
|
1205
1281
|
}
|
|
1206
1282
|
|
|
1207
|
-
.header-info {
|
|
1283
|
+
.header-info {
|
|
1208
1284
|
display: flex;
|
|
1209
1285
|
flex-direction: column;
|
|
1210
1286
|
}
|
|
@@ -1214,22 +1290,6 @@ KRChatbot.styles = css `
|
|
|
1214
1290
|
font-weight: 600;
|
|
1215
1291
|
}
|
|
1216
1292
|
|
|
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
1293
|
.header-actions {
|
|
1234
1294
|
display: flex;
|
|
1235
1295
|
align-items: center;
|
|
@@ -1261,17 +1321,29 @@ KRChatbot.styles = css `
|
|
|
1261
1321
|
}
|
|
1262
1322
|
|
|
1263
1323
|
/* Messages */
|
|
1324
|
+
/* Full-width scroll container — keeps the scrollbar at the panel edge. */
|
|
1264
1325
|
.messages {
|
|
1265
1326
|
flex: 1;
|
|
1266
1327
|
overflow-y: auto;
|
|
1267
|
-
padding: 32px;
|
|
1268
1328
|
display: flex;
|
|
1269
1329
|
flex-direction: column;
|
|
1270
|
-
gap: 24px;
|
|
1271
1330
|
scroll-behavior: smooth;
|
|
1272
1331
|
background: white;
|
|
1273
1332
|
}
|
|
1274
1333
|
|
|
1334
|
+
/* Centered, width-capped column that actually holds the messages. */
|
|
1335
|
+
.messages-inner {
|
|
1336
|
+
flex: 1;
|
|
1337
|
+
display: flex;
|
|
1338
|
+
flex-direction: column;
|
|
1339
|
+
gap: 24px;
|
|
1340
|
+
padding: 32px;
|
|
1341
|
+
width: 100%;
|
|
1342
|
+
max-width: 768px;
|
|
1343
|
+
margin-left: auto;
|
|
1344
|
+
margin-right: auto;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1275
1347
|
.messages::-webkit-scrollbar {
|
|
1276
1348
|
width: 4px;
|
|
1277
1349
|
}
|
|
@@ -1295,7 +1367,7 @@ KRChatbot.styles = css `
|
|
|
1295
1367
|
gap: 8px;
|
|
1296
1368
|
}
|
|
1297
1369
|
|
|
1298
|
-
.empty-text {
|
|
1370
|
+
.empty-text {
|
|
1299
1371
|
font-size: 14px;
|
|
1300
1372
|
color: rgb(0 0 0 / 80%);
|
|
1301
1373
|
}
|
|
@@ -1407,7 +1479,7 @@ KRChatbot.styles = css `
|
|
|
1407
1479
|
word-wrap: break-word;
|
|
1408
1480
|
}
|
|
1409
1481
|
|
|
1410
|
-
.typing {
|
|
1482
|
+
.typing {
|
|
1411
1483
|
display: flex;
|
|
1412
1484
|
gap: 4px;
|
|
1413
1485
|
padding: 4px 0;
|
|
@@ -1475,6 +1547,15 @@ KRChatbot.styles = css `
|
|
|
1475
1547
|
padding: 16px 16px 16px;
|
|
1476
1548
|
flex-shrink: 0;
|
|
1477
1549
|
background: white;
|
|
1550
|
+
width: 100%;
|
|
1551
|
+
max-width: 768px;
|
|
1552
|
+
margin-left: auto;
|
|
1553
|
+
margin-right: auto;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/* Inline mode: keep the composer clear of the bottom edge. */
|
|
1557
|
+
:host([variant='inline']) .footer {
|
|
1558
|
+
padding-bottom: 40px;
|
|
1478
1559
|
}
|
|
1479
1560
|
|
|
1480
1561
|
.input-wrapper {
|
|
@@ -1493,6 +1574,15 @@ KRChatbot.styles = css `
|
|
|
1493
1574
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
1494
1575
|
}
|
|
1495
1576
|
|
|
1577
|
+
/* Inline mode: keep the border and add a soft floating shadow (ChatGPT/Gemini style). */
|
|
1578
|
+
:host([variant='inline']) .input-wrapper {
|
|
1579
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
:host([variant='inline']) .input-wrapper:focus-within {
|
|
1583
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 12px 32px rgba(0, 0, 0, 0.14);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1496
1586
|
.input-plus {
|
|
1497
1587
|
width: 34px;
|
|
1498
1588
|
height: 34px;
|
|
@@ -1600,6 +1690,12 @@ KRChatbot.styles = css `
|
|
|
1600
1690
|
cursor: grabbing;
|
|
1601
1691
|
}
|
|
1602
1692
|
|
|
1693
|
+
/* Inline mode isn't draggable — don't show the grab/hand cursor. */
|
|
1694
|
+
:host([variant='inline']) .header,
|
|
1695
|
+
:host([variant='inline']) .header:active {
|
|
1696
|
+
cursor: default;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1603
1699
|
/* Drag / Resize states */
|
|
1604
1700
|
.panel--dragging,
|
|
1605
1701
|
.panel--resizing {
|
|
@@ -1751,10 +1847,7 @@ __decorate([
|
|
|
1751
1847
|
], KRChatbot.prototype, "messages", void 0);
|
|
1752
1848
|
__decorate([
|
|
1753
1849
|
property({ type: String })
|
|
1754
|
-
], KRChatbot.prototype, "
|
|
1755
|
-
__decorate([
|
|
1756
|
-
property({ type: String })
|
|
1757
|
-
], KRChatbot.prototype, "subtitle", void 0);
|
|
1850
|
+
], KRChatbot.prototype, "label", void 0);
|
|
1758
1851
|
__decorate([
|
|
1759
1852
|
property({ type: String })
|
|
1760
1853
|
], KRChatbot.prototype, "placeholder", void 0);
|
|
@@ -1771,21 +1864,24 @@ __decorate([
|
|
|
1771
1864
|
reflect: true
|
|
1772
1865
|
})
|
|
1773
1866
|
], KRChatbot.prototype, "hideToggle", void 0);
|
|
1867
|
+
__decorate([
|
|
1868
|
+
property({
|
|
1869
|
+
type: String,
|
|
1870
|
+
reflect: true
|
|
1871
|
+
})
|
|
1872
|
+
], KRChatbot.prototype, "variant", void 0);
|
|
1774
1873
|
__decorate([
|
|
1775
1874
|
state()
|
|
1776
1875
|
], KRChatbot.prototype, "_inputValue", void 0);
|
|
1777
1876
|
__decorate([
|
|
1778
1877
|
state()
|
|
1779
1878
|
], KRChatbot.prototype, "_isStreaming", void 0);
|
|
1780
|
-
__decorate([
|
|
1781
|
-
state()
|
|
1782
|
-
], KRChatbot.prototype, "_statusText", void 0);
|
|
1783
1879
|
__decorate([
|
|
1784
1880
|
state()
|
|
1785
1881
|
], KRChatbot.prototype, "_error", void 0);
|
|
1786
1882
|
__decorate([
|
|
1787
1883
|
state()
|
|
1788
|
-
], KRChatbot.prototype, "
|
|
1884
|
+
], KRChatbot.prototype, "_pendingInternalFiles", void 0);
|
|
1789
1885
|
__decorate([
|
|
1790
1886
|
state()
|
|
1791
1887
|
], KRChatbot.prototype, "_isDraggingFile", void 0);
|