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