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