@kodaris/krubble-app-components 1.0.125 → 1.0.127
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 +6 -13
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +153 -130
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +153 -130
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +19 -19
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +153 -130
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +21 -21
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -3344,11 +3344,6 @@ class KRChatbot extends i$3 {
|
|
|
3344
3344
|
// --- Lifecycle ---
|
|
3345
3345
|
connectedCallback() {
|
|
3346
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;
|
|
3352
3347
|
// Default to loading a conversation's messages when the consumer hasn't
|
|
3353
3348
|
// provided their own `load`. With `conversationId` set we rebuild that
|
|
3354
3349
|
// conversation; with none set the user wants a NEW conversation, so we start
|
|
@@ -3436,49 +3431,48 @@ class KRChatbot extends i$3 {
|
|
|
3436
3431
|
// most-recent one. The pinned id lives in sessionStorage (per-tab — unlike
|
|
3437
3432
|
// localStorage it isn't shared across tabs, so two tabs can sit on different
|
|
3438
3433
|
// 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 (
|
|
3440
|
-
//
|
|
3441
|
-
//
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
return Promise.resolve();
|
|
3445
|
-
}
|
|
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) {
|
|
3446
3439
|
const stored = Number(sessionStorage.getItem('kr-chatbot-conversation-id'));
|
|
3447
3440
|
if (stored > 0) {
|
|
3448
3441
|
this.conversationId = stored;
|
|
3449
3442
|
// Fetch the conversation so the header shows its title on reload (the
|
|
3450
3443
|
// transcript load doesn't carry it). Best-effort — the header falls back
|
|
3451
3444
|
// to the default label if this fails.
|
|
3452
|
-
|
|
3445
|
+
resolveConversationId = KRHttp.fetch({ url: `/api/system/conversation/${stored}` })
|
|
3453
3446
|
.then((r) => r.json())
|
|
3454
3447
|
.then((res) => {
|
|
3455
3448
|
this._conversationTitle = res?.data?.title || '';
|
|
3456
3449
|
})
|
|
3457
3450
|
.catch(() => { });
|
|
3458
3451
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
.
|
|
3463
|
-
|
|
3464
|
-
|
|
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();
|
|
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;
|
|
3472
3459
|
}
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
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
|
+
}
|
|
3477
3472
|
// `this.load` is always set by here (defaulted above when the consumer didn't
|
|
3478
|
-
// supply one)
|
|
3479
|
-
//
|
|
3480
|
-
|
|
3481
|
-
resolveConversationId().then(() => load()).then((messages) => {
|
|
3473
|
+
// supply one); the `!` is because TS can't narrow the nullable property across
|
|
3474
|
+
// the promise boundary.
|
|
3475
|
+
resolveConversationId.then(() => this.load()).then((messages) => {
|
|
3482
3476
|
if (messages && messages.length) {
|
|
3483
3477
|
this.messages = messages;
|
|
3484
3478
|
}
|
|
@@ -3728,6 +3722,13 @@ class KRChatbot extends i$3 {
|
|
|
3728
3722
|
* embeds and custom-`load` consumers own their own targeting.
|
|
3729
3723
|
*/
|
|
3730
3724
|
_persistConversationId() {
|
|
3725
|
+
// Floating-only: inline embeds (and custom-`load` consumers) own their own
|
|
3726
|
+
// targeting and never resume from this key, so they must not write it either —
|
|
3727
|
+
// otherwise an inline chat sharing the page/origin would clobber the floating
|
|
3728
|
+
// widget's pinned id.
|
|
3729
|
+
if (this.variant !== 'floating') {
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3731
3732
|
try {
|
|
3732
3733
|
if (this.conversationId != null) {
|
|
3733
3734
|
sessionStorage.setItem('kr-chatbot-conversation-id', String(this.conversationId));
|
|
@@ -3740,11 +3741,10 @@ class KRChatbot extends i$3 {
|
|
|
3740
3741
|
}
|
|
3741
3742
|
_saveState() {
|
|
3742
3743
|
try {
|
|
3743
|
-
// `toolDetail`
|
|
3744
|
-
//
|
|
3745
|
-
// stale conversation list.
|
|
3744
|
+
// `toolDetail` is a transient peek, not a saved view — persist only the two
|
|
3745
|
+
// real preference views so a reload never reopens a stale tool.
|
|
3746
3746
|
let persistedDrawerView = this._drawerView;
|
|
3747
|
-
if (persistedDrawerView === 'toolDetail'
|
|
3747
|
+
if (persistedDrawerView === 'toolDetail') {
|
|
3748
3748
|
persistedDrawerView = 'settings';
|
|
3749
3749
|
}
|
|
3750
3750
|
const state = {
|
|
@@ -3803,8 +3803,14 @@ class KRChatbot extends i$3 {
|
|
|
3803
3803
|
this._showToolInputs = state.showToolInputs === 'true';
|
|
3804
3804
|
this._showConfirmationInput = state.showConfirmationInput === 'true';
|
|
3805
3805
|
this._drawerOpen = state.drawerOpen === 'true';
|
|
3806
|
-
if (state.drawerView === 'settings' || state.drawerView === 'debug') {
|
|
3806
|
+
if (state.drawerView === 'settings' || state.drawerView === 'debug' || state.drawerView === 'history') {
|
|
3807
3807
|
this._drawerView = state.drawerView;
|
|
3808
|
+
// History's list isn't persisted (it can go stale); refetch it so a reload
|
|
3809
|
+
// that was on the conversations panel comes back to a populated list rather
|
|
3810
|
+
// than an empty one.
|
|
3811
|
+
if (state.drawerView === 'history') {
|
|
3812
|
+
this._loadConversations();
|
|
3813
|
+
}
|
|
3808
3814
|
}
|
|
3809
3815
|
const w = Number(state.drawerWidth);
|
|
3810
3816
|
if (w >= 300) {
|
|
@@ -4948,41 +4954,47 @@ class KRChatbot extends i$3 {
|
|
|
4948
4954
|
else {
|
|
4949
4955
|
this._drawerOpen = true;
|
|
4950
4956
|
this._drawerView = view;
|
|
4951
|
-
// Opening the history view refetches the
|
|
4952
|
-
//
|
|
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.
|
|
4957
|
+
// Opening the history view refetches the list so a just-created or renamed
|
|
4958
|
+
// conversation shows without a reload.
|
|
4955
4959
|
if (view === 'history') {
|
|
4956
|
-
this.
|
|
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
|
-
});
|
|
4960
|
+
this._loadConversations();
|
|
4982
4961
|
}
|
|
4983
4962
|
}
|
|
4984
4963
|
this._saveState();
|
|
4985
4964
|
}
|
|
4965
|
+
/**
|
|
4966
|
+
* Fetch the user's previous conversations for the history list, most-recently
|
|
4967
|
+
* modified first. Ownership is enforced server-side (the endpoint scopes to the
|
|
4968
|
+
* current administrator), so we only filter to active ones.
|
|
4969
|
+
*/
|
|
4970
|
+
_loadConversations() {
|
|
4971
|
+
this._conversationsLoading = true;
|
|
4972
|
+
KRHttp.fetch({
|
|
4973
|
+
url: '/api/system/conversation/list',
|
|
4974
|
+
method: 'POST',
|
|
4975
|
+
body: {
|
|
4976
|
+
page: 0,
|
|
4977
|
+
size: 50,
|
|
4978
|
+
sortBy: 'lastModified',
|
|
4979
|
+
sortDirection: 'desc',
|
|
4980
|
+
filterFields: [{
|
|
4981
|
+
name: 'active',
|
|
4982
|
+
operation: 'IS',
|
|
4983
|
+
value: 'true',
|
|
4984
|
+
}],
|
|
4985
|
+
},
|
|
4986
|
+
})
|
|
4987
|
+
.then((r) => r.json())
|
|
4988
|
+
.then((res) => {
|
|
4989
|
+
this._conversations = res?.data?.content || [];
|
|
4990
|
+
})
|
|
4991
|
+
.catch(() => {
|
|
4992
|
+
this._conversations = [];
|
|
4993
|
+
})
|
|
4994
|
+
.then(() => {
|
|
4995
|
+
this._conversationsLoading = false;
|
|
4996
|
+
});
|
|
4997
|
+
}
|
|
4986
4998
|
/**
|
|
4987
4999
|
* Switch the floating widget to a previously-created conversation (clicked in the
|
|
4988
5000
|
* history list): stop any live turn, reset the transient turn/context state, pin
|
|
@@ -5001,7 +5013,22 @@ class KRChatbot extends i$3 {
|
|
|
5001
5013
|
// one, so the header falls back to the default label).
|
|
5002
5014
|
this._conversationTitle = this._conversations.find((c) => c.conversationID === conversationID)?.title || '';
|
|
5003
5015
|
this._persistConversationId();
|
|
5004
|
-
|
|
5016
|
+
// Clear the transient, per-conversation state so it doesn't bleed into the one
|
|
5017
|
+
// we're switching to — pending confirmations, context banners, errors, the
|
|
5018
|
+
// reconnect counter, and staged composer files (the transcript is loaded below).
|
|
5019
|
+
this._pendingActions = [];
|
|
5020
|
+
this._inlineConfirm = true;
|
|
5021
|
+
this._contextWarning = '';
|
|
5022
|
+
this._contextLimitReached = false;
|
|
5023
|
+
this._contextLimitMessage = '';
|
|
5024
|
+
this._error = '';
|
|
5025
|
+
this._reconnectAttempts = 0;
|
|
5026
|
+
this._pendingConversationFiles.forEach((p) => {
|
|
5027
|
+
if (p.previewUrl) {
|
|
5028
|
+
URL.revokeObjectURL(p.previewUrl);
|
|
5029
|
+
}
|
|
5030
|
+
});
|
|
5031
|
+
this._pendingConversationFiles = [];
|
|
5005
5032
|
this._drawerOpen = false;
|
|
5006
5033
|
this._saveState();
|
|
5007
5034
|
if (this.load) {
|
|
@@ -5028,17 +5055,9 @@ class KRChatbot extends i$3 {
|
|
|
5028
5055
|
this._conversationTitle = '';
|
|
5029
5056
|
this._persistConversationId();
|
|
5030
5057
|
this.messages = [];
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
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() {
|
|
5058
|
+
// Clear the transient, per-conversation state so it doesn't bleed into the new
|
|
5059
|
+
// conversation — pending confirmations, context banners, errors, the reconnect
|
|
5060
|
+
// counter, and staged composer files.
|
|
5042
5061
|
this._pendingActions = [];
|
|
5043
5062
|
this._inlineConfirm = true;
|
|
5044
5063
|
this._contextWarning = '';
|
|
@@ -5052,6 +5071,8 @@ class KRChatbot extends i$3 {
|
|
|
5052
5071
|
}
|
|
5053
5072
|
});
|
|
5054
5073
|
this._pendingConversationFiles = [];
|
|
5074
|
+
this._drawerOpen = false;
|
|
5075
|
+
this._saveState();
|
|
5055
5076
|
}
|
|
5056
5077
|
/** Close the drawer. `_drawerView` is left intact so its content stays visible
|
|
5057
5078
|
* while the drawer slides shut. */
|
|
@@ -5934,55 +5955,58 @@ class KRChatbot extends i$3 {
|
|
|
5934
5955
|
<div class="history__empty">No previous conversations</div>
|
|
5935
5956
|
` : b `
|
|
5936
5957
|
<div class="history__list">
|
|
5937
|
-
${this._conversations.map((conversation) =>
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
'
|
|
5942
|
-
|
|
5958
|
+
${this._conversations.map((conversation) => {
|
|
5959
|
+
// Format lastModified for the row: today shows the time, this year
|
|
5960
|
+
// shows month/day, older shows month/day/year; the raw value if it
|
|
5961
|
+
// isn't a parseable date. Empty when the conversation has no date.
|
|
5962
|
+
let dateLabel = '';
|
|
5963
|
+
if (conversation.lastModified) {
|
|
5964
|
+
const date = new Date(conversation.lastModified);
|
|
5965
|
+
const now = new Date();
|
|
5966
|
+
if (isNaN(date.getTime())) {
|
|
5967
|
+
dateLabel = conversation.lastModified;
|
|
5968
|
+
}
|
|
5969
|
+
else if (date.toDateString() === now.toDateString()) {
|
|
5970
|
+
dateLabel = date.toLocaleTimeString('en-US', {
|
|
5971
|
+
hour: 'numeric',
|
|
5972
|
+
minute: '2-digit',
|
|
5973
|
+
});
|
|
5974
|
+
}
|
|
5975
|
+
else if (date.getFullYear() === now.getFullYear()) {
|
|
5976
|
+
dateLabel = date.toLocaleDateString('en-US', {
|
|
5977
|
+
month: 'short',
|
|
5978
|
+
day: 'numeric',
|
|
5979
|
+
});
|
|
5980
|
+
}
|
|
5981
|
+
else {
|
|
5982
|
+
dateLabel = date.toLocaleDateString('en-US', {
|
|
5983
|
+
month: 'short',
|
|
5984
|
+
day: 'numeric',
|
|
5985
|
+
year: 'numeric',
|
|
5986
|
+
});
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
return b `
|
|
5990
|
+
<button
|
|
5991
|
+
type="button"
|
|
5992
|
+
class=${e({
|
|
5993
|
+
'history__item': true,
|
|
5994
|
+
'history__item--active': conversation.conversationID === this.conversationId,
|
|
5995
|
+
})}
|
|
5996
|
+
@click=${() => this._handleSwitchConversation(conversation.conversationID)}
|
|
5997
|
+
>
|
|
5998
|
+
<span class="history__item-title">${conversation.title || 'Untitled conversation'}</span>
|
|
5999
|
+
${dateLabel ? b `
|
|
6000
|
+
<span class="history__item-date">${dateLabel}</span>
|
|
6001
|
+
` : A}
|
|
6002
|
+
</button>
|
|
6003
|
+
`;
|
|
5943
6004
|
})}
|
|
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
6005
|
</div>
|
|
5953
6006
|
`}
|
|
5954
6007
|
</div>
|
|
5955
6008
|
`;
|
|
5956
6009
|
}
|
|
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
6010
|
/**
|
|
5987
6011
|
* The side drawer — one sliding panel that holds the Settings, Debug, History,
|
|
5988
6012
|
* and tool-detail views. The header buttons switch `_drawerView`; the title and
|
|
@@ -6013,25 +6037,24 @@ class KRChatbot extends i$3 {
|
|
|
6013
6037
|
// In the floating widget the drawer fills the panel over the transcript (a
|
|
6014
6038
|
// full-panel view with a back button) rather than sliding in beside it — the
|
|
6015
6039
|
// panel is too narrow to show both. Inline keeps the side-drawer behavior.
|
|
6016
|
-
const fullPanel = this.variant === 'floating';
|
|
6017
6040
|
return b `
|
|
6018
6041
|
<div
|
|
6019
6042
|
class=${e({
|
|
6020
6043
|
'drawer': true,
|
|
6021
6044
|
'drawer--open': this._drawerOpen,
|
|
6022
6045
|
'drawer--resizing': this._isDrawerResizing,
|
|
6023
|
-
'drawer--full':
|
|
6046
|
+
'drawer--full': this.variant === 'floating',
|
|
6024
6047
|
})}
|
|
6025
|
-
style=${o(
|
|
6048
|
+
style=${o(this.variant === 'floating' ? {} : { width: `${this._drawerWidth}px` })}
|
|
6026
6049
|
>
|
|
6027
|
-
${
|
|
6050
|
+
${this.variant === 'floating' ? A : b `
|
|
6028
6051
|
<div
|
|
6029
6052
|
class="drawer__resize"
|
|
6030
6053
|
@mousedown=${this._handleDrawerResizeMouseDown}
|
|
6031
6054
|
></div>
|
|
6032
6055
|
`}
|
|
6033
6056
|
<div class="drawer__header">
|
|
6034
|
-
${
|
|
6057
|
+
${this.variant === 'floating' ? b `
|
|
6035
6058
|
<button
|
|
6036
6059
|
class="header-action"
|
|
6037
6060
|
@click=${this._handleCloseDrawer}
|
|
@@ -6079,7 +6102,7 @@ class KRChatbot extends i$3 {
|
|
|
6079
6102
|
New conversation
|
|
6080
6103
|
</kr-button>
|
|
6081
6104
|
` : A}
|
|
6082
|
-
${
|
|
6105
|
+
${this.variant === 'floating' ? A : b `
|
|
6083
6106
|
<button
|
|
6084
6107
|
class="header-action"
|
|
6085
6108
|
@click=${this._handleCloseDrawer}
|