@kodaris/krubble-app-components 1.0.69 → 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 +113 -12
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +648 -178
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +688 -208
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +428 -178
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +687 -207
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +418 -168
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -8,7 +8,12 @@ import { LitElement, html, css, nothing } from 'lit';
|
|
|
8
8
|
import { customElement, property, state, query } from 'lit/decorators.js';
|
|
9
9
|
import { classMap } from 'lit/directives/class-map.js';
|
|
10
10
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
11
|
+
import { repeat } from 'lit/directives/repeat.js';
|
|
11
12
|
import { marked } from 'marked';
|
|
13
|
+
import { KRFilePreview } from '@kodaris/krubble-components';
|
|
14
|
+
import { KRHttp } from '@kodaris/krubble-http';
|
|
15
|
+
/** Image MIME types the chatbot accepts (matches what Bedrock can ingest). */
|
|
16
|
+
const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
|
12
17
|
/**
|
|
13
18
|
* AI chatbot component with SSE streaming.
|
|
14
19
|
*
|
|
@@ -29,7 +34,7 @@ import { marked } from 'marked';
|
|
|
29
34
|
* ## Usage
|
|
30
35
|
* ```html
|
|
31
36
|
* <kr-chatbot
|
|
32
|
-
*
|
|
37
|
+
* label="AI Assistant"
|
|
33
38
|
* .send=${(params) => {
|
|
34
39
|
* return fetch('/api/ai/chat/stream', {
|
|
35
40
|
* method: 'POST',
|
|
@@ -64,20 +69,33 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
64
69
|
this._dragStartTop = 0;
|
|
65
70
|
this._resizeStartW = 0;
|
|
66
71
|
this._resizeStartH = 0;
|
|
72
|
+
/** Counter for depth-aware dragenter/dragleave tracking. */
|
|
73
|
+
this._dragDepth = 0;
|
|
74
|
+
/** Monotonic id source for pending attachments. */
|
|
75
|
+
this._pendingSeq = 0;
|
|
67
76
|
// --- @property (public API) ---
|
|
68
77
|
/**
|
|
69
78
|
* Callback function for sending messages. Receives the user's message and
|
|
70
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.
|
|
71
83
|
*/
|
|
72
84
|
this.send = null;
|
|
73
85
|
/**
|
|
74
86
|
* Callback function called when the user clears the conversation.
|
|
75
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.
|
|
76
91
|
*/
|
|
77
92
|
this.clear = null;
|
|
78
93
|
/**
|
|
79
94
|
* Callback function called on init to load existing conversation messages.
|
|
80
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.
|
|
81
99
|
*/
|
|
82
100
|
this.load = null;
|
|
83
101
|
/**
|
|
@@ -86,13 +104,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
86
104
|
*/
|
|
87
105
|
this.messages = [];
|
|
88
106
|
/**
|
|
89
|
-
* Header
|
|
107
|
+
* Header label text.
|
|
90
108
|
*/
|
|
91
|
-
this.
|
|
92
|
-
/**
|
|
93
|
-
* Header subtitle text (shown below title with a green status dot).
|
|
94
|
-
*/
|
|
95
|
-
this.subtitle = '';
|
|
109
|
+
this.label = 'Kat';
|
|
96
110
|
/**
|
|
97
111
|
* Input placeholder text.
|
|
98
112
|
*/
|
|
@@ -101,11 +115,31 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
101
115
|
* Whether the chat panel is open. Only relevant in floating mode.
|
|
102
116
|
*/
|
|
103
117
|
this.opened = false;
|
|
118
|
+
/**
|
|
119
|
+
* Hide the built-in floating toggle button. Use this when an external
|
|
120
|
+
* control (e.g. the site-editor bar) owns opening the chat — the panel is
|
|
121
|
+
* then driven via the `open()` / `close()` / `toggle()` methods.
|
|
122
|
+
*/
|
|
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';
|
|
104
135
|
// --- @state (internal) ---
|
|
105
136
|
this._inputValue = '';
|
|
106
137
|
this._isStreaming = false;
|
|
107
|
-
this._statusText = '';
|
|
108
138
|
this._error = '';
|
|
139
|
+
/** Images staged in the composer, before the message is sent. */
|
|
140
|
+
this._pendingInternalFiles = [];
|
|
141
|
+
/** True while a file is being dragged over the panel (shows the drop overlay). */
|
|
142
|
+
this._isDraggingFile = false;
|
|
109
143
|
this._handleMouseMove = (e) => {
|
|
110
144
|
if (this._isDragging) {
|
|
111
145
|
let newLeft = this._dragStartLeft + (e.clientX - this._dragStartX);
|
|
@@ -167,12 +201,27 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
167
201
|
// --- Lifecycle ---
|
|
168
202
|
connectedCallback() {
|
|
169
203
|
super.connectedCallback();
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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;
|
|
176
225
|
}
|
|
177
226
|
this._restoreState();
|
|
178
227
|
}
|
|
@@ -184,9 +233,11 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
184
233
|
}
|
|
185
234
|
document.removeEventListener('mousemove', this._handleMouseMove);
|
|
186
235
|
document.removeEventListener('mouseup', this._handleMouseUp);
|
|
236
|
+
// Release any object URLs still held by staged attachments.
|
|
237
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
187
238
|
}
|
|
188
239
|
updated(changed) {
|
|
189
|
-
if (changed.has('messages')
|
|
240
|
+
if (changed.has('messages')) {
|
|
190
241
|
if (!this._userHasScrolledUp && !this._isStreaming && this._messagesEl) {
|
|
191
242
|
requestAnimationFrame(() => {
|
|
192
243
|
if (this._messagesEl) {
|
|
@@ -210,6 +261,22 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
210
261
|
stop() {
|
|
211
262
|
this._handleStop();
|
|
212
263
|
}
|
|
264
|
+
/** Open the chat panel. */
|
|
265
|
+
open() {
|
|
266
|
+
if (!this.opened) {
|
|
267
|
+
this._handleToggle();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/** Close the chat panel. */
|
|
271
|
+
close() {
|
|
272
|
+
if (this.opened) {
|
|
273
|
+
this._handleToggle();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/** Toggle the chat panel open/closed. */
|
|
277
|
+
toggle() {
|
|
278
|
+
this._handleToggle();
|
|
279
|
+
}
|
|
213
280
|
// --- Private methods ---
|
|
214
281
|
/**
|
|
215
282
|
* Shows a full-viewport transparent overlay during drag/resize operations.
|
|
@@ -244,6 +311,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
244
311
|
}
|
|
245
312
|
}
|
|
246
313
|
_handleHeaderMouseDown(e) {
|
|
314
|
+
// Inline mode fills its parent — dragging the panel doesn't apply.
|
|
315
|
+
if (this.variant === 'inline') {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
247
318
|
// Ignore clicks on buttons inside the header
|
|
248
319
|
if (e.target.closest('button')) {
|
|
249
320
|
return;
|
|
@@ -269,6 +340,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
269
340
|
document.addEventListener('mouseup', this._handleMouseUp);
|
|
270
341
|
}
|
|
271
342
|
_handleResizeMouseDown(e) {
|
|
343
|
+
// Inline mode fills its parent — resizing the panel doesn't apply.
|
|
344
|
+
if (this.variant === 'inline') {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
272
347
|
if (!this._panelEl) {
|
|
273
348
|
return;
|
|
274
349
|
}
|
|
@@ -378,14 +453,132 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
378
453
|
this._handleSubmit();
|
|
379
454
|
}
|
|
380
455
|
}
|
|
456
|
+
// --- Attachments ---
|
|
457
|
+
_openFilePicker() {
|
|
458
|
+
this._fileInputEl?.click();
|
|
459
|
+
}
|
|
460
|
+
_handleFileInputChange(e) {
|
|
461
|
+
const input = e.target;
|
|
462
|
+
if (input.files) {
|
|
463
|
+
this._addFiles(input.files);
|
|
464
|
+
}
|
|
465
|
+
// Reset so picking the same file again re-fires change.
|
|
466
|
+
input.value = '';
|
|
467
|
+
}
|
|
468
|
+
_handlePaste(e) {
|
|
469
|
+
const items = e.clipboardData?.items;
|
|
470
|
+
if (!items) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const files = [];
|
|
474
|
+
for (let i = 0; i < items.length; i++) {
|
|
475
|
+
if (items[i].kind === 'file') {
|
|
476
|
+
const file = items[i].getAsFile();
|
|
477
|
+
if (file) {
|
|
478
|
+
files.push(file);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (files.length) {
|
|
483
|
+
e.preventDefault();
|
|
484
|
+
this._addFiles(files);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
_handleDragEnter(e) {
|
|
488
|
+
if (!this._hasFiles(e)) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
e.preventDefault();
|
|
492
|
+
this._dragDepth++;
|
|
493
|
+
this._isDraggingFile = true;
|
|
494
|
+
}
|
|
495
|
+
_handleDragOver(e) {
|
|
496
|
+
if (!this._hasFiles(e)) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
// Required so the drop event fires.
|
|
500
|
+
e.preventDefault();
|
|
501
|
+
}
|
|
502
|
+
_handleDragLeave(e) {
|
|
503
|
+
if (!this._hasFiles(e)) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
this._dragDepth--;
|
|
507
|
+
if (this._dragDepth <= 0) {
|
|
508
|
+
this._dragDepth = 0;
|
|
509
|
+
this._isDraggingFile = false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
_handleDrop(e) {
|
|
513
|
+
if (!this._hasFiles(e)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
e.preventDefault();
|
|
517
|
+
this._dragDepth = 0;
|
|
518
|
+
this._isDraggingFile = false;
|
|
519
|
+
if (e.dataTransfer?.files) {
|
|
520
|
+
this._addFiles(e.dataTransfer.files);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
_hasFiles(e) {
|
|
524
|
+
return Array.from(e.dataTransfer?.types || []).includes('Files');
|
|
525
|
+
}
|
|
526
|
+
/** Stage a set of image files in the composer (held locally until send). */
|
|
527
|
+
_addFiles(fileList) {
|
|
528
|
+
const files = Array.from(fileList).filter((f) => ACCEPTED_IMAGE_TYPES.includes(f.type));
|
|
529
|
+
if (!files.length) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
files.forEach((file) => {
|
|
533
|
+
const pending = {
|
|
534
|
+
localId: 'att-' + this._pendingSeq++,
|
|
535
|
+
file,
|
|
536
|
+
name: file.name,
|
|
537
|
+
previewUrl: URL.createObjectURL(file),
|
|
538
|
+
};
|
|
539
|
+
this._pendingInternalFiles = [...this._pendingInternalFiles, pending];
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
_removePending(localId) {
|
|
543
|
+
const target = this._pendingInternalFiles.find((p) => p.localId === localId);
|
|
544
|
+
if (target) {
|
|
545
|
+
URL.revokeObjectURL(target.previewUrl);
|
|
546
|
+
}
|
|
547
|
+
this._pendingInternalFiles = this._pendingInternalFiles.filter((p) => p.localId !== localId);
|
|
548
|
+
}
|
|
549
|
+
_clearPending() {
|
|
550
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
551
|
+
this._pendingInternalFiles = [];
|
|
552
|
+
}
|
|
553
|
+
/** Open the shared full-screen previewer for a message image. */
|
|
554
|
+
_openImage(img) {
|
|
555
|
+
KRFilePreview.open({
|
|
556
|
+
src: `/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view`,
|
|
557
|
+
name: img.niceName || 'image',
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/** Whether the send button is active: needs text or at least one image. */
|
|
561
|
+
_canSend() {
|
|
562
|
+
return Boolean(this._inputValue.trim()) || this._pendingInternalFiles.length > 0;
|
|
563
|
+
}
|
|
381
564
|
_handleSubmit() {
|
|
382
|
-
if (
|
|
565
|
+
if (this._isStreaming) {
|
|
383
566
|
return;
|
|
384
567
|
}
|
|
385
568
|
const messageText = this._inputValue.trim();
|
|
569
|
+
const pending = this._pendingInternalFiles;
|
|
570
|
+
// Allow sending an image with no text, but require at least one of them.
|
|
571
|
+
if (!messageText && !pending.length) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
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);
|
|
386
578
|
const submitEvent = new CustomEvent('message-submit', {
|
|
387
579
|
detail: {
|
|
388
580
|
message: messageText,
|
|
581
|
+
internalFiles,
|
|
389
582
|
},
|
|
390
583
|
bubbles: true,
|
|
391
584
|
composed: true,
|
|
@@ -395,24 +588,27 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
395
588
|
if (submitEvent.defaultPrevented) {
|
|
396
589
|
return;
|
|
397
590
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
591
|
+
const userMessage = {
|
|
592
|
+
type: 'user',
|
|
593
|
+
content: messageText,
|
|
594
|
+
};
|
|
595
|
+
this.messages = [...this.messages, userMessage];
|
|
402
596
|
this._inputValue = '';
|
|
403
597
|
this._error = '';
|
|
404
598
|
this._userHasScrolledUp = false;
|
|
599
|
+
// Clear the composer and release its preview object URLs.
|
|
600
|
+
this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
|
|
601
|
+
this._pendingInternalFiles = [];
|
|
405
602
|
// Reset textarea height
|
|
406
603
|
if (this._inputEl) {
|
|
407
604
|
this._inputEl.style.height = 'auto';
|
|
408
605
|
}
|
|
409
606
|
// Add empty assistant message to stream into
|
|
410
607
|
this.messages = [...this.messages, {
|
|
411
|
-
|
|
608
|
+
type: 'assistant',
|
|
412
609
|
content: '',
|
|
413
610
|
}];
|
|
414
611
|
this._isStreaming = true;
|
|
415
|
-
this._statusText = '';
|
|
416
612
|
// Scroll user's message to the top (Gemini-style).
|
|
417
613
|
// Set min-height on the assistant message = container height so there's enough
|
|
418
614
|
// scroll room below the user message to push it to the top.
|
|
@@ -434,9 +630,34 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
434
630
|
this._messagesEl.scrollTop = userMsgEl.offsetTop - this._messagesEl.offsetTop - 24;
|
|
435
631
|
}
|
|
436
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
|
+
}
|
|
437
657
|
this.send({
|
|
438
658
|
message: messageText,
|
|
439
659
|
messages: this.messages.slice(0, -1),
|
|
660
|
+
internalFiles,
|
|
440
661
|
}).then((response) => {
|
|
441
662
|
if (!response.ok) {
|
|
442
663
|
this._handleStreamError('Request failed: ' + response.status);
|
|
@@ -462,11 +683,15 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
462
683
|
this._reader = null;
|
|
463
684
|
}
|
|
464
685
|
this._isStreaming = false;
|
|
465
|
-
this._statusText = '';
|
|
466
686
|
}
|
|
467
687
|
_handleClear() {
|
|
688
|
+
// Default to clearing the Kodaris Bedrock conversation state when the consumer
|
|
689
|
+
// hasn't provided their own `clear`.
|
|
468
690
|
if (!this.clear) {
|
|
469
|
-
|
|
691
|
+
this.clear = () => KRHttp.fetch({
|
|
692
|
+
url: '/api/system/integration/aws/bedrock/clearConversationWithState',
|
|
693
|
+
method: 'POST',
|
|
694
|
+
});
|
|
470
695
|
}
|
|
471
696
|
this.clear().then(() => {
|
|
472
697
|
if (this._reader) {
|
|
@@ -475,7 +700,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
475
700
|
}
|
|
476
701
|
this.messages = [];
|
|
477
702
|
this._isStreaming = false;
|
|
478
|
-
this._statusText = '';
|
|
479
703
|
this._error = '';
|
|
480
704
|
this._inputValue = '';
|
|
481
705
|
this._userHasScrolledUp = false;
|
|
@@ -501,7 +725,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
501
725
|
this._reader.read().then((result) => {
|
|
502
726
|
if (result.done) {
|
|
503
727
|
this._isStreaming = false;
|
|
504
|
-
this._statusText = '';
|
|
505
728
|
this._reader = null;
|
|
506
729
|
return;
|
|
507
730
|
}
|
|
@@ -534,15 +757,10 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
534
757
|
bubbles: true,
|
|
535
758
|
composed: true,
|
|
536
759
|
}));
|
|
537
|
-
if (data.type === '
|
|
538
|
-
if (data.message) {
|
|
539
|
-
this._statusText = String(data.message);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
else if (data.type === 'REASONING_PART') {
|
|
760
|
+
if (data.type === 'REASONING_PART') {
|
|
543
761
|
if (data.message) {
|
|
544
762
|
const lastIndex = this.messages.length - 1;
|
|
545
|
-
if (lastIndex >= 0 && this.messages[lastIndex].
|
|
763
|
+
if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
|
|
546
764
|
if (!this.messages[lastIndex].reasoning) {
|
|
547
765
|
this.messages[lastIndex].reasoning = '';
|
|
548
766
|
}
|
|
@@ -553,9 +771,8 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
553
771
|
}
|
|
554
772
|
else if (data.type === 'RESPONSE_PART') {
|
|
555
773
|
if (data.message) {
|
|
556
|
-
this._statusText = '';
|
|
557
774
|
const lastIndex = this.messages.length - 1;
|
|
558
|
-
if (lastIndex >= 0 && this.messages[lastIndex].
|
|
775
|
+
if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
|
|
559
776
|
this.messages[lastIndex].content += String(data.message);
|
|
560
777
|
this.requestUpdate();
|
|
561
778
|
}
|
|
@@ -566,7 +783,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
566
783
|
}
|
|
567
784
|
if (data.finished) {
|
|
568
785
|
this._isStreaming = false;
|
|
569
|
-
this._statusText = '';
|
|
570
786
|
this._reader = null;
|
|
571
787
|
}
|
|
572
788
|
}
|
|
@@ -580,7 +796,6 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
580
796
|
}
|
|
581
797
|
_handleStreamError(message) {
|
|
582
798
|
this._isStreaming = false;
|
|
583
|
-
this._statusText = '';
|
|
584
799
|
this._error = message;
|
|
585
800
|
if (this._reader) {
|
|
586
801
|
this._reader.cancel();
|
|
@@ -589,7 +804,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
589
804
|
// Remove the empty assistant message if it has no content
|
|
590
805
|
if (this.messages.length > 0) {
|
|
591
806
|
const lastMessage = this.messages[this.messages.length - 1];
|
|
592
|
-
if (lastMessage.
|
|
807
|
+
if (lastMessage.type === 'assistant' && !lastMessage.content) {
|
|
593
808
|
this.messages = this.messages.slice(0, -1);
|
|
594
809
|
}
|
|
595
810
|
}
|
|
@@ -597,78 +812,103 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
597
812
|
// --- Render ---
|
|
598
813
|
render() {
|
|
599
814
|
return html `
|
|
600
|
-
|
|
601
|
-
|
|
815
|
+
${(this.hideToggle || this.variant === 'inline') ? nothing : html `
|
|
816
|
+
<button
|
|
817
|
+
class=${classMap({
|
|
602
818
|
'toggle': true,
|
|
603
819
|
'toggle--hidden': this.opened,
|
|
604
820
|
})}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
>
|
|
608
|
-
<svg
|
|
609
|
-
viewBox="0 0 24 24"
|
|
610
|
-
fill="none"
|
|
611
|
-
stroke="currentColor"
|
|
612
|
-
stroke-width="2"
|
|
613
|
-
stroke-linecap="round"
|
|
614
|
-
class="toggle-icon"
|
|
821
|
+
@click=${this._handleToggle}
|
|
822
|
+
title="Open chat"
|
|
615
823
|
>
|
|
616
|
-
<
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
824
|
+
<svg
|
|
825
|
+
viewBox="0 0 24 24"
|
|
826
|
+
fill="none"
|
|
827
|
+
stroke="currentColor"
|
|
828
|
+
stroke-width="2"
|
|
829
|
+
stroke-linecap="round"
|
|
830
|
+
class="toggle-icon"
|
|
831
|
+
>
|
|
832
|
+
<path d="M12 2C6.48 2 2 6.03 2 11c0 2.61 1.19 4.94 3.05 6.55L4 22l4.8-2.4C9.82 19.85 10.88 20 12 20c5.52 0 10-4.03 10-9S17.52 2 12 2z"/>
|
|
833
|
+
<path d="M8 11h.01M12 11h.01M16 11h.01" stroke-width="2.5"/>
|
|
834
|
+
</svg>
|
|
835
|
+
</button>
|
|
836
|
+
`}
|
|
837
|
+
|
|
838
|
+
<div
|
|
839
|
+
class=${classMap({
|
|
622
840
|
'panel': true,
|
|
623
841
|
'panel--opened': this.opened,
|
|
624
|
-
})}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
class="
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
842
|
+
})}
|
|
843
|
+
@dragenter=${this._handleDragEnter}
|
|
844
|
+
@dragover=${this._handleDragOver}
|
|
845
|
+
@dragleave=${this._handleDragLeave}
|
|
846
|
+
@drop=${this._handleDrop}
|
|
847
|
+
>
|
|
848
|
+
${this._isDraggingFile ? html `
|
|
849
|
+
<div class="drop-overlay">
|
|
850
|
+
<svg
|
|
851
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
852
|
+
fill="none"
|
|
853
|
+
viewBox="0 0 24 24"
|
|
854
|
+
stroke-width="1.5"
|
|
855
|
+
stroke="currentColor"
|
|
856
|
+
>
|
|
857
|
+
<path
|
|
858
|
+
stroke-linecap="round"
|
|
859
|
+
stroke-linejoin="round"
|
|
860
|
+
d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"
|
|
861
|
+
/>
|
|
862
|
+
</svg>
|
|
863
|
+
<span>Drop images to attach</span>
|
|
864
|
+
</div>
|
|
865
|
+
` : nothing}
|
|
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
|
+
`}
|
|
665
908
|
<div class="header" @mousedown=${this._handleHeaderMouseDown}>
|
|
666
909
|
<div class="header-left">
|
|
667
910
|
<div class="header-info">
|
|
668
|
-
<span class="header-title">${this.
|
|
669
|
-
${this.subtitle ? html `
|
|
670
|
-
<span class="header-subtitle">${this.subtitle}</span>
|
|
671
|
-
` : nothing}
|
|
911
|
+
<span class="header-title">${this.label}</span>
|
|
672
912
|
</div>
|
|
673
913
|
</div>
|
|
674
914
|
<div class="header-actions">
|
|
@@ -694,63 +934,84 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
694
934
|
</svg>
|
|
695
935
|
</button>
|
|
696
936
|
` : nothing}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
<svg
|
|
703
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
704
|
-
fill="none"
|
|
705
|
-
viewBox="0 0 24 24"
|
|
706
|
-
stroke-width="2"
|
|
707
|
-
stroke="currentColor"
|
|
708
|
-
class="header-action-icon"
|
|
937
|
+
${this.variant === 'inline' ? nothing : html `
|
|
938
|
+
<button
|
|
939
|
+
class="header-action"
|
|
940
|
+
@click=${this._handleToggle}
|
|
941
|
+
title="Close"
|
|
709
942
|
>
|
|
710
|
-
<
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
+
`}
|
|
717
959
|
</div>
|
|
718
960
|
</div>
|
|
719
961
|
|
|
720
962
|
<div class="messages" @scroll=${this._handleMessagesScroll}>
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
<
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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({
|
|
728
971
|
'message': true,
|
|
729
|
-
'message--user': msg.
|
|
730
|
-
'message--assistant': msg.
|
|
972
|
+
'message--user': msg.type === 'user',
|
|
973
|
+
'message--assistant': msg.type === 'assistant',
|
|
731
974
|
})}>
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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'
|
|
741
1001
|
? msg.content
|
|
742
1002
|
: unsafeHTML(marked.parse(msg.content, { breaks: true }))}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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>
|
|
754
1015
|
</div>
|
|
755
1016
|
|
|
756
1017
|
${this._error ? html `
|
|
@@ -776,8 +1037,48 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
776
1037
|
` : nothing}
|
|
777
1038
|
|
|
778
1039
|
<div class="footer">
|
|
1040
|
+
${this._pendingInternalFiles.length ? html `
|
|
1041
|
+
<div class="composer-attachments">
|
|
1042
|
+
${repeat(this._pendingInternalFiles, (p) => p.localId, (p) => html `
|
|
1043
|
+
<div class="chip">
|
|
1044
|
+
<img src=${p.previewUrl} alt=${p.name} />
|
|
1045
|
+
<button
|
|
1046
|
+
class="chip-remove"
|
|
1047
|
+
title="Remove"
|
|
1048
|
+
@click=${() => this._removePending(p.localId)}
|
|
1049
|
+
>
|
|
1050
|
+
<svg
|
|
1051
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1052
|
+
fill="none"
|
|
1053
|
+
viewBox="0 0 24 24"
|
|
1054
|
+
stroke-width="3"
|
|
1055
|
+
stroke="currentColor"
|
|
1056
|
+
>
|
|
1057
|
+
<path
|
|
1058
|
+
stroke-linecap="round"
|
|
1059
|
+
stroke-linejoin="round"
|
|
1060
|
+
d="M6 18 18 6M6 6l12 12"
|
|
1061
|
+
/>
|
|
1062
|
+
</svg>
|
|
1063
|
+
</button>
|
|
1064
|
+
</div>
|
|
1065
|
+
`)}
|
|
1066
|
+
</div>
|
|
1067
|
+
` : nothing}
|
|
1068
|
+
<input
|
|
1069
|
+
class="file-input"
|
|
1070
|
+
type="file"
|
|
1071
|
+
accept="image/png,image/jpeg,image/gif,image/webp"
|
|
1072
|
+
multiple
|
|
1073
|
+
hidden
|
|
1074
|
+
@change=${this._handleFileInputChange}
|
|
1075
|
+
/>
|
|
779
1076
|
<div class="input-wrapper">
|
|
780
|
-
<button
|
|
1077
|
+
<button
|
|
1078
|
+
class="input-plus"
|
|
1079
|
+
title="Attach image"
|
|
1080
|
+
@click=${this._openFilePicker}
|
|
1081
|
+
>+</button>
|
|
781
1082
|
<textarea
|
|
782
1083
|
class="input"
|
|
783
1084
|
.value=${this._inputValue}
|
|
@@ -785,6 +1086,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
785
1086
|
rows="1"
|
|
786
1087
|
@input=${this._handleInputChange}
|
|
787
1088
|
@keydown=${this._handleKeyDown}
|
|
1089
|
+
@paste=${this._handlePaste}
|
|
788
1090
|
></textarea>
|
|
789
1091
|
${this._isStreaming ? html `
|
|
790
1092
|
<button
|
|
@@ -811,7 +1113,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
811
1113
|
<button
|
|
812
1114
|
class=${classMap({
|
|
813
1115
|
'send': true,
|
|
814
|
-
'send--disabled': !this.
|
|
1116
|
+
'send--disabled': !this._canSend(),
|
|
815
1117
|
})}
|
|
816
1118
|
@click=${this._handleSubmit}
|
|
817
1119
|
title="Send message"
|
|
@@ -926,6 +1228,28 @@ KRChatbot.styles = css `
|
|
|
926
1228
|
display: flex;
|
|
927
1229
|
}
|
|
928
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
|
+
|
|
929
1253
|
@keyframes chatbot-panel-in {
|
|
930
1254
|
from {
|
|
931
1255
|
opacity: 0;
|
|
@@ -956,7 +1280,7 @@ KRChatbot.styles = css `
|
|
|
956
1280
|
gap: 10px;
|
|
957
1281
|
}
|
|
958
1282
|
|
|
959
|
-
.header-info {
|
|
1283
|
+
.header-info {
|
|
960
1284
|
display: flex;
|
|
961
1285
|
flex-direction: column;
|
|
962
1286
|
}
|
|
@@ -966,22 +1290,6 @@ KRChatbot.styles = css `
|
|
|
966
1290
|
font-weight: 600;
|
|
967
1291
|
}
|
|
968
1292
|
|
|
969
|
-
.header-subtitle {
|
|
970
|
-
color: rgba(255, 255, 255, 0.5);
|
|
971
|
-
font-size: 11px;
|
|
972
|
-
display: flex;
|
|
973
|
-
align-items: center;
|
|
974
|
-
gap: 4px;
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
.header-subtitle::before {
|
|
978
|
-
content: '';
|
|
979
|
-
width: 5px;
|
|
980
|
-
height: 5px;
|
|
981
|
-
background: var(--kr-chatbot-success);
|
|
982
|
-
border-radius: 50%;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
1293
|
.header-actions {
|
|
986
1294
|
display: flex;
|
|
987
1295
|
align-items: center;
|
|
@@ -1013,17 +1321,29 @@ KRChatbot.styles = css `
|
|
|
1013
1321
|
}
|
|
1014
1322
|
|
|
1015
1323
|
/* Messages */
|
|
1324
|
+
/* Full-width scroll container — keeps the scrollbar at the panel edge. */
|
|
1016
1325
|
.messages {
|
|
1017
1326
|
flex: 1;
|
|
1018
1327
|
overflow-y: auto;
|
|
1019
|
-
padding: 32px;
|
|
1020
1328
|
display: flex;
|
|
1021
1329
|
flex-direction: column;
|
|
1022
|
-
gap: 24px;
|
|
1023
1330
|
scroll-behavior: smooth;
|
|
1024
1331
|
background: white;
|
|
1025
1332
|
}
|
|
1026
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
|
+
|
|
1027
1347
|
.messages::-webkit-scrollbar {
|
|
1028
1348
|
width: 4px;
|
|
1029
1349
|
}
|
|
@@ -1047,7 +1367,7 @@ KRChatbot.styles = css `
|
|
|
1047
1367
|
gap: 8px;
|
|
1048
1368
|
}
|
|
1049
1369
|
|
|
1050
|
-
.empty-text {
|
|
1370
|
+
.empty-text {
|
|
1051
1371
|
font-size: 14px;
|
|
1052
1372
|
color: rgb(0 0 0 / 80%);
|
|
1053
1373
|
}
|
|
@@ -1159,7 +1479,7 @@ KRChatbot.styles = css `
|
|
|
1159
1479
|
word-wrap: break-word;
|
|
1160
1480
|
}
|
|
1161
1481
|
|
|
1162
|
-
.typing {
|
|
1482
|
+
.typing {
|
|
1163
1483
|
display: flex;
|
|
1164
1484
|
gap: 4px;
|
|
1165
1485
|
padding: 4px 0;
|
|
@@ -1227,6 +1547,15 @@ KRChatbot.styles = css `
|
|
|
1227
1547
|
padding: 16px 16px 16px;
|
|
1228
1548
|
flex-shrink: 0;
|
|
1229
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;
|
|
1230
1559
|
}
|
|
1231
1560
|
|
|
1232
1561
|
.input-wrapper {
|
|
@@ -1245,6 +1574,15 @@ KRChatbot.styles = css `
|
|
|
1245
1574
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04);
|
|
1246
1575
|
}
|
|
1247
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
|
+
|
|
1248
1586
|
.input-plus {
|
|
1249
1587
|
width: 34px;
|
|
1250
1588
|
height: 34px;
|
|
@@ -1352,6 +1690,12 @@ KRChatbot.styles = css `
|
|
|
1352
1690
|
cursor: grabbing;
|
|
1353
1691
|
}
|
|
1354
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
|
+
|
|
1355
1699
|
/* Drag / Resize states */
|
|
1356
1700
|
.panel--dragging,
|
|
1357
1701
|
.panel--resizing {
|
|
@@ -1378,6 +1722,116 @@ KRChatbot.styles = css `
|
|
|
1378
1722
|
.resize--sw { bottom: -4px; left: -4px; width: 16px; height: 16px; cursor: sw-resize; }
|
|
1379
1723
|
.resize--se { bottom: -4px; right: -4px; width: 16px; height: 16px; cursor: se-resize; }
|
|
1380
1724
|
|
|
1725
|
+
/* Attachment thumbnails inside a sent message bubble */
|
|
1726
|
+
.attachments {
|
|
1727
|
+
display: flex;
|
|
1728
|
+
flex-wrap: wrap;
|
|
1729
|
+
gap: 8px;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.message--user .attachments {
|
|
1733
|
+
justify-content: flex-end;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.attachment {
|
|
1737
|
+
width: 120px;
|
|
1738
|
+
height: 120px;
|
|
1739
|
+
border-radius: 12px;
|
|
1740
|
+
overflow: hidden;
|
|
1741
|
+
background: #f2f2f4;
|
|
1742
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
1743
|
+
cursor: zoom-in;
|
|
1744
|
+
flex-shrink: 0;
|
|
1745
|
+
padding: 0;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.attachment img {
|
|
1749
|
+
width: 100%;
|
|
1750
|
+
height: 100%;
|
|
1751
|
+
object-fit: cover;
|
|
1752
|
+
display: block;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
/* Composer attachment chips (pre-send) */
|
|
1756
|
+
.composer-attachments {
|
|
1757
|
+
display: flex;
|
|
1758
|
+
flex-wrap: wrap;
|
|
1759
|
+
gap: 8px;
|
|
1760
|
+
padding: 0 6px 10px;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
.chip {
|
|
1764
|
+
position: relative;
|
|
1765
|
+
width: 56px;
|
|
1766
|
+
height: 56px;
|
|
1767
|
+
border-radius: 10px;
|
|
1768
|
+
overflow: hidden;
|
|
1769
|
+
background: #f2f2f4;
|
|
1770
|
+
border: 1px solid var(--kr-chatbot-border);
|
|
1771
|
+
flex-shrink: 0;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.chip img {
|
|
1775
|
+
width: 100%;
|
|
1776
|
+
height: 100%;
|
|
1777
|
+
object-fit: cover;
|
|
1778
|
+
display: block;
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
.chip-remove {
|
|
1782
|
+
position: absolute;
|
|
1783
|
+
top: 2px;
|
|
1784
|
+
right: 2px;
|
|
1785
|
+
width: 18px;
|
|
1786
|
+
height: 18px;
|
|
1787
|
+
border-radius: 50%;
|
|
1788
|
+
border: none;
|
|
1789
|
+
background: rgba(0, 0, 0, 0.6);
|
|
1790
|
+
color: #fff;
|
|
1791
|
+
cursor: pointer;
|
|
1792
|
+
display: flex;
|
|
1793
|
+
align-items: center;
|
|
1794
|
+
justify-content: center;
|
|
1795
|
+
padding: 0;
|
|
1796
|
+
line-height: 1;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
.chip-remove:hover {
|
|
1800
|
+
background: rgba(0, 0, 0, 0.8);
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
.chip-remove svg {
|
|
1804
|
+
width: 10px;
|
|
1805
|
+
height: 10px;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
/* Drag-and-drop overlay */
|
|
1809
|
+
.drop-overlay {
|
|
1810
|
+
position: absolute;
|
|
1811
|
+
inset: 0;
|
|
1812
|
+
z-index: 20;
|
|
1813
|
+
display: flex;
|
|
1814
|
+
align-items: center;
|
|
1815
|
+
justify-content: center;
|
|
1816
|
+
flex-direction: column;
|
|
1817
|
+
gap: 10px;
|
|
1818
|
+
/* Frosted, mostly-opaque surface so the chat content behind is masked
|
|
1819
|
+
and doesn't clash with the overlay label. */
|
|
1820
|
+
background: rgba(255, 255, 255, 0.92);
|
|
1821
|
+
backdrop-filter: blur(3px);
|
|
1822
|
+
border: 2px dashed var(--kr-chatbot-primary);
|
|
1823
|
+
border-radius: 16px;
|
|
1824
|
+
color: var(--kr-chatbot-primary);
|
|
1825
|
+
font-weight: 600;
|
|
1826
|
+
font-size: 15px;
|
|
1827
|
+
pointer-events: none;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
.drop-overlay svg {
|
|
1831
|
+
width: 32px;
|
|
1832
|
+
height: 32px;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1381
1835
|
`;
|
|
1382
1836
|
__decorate([
|
|
1383
1837
|
property({ attribute: false })
|
|
@@ -1393,10 +1847,7 @@ __decorate([
|
|
|
1393
1847
|
], KRChatbot.prototype, "messages", void 0);
|
|
1394
1848
|
__decorate([
|
|
1395
1849
|
property({ type: String })
|
|
1396
|
-
], KRChatbot.prototype, "
|
|
1397
|
-
__decorate([
|
|
1398
|
-
property({ type: String })
|
|
1399
|
-
], KRChatbot.prototype, "subtitle", void 0);
|
|
1850
|
+
], KRChatbot.prototype, "label", void 0);
|
|
1400
1851
|
__decorate([
|
|
1401
1852
|
property({ type: String })
|
|
1402
1853
|
], KRChatbot.prototype, "placeholder", void 0);
|
|
@@ -1406,6 +1857,19 @@ __decorate([
|
|
|
1406
1857
|
reflect: true
|
|
1407
1858
|
})
|
|
1408
1859
|
], KRChatbot.prototype, "opened", void 0);
|
|
1860
|
+
__decorate([
|
|
1861
|
+
property({
|
|
1862
|
+
type: Boolean,
|
|
1863
|
+
attribute: 'hide-toggle',
|
|
1864
|
+
reflect: true
|
|
1865
|
+
})
|
|
1866
|
+
], KRChatbot.prototype, "hideToggle", void 0);
|
|
1867
|
+
__decorate([
|
|
1868
|
+
property({
|
|
1869
|
+
type: String,
|
|
1870
|
+
reflect: true
|
|
1871
|
+
})
|
|
1872
|
+
], KRChatbot.prototype, "variant", void 0);
|
|
1409
1873
|
__decorate([
|
|
1410
1874
|
state()
|
|
1411
1875
|
], KRChatbot.prototype, "_inputValue", void 0);
|
|
@@ -1414,10 +1878,13 @@ __decorate([
|
|
|
1414
1878
|
], KRChatbot.prototype, "_isStreaming", void 0);
|
|
1415
1879
|
__decorate([
|
|
1416
1880
|
state()
|
|
1417
|
-
], KRChatbot.prototype, "
|
|
1881
|
+
], KRChatbot.prototype, "_error", void 0);
|
|
1418
1882
|
__decorate([
|
|
1419
1883
|
state()
|
|
1420
|
-
], KRChatbot.prototype, "
|
|
1884
|
+
], KRChatbot.prototype, "_pendingInternalFiles", void 0);
|
|
1885
|
+
__decorate([
|
|
1886
|
+
state()
|
|
1887
|
+
], KRChatbot.prototype, "_isDraggingFile", void 0);
|
|
1421
1888
|
__decorate([
|
|
1422
1889
|
query('.panel')
|
|
1423
1890
|
], KRChatbot.prototype, "_panelEl", void 0);
|
|
@@ -1427,6 +1894,9 @@ __decorate([
|
|
|
1427
1894
|
__decorate([
|
|
1428
1895
|
query('.input')
|
|
1429
1896
|
], KRChatbot.prototype, "_inputEl", void 0);
|
|
1897
|
+
__decorate([
|
|
1898
|
+
query('.file-input')
|
|
1899
|
+
], KRChatbot.prototype, "_fileInputEl", void 0);
|
|
1430
1900
|
KRChatbot = __decorate([
|
|
1431
1901
|
customElement('kr-chatbot')
|
|
1432
1902
|
], KRChatbot);
|