@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.
@@ -5567,7 +5567,7 @@ const ACCEPTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/web
5567
5567
  * ## Usage
5568
5568
  * ```html
5569
5569
  * <kr-chatbot
5570
- * title="AI Assistant"
5570
+ * label="AI Assistant"
5571
5571
  * .send=${(params) => {
5572
5572
  * return fetch('/api/ai/chat/stream', {
5573
5573
  * method: 'POST',
@@ -5610,16 +5610,25 @@ let KRChatbot = class KRChatbot extends i$2 {
5610
5610
  /**
5611
5611
  * Callback function for sending messages. Receives the user's message and
5612
5612
  * current conversation history. Must return a Response with a streaming SSE body.
5613
+ *
5614
+ * Optional — when not set, the chatbot uses its built-in default that streams
5615
+ * from the Kodaris Bedrock conversation endpoints. Set this to override.
5613
5616
  */
5614
5617
  this.send = null;
5615
5618
  /**
5616
5619
  * Callback function called when the user clears the conversation.
5617
5620
  * Must return a Promise — messages are only cleared if it resolves.
5621
+ *
5622
+ * Optional — when not set, the chatbot uses its built-in default that clears
5623
+ * the Kodaris Bedrock conversation state. Set this to override.
5618
5624
  */
5619
5625
  this.clear = null;
5620
5626
  /**
5621
5627
  * Callback function called on init to load existing conversation messages.
5622
5628
  * Must return a Promise that resolves with an array of KRChatbotMessage.
5629
+ *
5630
+ * Optional — when not set, the chatbot uses its built-in default that loads
5631
+ * existing messages from the Kodaris Bedrock conversation. Set this to override.
5623
5632
  */
5624
5633
  this.load = null;
5625
5634
  /**
@@ -5628,13 +5637,9 @@ let KRChatbot = class KRChatbot extends i$2 {
5628
5637
  */
5629
5638
  this.messages = [];
5630
5639
  /**
5631
- * Header title text.
5640
+ * Header label text.
5632
5641
  */
5633
- this.title = 'Kat';
5634
- /**
5635
- * Header subtitle text (shown below title with a green status dot).
5636
- */
5637
- this.subtitle = '';
5642
+ this.label = 'Kat';
5638
5643
  /**
5639
5644
  * Input placeholder text.
5640
5645
  */
@@ -5649,13 +5654,23 @@ let KRChatbot = class KRChatbot extends i$2 {
5649
5654
  * then driven via the `open()` / `close()` / `toggle()` methods.
5650
5655
  */
5651
5656
  this.hideToggle = false;
5657
+ /**
5658
+ * Layout mode.
5659
+ * - `'floating'` (default): the chatbot is a fixed bottom-right widget with a
5660
+ * toggle button, and the panel can be dragged and resized. This is the
5661
+ * traditional chat-widget appearance.
5662
+ * - `'inline'`: the chatbot drops its fixed positioning and fills its parent
5663
+ * container (100% width/height, no border radius/shadow, no toggle, no
5664
+ * drag/resize). The panel is always open. Use this to embed the chat as a
5665
+ * full page or panel region.
5666
+ */
5667
+ this.variant = 'floating';
5652
5668
  // --- @state (internal) ---
5653
5669
  this._inputValue = '';
5654
5670
  this._isStreaming = false;
5655
- this._statusText = '';
5656
5671
  this._error = '';
5657
5672
  /** Images staged in the composer, before the message is sent. */
5658
- this._pendingAttachments = [];
5673
+ this._pendingInternalFiles = [];
5659
5674
  /** True while a file is being dragged over the panel (shows the drop overlay). */
5660
5675
  this._isDraggingFile = false;
5661
5676
  this._handleMouseMove = (e) => {
@@ -5719,12 +5734,27 @@ let KRChatbot = class KRChatbot extends i$2 {
5719
5734
  // --- Lifecycle ---
5720
5735
  connectedCallback() {
5721
5736
  super.connectedCallback();
5722
- if (this.load) {
5723
- this.load().then((messages) => {
5724
- if (messages && messages.length) {
5725
- this.messages = messages;
5726
- }
5727
- }).catch(() => { });
5737
+ // Default to loading existing messages from the Kodaris Bedrock conversation
5738
+ // when the consumer hasn't provided their own `load`. The endpoint returns
5739
+ // messages already in KRChatbotMessage shape ({ type, content, images }), so
5740
+ // they pass straight through.
5741
+ if (!this.load) {
5742
+ this.load = () => KRHttp.fetch({
5743
+ url: '/api/system/integration/aws/bedrock/getConversationMessages',
5744
+ })
5745
+ .then((r) => r.json())
5746
+ .then((res) => res.data || []);
5747
+ }
5748
+ this.load().then((messages) => {
5749
+ if (messages && messages.length) {
5750
+ this.messages = messages;
5751
+ }
5752
+ }).catch(() => { });
5753
+ // Inline mode fills its container and is always open — the floating widget's
5754
+ // toggle/drag/resize state (including any persisted layout) doesn't apply.
5755
+ if (this.variant === 'inline') {
5756
+ this.opened = true;
5757
+ return;
5728
5758
  }
5729
5759
  this._restoreState();
5730
5760
  }
@@ -5737,10 +5767,10 @@ let KRChatbot = class KRChatbot extends i$2 {
5737
5767
  document.removeEventListener('mousemove', this._handleMouseMove);
5738
5768
  document.removeEventListener('mouseup', this._handleMouseUp);
5739
5769
  // Release any object URLs still held by staged attachments.
5740
- this._pendingAttachments.forEach((p) => URL.revokeObjectURL(p.previewUrl));
5770
+ this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
5741
5771
  }
5742
5772
  updated(changed) {
5743
- if (changed.has('messages') || changed.has('_statusText')) {
5773
+ if (changed.has('messages')) {
5744
5774
  if (!this._userHasScrolledUp && !this._isStreaming && this._messagesEl) {
5745
5775
  requestAnimationFrame(() => {
5746
5776
  if (this._messagesEl) {
@@ -5814,6 +5844,10 @@ let KRChatbot = class KRChatbot extends i$2 {
5814
5844
  }
5815
5845
  }
5816
5846
  _handleHeaderMouseDown(e) {
5847
+ // Inline mode fills its parent — dragging the panel doesn't apply.
5848
+ if (this.variant === 'inline') {
5849
+ return;
5850
+ }
5817
5851
  // Ignore clicks on buttons inside the header
5818
5852
  if (e.target.closest('button')) {
5819
5853
  return;
@@ -5839,6 +5873,10 @@ let KRChatbot = class KRChatbot extends i$2 {
5839
5873
  document.addEventListener('mouseup', this._handleMouseUp);
5840
5874
  }
5841
5875
  _handleResizeMouseDown(e) {
5876
+ // Inline mode fills its parent — resizing the panel doesn't apply.
5877
+ if (this.variant === 'inline') {
5878
+ return;
5879
+ }
5842
5880
  if (!this._panelEl) {
5843
5881
  return;
5844
5882
  }
@@ -6031,54 +6069,49 @@ let KRChatbot = class KRChatbot extends i$2 {
6031
6069
  name: file.name,
6032
6070
  previewUrl: URL.createObjectURL(file),
6033
6071
  };
6034
- this._pendingAttachments = [...this._pendingAttachments, pending];
6072
+ this._pendingInternalFiles = [...this._pendingInternalFiles, pending];
6035
6073
  });
6036
6074
  }
6037
6075
  _removePending(localId) {
6038
- const target = this._pendingAttachments.find((p) => p.localId === localId);
6076
+ const target = this._pendingInternalFiles.find((p) => p.localId === localId);
6039
6077
  if (target) {
6040
6078
  URL.revokeObjectURL(target.previewUrl);
6041
6079
  }
6042
- this._pendingAttachments = this._pendingAttachments.filter((p) => p.localId !== localId);
6080
+ this._pendingInternalFiles = this._pendingInternalFiles.filter((p) => p.localId !== localId);
6043
6081
  }
6044
6082
  _clearPending() {
6045
- this._pendingAttachments.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6046
- this._pendingAttachments = [];
6083
+ this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6084
+ this._pendingInternalFiles = [];
6047
6085
  }
6048
- /** Open the shared full-screen previewer for an attachment. */
6049
- _openAttachment(att) {
6086
+ /** Open the shared full-screen previewer for a message image. */
6087
+ _openImage(img) {
6050
6088
  KRFilePreview.open({
6051
- src: att.url,
6052
- name: att.name || 'image',
6089
+ src: `/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view`,
6090
+ name: img.niceName || 'image',
6053
6091
  });
6054
6092
  }
6055
6093
  /** Whether the send button is active: needs text or at least one image. */
6056
6094
  _canSend() {
6057
- return Boolean(this._inputValue.trim()) || this._pendingAttachments.length > 0;
6095
+ return Boolean(this._inputValue.trim()) || this._pendingInternalFiles.length > 0;
6058
6096
  }
6059
6097
  _handleSubmit() {
6060
- if (!this.send || this._isStreaming) {
6098
+ if (this._isStreaming) {
6061
6099
  return;
6062
6100
  }
6063
6101
  const messageText = this._inputValue.trim();
6064
- const pending = this._pendingAttachments;
6102
+ const pending = this._pendingInternalFiles;
6065
6103
  // Allow sending an image with no text, but require at least one of them.
6066
6104
  if (!messageText && !pending.length) {
6067
6105
  return;
6068
6106
  }
6069
- // The raw files sent to the backend, and the display attachments for the
6070
- // sent message. The message reuses each preview object URL, so ownership of
6071
- // those URLs transfers to the message (we don't revoke them here).
6072
- const files = pending.map((p) => p.file);
6073
- const attachments = pending.map((p) => ({
6074
- url: p.previewUrl,
6075
- name: p.name,
6076
- mimeType: p.file.type,
6077
- }));
6107
+ // The raw files sent to the backend. Sent images are shown once the
6108
+ // conversation reloads and they come back as stored internal files; the
6109
+ // just-sent message itself carries text only.
6110
+ const internalFiles = pending.map((p) => p.file);
6078
6111
  const submitEvent = new CustomEvent('message-submit', {
6079
6112
  detail: {
6080
6113
  message: messageText,
6081
- files,
6114
+ internalFiles,
6082
6115
  },
6083
6116
  bubbles: true,
6084
6117
  composed: true,
@@ -6089,30 +6122,26 @@ let KRChatbot = class KRChatbot extends i$2 {
6089
6122
  return;
6090
6123
  }
6091
6124
  const userMessage = {
6092
- role: 'user',
6125
+ type: 'user',
6093
6126
  content: messageText,
6094
6127
  };
6095
- if (attachments.length) {
6096
- userMessage.attachments = attachments;
6097
- }
6098
6128
  this.messages = [...this.messages, userMessage];
6099
6129
  this._inputValue = '';
6100
6130
  this._error = '';
6101
6131
  this._userHasScrolledUp = false;
6102
- // Clear the composer without revoking the object URLs — the sent message now
6103
- // owns them for display.
6104
- this._pendingAttachments = [];
6132
+ // Clear the composer and release its preview object URLs.
6133
+ this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6134
+ this._pendingInternalFiles = [];
6105
6135
  // Reset textarea height
6106
6136
  if (this._inputEl) {
6107
6137
  this._inputEl.style.height = 'auto';
6108
6138
  }
6109
6139
  // Add empty assistant message to stream into
6110
6140
  this.messages = [...this.messages, {
6111
- role: 'assistant',
6141
+ type: 'assistant',
6112
6142
  content: '',
6113
6143
  }];
6114
6144
  this._isStreaming = true;
6115
- this._statusText = '';
6116
6145
  // Scroll user's message to the top (Gemini-style).
6117
6146
  // Set min-height on the assistant message = container height so there's enough
6118
6147
  // scroll room below the user message to push it to the top.
@@ -6134,10 +6163,34 @@ let KRChatbot = class KRChatbot extends i$2 {
6134
6163
  this._messagesEl.scrollTop = userMsgEl.offsetTop - this._messagesEl.offsetTop - 24;
6135
6164
  }
6136
6165
  });
6166
+ // Default to streaming from the Kodaris Bedrock conversation endpoints when
6167
+ // the consumer hasn't provided their own `send`.
6168
+ if (!this.send) {
6169
+ this.send = (params) => {
6170
+ // With images, post text + files as multipart; without, post plain JSON.
6171
+ if (params.internalFiles.length) {
6172
+ const form = new FormData();
6173
+ form.append('userText', params.message);
6174
+ params.internalFiles.forEach((file) => form.append('images', file));
6175
+ return KRHttp.fetch({
6176
+ url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithImages',
6177
+ method: 'POST',
6178
+ headers: { Accept: 'text/event-stream' },
6179
+ body: form,
6180
+ });
6181
+ }
6182
+ return KRHttp.fetch({
6183
+ url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithState',
6184
+ method: 'POST',
6185
+ headers: { Accept: 'text/event-stream' },
6186
+ body: { userText: params.message },
6187
+ });
6188
+ };
6189
+ }
6137
6190
  this.send({
6138
6191
  message: messageText,
6139
6192
  messages: this.messages.slice(0, -1),
6140
- files,
6193
+ internalFiles,
6141
6194
  }).then((response) => {
6142
6195
  if (!response.ok) {
6143
6196
  this._handleStreamError('Request failed: ' + response.status);
@@ -6163,11 +6216,15 @@ let KRChatbot = class KRChatbot extends i$2 {
6163
6216
  this._reader = null;
6164
6217
  }
6165
6218
  this._isStreaming = false;
6166
- this._statusText = '';
6167
6219
  }
6168
6220
  _handleClear() {
6221
+ // Default to clearing the Kodaris Bedrock conversation state when the consumer
6222
+ // hasn't provided their own `clear`.
6169
6223
  if (!this.clear) {
6170
- return;
6224
+ this.clear = () => KRHttp.fetch({
6225
+ url: '/api/system/integration/aws/bedrock/clearConversationWithState',
6226
+ method: 'POST',
6227
+ });
6171
6228
  }
6172
6229
  this.clear().then(() => {
6173
6230
  if (this._reader) {
@@ -6176,7 +6233,6 @@ let KRChatbot = class KRChatbot extends i$2 {
6176
6233
  }
6177
6234
  this.messages = [];
6178
6235
  this._isStreaming = false;
6179
- this._statusText = '';
6180
6236
  this._error = '';
6181
6237
  this._inputValue = '';
6182
6238
  this._userHasScrolledUp = false;
@@ -6202,7 +6258,6 @@ let KRChatbot = class KRChatbot extends i$2 {
6202
6258
  this._reader.read().then((result) => {
6203
6259
  if (result.done) {
6204
6260
  this._isStreaming = false;
6205
- this._statusText = '';
6206
6261
  this._reader = null;
6207
6262
  return;
6208
6263
  }
@@ -6235,15 +6290,10 @@ let KRChatbot = class KRChatbot extends i$2 {
6235
6290
  bubbles: true,
6236
6291
  composed: true,
6237
6292
  }));
6238
- if (data.type === 'STATUS') {
6239
- if (data.message) {
6240
- this._statusText = String(data.message);
6241
- }
6242
- }
6243
- else if (data.type === 'REASONING_PART') {
6293
+ if (data.type === 'REASONING_PART') {
6244
6294
  if (data.message) {
6245
6295
  const lastIndex = this.messages.length - 1;
6246
- if (lastIndex >= 0 && this.messages[lastIndex].role === 'assistant') {
6296
+ if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
6247
6297
  if (!this.messages[lastIndex].reasoning) {
6248
6298
  this.messages[lastIndex].reasoning = '';
6249
6299
  }
@@ -6254,9 +6304,8 @@ let KRChatbot = class KRChatbot extends i$2 {
6254
6304
  }
6255
6305
  else if (data.type === 'RESPONSE_PART') {
6256
6306
  if (data.message) {
6257
- this._statusText = '';
6258
6307
  const lastIndex = this.messages.length - 1;
6259
- if (lastIndex >= 0 && this.messages[lastIndex].role === 'assistant') {
6308
+ if (lastIndex >= 0 && this.messages[lastIndex].type === 'assistant') {
6260
6309
  this.messages[lastIndex].content += String(data.message);
6261
6310
  this.requestUpdate();
6262
6311
  }
@@ -6267,7 +6316,6 @@ let KRChatbot = class KRChatbot extends i$2 {
6267
6316
  }
6268
6317
  if (data.finished) {
6269
6318
  this._isStreaming = false;
6270
- this._statusText = '';
6271
6319
  this._reader = null;
6272
6320
  }
6273
6321
  }
@@ -6281,7 +6329,6 @@ let KRChatbot = class KRChatbot extends i$2 {
6281
6329
  }
6282
6330
  _handleStreamError(message) {
6283
6331
  this._isStreaming = false;
6284
- this._statusText = '';
6285
6332
  this._error = message;
6286
6333
  if (this._reader) {
6287
6334
  this._reader.cancel();
@@ -6290,7 +6337,7 @@ let KRChatbot = class KRChatbot extends i$2 {
6290
6337
  // Remove the empty assistant message if it has no content
6291
6338
  if (this.messages.length > 0) {
6292
6339
  const lastMessage = this.messages[this.messages.length - 1];
6293
- if (lastMessage.role === 'assistant' && !lastMessage.content) {
6340
+ if (lastMessage.type === 'assistant' && !lastMessage.content) {
6294
6341
  this.messages = this.messages.slice(0, -1);
6295
6342
  }
6296
6343
  }
@@ -6298,7 +6345,7 @@ let KRChatbot = class KRChatbot extends i$2 {
6298
6345
  // --- Render ---
6299
6346
  render() {
6300
6347
  return b `
