@kodaris/krubble-app-components 1.0.87 → 1.0.89

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,31 +5862,42 @@
5862
5862
  // --- Lifecycle ---
5863
5863
  connectedCallback() {
5864
5864
  super.connectedCallback();
5865
- // Default to loading a specific conversation's messages when the consumer
5866
- // hasn't provided their own `load`. With `conversationId` set we rebuild that
5867
- // conversation from `/conversation/{id}/messages`; with none set there's
5868
- // nothing to resume, so start empty. Rows already match KRChatbotMessage
5869
- // ({ type, content }); we only parse each row's `conversationFileIDs` (a JSON
5870
- // array string of ids) into the `conversationFiles` the render iterates.
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 the user wants a NEW conversation, so we start
5868
+ // blank (no resume — the first message creates one via `send`). Rows already
5869
+ // match KRChatbotMessage ({ type, content }); we only parse each row's
5870
+ // `conversationFileIDs` (a JSON array string of ids) into `conversationFiles`.
5871
5871
  if (!this.load) {
5872
5872
  this.load = () => {
5873
5873
  if (this.conversationId == null) {
5874
5874
  return Promise.resolve([]);
5875
5875
  }
5876
- return krubbleHttp.Http.fetch({
5877
- url: `/api/system/conversation/${this.conversationId}/messages`,
5878
- })
5876
+ return krubbleHttp.Http.fetch({ url: `/api/system/conversation/${this.conversationId}/messages` })
5879
5877
  .then((r) => r.json())
5880
- .then((res) => (res?.data || []).map((msg) => {
5878
+ .then((res) => Promise.all((res?.data || []).map((msg) => {
5879
+ // Resolve this message's `conversationFileIDs` (a JSON array string of
5880
+ // ids) into full file objects — each fetch response IS the
5881
+ // KRChatbotConversationFile (id + niceName + mimeType), and niceName
5882
+ // drives the previewer's file-type detection. A failed fetch drops
5883
+ // that file rather than rendering a broken thumbnail.
5884
+ let ids = [];
5881
5885
  try {
5882
- const ids = JSON.parse(msg.conversationFileIDs || '[]');
5883
- if (ids.length) {
5884
- msg.conversationFiles = ids.map((id) => ({ conversationFileID: id }));
5885
- }
5886
+ ids = JSON.parse(msg.conversationFileIDs || '[]');
5886
5887
  }
5887
5888
  catch { /* no/invalid attachments — leave empty */ }
5888
- return msg;
5889
- }));
5889
+ if (!ids.length) {
5890
+ return Promise.resolve(msg);
5891
+ }
5892
+ return Promise.all(ids.map((id) => krubbleHttp.Http.fetch({ url: `/api/system/conversationFile/${id}` })
5893
+ .then((r) => r.json())
5894
+ .then((meta) => meta?.data)
5895
+ .catch(() => null)))
5896
+ .then((files) => {
5897
+ msg.conversationFiles = files.filter((f) => f != null);
5898
+ return msg;
5899
+ });
5900
+ })));
5890
5901
  };
5891
5902
  }
5892
5903
  this.load().then((messages) => {
@@ -6681,10 +6692,18 @@
6681
6692
  }
6682
6693
  /** Open the shared full-screen previewer for a message image. */
6683
6694
  _openImage(img) {
6684
- krubbleComponents.FilePreview.open({
6685
- src: `/api/system/conversationFile/${img.conversationFileID}/view`,
6695
+ // Prefix the configured backend base URL — a raw fetch of a relative path
6696
+ // would hit the page origin (e.g. the dev server), not the backend.
6697
+ const preview = krubbleComponents.FilePreview.open({
6698
+ src: `${krubbleHttp.Http.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view`,
6686
6699
  name: img.niceName || 'image',
6687
6700
  });
6701
+ // The previewer's download button only fires a `download` event — the
6702
+ // consumer performs the actual download. Hit the view endpoint with
6703
+ // `?download=true` (Content-Disposition: attachment) in a new tab.
6704
+ preview.addEventListener('download', () => {
6705
+ window.open(`${krubbleHttp.Http.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view?download=true`, '_blank');
6706
+ });
6688
6707
  }
6689
6708
  /** Whether the send button is active: needs text or at least one image. */
6690
6709
  _canSend() {
@@ -7533,7 +7552,7 @@
7533
7552
  @click=${() => this._openImage(img)}
7534
7553
  >
7535
7554
  <img
7536
- src="/api/system/conversationFile/${img.conversationFileID}/view"
7555
+ src="${krubbleHttp.Http.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view"
7537
7556
  alt=${img.niceName || ''}
7538
7557
  />
7539
7558
  </button>
@@ -7557,7 +7576,7 @@
7557
7576
  ${msg.content ? b `
7558
7577
  <button
7559
7578
  class="message-copy"
7560
- title="Copy"
7579
+ title=${this._copiedIndex === index ? 'Copied' : 'Copy'}
7561
7580
  @click=${() => this._handleCopyMessage(msg.content, index)}
7562
7581
  >
7563
7582
  ${this._copiedIndex === index ? b `
@@ -7571,7 +7590,6 @@
7571
7590
  >
7572
7591
  <path d="M20 6 9 17l-5-5" />
7573
7592
  </svg>
7574
- Copied
7575
7593
  ` : b `
7576
7594
  <svg
7577
7595
  viewBox="0 0 24 24"
@@ -7590,7 +7608,6 @@
7590
7608
  />
7591
7609
  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
7592
7610
  </svg>
7593
- Copy
7594
7611
  `}
7595
7612
  </button>
7596
7613
  ` : A}
@@ -7957,7 +7974,7 @@
7957
7974
  flex: 1;
7958
7975
  display: flex;
7959
7976
  flex-direction: column;
7960
- gap: 24px;
7977
+ gap: 36px;
7961
7978
  padding: 32px;
7962
7979
  width: 100%;
7963
7980
  max-width: 768px;
@@ -7986,6 +8003,17 @@
7986
8003
  flex-direction: column;
7987
8004
  max-width: 100%;
7988
8005
  animation: chatbot-msg-in 0.3s ease-out;
8006
+ /* Reserve room for the copy button INSIDE the message box (so the button is
8007
+ part of the message's hover region — no dead zone to cross), then cancel
8008
+ that space for layout so message spacing is unchanged. */
8009
+ padding-bottom: 30px;
8010
+ margin-bottom: -30px;
8011
+ }
8012
+
8013
+ /* The reserved padding overhangs into the next message; lift the hovered one
8014
+ so its copy button isn't covered by (or stealing hover from) the sibling. */
8015
+ .message:hover {
8016
+ z-index: 1;
7989
8017
  }
7990
8018
 
7991
8019
  @keyframes chatbot-msg-in {
@@ -8034,43 +8062,41 @@
8034
8062
  color: #b45309;
8035
8063
  }
8036
8064
 
8037
- /* Per-message copy button. Kept in flow with a fixed height that is always
8038
- reserved (via a negative bottom margin that cancels it) so message spacing
8039
- is identical whether or not it's shown; only its opacity toggles on hover. */
8065
+ /* Per-message copy button. Sits in the message's reserved bottom padding —
8066
+ inside the hover box, so moving the cursor down onto it never leaves the
8067
+ message. Height is reserved (via the message padding) so spacing doesn't
8068
+ jump; only opacity toggles on hover. */
8040
8069
  .message-copy {
8041
- align-self: flex-start;
8042
- margin-top: 4px;
8043
- margin-bottom: -27px;
8070
+ position: absolute;
8071
+ bottom: 0;
8072
+ left: 0;
8044
8073
  display: inline-flex;
8045
8074
  align-items: center;
8046
- gap: 4px;
8047
- height: 23px;
8048
- padding: 4px 8px;
8075
+ justify-content: center;
8076
+ width: 26px;
8077
+ height: 26px;
8078
+ padding: 0;
8049
8079
  background: none;
8050
8080
  border: none;
8051
8081
  border-radius: 6px;
8052
- font: inherit;
8053
- font-size: 12px;
8054
- color: #64668b;
8082
+ color: #000000;
8055
8083
  cursor: pointer;
8056
8084
  opacity: 0;
8057
- pointer-events: none;
8058
8085
  transition: opacity 0.15s, background-color 0.15s, color 0.15s;
8059
8086
  }
8060
8087
 
8061
8088
  .message--user .message-copy {
8062
- align-self: flex-end;
8089
+ left: auto;
8090
+ right: 0;
8063
8091
  }
8064
8092
 
8065
8093
  .message:hover .message-copy,
8066
8094
  .message-copy:focus-visible {
8067
8095
  opacity: 1;
8068
- pointer-events: auto;
8069
8096
  }
8070
8097
 
8071
8098
  .message-copy:hover {
8072
8099
  background: rgba(0, 0, 0, 0.06);
8073
- color: #1a1a2e;
8074
8100
  }
8075
8101
 
8076
8102
  .message-copy svg {
@@ -8644,6 +8670,7 @@
8644
8670
 
8645
8671
  .message--user .attachments {
8646
8672
  justify-content: flex-end;
8673
+ margin-bottom: 24px;
8647
8674
  }
8648
8675
 
8649
8676
  .attachment {