@kodaris/krubble-app-components 1.0.124 → 1.0.126

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.
@@ -3017,6 +3017,14 @@ class KRChatbot extends i$3 {
3017
3017
  this._inputValue = '';
3018
3018
  this._isStreaming = false;
3019
3019
  this._error = '';
3020
+ /**
3021
+ * The current conversation's title. In the floating widget it replaces the
3022
+ * static header label so the user sees which conversation they're in. Set from
3023
+ * the loaded/switched conversation and from the live `CONVERSATION_TITLE` event
3024
+ * (the server generates a title at the end of a new conversation's first turn);
3025
+ * empty for a brand-new conversation, where the header falls back to `label`.
3026
+ */
3027
+ this._conversationTitle = '';
3020
3028
  /**
3021
3029
  * User-facing context warning text (`CONVERSATION_TOKEN_WARNING`). Shown as a
3022
3030
  * dismissible banner in all variants. Carries only friendly guidance, no numbers.
@@ -3076,12 +3084,23 @@ class KRChatbot extends i$3 {
3076
3084
  */
3077
3085
  this._drawerOpen = false;
3078
3086
  /**
3079
- * Which view the drawer shows — settings, debug, or a single tool call's full
3080
- * detail. One drawer holds all three; the header buttons switch settings/debug
3081
- * and clicking a tool line switches to `toolDetail`. The content/width swap
3082
- * inside. Never null, so the content stays visible while the drawer slides shut.
3087
+ * Which view the drawer shows — settings, debug, history, or a single tool
3088
+ * call's full detail. One drawer holds them all; header buttons switch
3089
+ * settings/debug/history and clicking a tool line switches to `toolDetail`. The
3090
+ * content/width swap inside. Never null, so the content stays visible while the
3091
+ * drawer slides shut. `history` is floating-only (the previous-conversations
3092
+ * picker); in floating the drawer fills the panel over the transcript.
3083
3093
  */
3084
3094
  this._drawerView = 'settings';
3095
+ /**
3096
+ * The user's previous conversations, loaded from `/conversation/list` when the
3097
+ * history view opens. Each is the raw ConversationViewDto (we read conversationID,
3098
+ * title, lastModified). Empty until the first open; reloaded on each open so a
3099
+ * freshly-created or renamed conversation shows.
3100
+ */
3101
+ this._conversations = [];
3102
+ /** True while the history list is being fetched (drives a loading state). */
3103
+ this._conversationsLoading = false;
3085
3104
  /**
3086
3105
  * The tool call whose full input/output the `toolDetail` drawer view shows —
3087
3106
  * the clicked `tool_use` message and its paired `tool_result` message, stored
@@ -3407,7 +3426,54 @@ class KRChatbot extends i$3 {
3407
3426
  })));
3408
3427
  };
3409
3428
  }
3410
- this.load().then((messages) => {
3429
+ // Tab-scoped resume (floating only): a reload should redisplay the exact
3430
+ // conversation this tab was on, and a brand-new tab should reopen the user's
3431
+ // most-recent one. The pinned id lives in sessionStorage (per-tab — unlike
3432
+ // localStorage it isn't shared across tabs, so two tabs can sit on different
3433
+ // conversations). We resolve the id FIRST, then run the built-in load so it
3434
+ // rebuilds the right transcript. Skipped when the consumer owns targeting (an
3435
+ // explicit `conversation-id`) or in inline mode (embedded as a page — the host
3436
+ // route, not the tab, decides the conversation).
3437
+ let resolveConversationId = Promise.resolve();
3438
+ if (this.variant === 'floating' && this.conversationId == null) {
3439
+ const stored = Number(sessionStorage.getItem('kr-chatbot-conversation-id'));
3440
+ if (stored > 0) {
3441
+ this.conversationId = stored;
3442
+ // Fetch the conversation so the header shows its title on reload (the
3443
+ // transcript load doesn't carry it). Best-effort — the header falls back
3444
+ // to the default label if this fails.
3445
+ resolveConversationId = KRHttp.fetch({ url: `/api/system/conversation/${stored}` })
3446
+ .then((r) => r.json())
3447
+ .then((res) => {
3448
+ this._conversationTitle = res?.data?.title || '';
3449
+ })
3450
+ .catch(() => { });
3451
+ }
3452
+ else {
3453
+ // Fresh tab, nothing pinned yet — resume the user's most recent conversation
3454
+ // (404 when they have none, which leaves us blank for a new one).
3455
+ resolveConversationId = KRHttp.fetch({ url: '/api/system/conversation/last' })
3456
+ .then((r) => {
3457
+ if (!r.ok) {
3458
+ return;
3459
+ }
3460
+ return r.json().then((res) => {
3461
+ const id = res?.data?.conversationID;
3462
+ if (typeof id === 'number') {
3463
+ this.conversationId = id;
3464
+ this._conversationTitle = res?.data?.title || '';
3465
+ this._persistConversationId();
3466
+ }
3467
+ });
3468
+ })
3469
+ .catch(() => { });
3470
+ }
3471
+ }
3472
+ // `this.load` is always set by here (defaulted above when the consumer didn't
3473
+ // supply one), so capture it for the async chain — TS can't narrow the nullable
3474
+ // property across the promise boundary.
3475
+ const load = this.load;
3476
+ resolveConversationId.then(() => load()).then((messages) => {
3411
3477
  if (messages && messages.length) {
3412
3478
  this.messages = messages;
3413
3479
  }
@@ -3649,12 +3715,38 @@ class KRChatbot extends i$3 {
3649
3715
  }
3650
3716
  this._saveState();
3651
3717
  }
3718
+ /**
3719
+ * Persist the pinned conversation id per-tab so a reload redisplays this exact
3720
+ * conversation. sessionStorage (not localStorage) is deliberate: it's scoped to
3721
+ * the tab, so two tabs can sit on different conversations without clobbering
3722
+ * each other. Only meaningful for the floating widget's built-in flow — inline
3723
+ * embeds and custom-`load` consumers own their own targeting.
3724
+ */
3725
+ _persistConversationId() {
3726
+ // Floating-only: inline embeds (and custom-`load` consumers) own their own
3727
+ // targeting and never resume from this key, so they must not write it either —
3728
+ // otherwise an inline chat sharing the page/origin would clobber the floating
3729
+ // widget's pinned id.
3730
+ if (this.variant !== 'floating') {
3731
+ return;
3732
+ }
3733
+ try {
3734
+ if (this.conversationId != null) {
3735
+ sessionStorage.setItem('kr-chatbot-conversation-id', String(this.conversationId));
3736
+ }
3737
+ else {
3738
+ sessionStorage.removeItem('kr-chatbot-conversation-id');
3739
+ }
3740
+ }
3741
+ catch { /* sessionStorage not available */ }
3742
+ }
3652
3743
  _saveState() {
3653
3744
  try {
3654
- // `toolDetail` is a transient peek, not a saved view — persist only the two
3655
- // real preference views so a reload never reopens a stale tool.
3745
+ // `toolDetail` and `history` are transient views, not saved ones — persist
3746
+ // only the real preference views so a reload never reopens a stale tool or a
3747
+ // stale conversation list.
3656
3748
  let persistedDrawerView = this._drawerView;
3657
- if (persistedDrawerView === 'toolDetail') {
3749
+ if (persistedDrawerView === 'toolDetail' || persistedDrawerView === 'history') {
3658
3750
  persistedDrawerView = 'settings';
3659
3751
  }
3660
3752
  const state = {
@@ -4659,6 +4751,8 @@ class KRChatbot extends i$3 {
4659
4751
  const id = conversation?.conversationID;
4660
4752
  if (typeof id === 'number') {
4661
4753
  this.conversationId = id;
4754
+ // Pin it to this tab so a reload redisplays this new conversation.
4755
+ this._persistConversationId();
4662
4756
  // Announce the newly-created conversation so consumers can react
4663
4757
  // (e.g. update the route from /chat/new to /chat/{id}). The full
4664
4758
  // conversation object is passed, not just the id, so consumers
@@ -4856,9 +4950,111 @@ class KRChatbot extends i$3 {
4856
4950
  else {
4857
4951
  this._drawerOpen = true;
4858
4952
  this._drawerView = view;
4953
+ // Opening the history view refetches the user's previous conversations
4954
+ // (most-recently modified first) so a just-created or renamed conversation
4955
+ // shows without a reload. Ownership is enforced server-side (the endpoint
4956
+ // scopes to the current administrator), so we only filter to active ones.
4957
+ if (view === 'history') {
4958
+ this._conversationsLoading = true;
4959
+ KRHttp.fetch({
4960
+ url: '/api/system/conversation/list',
4961
+ method: 'POST',
4962
+ body: {
4963
+ page: 0,
4964
+ size: 50,
4965
+ sortBy: 'lastModified',
4966
+ sortDirection: 'desc',
4967
+ filterFields: [{
4968
+ name: 'active',
4969
+ operation: 'IS',
4970
+ value: 'true',
4971
+ }],
4972
+ },
4973
+ })
4974
+ .then((r) => r.json())
4975
+ .then((res) => {
4976
+ this._conversations = res?.data?.content || [];
4977
+ })
4978
+ .catch(() => {
4979
+ this._conversations = [];
4980
+ })
4981
+ .then(() => {
4982
+ this._conversationsLoading = false;
4983
+ });
4984
+ }
4859
4985
  }
4860
4986
  this._saveState();
4861
4987
  }
4988
+ /**
4989
+ * Switch the floating widget to a previously-created conversation (clicked in the
4990
+ * history list): stop any live turn, reset the transient turn/context state, pin
4991
+ * the new id (persisted per-tab so a reload stays here), then rebuild its
4992
+ * transcript via the built-in `load`. The drawer closes so the user lands back
4993
+ * in the conversation.
4994
+ */
4995
+ _handleSwitchConversation(conversationID) {
4996
+ if (conversationID === this.conversationId) {
4997
+ this._handleCloseDrawer();
4998
+ return;
4999
+ }
5000
+ this._handleStop();
5001
+ this.conversationId = conversationID;
5002
+ // Adopt the picked conversation's title for the header (empty for an untitled
5003
+ // one, so the header falls back to the default label).
5004
+ this._conversationTitle = this._conversations.find((c) => c.conversationID === conversationID)?.title || '';
5005
+ this._persistConversationId();
5006
+ this._resetConversationState();
5007
+ this._drawerOpen = false;
5008
+ this._saveState();
5009
+ if (this.load) {
5010
+ this.load().then((messages) => {
5011
+ this.messages = messages || [];
5012
+ // A turn may still be streaming server-side for the conversation we just
5013
+ // opened (started in another tab/device) — attach to it if so.
5014
+ if (this.conversationId && !this._isStreaming) {
5015
+ this._reconnectStream();
5016
+ }
5017
+ }).catch(() => { });
5018
+ }
5019
+ }
5020
+ /**
5021
+ * Start a brand-new conversation (clicked in the history list): unpin the id (the
5022
+ * next turn sends `createNew` and the resolved id re-pins), clear the transcript
5023
+ * and transient state, and close the drawer back to an empty composer.
5024
+ */
5025
+ _handleNewConversation() {
5026
+ this._handleStop();
5027
+ this.conversationId = null;
5028
+ // New conversation has no title yet — the header shows the default label until
5029
+ // the server generates one at the end of the first turn.
5030
+ this._conversationTitle = '';
5031
+ this._persistConversationId();
5032
+ this.messages = [];
5033
+ this._resetConversationState();
5034
+ this._drawerOpen = false;
5035
+ this._saveState();
5036
+ }
5037
+ /**
5038
+ * Clear the transient, per-conversation state that must not bleed across a
5039
+ * conversation switch/new — pending confirmations, context banners, errors, the
5040
+ * reconnect counter, and staged composer files. The transcript itself is set by
5041
+ * the caller (loaded for a switch, emptied for new).
5042
+ */
5043
+ _resetConversationState() {
5044
+ this._pendingActions = [];
5045
+ this._inlineConfirm = true;
5046
+ this._contextWarning = '';
5047
+ this._contextLimitReached = false;
5048
+ this._contextLimitMessage = '';
5049
+ this._error = '';
5050
+ this._reconnectAttempts = 0;
5051
+ this._pendingConversationFiles.forEach((p) => {
5052
+ if (p.previewUrl) {
5053
+ URL.revokeObjectURL(p.previewUrl);
5054
+ }
5055
+ });
5056
+ this._pendingConversationFiles = [];
5057
+ }
4862
5058
  /** Close the drawer. `_drawerView` is left intact so its content stays visible
4863
5059
  * while the drawer slides shut. */
4864
5060
  _handleCloseDrawer() {
@@ -5140,7 +5336,7 @@ class KRChatbot extends i$3 {
5140
5336
  // deliberately never show). Adopt it as the header label and pass it
5141
5337
  // up so consumers can update their own chrome, e.g. a breadcrumb.
5142
5338
  if (data.data?.title) {
5143
- this.label = data.data.title;
5339
+ this._conversationTitle = data.data.title;
5144
5340
  this.dispatchEvent(new CustomEvent('conversation-title', {
5145
5341
  detail: { title: data.data.title },
5146
5342
  bubbles: true,
@@ -5726,10 +5922,75 @@ class KRChatbot extends i$3 {
5726
5922
  * lifetime — plus a verbose per-iteration table. Never customer-facing.
5727
5923
  */
5728
5924
  /**
5729
- * The side drawer — one sliding panel that holds both the Settings and Debug
5730
- * views. The header buttons switch `_drawerView`; the title and body swap
5731
- * inside. The panel keeps one consistent (resizable) width across both views —
5732
- * switching views only changes the content, never the panel size.
5925
+ * The History view's body — the user's previous conversations (most recent
5926
+ * first) plus a "New conversation" action. Clicking a row switches the widget to
5927
+ * that conversation; the current one is marked so the user sees where they are.
5928
+ * Floating-only (opened from the header history button).
5929
+ */
5930
+ _renderHistoryContent() {
5931
+ return b `
5932
+ <div class="history">
5933
+ ${this._conversationsLoading ? b `
5934
+ <div class="history__empty">Loading…</div>
5935
+ ` : this._conversations.length === 0 ? b `
5936
+ <div class="history__empty">No previous conversations</div>
5937
+ ` : b `
5938
+ <div class="history__list">
5939
+ ${this._conversations.map((conversation) => b `
5940
+ <button
5941
+ type="button"
5942
+ class=${e({
5943
+ 'history__item': true,
5944
+ 'history__item--active': conversation.conversationID === this.conversationId,
5945
+ })}
5946
+ @click=${() => this._handleSwitchConversation(conversation.conversationID)}
5947
+ >
5948
+ <span class="history__item-title">${conversation.title || 'Untitled conversation'}</span>
5949
+ ${conversation.lastModified ? b `
5950
+ <span class="history__item-date">${this._renderConversationDate(conversation.lastModified)}</span>
5951
+ ` : A}
5952
+ </button>
5953
+ `)}
5954
+ </div>
5955
+ `}
5956
+ </div>
5957
+ `;
5958
+ }
5959
+ /**
5960
+ * Format a conversation's lastModified for the history list: today shows the
5961
+ * time, this year shows month/day, older shows month/day/year. Falls back to the
5962
+ * raw value if it isn't a parseable date.
5963
+ */
5964
+ _renderConversationDate(lastModified) {
5965
+ const date = new Date(lastModified);
5966
+ if (isNaN(date.getTime())) {
5967
+ return lastModified;
5968
+ }
5969
+ const now = new Date();
5970
+ if (date.toDateString() === now.toDateString()) {
5971
+ return date.toLocaleTimeString('en-US', {
5972
+ hour: 'numeric',
5973
+ minute: '2-digit',
5974
+ });
5975
+ }
5976
+ if (date.getFullYear() === now.getFullYear()) {
5977
+ return date.toLocaleDateString('en-US', {
5978
+ month: 'short',
5979
+ day: 'numeric',
5980
+ });
5981
+ }
5982
+ return date.toLocaleDateString('en-US', {
5983
+ month: 'short',
5984
+ day: 'numeric',
5985
+ year: 'numeric',
5986
+ });
5987
+ }
5988
+ /**
5989
+ * The side drawer — one sliding panel that holds the Settings, Debug, History,
5990
+ * and tool-detail views. The header buttons switch `_drawerView`; the title and
5991
+ * body swap inside. In inline mode it slides in beside the transcript at a
5992
+ * consistent (resizable) width; in floating mode it fills the panel over the
5993
+ * transcript with a back button (the panel is too narrow to show both).
5733
5994
  */
5734
5995
  _renderDrawer() {
5735
5996
  // The debug view is gated on the server-side flag (inline variant + debug
@@ -5747,41 +6008,101 @@ class KRChatbot extends i$3 {
5747
6008
  title = this._toolDetail.use.data?.displayName || this._toolDetail.use.data?.toolName || 'Tool';
5748
6009
  body = this._renderToolDetailContent();
5749
6010
  }
6011
+ else if (this._drawerView === 'history') {
6012
+ title = 'Conversations';
6013
+ body = this._renderHistoryContent();
6014
+ }
6015
+ // In the floating widget the drawer fills the panel over the transcript (a
6016
+ // full-panel view with a back button) rather than sliding in beside it — the
6017
+ // panel is too narrow to show both. Inline keeps the side-drawer behavior.
6018
+ const fullPanel = this.variant === 'floating';
5750
6019
  return b `
5751
6020
  <div
5752
6021
  class=${e({
5753
6022
  'drawer': true,
5754
6023
  'drawer--open': this._drawerOpen,
5755
6024
  'drawer--resizing': this._isDrawerResizing,
6025
+ 'drawer--full': fullPanel,
5756
6026
  })}
5757
- style=${o({ width: `${this._drawerWidth}px` })}
6027
+ style=${o(fullPanel ? {} : { width: `${this._drawerWidth}px` })}
5758
6028
  >
5759
- <div
5760
- class="drawer__resize"
5761
- @mousedown=${this._handleDrawerResizeMouseDown}
5762
- ></div>
6029
+ ${fullPanel ? A : b `
6030
+ <div
6031
+ class="drawer__resize"
6032
+ @mousedown=${this._handleDrawerResizeMouseDown}
6033
+ ></div>
6034
+ `}
5763
6035
  <div class="drawer__header">
6036
+ ${fullPanel ? b `
6037
+ <button
6038
+ class="header-action"
6039
+ @click=${this._handleCloseDrawer}
6040
+ title="Back"
6041
+ >
6042
+ <svg
6043
+ xmlns="http://www.w3.org/2000/svg"
6044
+ fill="none"
6045
+ viewBox="0 0 24 24"
6046
+ stroke-width="2"
6047
+ stroke="currentColor"
6048
+ class="header-action-icon"
6049
+ >
6050
+ <path
6051
+ stroke-linecap="round"
6052
+ stroke-linejoin="round"
6053
+ d="M15.75 19.5 8.25 12l7.5-7.5"
6054
+ />
6055
+ </svg>
6056
+ </button>
6057
+ ` : A}
5764
6058
  <span class="drawer__title">${title}</span>
5765
- <button
5766
- class="header-action"
5767
- @click=${this._handleCloseDrawer}
5768
- title="Close"
5769
- >
5770
- <svg
5771
- xmlns="http://www.w3.org/2000/svg"
5772
- fill="none"
5773
- viewBox="0 0 24 24"
5774
- stroke-width="2"
5775
- stroke="currentColor"
5776
- class="header-action-icon"
6059
+ ${this._drawerView === 'history' ? b `
6060
+ <kr-button
6061
+ class="history__new"
6062
+ variant="flat"
6063
+ color="primary"
6064
+ size="small"
6065
+ @click=${this._handleNewConversation}
5777
6066
  >
5778
- <path
5779
- stroke-linecap="round"
5780
- stroke-linejoin="round"
5781
- d="M6 18 18 6M6 6l12 12"
5782
- />
5783
- </svg>
5784
- </button>
6067
+ <svg
6068
+ slot="icon"
6069
+ xmlns="http://www.w3.org/2000/svg"
6070
+ fill="none"
6071
+ viewBox="0 0 24 24"
6072
+ stroke-width="1.75"
6073
+ stroke="currentColor"
6074
+ >
6075
+ <path
6076
+ stroke-linecap="round"
6077
+ stroke-linejoin="round"
6078
+ d="M12 4.5v15m7.5-7.5h-15"
6079
+ />
6080
+ </svg>
6081
+ New conversation
6082
+ </kr-button>
6083
+ ` : A}
6084
+ ${fullPanel ? A : b `
6085
+ <button
6086
+ class="header-action"
6087
+ @click=${this._handleCloseDrawer}
6088
+ title="Close"
6089
+ >
6090
+ <svg
6091
+ xmlns="http://www.w3.org/2000/svg"
6092
+ fill="none"
6093
+ viewBox="0 0 24 24"
6094
+ stroke-width="2"
6095
+ stroke="currentColor"
6096
+ class="header-action-icon"
6097
+ >
6098
+ <path
6099
+ stroke-linecap="round"
6100
+ stroke-linejoin="round"
6101
+ d="M6 18 18 6M6 6l12 12"
6102
+ />
6103
+ </svg>
6104
+ </button>
6105
+ `}
5785
6106
  </div>
5786
6107
  <div class="drawer__body">
5787
6108
  ${body}
@@ -6042,9 +6363,10 @@ class KRChatbot extends i$3 {
6042
6363
  const isDebugEnabled = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
6043
6364
  // Push the content over by the open drawer's width so the drawer sits beside
6044
6365
  // the content rather than overlaying it. One consistent (resizable) width for
6045
- // both views. Only pushes while the drawer is open.
6366
+ // both views. Only pushes while the drawer is open, and only inline — the
6367
+ // floating drawer fills the panel over the transcript, so it must not push.
6046
6368
  const contentPadding = {};
6047
- if (this._drawerOpen) {
6369
+ if (this._drawerOpen && this.variant === 'inline') {
6048
6370
  contentPadding.paddingRight = `${this._drawerWidth}px`;
6049
6371
  }
6050
6372
  // Group the transcript into runs so an agent turn (its reasoning/tool/text/
@@ -6162,10 +6484,52 @@ class KRChatbot extends i$3 {
6162
6484
  <div class="header" @mousedown=${this._handleHeaderMouseDown}>
6163
6485
  <div class="header-left">
6164
6486
  <div class="header-info">
6165
- <span class="header-title">${this.label}</span>
6487
+ <span class="header-title">${this._conversationTitle || this.label}</span>
6166
6488
  </div>
6167
6489
  </div>
6168
6490
  <div class="header-actions">
6491
+ ${this.variant === 'floating' ? b `
6492
+ <button
6493
+ class="header-action"
6494
+ @click=${this._handleNewConversation}
6495
+ title="New conversation"
6496
+ >
6497
+ <svg
6498
+ xmlns="http://www.w3.org/2000/svg"
6499
+ fill="none"
6500
+ viewBox="0 0 24 24"
6501
+ stroke-width="1.75"
6502
+ stroke="currentColor"
6503
+ class="header-action-icon"
6504
+ >
6505
+ <path
6506
+ stroke-linecap="round"
6507
+ stroke-linejoin="round"
6508
+ d="M12 4.5v15m7.5-7.5h-15"
6509
+ />
6510
+ </svg>
6511
+ </button>
6512
+ <button
6513
+ class="header-action"
6514
+ @click=${() => this._toggleDrawer('history')}
6515
+ title="Conversations"
6516
+ >
6517
+ <svg
6518
+ xmlns="http://www.w3.org/2000/svg"
6519
+ fill="none"
6520
+ viewBox="0 0 24 24"
6521
+ stroke-width="1.5"
6522
+ stroke="currentColor"
6523
+ class="header-action-icon"
6524
+ >
6525
+ <path
6526
+ stroke-linecap="round"
6527
+ stroke-linejoin="round"
6528
+ d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
6529
+ />
6530
+ </svg>
6531
+ </button>
6532
+ ` : A}
6169
6533
  <button
6170
6534
  class="header-action"
6171
6535
  @click=${() => this._toggleDrawer('settings')}
@@ -6920,22 +7284,28 @@ KRChatbot.styles = i$6 `
6920
7284
  display: flex;
6921
7285
  align-items: center;
6922
7286
  gap: 10px;
7287
+ min-width: 0;
6923
7288
  }
6924
7289
 
6925
7290
  .header-info {
6926
7291
  display: flex;
6927
7292
  flex-direction: column;
7293
+ min-width: 0;
6928
7294
  }
6929
7295
 
6930
7296
  .header-title {
6931
7297
  font-size: 16px;
6932
7298
  font-weight: 600;
7299
+ white-space: nowrap;
7300
+ overflow: hidden;
7301
+ text-overflow: ellipsis;
6933
7302
  }
6934
7303
 
6935
7304
  .header-actions {
6936
7305
  display: flex;
6937
7306
  align-items: center;
6938
7307
  gap: 4px;
7308
+ flex-shrink: 0;
6939
7309
  }
6940
7310
 
6941
7311
  .header-action {
@@ -7790,6 +8160,13 @@ KRChatbot.styles = i$6 `
7790
8160
  border-bottom: 1px solid rgba(0, 0, 0, 0.08);
7791
8161
  }
7792
8162
 
8163
+ /* Full-panel (floating) header: back button then title, left-aligned. */
8164
+ .drawer--full .drawer__header {
8165
+ justify-content: flex-start;
8166
+ gap: 8px;
8167
+ padding: 0 12px;
8168
+ }
8169
+
7793
8170
  .drawer__title {
7794
8171
  font-size: 14px;
7795
8172
  font-weight: 600;
@@ -7803,6 +8180,78 @@ KRChatbot.styles = i$6 `
7803
8180
  padding: 4px 0;
7804
8181
  }
7805
8182
 
8183
+ /* Floating variant: the drawer fills the whole panel over the transcript (a
8184
+ full-panel view with a back button), rather than sliding in beside it. */
8185
+ .drawer--full {
8186
+ left: 0;
8187
+ width: 100%;
8188
+ max-width: 100%;
8189
+ z-index: 6;
8190
+ }
8191
+
8192
+ .drawer--full.drawer--open {
8193
+ box-shadow: none;
8194
+ }
8195
+
8196
+ /* History (previous conversations) view. */
8197
+ .history {
8198
+ display: flex;
8199
+ flex-direction: column;
8200
+ }
8201
+
8202
+ /* "New conversation" action in the Conversations header — pushed to the right. */
8203
+ .history__new {
8204
+ margin-left: auto;
8205
+ }
8206
+
8207
+ .history__empty {
8208
+ padding: 24px 20px;
8209
+ text-align: center;
8210
+ font-size: 13px;
8211
+ color: rgb(0 0 0 / 50%);
8212
+ }
8213
+
8214
+ .history__list {
8215
+ display: flex;
8216
+ flex-direction: column;
8217
+ padding: 4px 0;
8218
+ }
8219
+
8220
+ .history__item {
8221
+ display: flex;
8222
+ flex-direction: column;
8223
+ align-items: flex-start;
8224
+ gap: 5px;
8225
+ padding: 10px 20px;
8226
+ background: transparent;
8227
+ border: none;
8228
+ text-align: left;
8229
+ cursor: pointer;
8230
+ transition: background 0.15s;
8231
+ }
8232
+
8233
+ .history__item:hover {
8234
+ background: rgba(0, 0, 0, 0.04);
8235
+ }
8236
+
8237
+ .history__item--active {
8238
+ background: rgba(0, 0, 0, 0.06);
8239
+ }
8240
+
8241
+ .history__item-title {
8242
+ font-size: 14px;
8243
+ color: #000000;
8244
+ white-space: nowrap;
8245
+ overflow: hidden;
8246
+ text-overflow: ellipsis;
8247
+ max-width: 100%;
8248
+ }
8249
+
8250
+ .history__item-date {
8251
+ font-size: 12px;
8252
+ color: rgb(0 0 0 / 60%);
8253
+ }
8254
+
7806
8255
  .debug-section {
7807
8256
  padding: 14px 20px;
7808
8257
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
@@ -8737,6 +9186,9 @@ __decorate$4([
8737
9186
  __decorate$4([
8738
9187
  r()
8739
9188
  ], KRChatbot.prototype, "_error", void 0);
9189
+ __decorate$4([
9190
+ r()
9191
+ ], KRChatbot.prototype, "_conversationTitle", void 0);
8740
9192
  __decorate$4([
8741
9193
  r()
8742
9194
  ], KRChatbot.prototype, "_contextWarning", void 0);
@@ -8770,6 +9222,12 @@ __decorate$4([
8770
9222
  __decorate$4([
8771
9223
  r()
8772
9224
  ], KRChatbot.prototype, "_drawerView", void 0);
9225
+ __decorate$4([
9226
+ r()
9227
+ ], KRChatbot.prototype, "_conversations", void 0);
9228
+ __decorate$4([
9229
+ r()
9230
+ ], KRChatbot.prototype, "_conversationsLoading", void 0);
8773
9231
  __decorate$4([
8774
9232
  r()
8775
9233
  ], KRChatbot.prototype, "_toolDetail", void 0);