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