6301
- ${this.hideToggle ? A : b `
6348
+ ${(this.hideToggle || this.variant === 'inline') ? A : b `
6302
6349
  <button
6303
6350
  class=${e({
6304
6351
  'toggle': true,
@@ -6349,53 +6396,52 @@ let KRChatbot = class KRChatbot extends i$2 {
6349
6396
  <span>Drop images to attach</span>
6350
6397
  </div>
6351
6398
  ` : A}
6352
- <div
6353
- class="resize resize--n"
6354
- data-dir="n"
6355
- @mousedown=${this._handleResizeMouseDown}
6356
- ></div>
6357
- <div
6358
- class="resize resize--s"
6359
- data-dir="s"
6360
- @mousedown=${this._handleResizeMouseDown}
6361
- ></div>
6362
- <div
6363
- class="resize resize--w"
6364
- data-dir="w"
6365
- @mousedown=${this._handleResizeMouseDown}
6366
- ></div>
6367
- <div
6368
- class="resize resize--e"
6369
- data-dir="e"
6370
- @mousedown=${this._handleResizeMouseDown}
6371
- ></div>
6372
- <div
6373
- class="resize resize--nw"
6374
- data-dir="nw"
6375
- @mousedown=${this._handleResizeMouseDown}
6376
- ></div>
6377
- <div
6378
- class="resize resize--ne"
6379
- data-dir="ne"
6380
- @mousedown=${this._handleResizeMouseDown}
6381
- ></div>
6382
- <div
6383
- class="resize resize--sw"
6384
- data-dir="sw"
6385
- @mousedown=${this._handleResizeMouseDown}
6386
- ></div>
6387
- <div
6388
- class="resize resize--se"
6389
- data-dir="se"
6390
- @mousedown=${this._handleResizeMouseDown}
6391
- ></div>
6399
+ ${this.variant === 'inline' ? A : b `
6400
+ <div
6401
+ class="resize resize--n"
6402
+ data-dir="n"
6403
+ @mousedown=${this._handleResizeMouseDown}
6404
+ ></div>
6405
+ <div
6406
+ class="resize resize--s"
6407
+ data-dir="s"
6408
+ @mousedown=${this._handleResizeMouseDown}
6409
+ ></div>
6410
+ <div
6411
+ class="resize resize--w"
6412
+ data-dir="w"
6413
+ @mousedown=${this._handleResizeMouseDown}
6414
+ ></div>
6415
+ <div
6416
+ class="resize resize--e"
6417
+ data-dir="e"
6418
+ @mousedown=${this._handleResizeMouseDown}
6419
+ ></div>
6420
+ <div
6421
+ class="resize resize--nw"
6422
+ data-dir="nw"
6423
+ @mousedown=${this._handleResizeMouseDown}
6424
+ ></div>
6425
+ <div
6426
+ class="resize resize--ne"
6427
+ data-dir="ne"
6428
+ @mousedown=${this._handleResizeMouseDown}
6429
+ ></div>
6430
+ <div
6431
+ class="resize resize--sw"
6432
+ data-dir="sw"
6433
+ @mousedown=${this._handleResizeMouseDown}
6434
+ ></div>
6435
+ <div
6436
+ class="resize resize--se"
6437
+ data-dir="se"
6438
+ @mousedown=${this._handleResizeMouseDown}
6439
+ ></div>
6440
+ `}
6392
6441
  <div class="header" @mousedown=${this._handleHeaderMouseDown}>
6393
6442
  <div class="header-left">
6394
6443
  <div class="header-info">
6395
- <span class="header-title">${this.title}</span>
6396
- ${this.subtitle ? b `
6397
- <span class="header-subtitle">${this.subtitle}</span>
6398
- ` : A}
6444
+ <span class="header-title">${this.label}</span>
6399
6445
  </div>
6400
6446
  </div>
6401
6447
  <div class="header-actions">
@@ -6421,77 +6467,84 @@ let KRChatbot = class KRChatbot extends i$2 {
6421
6467
  </svg>
6422
6468
  </button>
6423
6469
  ` : A}
6424
- <button
6425
- class="header-action"
6426
- @click=${this._handleToggle}
6427
- title="Close"
6428
- >
6429
- <svg
6430
- xmlns="http://www.w3.org/2000/svg"
6431
- fill="none"
6432
- viewBox="0 0 24 24"
6433
- stroke-width="2"
6434
- stroke="currentColor"
6435
- class="header-action-icon"
6470
+ ${this.variant === 'inline' ? A : b `
6471
+ <button
6472
+ class="header-action"
6473
+ @click=${this._handleToggle}
6474
+ title="Close"
6436
6475
  >
6437
- <path
6438
- stroke-linecap="round"
6439
- stroke-linejoin="round"
6440
- d="M6 18 18 6M6 6l12 12"
6441
- />
6442
- </svg>
6443
- </button>
6476
+ <svg
6477
+ xmlns="http://www.w3.org/2000/svg"
6478
+ fill="none"
6479
+ viewBox="0 0 24 24"
6480
+ stroke-width="2"
6481
+ stroke="currentColor"
6482
+ class="header-action-icon"
6483
+ >
6484
+ <path
6485
+ stroke-linecap="round"
6486
+ stroke-linejoin="round"
6487
+ d="M6 18 18 6M6 6l12 12"
6488
+ />
6489
+ </svg>
6490
+ </button>
6491
+ `}
6444
6492
  </div>
6445
6493
  </div>
6446
6494
 
6447
6495
  <div class="messages" @scroll=${this._handleMessagesScroll}>
6448
- ${this.messages.length === 0 ? b `
6449
- <div class="empty">
6450
- <span class="empty-text">What can I help you with?</span>
6451
- </div>
6452
- ` : A}
6453
- ${this.messages.map((msg) => b `
6454
- <div class=${e({
6496
+ <div class="messages-inner">
6497
+ ${this.messages.length === 0 ? b `
6498
+ <div class="empty">
6499
+ <span class="empty-text">What can I help you with?</span>
6500
+ </div>
6501
+ ` : A}
6502
+ ${this.messages.map((msg) => b `
6503
+ <div class=${e({
6455
6504
  'message': true,
6456
- 'message--user': msg.role === 'user',
6457
- 'message--assistant': msg.role === 'assistant',
6505
+ 'message--user': msg.type === 'user',
6506
+ 'message--assistant': msg.type === 'assistant',
6458
6507
  })}>
6459
- ${msg.reasoning ? b `
6460
- <details class="reasoning">
6461
- <summary>Thinking</summary>
6462
- <div class="reasoning-content">${msg.reasoning}</div>
6463
- </details>
6464
- ` : A}
6465
- ${msg.attachments && msg.attachments.length ? b `
6466
- <div class="attachments">
6467
- ${msg.attachments.map((att) => b `
6468
- <button
6469
- class="attachment"
6470
- type="button"
6471
- title=${att.name || 'View image'}
6472
- @click=${() => this._openAttachment(att)}
6473
- >
6474
- <img src=${att.url} alt=${att.name || ''} />
6475
- </button>
6476
- `)}
6477
- </div>
6478
- ` : A}
6479
- ${msg.content ? b `
6480
- <div class="message-content">
6481
- ${msg.role === 'user'
6508
+ ${msg.reasoning ? b `
6509
+ <details class="reasoning">
6510
+ <summary>Thinking</summary>
6511
+ <div class="reasoning-content">${msg.reasoning}</div>
6512
+ </details>
6513
+ ` : A}
6514
+ ${msg.internalFiles && msg.internalFiles.length ? b `
6515
+ <div class="attachments">
6516
+ ${msg.internalFiles.map((img) => b `
6517
+ <button
6518
+ class="attachment"
6519
+ type="button"
6520
+ title=${img.niceName || 'View image'}
6521
+ @click=${() => this._openImage(img)}
6522
+ >
6523
+ <img
6524
+ src="/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view"
6525
+ alt=${img.niceName || ''}
6526
+ />
6527
+ </button>
6528
+ `)}
6529
+ </div>
6530
+ ` : A}
6531
+ ${msg.content ? b `
6532
+ <div class="message-content">
6533
+ ${msg.type === 'user'
6482
6534
  ? msg.content
6483
6535
  : o(marked.parse(msg.content, { breaks: true }))}
6484
- </div>
6485
- ` : A}
6486
- ${msg.role === 'assistant' && this._isStreaming && msg === this.messages[this.messages.length - 1] ? b `
6487
- <div class="typing">
6488
- <span></span>
6489
- <span></span>
6490
- <span></span>
6491
- </div>
6492
- ` : A}
6493
- </div>
6494
- `)}
6536
+ </div>
6537
+ ` : A}
6538
+ ${msg.type === 'assistant' && this._isStreaming && msg === this.messages[this.messages.length - 1] ? b `
6539
+ <div class="typing">
6540
+ <span></span>
6541
+ <span></span>
6542
+ <span></span>
6543
+ </div>
6544
+ ` : A}
6545
+ </div>
6546
+ `)}
6547
+ </div>
6495
6548
  </div>
6496
6549
 
6497
6550
  ${this._error ? b `
@@ -6517,9 +6570,9 @@ let KRChatbot = class KRChatbot extends i$2 {
6517
6570
  ` : A}
6518
6571
 
6519
6572
  <div class="footer">
6520
- ${this._pendingAttachments.length ? b `
6573
+ ${this._pendingInternalFiles.length ? b `
6521
6574
  <div class="composer-attachments">
6522
- ${c(this._pendingAttachments, (p) => p.localId, (p) => b `
6575
+ ${c(this._pendingInternalFiles, (p) => p.localId, (p) => b `
6523
6576
  <div class="chip">
6524
6577
  <img src=${p.previewUrl} alt=${p.name} />
6525
6578
  <button
@@ -6708,6 +6761,28 @@ KRChatbot.styles = i$5 `
6708
6761
  display: flex;
6709
6762
  }
6710
6763
 
6764
+ /* Inline mode: fill the parent container instead of floating bottom-right. */
6765
+ :host([variant='inline']) {
6766
+ position: static;
6767
+ bottom: auto;
6768
+ right: auto;
6769
+ z-index: auto;
6770
+ height: 100%;
6771
+ }
6772
+
6773
+ :host([variant='inline']) .panel {
6774
+ position: relative;
6775
+ bottom: auto;
6776
+ right: auto;
6777
+ width: 100%;
6778
+ height: 100%;
6779
+ max-height: none;
6780
+ border-radius: 0;
6781
+ box-shadow: none;
6782
+ border: none;
6783
+ animation: none;
6784
+ }
6785
+
6711
6786
  @keyframes chatbot-panel-in {
6712
6787
  from {
6713
6788
  opacity: 0;
@@ -6738,7 +6813,7 @@ KRChatbot.styles = i$5 `
6738
6813
  gap: 10px;
6739
6814
  }
6740
6815
 
6741
- .header-info {
6816
+ .header-info {
6742
6817
  display: flex;
6743
6818
  flex-direction: column;
6744
6819
  }
@@ -6748,22 +6823,6 @@ KRChatbot.styles = i$5 `
6748
6823
  font-weight: 600;
6749
6824
  }
6750
6825
 
6751
- .header-subtitle {
6752
- color: rgba(255, 255, 255, 0.5);
6753
- font-size: 11px;
6754
- display: flex;
6755
- align-items: center;
6756
- gap: 4px;
6757
- }
6758
-
6759
- .header-subtitle::before {
6760
- content: '';
6761
- width: 5px;
6762
- height: 5px;
6763
- background: var(--kr-chatbot-success);
6764
- border-radius: 50%;
6765
- }
6766
-
6767
6826
  .header-actions {
6768
6827
  display: flex;
6769
6828
  align-items: center;
@@ -6795,17 +6854,29 @@ KRChatbot.styles = i$5 `
6795
6854
  }
6796
6855
 
6797
6856
  /* Messages */
6857
+ /* Full-width scroll container — keeps the scrollbar at the panel edge. */
6798
6858
  .messages {
6799
6859
  flex: 1;
6800
6860
  overflow-y: auto;
6801
- padding: 32px;
6802
6861
  display: flex;
6803
6862
  flex-direction: column;
6804
- gap: 24px;
6805
6863
  scroll-behavior: smooth;
6806
6864
  background: white;
6807
6865
  }
6808
6866
 
6867
+ /* Centered, width-capped column that actually holds the messages. */
6868
+ .messages-inner {
6869
+ flex: 1;
6870
+ display: flex;
6871
+ flex-direction: column;
6872
+ gap: 24px;
6873
+ padding: 32px;
6874
+ width: 100%;
6875
+ max-width: 768px;
6876
+ margin-left: auto;
6877
+ margin-right: auto;
6878
+ }
6879
+
6809
6880
  .messages::-webkit-scrollbar {
6810
6881
  width: 4px;
6811
6882
  }
@@ -6829,7 +6900,7 @@ KRChatbot.styles = i$5 `
6829
6900
  gap: 8px;
6830
6901
  }
6831
6902
 
6832
- .empty-text {
6903
+ .empty-text {
6833
6904
  font-size: 14px;
6834
6905
  color: rgb(0 0 0 / 80%);
6835
6906
  }
@@ -6941,7 +7012,7 @@ KRChatbot.styles = i$5 `
6941
7012
  word-wrap: break-word;
6942
7013
  }
6943
7014
 
6944
- .typing {
7015
+ .typing {
6945
7016
  display: flex;
6946
7017
  gap: 4px;
6947
7018
  padding: 4px 0;
@@ -7009,6 +7080,15 @@ KRChatbot.styles = i$5 `
7009
7080
  padding: 16px 16px 16px;
7010
7081
  flex-shrink: 0;
7011
7082
  background: white;
7083
+ width: 100%;
7084
+ max-width: 768px;
7085
+ margin-left: auto;
7086
+ margin-right: auto;
7087
+ }
7088
+
7089
+ /* Inline mode: keep the composer clear of the bottom edge. */
7090
+ :host([variant='inline']) .footer {
7091
+ padding-bottom: 40px;
7012
7092
  }
7013
7093
 
7014
7094
  .input-wrapper {
@@ -7027,6 +7107,15 @@ KRChatbot.styles = i$5 `
7027
7107
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04);
7028
7108
  }
7029
7109
 
7110
+ /* Inline mode: keep the border and add a soft floating shadow (ChatGPT/Gemini style). */
7111
+ :host([variant='inline']) .input-wrapper {
7112
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.1);
7113
+ }
7114
+
7115
+ :host([variant='inline']) .input-wrapper:focus-within {
7116
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), 0 12px 32px rgba(0, 0, 0, 0.14);
7117
+ }
7118
+
7030
7119
  .input-plus {
7031
7120
  width: 34px;
7032
7121
  height: 34px;
@@ -7134,6 +7223,12 @@ KRChatbot.styles = i$5 `
7134
7223
  cursor: grabbing;
7135
7224
  }
7136
7225
 
7226
+ /* Inline mode isn't draggable — don't show the grab/hand cursor. */
7227
+ :host([variant='inline']) .header,
7228
+ :host([variant='inline']) .header:active {
7229
+ cursor: default;
7230
+ }
7231
+
7137
7232
  /* Drag / Resize states */
7138
7233
  .panel--dragging,
7139
7234
  .panel--resizing {
@@ -7285,10 +7380,7 @@ __decorate([
7285
7380
  ], KRChatbot.prototype, "messages", void 0);
7286
7381
  __decorate([
7287
7382
  n({ type: String })
7288
- ], KRChatbot.prototype, "title", void 0);
7289
- __decorate([
7290
- n({ type: String })
7291
- ], KRChatbot.prototype, "subtitle", void 0);
7383
+ ], KRChatbot.prototype, "label", void 0);
7292
7384
  __decorate([
7293
7385
  n({ type: String })
7294
7386
  ], KRChatbot.prototype, "placeholder", void 0);
@@ -7305,21 +7397,24 @@ __decorate([
7305
7397
  reflect: true
7306
7398
  })
7307
7399
  ], KRChatbot.prototype, "hideToggle", void 0);
7400
+ __decorate([
7401
+ n({
7402
+ type: String,
7403
+ reflect: true
7404
+ })
7405
+ ], KRChatbot.prototype, "variant", void 0);
7308
7406
  __decorate([
7309
7407
  r()
7310
7408
  ], KRChatbot.prototype, "_inputValue", void 0);
7311
7409
  __decorate([
7312
7410
  r()
7313
7411
  ], KRChatbot.prototype, "_isStreaming", void 0);
7314
- __decorate([
7315
- r()
7316
- ], KRChatbot.prototype, "_statusText", void 0);
7317
7412
  __decorate([
7318
7413
  r()
7319
7414
  ], KRChatbot.prototype, "_error", void 0);
7320
7415
  __decorate([
7321
7416
  r()
7322
- ], KRChatbot.prototype, "_pendingAttachments", void 0);
7417
+ ], KRChatbot.prototype, "_pendingInternalFiles", void 0);
7323
7418
  __decorate([
7324
7419
  r()
7325
7420
  ], KRChatbot.prototype, "_isDraggingFile", void 0);