@kodaris/krubble-app-components 1.0.87 → 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.
@@ -5859,31 +5859,54 @@ 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 we resume the caller's most recent one via
5865
+ // `/conversation/last` (KRHttp rejects its 404 when they have none yet →
5866
+ // stays null → empty chat) and pin it so this session continues it. Rows
5867
+ // already match KRChatbotMessage ({ type, content }); we only parse each row's
5868
+ // `conversationFileIDs` (a JSON array string of ids) into `conversationFiles`.
5868
5869
  if (!this.load) {
5869
5870
  this.load = () => {
5871
+ let resolve = Promise.resolve();
5870
5872
  if (this.conversationId == null) {
5871
- return Promise.resolve([]);
5873
+ resolve = KRHttp.fetch({ url: '/api/system/conversation/last' })
5874
+ .then((r) => r.json())
5875
+ .then((res) => {
5876
+ this.conversationId = res?.data?.conversationID ?? null;
5877
+ })
5878
+ .catch(() => { });
5872
5879
  }
5873
- return KRHttp.fetch({
5874
- url: `/api/system/conversation/${this.conversationId}/messages`,
5880
+ return resolve
5881
+ .then(() => {
5882
+ if (this.conversationId == null) {
5883
+ return { data: [] };
5884
+ }
5885
+ return KRHttp.fetch({ url: `/api/system/conversation/${this.conversationId}/messages` }).then((r) => r.json());
5875
5886
  })
5876
- .then((r) => r.json())
5877
- .then((res) => (res?.data || []).map((msg) => {
5887
+ .then((res) => Promise.all((res?.data || []).map((msg) => {
5888
+ // Resolve this message's `conversationFileIDs` (a JSON array string of
5889
+ // ids) into full file objects — each fetch response IS the
5890
+ // KRChatbotConversationFile (id + niceName + mimeType), and niceName
5891
+ // drives the previewer's file-type detection. A failed fetch drops
5892
+ // that file rather than rendering a broken thumbnail.
5893
+ let ids = [];
5878
5894
  try {
5879
- const ids = JSON.parse(msg.conversationFileIDs || '[]');
5880
- if (ids.length) {
5881
- msg.conversationFiles = ids.map((id) => ({ conversationFileID: id }));
5882
- }
5895
+ ids = JSON.parse(msg.conversationFileIDs || '[]');
5883
5896
  }
5884
5897
  catch { /* no/invalid attachments — leave empty */ }
5885
- return msg;
5886
- }));
5898
+ if (!ids.length) {
5899
+ return Promise.resolve(msg);
5900
+ }
5901
+ return Promise.all(ids.map((id) => KRHttp.fetch({ url: `/api/system/conversationFile/${id}` })
5902
+ .then((r) => r.json())
5903
+ .then((meta) => meta?.data)
5904
+ .catch(() => null)))
5905
+ .then((files) => {
5906
+ msg.conversationFiles = files.filter((f) => f != null);
5907
+ return msg;
5908
+ });
5909
+ })));
5887
5910
  };
5888
5911
  }
5889
5912
  this.load().then((messages) => {
@@ -6678,10 +6701,18 @@ let KRChatbot = class KRChatbot extends i$3 {
6678
6701
  }
6679
6702
  /** Open the shared full-screen previewer for a message image. */
6680
6703
  _openImage(img) {
6681
- KRFilePreview.open({
6682
- src: `/api/system/conversationFile/${img.conversationFileID}/view`,
6704
+ // Prefix the configured backend base URL — a raw fetch of a relative path
6705
+ // would hit the page origin (e.g. the dev server), not the backend.
6706
+ const preview = KRFilePreview.open({
6707
+ src: `${KRHttp.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view`,
6683
6708
  name: img.niceName || 'image',
6684
6709
  });
6710
+ // The previewer's download button only fires a `download` event — the
6711
+ // consumer performs the actual download. Hit the view endpoint with
6712
+ // `?download=true` (Content-Disposition: attachment) in a new tab.
6713
+ preview.addEventListener('download', () => {
6714
+ window.open(`${KRHttp.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view?download=true`, '_blank');
6715
+ });
6685
6716
  }
6686
6717
  /** Whether the send button is active: needs text or at least one image. */
6687
6718
  _canSend() {
@@ -7530,7 +7561,7 @@ let KRChatbot = class KRChatbot extends i$3 {
7530
7561
  @click=${() => this._openImage(img)}
7531
7562
  >
7532
7563
  <img
7533
- src="/api/system/conversationFile/${img.conversationFileID}/view"
7564
+ src="${KRHttp.getBaseUrl()}/api/system/conversationFile/${img.conversationFileID}/view"
7534
7565
  alt=${img.niceName || ''}
7535
7566
  />
7536
7567
  </button>
@@ -7554,7 +7585,7 @@ let KRChatbot = class KRChatbot extends i$3 {
7554
7585
  ${msg.content ? b `
7555
7586
  <button
7556
7587
  class="message-copy"
7557
- title="Copy"
7588
+ title=${this._copiedIndex === index ? 'Copied' : 'Copy'}
7558
7589
  @click=${() => this._handleCopyMessage(msg.content, index)}
7559
7590
  >
7560
7591
  ${this._copiedIndex === index ? b `
@@ -7568,7 +7599,6 @@ let KRChatbot = class KRChatbot extends i$3 {
7568
7599
  >
7569
7600
  <path d="M20 6 9 17l-5-5" />
7570
7601
  </svg>
7571
- Copied
7572
7602
  ` : b `
7573
7603
  <svg
7574
7604
  viewBox="0 0 24 24"
@@ -7587,7 +7617,6 @@ let KRChatbot = class KRChatbot extends i$3 {
7587
7617
  />
7588
7618
  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
7589
7619
  </svg>
7590
- Copy
7591
7620
  `}
7592
7621
  </button>
7593
7622
  ` : A}
@@ -7954,7 +7983,7 @@ KRChatbot.styles = i$6 `
7954
7983
  flex: 1;
7955
7984
  display: flex;
7956
7985
  flex-direction: column;
7957
- gap: 24px;
7986
+ gap: 36px;
7958
7987
  padding: 32px;
7959
7988
  width: 100%;
7960
7989
  max-width: 768px;
@@ -7983,6 +8012,17 @@ KRChatbot.styles = i$6 `
7983
8012
  flex-direction: column;
7984
8013
  max-width: 100%;
7985
8014
  animation: chatbot-msg-in 0.3s ease-out;
8015
+ /* Reserve room for the copy button INSIDE the message box (so the button is
8016
+ part of the message's hover region — no dead zone to cross), then cancel
8017
+ that space for layout so message spacing is unchanged. */
8018
+ padding-bottom: 30px;
8019
+ margin-bottom: -30px;
8020
+ }
8021
+
8022
+ /* The reserved padding overhangs into the next message; lift the hovered one
8023
+ so its copy button isn't covered by (or stealing hover from) the sibling. */
8024
+ .message:hover {
8025
+ z-index: 1;
7986
8026
  }
7987
8027
 
7988
8028
  @keyframes chatbot-msg-in {
@@ -8031,43 +8071,41 @@ KRChatbot.styles = i$6 `
8031
8071
  color: #b45309;
8032
8072
  }
8033
8073
 
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. */
8074
+ /* Per-message copy button. Sits in the message's reserved bottom padding —
8075
+ inside the hover box, so moving the cursor down onto it never leaves the
8076
+ message. Height is reserved (via the message padding) so spacing doesn't
8077
+ jump; only opacity toggles on hover. */
8037
8078
  .message-copy {
8038
- align-self: flex-start;
8039
- margin-top: 4px;
8040
- margin-bottom: -27px;
8079
+ position: absolute;
8080
+ bottom: 0;
8081
+ left: 0;
8041
8082
  display: inline-flex;
8042
8083
  align-items: center;
8043
- gap: 4px;
8044
- height: 23px;
8045
- padding: 4px 8px;
8084
+ justify-content: center;
8085
+ width: 26px;
8086
+ height: 26px;
8087
+ padding: 0;
8046
8088
  background: none;
8047
8089
  border: none;
8048
8090
  border-radius: 6px;
8049
- font: inherit;
8050
- font-size: 12px;
8051
- color: #64668b;
8091
+ color: #000000;
8052
8092
  cursor: pointer;
8053
8093
  opacity: 0;
8054
- pointer-events: none;
8055
8094
  transition: opacity 0.15s, background-color 0.15s, color 0.15s;
8056
8095
  }
8057
8096
 
8058
8097
  .message--user .message-copy {
8059
- align-self: flex-end;
8098
+ left: auto;
8099
+ right: 0;
8060
8100
  }
8061
8101
 
8062
8102
  .message:hover .message-copy,
8063
8103
  .message-copy:focus-visible {
8064
8104
  opacity: 1;
8065
- pointer-events: auto;
8066
8105
  }
8067
8106
 
8068
8107
  .message-copy:hover {
8069
8108
  background: rgba(0, 0, 0, 0.06);
8070
- color: #1a1a2e;
8071
8109
  }
8072
8110
 
8073
8111
  .message-copy svg {
@@ -8641,6 +8679,7 @@ KRChatbot.styles = i$6 `
8641
8679
 
8642
8680
  .message--user .attachments {
8643
8681
  justify-content: flex-end;
8682
+ margin-bottom: 24px;
8644
8683
  }
8645
8684
 
8646
8685
  .attachment {