@kodaris/krubble-app-components 1.0.85 → 1.0.87

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.
@@ -5639,14 +5639,6 @@ let KRChatbot = class KRChatbot extends i$3 {
5639
5639
  * from the Kodaris Bedrock conversation endpoints. Set this to override.
5640
5640
  */
5641
5641
  this.send = null;
5642
- /**
5643
- * Callback function called when the user clears the conversation.
5644
- * Must return a Promise — messages are only cleared if it resolves.
5645
- *
5646
- * Optional — when not set, the chatbot uses its built-in default that clears
5647
- * the Kodaris Bedrock conversation state. Set this to override.
5648
- */
5649
- this.clear = null;
5650
5642
  /**
5651
5643
  * Callback function called on init to load existing conversation messages.
5652
5644
  * Must return a Promise that resolves with an array of KRChatbotMessage.
@@ -5689,6 +5681,20 @@ let KRChatbot = class KRChatbot extends i$3 {
5689
5681
  * full page or panel region.
5690
5682
  */
5691
5683
  this.variant = 'floating';
5684
+ /**
5685
+ * The conversation this chatbot's turns target (backend `conversationID`).
5686
+ *
5687
+ * - Set → every turn is sent with this `conversationID`, so the chat continues
5688
+ * that specific conversation.
5689
+ * - Unset → the first turn is sent with `createNew: true` (a fresh
5690
+ * conversation); the component then resolves the new conversation's id (via
5691
+ * `/conversation/last`) and pins it here so the rest of the session stays one
5692
+ * conversation instead of spawning a new one per message.
5693
+ *
5694
+ * Only used by the built-in `send` default; a custom `send` owns its own
5695
+ * targeting.
5696
+ */
5697
+ this.conversationId = null;
5692
5698
  // --- @state (internal) ---
5693
5699
  this._inputValue = '';
5694
5700
  this._isStreaming = false;
@@ -5727,7 +5733,7 @@ let KRChatbot = class KRChatbot extends i$3 {
5727
5733
  /** True while the user is dragging the debug drawer's resize handle. */
5728
5734
  this._isDebugResizing = false;
5729
5735
  /** Images staged in the composer, before the message is sent. */
5730
- this._pendingInternalFiles = [];
5736
+ this._pendingConversationFiles = [];
5731
5737
  /** True while a file is being dragged over the panel (shows the drop overlay). */
5732
5738
  this._isDraggingFile = false;
5733
5739
  /** Whether the `/` skill menu is open above the composer. */
@@ -5853,16 +5859,32 @@ let KRChatbot = class KRChatbot extends i$3 {
5853
5859
  // --- Lifecycle ---
5854
5860
  connectedCallback() {
5855
5861
  super.connectedCallback();
5856
- // Default to loading existing messages from the Kodaris Bedrock conversation
5857
- // when the consumer hasn't provided their own `load`. The endpoint returns
5858
- // messages already in KRChatbotMessage shape ({ type, content, images }), so
5859
- // they pass straight through.
5862
+ // Default to loading a specific conversation's messages when the consumer
5863
+ // hasn't provided their own `load`. With `conversationId` set we rebuild that
5864
+ // conversation from `/conversation/{id}/messages`; with none set there's
5865
+ // nothing to resume, so start empty. Rows already match KRChatbotMessage
5866
+ // ({ type, content }); we only parse each row's `conversationFileIDs` (a JSON
5867
+ // array string of ids) into the `conversationFiles` the render iterates.
5860
5868
  if (!this.load) {
5861
- this.load = () => KRHttp.fetch({
5862
- url: '/api/system/integration/aws/bedrock/getConversationMessages',
5863
- })
5864
- .then((r) => r.json())
5865
- .then((res) => res.data || []);
5869
+ this.load = () => {
5870
+ if (this.conversationId == null) {
5871
+ return Promise.resolve([]);
5872
+ }
5873
+ return KRHttp.fetch({
5874
+ url: `/api/system/conversation/${this.conversationId}/messages`,
5875
+ })
5876
+ .then((r) => r.json())
5877
+ .then((res) => (res?.data || []).map((msg) => {
5878
+ try {
5879
+ const ids = JSON.parse(msg.conversationFileIDs || '[]');
5880
+ if (ids.length) {
5881
+ msg.conversationFiles = ids.map((id) => ({ conversationFileID: id }));
5882
+ }
5883
+ }
5884
+ catch { /* no/invalid attachments — leave empty */ }
5885
+ return msg;
5886
+ }));
5887
+ };
5866
5888
  }
5867
5889
  this.load().then((messages) => {
5868
5890
  if (messages && messages.length) {
@@ -5897,7 +5919,7 @@ let KRChatbot = class KRChatbot extends i$3 {
5897
5919
  document.removeEventListener('mousemove', this._handleMouseMove);
5898
5920
  document.removeEventListener('mouseup', this._handleMouseUp);
5899
5921
  // Release any object URLs still held by staged attachments.
5900
- this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
5922
+ this._pendingConversationFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
5901
5923
  }
5902
5924
  updated(changed) {
5903
5925
  if (changed.has('messages')) {
@@ -6230,7 +6252,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6230
6252
  this._skillMenuOpened = false;
6231
6253
  this._send({
6232
6254
  message: '/test-context-warning',
6233
- internalFiles: [],
6255
+ conversationFiles: [],
6234
6256
  });
6235
6257
  return;
6236
6258
  }
@@ -6238,7 +6260,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6238
6260
  this._skillMenuOpened = false;
6239
6261
  this._send({
6240
6262
  message: '/test-context-limit',
6241
- internalFiles: [],
6263
+ conversationFiles: [],
6242
6264
  });
6243
6265
  return;
6244
6266
  }
@@ -6555,6 +6577,10 @@ let KRChatbot = class KRChatbot extends i$3 {
6555
6577
  }
6556
6578
  // --- Attachments ---
6557
6579
  _openFilePicker() {
6580
+ // Close the skill/attachment menu — opening the OS file picker is the end of
6581
+ // the menu interaction, and the picker is a separate window so the menu would
6582
+ // otherwise linger open behind it (and after a file is chosen).
6583
+ this._skillMenuOpened = false;
6558
6584
  this._fileInputEl?.click();
6559
6585
  }
6560
6586
  _handleFileInputChange(e) {
@@ -6636,49 +6662,49 @@ let KRChatbot = class KRChatbot extends i$3 {
6636
6662
  name: file.name,
6637
6663
  previewUrl: URL.createObjectURL(file),
6638
6664
  };
6639
- this._pendingInternalFiles = [...this._pendingInternalFiles, pending];
6665
+ this._pendingConversationFiles = [...this._pendingConversationFiles, pending];
6640
6666
  });
6641
6667
  }
6642
6668
  _removePending(localId) {
6643
- const target = this._pendingInternalFiles.find((p) => p.localId === localId);
6669
+ const target = this._pendingConversationFiles.find((p) => p.localId === localId);
6644
6670
  if (target) {
6645
6671
  URL.revokeObjectURL(target.previewUrl);
6646
6672
  }
6647
- this._pendingInternalFiles = this._pendingInternalFiles.filter((p) => p.localId !== localId);
6673
+ this._pendingConversationFiles = this._pendingConversationFiles.filter((p) => p.localId !== localId);
6648
6674
  }
6649
6675
  _clearPending() {
6650
- this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6651
- this._pendingInternalFiles = [];
6676
+ this._pendingConversationFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6677
+ this._pendingConversationFiles = [];
6652
6678
  }
6653
6679
  /** Open the shared full-screen previewer for a message image. */
6654
6680
  _openImage(img) {
6655
6681
  KRFilePreview.open({
6656
- src: `/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view`,
6682
+ src: `/api/system/conversationFile/${img.conversationFileID}/view`,
6657
6683
  name: img.niceName || 'image',
6658
6684
  });
6659
6685
  }
6660
6686
  /** Whether the send button is active: needs text or at least one image. */
6661
6687
  _canSend() {
6662
- return Boolean(this._inputValue.trim()) || this._pendingInternalFiles.length > 0;
6688
+ return Boolean(this._inputValue.trim()) || this._pendingConversationFiles.length > 0;
6663
6689
  }
6664
6690
  _handleSubmit() {
6665
6691
  if (this._isStreaming) {
6666
6692
  return;
6667
6693
  }
6668
6694
  const messageText = this._inputValue.trim();
6669
- const pending = this._pendingInternalFiles;
6695
+ const pending = this._pendingConversationFiles;
6670
6696
  // Allow sending an image with no text, but require at least one of them.
6671
6697
  if (!messageText && !pending.length) {
6672
6698
  return;
6673
6699
  }
6674
6700
  // The raw files sent to the backend. Sent images are shown once the
6675
- // conversation reloads and they come back as stored internal files; the
6701
+ // conversation reloads and they come back as stored conversation files; the
6676
6702
  // just-sent message itself carries text only.
6677
- const internalFiles = pending.map((p) => p.file);
6703
+ const conversationFiles = pending.map((p) => p.file);
6678
6704
  const submitEvent = new CustomEvent('message-submit', {
6679
6705
  detail: {
6680
6706
  message: messageText,
6681
- internalFiles,
6707
+ conversationFiles,
6682
6708
  },
6683
6709
  bubbles: true,
6684
6710
  composed: true,
@@ -6690,7 +6716,7 @@ let KRChatbot = class KRChatbot extends i$3 {
6690
6716
  }
6691
6717
  this._send({
6692
6718
  message: messageText,
6693
- internalFiles
6719
+ conversationFiles
6694
6720
  });
6695
6721
  }
6696
6722
  /**
@@ -6710,8 +6736,8 @@ let KRChatbot = class KRChatbot extends i$3 {
6710
6736
  this._error = '';
6711
6737
  this._userHasScrolledUp = false;
6712
6738
  // Clear the composer and release its preview object URLs.
6713
- this._pendingInternalFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6714
- this._pendingInternalFiles = [];
6739
+ this._pendingConversationFiles.forEach((p) => URL.revokeObjectURL(p.previewUrl));
6740
+ this._pendingConversationFiles = [];
6715
6741
  // Reset textarea height
6716
6742
  if (this._inputEl) {
6717
6743
  this._inputEl.style.height = 'auto';
@@ -6747,30 +6773,74 @@ let KRChatbot = class KRChatbot extends i$3 {
6747
6773
  // the consumer hasn't provided their own `send`.
6748
6774
  if (!this.send) {
6749
6775
  this.send = (params) => {
6750
- // With images, post text + files as multipart; without, post plain JSON.
6751
- if (params.internalFiles.length) {
6752
- const form = new FormData();
6753
- form.append('userText', params.message);
6754
- params.internalFiles.forEach((file) => form.append('images', file));
6776
+ // Images are upload-first: each file is stored as a conversation file
6777
+ // (type LIBRARY — the format lives in mimeType), then the resulting
6778
+ // conversationFileIDs are attached to the message via `fileIds` on the
6779
+ // normal JSON stream call. There is no multipart chat endpoint. With no
6780
+ // images the upload resolves to an empty list immediately; a failed
6781
+ // upload is dropped so an undefined id never reaches `fileIds`.
6782
+ let uploaded = Promise.resolve([]);
6783
+ if (params.conversationFiles.length) {
6784
+ uploaded = Promise.all(params.conversationFiles.map((file) => {
6785
+ const form = new FormData();
6786
+ form.append('file', file);
6787
+ form.append('type', 'LIBRARY');
6788
+ form.append('niceName', file.name);
6789
+ return KRHttp.fetch({
6790
+ url: '/api/system/conversationFile/upload',
6791
+ method: 'POST',
6792
+ body: form,
6793
+ })
6794
+ .then((r) => r.json())
6795
+ .then((res) => res?.data?.conversationFileID);
6796
+ })).then((ids) => ids.filter((id) => typeof id === 'number'));
6797
+ }
6798
+ return uploaded.then((fileIds) => {
6799
+ // Conversation targeting: continue `conversationId` when set, else
6800
+ // start a fresh conversation (createNew) for this turn. The pin below
6801
+ // means createNew only fires on the very first untargeted turn.
6802
+ const body = {
6803
+ userText: params.message,
6804
+ fileIds,
6805
+ };
6806
+ if (this.conversationId != null) {
6807
+ body.conversationID = this.conversationId;
6808
+ }
6809
+ else {
6810
+ body.createNew = true;
6811
+ }
6755
6812
  return KRHttp.fetch({
6756
- url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithImages',
6813
+ url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithState',
6757
6814
  method: 'POST',
6758
6815
  headers: { Accept: 'text/event-stream' },
6759
- body: form,
6816
+ body,
6817
+ }).then((response) => {
6818
+ // Pin the session to the conversation this turn used. The stream
6819
+ // doesn't echo the resolved id, so when we let the server create one
6820
+ // (no conversationId set) we resolve it via `/last` and keep it, so
6821
+ // subsequent turns continue this conversation instead of each sending
6822
+ // createNew and spawning a new one. Best-effort, fire-and-forget: a
6823
+ // failure leaves it unpinned and the next turn resolves server-side.
6824
+ if (this.conversationId == null) {
6825
+ KRHttp.fetch({ url: '/api/system/conversation/last' })
6826
+ .then((r) => r.json())
6827
+ .then((res) => {
6828
+ const id = res?.data?.conversationID;
6829
+ if (typeof id === 'number') {
6830
+ this.conversationId = id;
6831
+ }
6832
+ })
6833
+ .catch(() => { });
6834
+ }
6835
+ return response;
6760
6836
  });
6761
- }
6762
- return KRHttp.fetch({
6763
- url: '/api/system/integration/aws/bedrock/stream/invokeModelForConversationWithState',
6764
- method: 'POST',
6765
- headers: { Accept: 'text/event-stream' },
6766
- body: { userText: params.message },
6767
6837
  });
6768
6838
  };
6769
6839
  }
6770
6840
  this.send({
6771
6841
  message: params.message,
6772
6842
  messages: this.messages.slice(0, -1),
6773
- internalFiles: params.internalFiles,
6843
+ conversationFiles: params.conversationFiles,
6774
6844
  }).then((response) => {
6775
6845
  if (!response.ok) {
6776
6846
  this._handleStreamError('Request failed: ' + response.status);
@@ -6797,35 +6867,6 @@ let KRChatbot = class KRChatbot extends i$3 {
6797
6867
  }
6798
6868
  this._isStreaming = false;
6799
6869
  }
6800
- _handleClear() {
6801
- // Default to clearing the Kodaris Bedrock conversation state when the consumer
6802
- // hasn't provided their own `clear`.
6803
- if (!this.clear) {
6804
- this.clear = () => KRHttp.fetch({
6805
- url: '/api/system/integration/aws/bedrock/clearConversationWithState',
6806
- method: 'POST',
6807
- });
6808
- }
6809
- this.clear().then(() => {
6810
- if (this._reader) {
6811
- this._reader.cancel();
6812
- this._reader = null;
6813
- }
6814
- this.messages = [];
6815
- this._isStreaming = false;
6816
- this._error = '';
6817
- this._inputValue = '';
6818
- this._userHasScrolledUp = false;
6819
- // Reset conversation-scoped context + telemetry state for the new convo.
6820
- this._contextWarning = '';
6821
- this._contextLimitReached = false;
6822
- this._contextLimitMessage = '';
6823
- this._telemetry = null;
6824
- this._iterationLog = [];
6825
- this._cacheCheckpoints = {};
6826
- this._saveState();
6827
- });
6828
- }
6829
6870
  _handleMessagesScroll(e) {
6830
6871
  const container = e.target;
6831
6872
  const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
@@ -7435,28 +7476,6 @@ let KRChatbot = class KRChatbot extends i$3 {
7435
7476
  </svg>
7436
7477
  </button>
7437
7478
  ` : A}
7438
- ${this.messages.length > 0 ? b `
7439
- <button
7440
- class="header-action"
7441
- @click=${this._handleClear}
7442
- title="Clear conversation"
7443
- >
7444
- <svg
7445
- xmlns="http://www.w3.org/2000/svg"
7446
- fill="none"
7447
- viewBox="0 0 24 24"
7448
- stroke-width="1.5"
7449
- stroke="currentColor"
7450
- class="header-action-icon"
7451
- >
7452
- <path
7453
- stroke-linecap="round"
7454
- stroke-linejoin="round"
7455
- d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
7456
- />
7457
- </svg>
7458
- </button>
7459
- ` : A}
7460
7479
  ${this.variant === 'inline' ? A : b `
7461
7480
  <button
7462
7481
  class="header-action"
@@ -7501,9 +7520,9 @@ let KRChatbot = class KRChatbot extends i$3 {
7501
7520
  <div class="reasoning-content">${msg.reasoning}</div>
7502
7521
  </details>
7503
7522
  ` : A}
7504
- ${msg.internalFiles && msg.internalFiles.length ? b `
7523
+ ${msg.conversationFiles && msg.conversationFiles.length ? b `
7505
7524
  <div class="attachments">
7506
- ${msg.internalFiles.map((img) => b `
7525
+ ${msg.conversationFiles.map((img) => b `
7507
7526
  <button
7508
7527
  class="attachment"
7509
7528
  type="button"
@@ -7511,7 +7530,7 @@ let KRChatbot = class KRChatbot extends i$3 {
7511
7530
  @click=${() => this._openImage(img)}
7512
7531
  >
7513
7532
  <img
7514
- src="/api/system/integration/aws/bedrock/conversationImage/${img.internalFileID}/view"
7533
+ src="/api/system/conversationFile/${img.conversationFileID}/view"
7515
7534
  alt=${img.niceName || ''}
7516
7535
  />
7517
7536
  </button>
@@ -7629,9 +7648,9 @@ let KRChatbot = class KRChatbot extends i$3 {
7629
7648
  ` : A}
7630
7649
 
7631
7650
  <div class="footer">
7632
- ${this._pendingInternalFiles.length ? b `
7651
+ ${this._pendingConversationFiles.length ? b `
7633
7652
  <div class="composer-attachments">
7634
- ${c(this._pendingInternalFiles, (p) => p.localId, (p) => b `
7653
+ ${c(this._pendingConversationFiles, (p) => p.localId, (p) => b `
7635
7654
  <div class="chip">
7636
7655
  <img src=${p.previewUrl} alt=${p.name} />
7637
7656
  <button
@@ -8921,9 +8940,6 @@ KRChatbot.styles = i$6 `
8921
8940
  __decorate([
8922
8941
  n$1({ attribute: false })
8923
8942
  ], KRChatbot.prototype, "send", void 0);
8924
- __decorate([
8925
- n$1({ attribute: false })
8926
- ], KRChatbot.prototype, "clear", void 0);
8927
8943
  __decorate([
8928
8944
  n$1({ attribute: false })
8929
8945
  ], KRChatbot.prototype, "load", void 0);
@@ -8955,6 +8971,13 @@ __decorate([
8955
8971
  reflect: true
8956
8972
  })
8957
8973
  ], KRChatbot.prototype, "variant", void 0);
8974
+ __decorate([
8975
+ n$1({
8976
+ type: Number,
8977
+ attribute: 'conversation-id',
8978
+ reflect: true
8979
+ })
8980
+ ], KRChatbot.prototype, "conversationId", void 0);
8958
8981
  __decorate([
8959
8982
  r()
8960
8983
  ], KRChatbot.prototype, "_inputValue", void 0);
@@ -8993,7 +9016,7 @@ __decorate([
8993
9016
  ], KRChatbot.prototype, "_isDebugResizing", void 0);
8994
9017
  __decorate([
8995
9018
  r()
8996
- ], KRChatbot.prototype, "_pendingInternalFiles", void 0);
9019
+ ], KRChatbot.prototype, "_pendingConversationFiles", void 0);
8997
9020
  __decorate([
8998
9021
  r()
8999
9022
  ], KRChatbot.prototype, "_isDraggingFile", void 0);