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