@kodaris/krubble-app-components 1.0.86 → 1.0.88

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.
@@ -5862,16 +5862,55 @@
5862
5862
  // --- Lifecycle ---
5863
5863
  connectedCallback() {
5864
5864
  super.connectedCallback();
5865
- // Default to loading existing messages from the Kodaris Bedrock conversation
5866
- // when the consumer hasn't provided their own `load`. The endpoint returns
5867
- // messages already in KRChatbotMessage shape ({ type, content, images }), so
5868
- // they pass straight through.
5865
+ // Default to loading a conversation's messages when the consumer hasn't
5866
+ // provided their own `load`. With `conversationId` set we rebuild that
5867
+ // conversation; with none set we resume the caller's most recent one via
5868
+ // `/conversation/last` (KRHttp rejects its 404 when they have none yet →
5869
+ // stays null → empty chat) and pin it so this session continues it. Rows
5870
+ // already match KRChatbotMessage ({ type, content }); we only parse each row's
5871
+ // `conversationFileIDs` (a JSON array string of ids) into `conversationFiles`.
5869
5872
  if (!this.load) {
5870
- this.load = () => krubbleHttp.Http.fetch({
5871
- url: '/api/system/integration/aws/bedrock/getConversationMessages',
5872
- })
5873
- .then((r) => r.json())
5874
- .then((res) => res.data || []);
5873
+ this.load = () => {
5874
+ let resolve = Promise.resolve();
5875
+ if (this.conversationId == null) {
5876
+ resolve = krubbleHttp.Http.fetch({ url: '/api/system/conversation/last' })
5877
+ .then((r) => r.json())
5878
+ .then((res) => {
5879
+ this.conversationId = res?.data?.conversationID ?? null;
5880
+ })
5881
+ .catch(() => { });
5882
+ }
5883
+ return resolve
5884
+ .then(() => {
5885
+ if (this.conversationId == null) {
5886
+ return { data: [] };
5887
+ }
5888
+ return krubbleHttp.Http.fetch({ url: `/api/system/conversation/${this.conversationId}/messages` }).then((r) => r.json());
5889
+ })
5890
+ .then((res) => Promise.all((res?.data || []).map((msg) => {
5891
+ // Resolve this message's `conversationFileIDs` (a JSON array string of
5892
+ // ids) into full file objects — each fetch response IS the
5893
+ // KRChatbotConversationFile (id + niceName + mimeType), and niceName
5894
+ // drives the previewer's file-type detection. A failed fetch drops
5895
+ // that file rather than rendering a broken thumbnail.
5896
+ let ids = [];
5897
+ try {
5898
+ ids = JSON.parse(msg.conversationFileIDs || '[]');
5899
+ }
5900
+ catch { /* no/invalid attachments — leave empty */ }
5901
+ if (!ids.length) {
5902
+ return Promise.resolve(msg);
5903
+ }
5904
+ return Promise.all(ids.map((id) => krubbleHttp.Http.fetch({ url: `/api/system/conversationFile/${id}` })
5905
+ .then((r) => r.json())
5906
+ .then((meta) => meta?.data)
5907
+ .catch(() => null)))
5908
+ .then((files) => {
5909
+ msg.conversationFiles = files.filter((f) => f != null);
5910
+ return msg;
5911
+ });
5912
+ })));
5913
+ };
5875
5914
  }
5876
5915
  this.load().then((messages) => {
5877
5916
  if (messages && messages.length) {
@@ -6665,10 +6704,18 @@
6665
6704
  }
6666
6705
  /** Open the shared full-screen previewer for a message image. */
6667
6706
  _openImage(img) {
6668
- krubbleComponents.FilePreview.open({
6669
- src: `/api/system/conversationFile/${img.conversationFileID}/view`,
6707
+ // Prefix the configured backend base URL — a raw fetch of a relative path
6708
+ // would hit the page origin (e.g. the dev server), not the backend.
6709
+ const preview = krubbleComponents.FilePreview.open({
6710
+ src: `${krubbleHttp.Http.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view`,
6670
6711
  name: img.niceName || 'image',
6671
6712
  });
6713
+ // The previewer's download button only fires a `download` event — the
6714
+ // consumer performs the actual download. Hit the view endpoint with
6715
+ // `?download=true` (Content-Disposition: attachment) in a new tab.
6716
+ preview.addEventListener('download', () => {
6717
+ window.open(`${krubbleHttp.Http.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view?download=true`, '_blank');
6718
+ });
6672
6719
  }
6673
6720
  /** Whether the send button is active: needs text or at least one image. */
6674
6721
  _canSend() {
@@ -7517,7 +7564,7 @@
7517
7564
  @click=${() => this._openImage(img)}
7518
7565
  >
7519
7566
  <img
7520
- src="/api/system/conversationFile/${img.conversationFileID}/view"
7567
+ src="${krubbleHttp.Http.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view"
7521
7568
  alt=${img.niceName || ''}
7522
7569
  />
7523
7570
  </button>
@@ -7541,7 +7588,7 @@
7541
7588
  ${msg.content ? b `
7542
7589
  <button
7543
7590
  class="message-copy"
7544
- title="Copy"
7591
+ title=${this._copiedIndex === index ? 'Copied' : 'Copy'}
7545
7592
  @click=${() => this._handleCopyMessage(msg.content, index)}
7546
7593
  >
7547
7594
  ${this._copiedIndex === index ? b `
@@ -7555,7 +7602,6 @@
7555
7602
  >
7556
7603
  <path d="M20 6 9 17l-5-5" />
7557
7604
  </svg>
7558
- Copied
7559
7605
  ` : b `
7560
7606
  <svg
7561
7607
  viewBox="0 0 24 24"
@@ -7574,7 +7620,6 @@
7574
7620
  />
7575
7621
  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
7576
7622
  </svg>
7577
- Copy
7578
7623
  `}
7579
7624
  </button>
7580
7625
  ` : A}
@@ -7941,7 +7986,7 @@
7941
7986
  flex: 1;
7942
7987
  display: flex;
7943
7988
  flex-direction: column;
7944
- gap: 24px;
7989
+ gap: 36px;
7945
7990
  padding: 32px;
7946
7991
  width: 100%;
7947
7992
  max-width: 768px;
@@ -7970,6 +8015,17 @@
7970
8015
  flex-direction: column;
7971
8016
  max-width: 100%;
7972
8017
  animation: chatbot-msg-in 0.3s ease-out;
8018
+ /* Reserve room for the copy button INSIDE the message box (so the button is
8019
+ part of the message's hover region — no dead zone to cross), then cancel
8020
+ that space for layout so message spacing is unchanged. */
8021
+ padding-bottom: 30px;
8022
+ margin-bottom: -30px;
8023
+ }
8024
+
8025
+ /* The reserved padding overhangs into the next message; lift the hovered one
8026
+ so its copy button isn't covered by (or stealing hover from) the sibling. */
8027
+ .message:hover {
8028
+ z-index: 1;
7973
8029
  }
7974
8030
 
7975
8031
  @keyframes chatbot-msg-in {
@@ -8018,43 +8074,41 @@
8018
8074
  color: #b45309;
8019
8075
  }
8020
8076
 
8021
- /* Per-message copy button. Kept in flow with a fixed height that is always
8022
- reserved (via a negative bottom margin that cancels it) so message spacing
8023
- is identical whether or not it's shown; only its opacity toggles on hover. */
8077
+ /* Per-message copy button. Sits in the message's reserved bottom padding —
8078
+ inside the hover box, so moving the cursor down onto it never leaves the
8079
+ message. Height is reserved (via the message padding) so spacing doesn't
8080
+ jump; only opacity toggles on hover. */
8024
8081
  .message-copy {
8025
- align-self: flex-start;
8026
- margin-top: 4px;
8027
- margin-bottom: -27px;
8082
+ position: absolute;
8083
+ bottom: 0;
8084
+ left: 0;
8028
8085
  display: inline-flex;
8029
8086
  align-items: center;
8030
- gap: 4px;
8031
- height: 23px;
8032
- padding: 4px 8px;
8087
+ justify-content: center;
8088
+ width: 26px;
8089
+ height: 26px;
8090
+ padding: 0;
8033
8091
  background: none;
8034
8092
  border: none;
8035
8093
  border-radius: 6px;
8036
- font: inherit;
8037
- font-size: 12px;
8038
- color: #64668b;
8094
+ color: #000000;
8039
8095
  cursor: pointer;
8040
8096
  opacity: 0;
8041
- pointer-events: none;
8042
8097
  transition: opacity 0.15s, background-color 0.15s, color 0.15s;
8043
8098
  }
8044
8099
 
8045
8100
  .message--user .message-copy {
8046
- align-self: flex-end;
8101
+ left: auto;
8102
+ right: 0;
8047
8103
  }
8048
8104
 
8049
8105
  .message:hover .message-copy,
8050
8106
  .message-copy:focus-visible {
8051
8107
  opacity: 1;
8052
- pointer-events: auto;
8053
8108
  }
8054
8109
 
8055
8110
  .message-copy:hover {
8056
8111
  background: rgba(0, 0, 0, 0.06);
8057
- color: #1a1a2e;
8058
8112
  }
8059
8113
 
8060
8114
  .message-copy svg {
@@ -8628,6 +8682,7 @@
8628
8682
 
8629
8683
  .message--user .attachments {
8630
8684
  justify-content: flex-end;
8685
+ margin-bottom: 24px;
8631
8686
  }
8632
8687
 
8633
8688
  .attachment {