@kodaris/krubble-app-components 1.0.123 → 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 +109 -8
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +603 -41
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +603 -41
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +290 -49
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +603 -41
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +276 -35
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -202,6 +202,7 @@ export class KRChatbot extends LitElement {
|
|
|
202
202
|
this._orgSkillRequestId = 0;
|
|
203
203
|
this._globalSkillRequestId = 0;
|
|
204
204
|
this._invocableSkillRequestId = 0;
|
|
205
|
+
this._suggestedActionRequestId = 0;
|
|
205
206
|
/**
|
|
206
207
|
* Codes of global skills with an in-flight enable/disable request. The `+`
|
|
207
208
|
* button reads this to show a pending state and ignore repeat clicks. Mutated
|
|
@@ -295,6 +296,14 @@ export class KRChatbot extends LitElement {
|
|
|
295
296
|
this._inputValue = '';
|
|
296
297
|
this._isStreaming = false;
|
|
297
298
|
this._error = '';
|
|
299
|
+
/**
|
|
300
|
+
* The current conversation's title. In the floating widget it replaces the
|
|
301
|
+
* static header label so the user sees which conversation they're in. Set from
|
|
302
|
+
* the loaded/switched conversation and from the live `CONVERSATION_TITLE` event
|
|
303
|
+
* (the server generates a title at the end of a new conversation's first turn);
|
|
304
|
+
* empty for a brand-new conversation, where the header falls back to `label`.
|
|
305
|
+
*/
|
|
306
|
+
this._conversationTitle = '';
|
|
298
307
|
/**
|
|
299
308
|
* User-facing context warning text (`CONVERSATION_TOKEN_WARNING`). Shown as a
|
|
300
309
|
* dismissible banner in all variants. Carries only friendly guidance, no numbers.
|
|
@@ -354,12 +363,23 @@ export class KRChatbot extends LitElement {
|
|
|
354
363
|
*/
|
|
355
364
|
this._drawerOpen = false;
|
|
356
365
|
/**
|
|
357
|
-
* Which view the drawer shows — settings, debug, or a single tool
|
|
358
|
-
* detail. One drawer holds all
|
|
359
|
-
* and clicking a tool line switches to `toolDetail`. The
|
|
360
|
-
* inside. Never null, so the content stays visible while the
|
|
366
|
+
* Which view the drawer shows — settings, debug, history, or a single tool
|
|
367
|
+
* call's full detail. One drawer holds them all; header buttons switch
|
|
368
|
+
* settings/debug/history and clicking a tool line switches to `toolDetail`. The
|
|
369
|
+
* content/width swap inside. Never null, so the content stays visible while the
|
|
370
|
+
* drawer slides shut. `history` is floating-only (the previous-conversations
|
|
371
|
+
* picker); in floating the drawer fills the panel over the transcript.
|
|
361
372
|
*/
|
|
362
373
|
this._drawerView = 'settings';
|
|
374
|
+
/**
|
|
375
|
+
* The user's previous conversations, loaded from `/conversation/list` when the
|
|
376
|
+
* history view opens. Each is the raw ConversationViewDto (we read conversationID,
|
|
377
|
+
* title, lastModified). Empty until the first open; reloaded on each open so a
|
|
378
|
+
* freshly-created or renamed conversation shows.
|
|
379
|
+
*/
|
|
380
|
+
this._conversations = [];
|
|
381
|
+
/** True while the history list is being fetched (drives a loading state). */
|
|
382
|
+
this._conversationsLoading = false;
|
|
363
383
|
/**
|
|
364
384
|
* The tool call whose full input/output the `toolDetail` drawer view shows —
|
|
365
385
|
* the clicked `tool_use` message and its paired `tool_result` message, stored
|
|
@@ -405,6 +425,12 @@ export class KRChatbot extends LitElement {
|
|
|
405
425
|
* so installed globals in the menu never leak into the "Your Org" tab.
|
|
406
426
|
*/
|
|
407
427
|
this._invocableSkills = [];
|
|
428
|
+
/**
|
|
429
|
+
* Context-aware action chips for the empty state, from `/conversation/suggestedActions`.
|
|
430
|
+
* Loaded fresh every time the panel opens on an empty conversation (see `_loadSuggestedActions`);
|
|
431
|
+
* cleared when the first message is sent so they don't linger over a live conversation.
|
|
432
|
+
*/
|
|
433
|
+
this._suggestedActions = [];
|
|
408
434
|
/**
|
|
409
435
|
* AI framework UI config flags fetched from `aiFrameworkConfig` on connect.
|
|
410
436
|
* `debugTelemetryEnabled` (same server-side gate as TOKEN_USAGE emission) turns
|
|
@@ -597,6 +623,11 @@ export class KRChatbot extends LitElement {
|
|
|
597
623
|
// --- Lifecycle ---
|
|
598
624
|
connectedCallback() {
|
|
599
625
|
super.connectedCallback();
|
|
626
|
+
// Capture the consumer's targeting BEFORE we default `load` below: a custom
|
|
627
|
+
// `load` or an explicitly-set `conversation-id` means the consumer owns which
|
|
628
|
+
// conversation shows, so the tab-scoped resume must not override it.
|
|
629
|
+
const usesBuiltInLoad = !this.load;
|
|
630
|
+
const consumerSetConversationId = this.conversationId != null;
|
|
600
631
|
// Default to loading a conversation's messages when the consumer hasn't
|
|
601
632
|
// provided their own `load`. With `conversationId` set we rebuild that
|
|
602
633
|
// conversation; with none set the user wants a NEW conversation, so we start
|
|
@@ -679,7 +710,54 @@ export class KRChatbot extends LitElement {
|
|
|
679
710
|
})));
|
|
680
711
|
};
|
|
681
712
|
}
|
|
682
|
-
|
|
713
|
+
// Tab-scoped resume (floating only): a reload should redisplay the exact
|
|
714
|
+
// conversation this tab was on, and a brand-new tab should reopen the user's
|
|
715
|
+
// most-recent one. The pinned id lives in sessionStorage (per-tab — unlike
|
|
716
|
+
// localStorage it isn't shared across tabs, so two tabs can sit on different
|
|
717
|
+
// conversations). We resolve the id FIRST, then run the built-in load so it
|
|
718
|
+
// rebuilds the right transcript. Skipped when the consumer owns targeting (a
|
|
719
|
+
// custom `load` or an explicit `conversation-id`) or in inline mode (embedded
|
|
720
|
+
// as a page — the host route, not the tab, decides the conversation).
|
|
721
|
+
const resolveConversationId = () => {
|
|
722
|
+
if (this.variant !== 'floating' || !usesBuiltInLoad || consumerSetConversationId) {
|
|
723
|
+
return Promise.resolve();
|
|
724
|
+
}
|
|
725
|
+
const stored = Number(sessionStorage.getItem('kr-chatbot-conversation-id'));
|
|
726
|
+
if (stored > 0) {
|
|
727
|
+
this.conversationId = stored;
|
|
728
|
+
// Fetch the conversation so the header shows its title on reload (the
|
|
729
|
+
// transcript load doesn't carry it). Best-effort — the header falls back
|
|
730
|
+
// to the default label if this fails.
|
|
731
|
+
return KRHttp.fetch({ url: `/api/system/conversation/${stored}` })
|
|
732
|
+
.then((r) => r.json())
|
|
733
|
+
.then((res) => {
|
|
734
|
+
this._conversationTitle = res?.data?.title || '';
|
|
735
|
+
})
|
|
736
|
+
.catch(() => { });
|
|
737
|
+
}
|
|
738
|
+
// Fresh tab, nothing pinned yet — resume the user's most recent conversation
|
|
739
|
+
// (404 when they have none, which leaves us blank for a new one).
|
|
740
|
+
return KRHttp.fetch({ url: '/api/system/conversation/last' })
|
|
741
|
+
.then((r) => {
|
|
742
|
+
if (!r.ok) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
return r.json().then((res) => {
|
|
746
|
+
const id = res?.data?.conversationID;
|
|
747
|
+
if (typeof id === 'number') {
|
|
748
|
+
this.conversationId = id;
|
|
749
|
+
this._conversationTitle = res?.data?.title || '';
|
|
750
|
+
this._persistConversationId();
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
})
|
|
754
|
+
.catch(() => { });
|
|
755
|
+
};
|
|
756
|
+
// `this.load` is always set by here (defaulted above when the consumer didn't
|
|
757
|
+
// supply one), so capture it for the async chain — TS can't narrow the nullable
|
|
758
|
+
// property across the promise boundary.
|
|
759
|
+
const load = this.load;
|
|
760
|
+
resolveConversationId().then(() => load()).then((messages) => {
|
|
683
761
|
if (messages && messages.length) {
|
|
684
762
|
this.messages = messages;
|
|
685
763
|
}
|
|
@@ -759,6 +837,13 @@ export class KRChatbot extends LitElement {
|
|
|
759
837
|
}
|
|
760
838
|
}, 200);
|
|
761
839
|
}
|
|
840
|
+
// Load context-aware action chips every time the panel opens on an empty conversation. Fires for
|
|
841
|
+
// the floating variant on toggle-open and for the inline variant on its first render (which sets
|
|
842
|
+
// `opened` true at connect). Only when empty — a resumed conversation shows its transcript, not chips.
|
|
843
|
+
// The request-id guard inside handles a stale reply if the user closes/reopens quickly.
|
|
844
|
+
if (changed.has('opened') && this.opened && this.messages.length === 0) {
|
|
845
|
+
this._loadSuggestedActions();
|
|
846
|
+
}
|
|
762
847
|
// Position the fixed skill menu above the composer, following the krubble
|
|
763
848
|
// select-field pattern (fixed element positioned from the trigger's rect).
|
|
764
849
|
if (this._skillMenuOpened) {
|
|
@@ -914,12 +999,31 @@ export class KRChatbot extends LitElement {
|
|
|
914
999
|
}
|
|
915
1000
|
this._saveState();
|
|
916
1001
|
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Persist the pinned conversation id per-tab so a reload redisplays this exact
|
|
1004
|
+
* conversation. sessionStorage (not localStorage) is deliberate: it's scoped to
|
|
1005
|
+
* the tab, so two tabs can sit on different conversations without clobbering
|
|
1006
|
+
* each other. Only meaningful for the floating widget's built-in flow — inline
|
|
1007
|
+
* embeds and custom-`load` consumers own their own targeting.
|
|
1008
|
+
*/
|
|
1009
|
+
_persistConversationId() {
|
|
1010
|
+
try {
|
|
1011
|
+
if (this.conversationId != null) {
|
|
1012
|
+
sessionStorage.setItem('kr-chatbot-conversation-id', String(this.conversationId));
|
|
1013
|
+
}
|
|
1014
|
+
else {
|
|
1015
|
+
sessionStorage.removeItem('kr-chatbot-conversation-id');
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
catch { /* sessionStorage not available */ }
|
|
1019
|
+
}
|
|
917
1020
|
_saveState() {
|
|
918
1021
|
try {
|
|
919
|
-
// `toolDetail`
|
|
920
|
-
// real preference views so a reload never reopens a stale tool
|
|
1022
|
+
// `toolDetail` and `history` are transient views, not saved ones — persist
|
|
1023
|
+
// only the real preference views so a reload never reopens a stale tool or a
|
|
1024
|
+
// stale conversation list.
|
|
921
1025
|
let persistedDrawerView = this._drawerView;
|
|
922
|
-
if (persistedDrawerView === 'toolDetail') {
|
|
1026
|
+
if (persistedDrawerView === 'toolDetail' || persistedDrawerView === 'history') {
|
|
923
1027
|
persistedDrawerView = 'settings';
|
|
924
1028
|
}
|
|
925
1029
|
const state = {
|
|
@@ -1191,6 +1295,41 @@ export class KRChatbot extends LitElement {
|
|
|
1191
1295
|
console.error('kr-chatbot: invocable skill search failed', error);
|
|
1192
1296
|
});
|
|
1193
1297
|
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Load the empty-state action chips from `/conversation/suggestedActions`, passing the user's
|
|
1300
|
+
* current page/selection context (captured live, same as a send). The server picks a few skills
|
|
1301
|
+
* the user can run that fit where they are and writes a label for each. Called every time the
|
|
1302
|
+
* panel opens on an empty conversation (per product decision — context can change between opens).
|
|
1303
|
+
*
|
|
1304
|
+
* AI-only, no fallback: any failure, or v2 chat being off (server returns `[]`), just leaves the
|
|
1305
|
+
* empty state with no chips. A request-id guard drops a response superseded by a newer open, so a
|
|
1306
|
+
* slow reply from a previous open can't land after the user has started typing.
|
|
1307
|
+
*/
|
|
1308
|
+
_loadSuggestedActions() {
|
|
1309
|
+
this._suggestedActionRequestId++;
|
|
1310
|
+
const requestId = this._suggestedActionRequestId;
|
|
1311
|
+
const context = this._captureContext();
|
|
1312
|
+
KRHttp.fetch({
|
|
1313
|
+
url: '/api/system/conversation/suggestedActions',
|
|
1314
|
+
method: 'POST',
|
|
1315
|
+
body: {
|
|
1316
|
+
pageContext: context.pageContext,
|
|
1317
|
+
selectionContext: context.selectionContext,
|
|
1318
|
+
},
|
|
1319
|
+
})
|
|
1320
|
+
.then((r) => r.json())
|
|
1321
|
+
.then((res) => {
|
|
1322
|
+
// Ignore a stale response from a prior open, and never overwrite chips once a conversation
|
|
1323
|
+
// has started (the user opened, the reply was slow, they typed and sent in the meantime).
|
|
1324
|
+
if (requestId !== this._suggestedActionRequestId || this.messages.length > 0) {
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
this._suggestedActions = (res?.data || []);
|
|
1328
|
+
})
|
|
1329
|
+
.catch((error) => {
|
|
1330
|
+
console.error('kr-chatbot: suggested actions load failed', error);
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1194
1333
|
/**
|
|
1195
1334
|
* Load the browse dialog's "Kodaris" tab into `_globalSkills` — the whole
|
|
1196
1335
|
* published Kodaris catalog for discovery, installed or not, runnable by this
|
|
@@ -1434,6 +1573,17 @@ export class KRChatbot extends LitElement {
|
|
|
1434
1573
|
this._handleSkillSelect(skill);
|
|
1435
1574
|
}
|
|
1436
1575
|
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Click an empty-state suggestion chip: prefill `/code ` into the composer, same as picking the
|
|
1578
|
+
* skill from the `/` menu, and focus so the user can add details before sending. No permission
|
|
1579
|
+
* guards here (unlike `_handleSkillSelect`) — a suggested action's code always comes from the
|
|
1580
|
+
* server's role-filtered runnable set, so it's already known to be invokable by this user.
|
|
1581
|
+
*/
|
|
1582
|
+
_handleSuggestedActionClick(action) {
|
|
1583
|
+
this._inputValue = `/${action.code} `;
|
|
1584
|
+
this._saveState();
|
|
1585
|
+
this._inputEl?.focus();
|
|
1586
|
+
}
|
|
1437
1587
|
/** Copy a message's text to the clipboard and briefly flash "Copied". */
|
|
1438
1588
|
_handleCopyMessage(content, index) {
|
|
1439
1589
|
navigator.clipboard.writeText(content).then(() => {
|
|
@@ -1716,6 +1866,9 @@ export class KRChatbot extends LitElement {
|
|
|
1716
1866
|
}
|
|
1717
1867
|
this.messages = [...this.messages, userMessage];
|
|
1718
1868
|
this._inputValue = '';
|
|
1869
|
+
// The conversation has started — drop the empty-state chips so they don't linger above the
|
|
1870
|
+
// transcript. A newer suggestedActions reply is also ignored now (its guard checks messages.length).
|
|
1871
|
+
this._suggestedActions = [];
|
|
1719
1872
|
this._saveState();
|
|
1720
1873
|
this._error = '';
|
|
1721
1874
|
this._userHasScrolledUp = false;
|
|
@@ -1875,6 +2028,8 @@ export class KRChatbot extends LitElement {
|
|
|
1875
2028
|
const id = conversation?.conversationID;
|
|
1876
2029
|
if (typeof id === 'number') {
|
|
1877
2030
|
this.conversationId = id;
|
|
2031
|
+
// Pin it to this tab so a reload redisplays this new conversation.
|
|
2032
|
+
this._persistConversationId();
|
|
1878
2033
|
// Announce the newly-created conversation so consumers can react
|
|
1879
2034
|
// (e.g. update the route from /chat/new to /chat/{id}). The full
|
|
1880
2035
|
// conversation object is passed, not just the id, so consumers
|
|
@@ -2072,9 +2227,111 @@ export class KRChatbot extends LitElement {
|
|
|
2072
2227
|
else {
|
|
2073
2228
|
this._drawerOpen = true;
|
|
2074
2229
|
this._drawerView = view;
|
|
2230
|
+
// Opening the history view refetches the user's previous conversations
|
|
2231
|
+
// (most-recently modified first) so a just-created or renamed conversation
|
|
2232
|
+
// shows without a reload. Ownership is enforced server-side (the endpoint
|
|
2233
|
+
// scopes to the current administrator), so we only filter to active ones.
|
|
2234
|
+
if (view === 'history') {
|
|
2235
|
+
this._conversationsLoading = true;
|
|
2236
|
+
KRHttp.fetch({
|
|
2237
|
+
url: '/api/system/conversation/list',
|
|
2238
|
+
method: 'POST',
|
|
2239
|
+
body: {
|
|
2240
|
+
page: 0,
|
|
2241
|
+
size: 50,
|
|
2242
|
+
sortBy: 'lastModified',
|
|
2243
|
+
sortDirection: 'desc',
|
|
2244
|
+
filterFields: [{
|
|
2245
|
+
name: 'active',
|
|
2246
|
+
operation: 'IS',
|
|
2247
|
+
value: 'true',
|
|
2248
|
+
}],
|
|
2249
|
+
},
|
|
2250
|
+
})
|
|
2251
|
+
.then((r) => r.json())
|
|
2252
|
+
.then((res) => {
|
|
2253
|
+
this._conversations = res?.data?.content || [];
|
|
2254
|
+
})
|
|
2255
|
+
.catch(() => {
|
|
2256
|
+
this._conversations = [];
|
|
2257
|
+
})
|
|
2258
|
+
.then(() => {
|
|
2259
|
+
this._conversationsLoading = false;
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2075
2262
|
}
|
|
2076
2263
|
this._saveState();
|
|
2077
2264
|
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Switch the floating widget to a previously-created conversation (clicked in the
|
|
2267
|
+
* history list): stop any live turn, reset the transient turn/context state, pin
|
|
2268
|
+
* the new id (persisted per-tab so a reload stays here), then rebuild its
|
|
2269
|
+
* transcript via the built-in `load`. The drawer closes so the user lands back
|
|
2270
|
+
* in the conversation.
|
|
2271
|
+
*/
|
|
2272
|
+
_handleSwitchConversation(conversationID) {
|
|
2273
|
+
if (conversationID === this.conversationId) {
|
|
2274
|
+
this._handleCloseDrawer();
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2277
|
+
this._handleStop();
|
|
2278
|
+
this.conversationId = conversationID;
|
|
2279
|
+
// Adopt the picked conversation's title for the header (empty for an untitled
|
|
2280
|
+
// one, so the header falls back to the default label).
|
|
2281
|
+
this._conversationTitle = this._conversations.find((c) => c.conversationID === conversationID)?.title || '';
|
|
2282
|
+
this._persistConversationId();
|
|
2283
|
+
this._resetConversationState();
|
|
2284
|
+
this._drawerOpen = false;
|
|
2285
|
+
this._saveState();
|
|
2286
|
+
if (this.load) {
|
|
2287
|
+
this.load().then((messages) => {
|
|
2288
|
+
this.messages = messages || [];
|
|
2289
|
+
// A turn may still be streaming server-side for the conversation we just
|
|
2290
|
+
// opened (started in another tab/device) — attach to it if so.
|
|
2291
|
+
if (this.conversationId && !this._isStreaming) {
|
|
2292
|
+
this._reconnectStream();
|
|
2293
|
+
}
|
|
2294
|
+
}).catch(() => { });
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Start a brand-new conversation (clicked in the history list): unpin the id (the
|
|
2299
|
+
* next turn sends `createNew` and the resolved id re-pins), clear the transcript
|
|
2300
|
+
* and transient state, and close the drawer back to an empty composer.
|
|
2301
|
+
*/
|
|
2302
|
+
_handleNewConversation() {
|
|
2303
|
+
this._handleStop();
|
|
2304
|
+
this.conversationId = null;
|
|
2305
|
+
// New conversation has no title yet — the header shows the default label until
|
|
2306
|
+
// the server generates one at the end of the first turn.
|
|
2307
|
+
this._conversationTitle = '';
|
|
2308
|
+
this._persistConversationId();
|
|
2309
|
+
this.messages = [];
|
|
2310
|
+
this._resetConversationState();
|
|
2311
|
+
this._drawerOpen = false;
|
|
2312
|
+
this._saveState();
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Clear the transient, per-conversation state that must not bleed across a
|
|
2316
|
+
* conversation switch/new — pending confirmations, context banners, errors, the
|
|
2317
|
+
* reconnect counter, and staged composer files. The transcript itself is set by
|
|
2318
|
+
* the caller (loaded for a switch, emptied for new).
|
|
2319
|
+
*/
|
|
2320
|
+
_resetConversationState() {
|
|
2321
|
+
this._pendingActions = [];
|
|
2322
|
+
this._inlineConfirm = true;
|
|
2323
|
+
this._contextWarning = '';
|
|
2324
|
+
this._contextLimitReached = false;
|
|
2325
|
+
this._contextLimitMessage = '';
|
|
2326
|
+
this._error = '';
|
|
2327
|
+
this._reconnectAttempts = 0;
|
|
2328
|
+
this._pendingConversationFiles.forEach((p) => {
|
|
2329
|
+
if (p.previewUrl) {
|
|
2330
|
+
URL.revokeObjectURL(p.previewUrl);
|
|
2331
|
+
}
|
|
2332
|
+
});
|
|
2333
|
+
this._pendingConversationFiles = [];
|
|
2334
|
+
}
|
|
2078
2335
|
/** Close the drawer. `_drawerView` is left intact so its content stays visible
|
|
2079
2336
|
* while the drawer slides shut. */
|
|
2080
2337
|
_handleCloseDrawer() {
|
|
@@ -2358,7 +2615,7 @@ export class KRChatbot extends LitElement {
|
|
|
2358
2615
|
// deliberately never show). Adopt it as the header label and pass it
|
|
2359
2616
|
// up so consumers can update their own chrome, e.g. a breadcrumb.
|
|
2360
2617
|
if (data.data?.title) {
|
|
2361
|
-
this.
|
|
2618
|
+
this._conversationTitle = data.data.title;
|
|
2362
2619
|
this.dispatchEvent(new CustomEvent('conversation-title', {
|
|
2363
2620
|
detail: { title: data.data.title },
|
|
2364
2621
|
bubbles: true,
|
|
@@ -2944,10 +3201,75 @@ export class KRChatbot extends LitElement {
|
|
|
2944
3201
|
* lifetime — plus a verbose per-iteration table. Never customer-facing.
|
|
2945
3202
|
*/
|
|
2946
3203
|
/**
|
|
2947
|
-
* The
|
|
2948
|
-
*
|
|
2949
|
-
*
|
|
2950
|
-
*
|
|
3204
|
+
* The History view's body — the user's previous conversations (most recent
|
|
3205
|
+
* first) plus a "New conversation" action. Clicking a row switches the widget to
|
|
3206
|
+
* that conversation; the current one is marked so the user sees where they are.
|
|
3207
|
+
* Floating-only (opened from the header history button).
|
|
3208
|
+
*/
|
|
3209
|
+
_renderHistoryContent() {
|
|
3210
|
+
return html `
|
|
3211
|
+
<div class="history">
|
|
3212
|
+
${this._conversationsLoading ? html `
|
|
3213
|
+
<div class="history__empty">Loading…</div>
|
|
3214
|
+
` : this._conversations.length === 0 ? html `
|
|
3215
|
+
<div class="history__empty">No previous conversations</div>
|
|
3216
|
+
` : html `
|
|
3217
|
+
<div class="history__list">
|
|
3218
|
+
${this._conversations.map((conversation) => html `
|
|
3219
|
+
<button
|
|
3220
|
+
type="button"
|
|
3221
|
+
class=${classMap({
|
|
3222
|
+
'history__item': true,
|
|
3223
|
+
'history__item--active': conversation.conversationID === this.conversationId,
|
|
3224
|
+
})}
|
|
3225
|
+
@click=${() => this._handleSwitchConversation(conversation.conversationID)}
|
|
3226
|
+
>
|
|
3227
|
+
<span class="history__item-title">${conversation.title || 'Untitled conversation'}</span>
|
|
3228
|
+
${conversation.lastModified ? html `
|
|
3229
|
+
<span class="history__item-date">${this._renderConversationDate(conversation.lastModified)}</span>
|
|
3230
|
+
` : nothing}
|
|
3231
|
+
</button>
|
|
3232
|
+
`)}
|
|
3233
|
+
</div>
|
|
3234
|
+
`}
|
|
3235
|
+
</div>
|
|
3236
|
+
`;
|
|
3237
|
+
}
|
|
3238
|
+
/**
|
|
3239
|
+
* Format a conversation's lastModified for the history list: today shows the
|
|
3240
|
+
* time, this year shows month/day, older shows month/day/year. Falls back to the
|
|
3241
|
+
* raw value if it isn't a parseable date.
|
|
3242
|
+
*/
|
|
3243
|
+
_renderConversationDate(lastModified) {
|
|
3244
|
+
const date = new Date(lastModified);
|
|
3245
|
+
if (isNaN(date.getTime())) {
|
|
3246
|
+
return lastModified;
|
|
3247
|
+
}
|
|
3248
|
+
const now = new Date();
|
|
3249
|
+
if (date.toDateString() === now.toDateString()) {
|
|
3250
|
+
return date.toLocaleTimeString('en-US', {
|
|
3251
|
+
hour: 'numeric',
|
|
3252
|
+
minute: '2-digit',
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
if (date.getFullYear() === now.getFullYear()) {
|
|
3256
|
+
return date.toLocaleDateString('en-US', {
|
|
3257
|
+
month: 'short',
|
|
3258
|
+
day: 'numeric',
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3261
|
+
return date.toLocaleDateString('en-US', {
|
|
3262
|
+
month: 'short',
|
|
3263
|
+
day: 'numeric',
|
|
3264
|
+
year: 'numeric',
|
|
3265
|
+
});
|
|
3266
|
+
}
|
|
3267
|
+
/**
|
|
3268
|
+
* The side drawer — one sliding panel that holds the Settings, Debug, History,
|
|
3269
|
+
* and tool-detail views. The header buttons switch `_drawerView`; the title and
|
|
3270
|
+
* body swap inside. In inline mode it slides in beside the transcript at a
|
|
3271
|
+
* consistent (resizable) width; in floating mode it fills the panel over the
|
|
3272
|
+
* transcript with a back button (the panel is too narrow to show both).
|
|
2951
3273
|
*/
|
|
2952
3274
|
_renderDrawer() {
|
|
2953
3275
|
// The debug view is gated on the server-side flag (inline variant + debug
|
|
@@ -2965,41 +3287,101 @@ export class KRChatbot extends LitElement {
|
|
|
2965
3287
|
title = this._toolDetail.use.data?.displayName || this._toolDetail.use.data?.toolName || 'Tool';
|
|
2966
3288
|
body = this._renderToolDetailContent();
|
|
2967
3289
|
}
|
|
3290
|
+
else if (this._drawerView === 'history') {
|
|
3291
|
+
title = 'Conversations';
|
|
3292
|
+
body = this._renderHistoryContent();
|
|
3293
|
+
}
|
|
3294
|
+
// In the floating widget the drawer fills the panel over the transcript (a
|
|
3295
|
+
// full-panel view with a back button) rather than sliding in beside it — the
|
|
3296
|
+
// panel is too narrow to show both. Inline keeps the side-drawer behavior.
|
|
3297
|
+
const fullPanel = this.variant === 'floating';
|
|
2968
3298
|
return html `
|
|
2969
3299
|
<div
|
|
2970
3300
|
class=${classMap({
|
|
2971
3301
|
'drawer': true,
|
|
2972
3302
|
'drawer--open': this._drawerOpen,
|
|
2973
3303
|
'drawer--resizing': this._isDrawerResizing,
|
|
3304
|
+
'drawer--full': fullPanel,
|
|
2974
3305
|
})}
|
|
2975
|
-
style=${styleMap({ width: `${this._drawerWidth}px` })}
|
|
3306
|
+
style=${styleMap(fullPanel ? {} : { width: `${this._drawerWidth}px` })}
|
|
2976
3307
|
>
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
3308
|
+
${fullPanel ? nothing : html `
|
|
3309
|
+
<div
|
|
3310
|
+
class="drawer__resize"
|
|
3311
|
+
@mousedown=${this._handleDrawerResizeMouseDown}
|
|
3312
|
+
></div>
|
|
3313
|
+
`}
|
|
2981
3314
|
<div class="drawer__header">
|
|
3315
|
+
${fullPanel ? html `
|
|
3316
|
+
<button
|
|
3317
|
+
class="header-action"
|
|
3318
|
+
@click=${this._handleCloseDrawer}
|
|
3319
|
+
title="Back"
|
|
3320
|
+
>
|
|
3321
|
+
<svg
|
|
3322
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3323
|
+
fill="none"
|
|
3324
|
+
viewBox="0 0 24 24"
|
|
3325
|
+
stroke-width="2"
|
|
3326
|
+
stroke="currentColor"
|
|
3327
|
+
class="header-action-icon"
|
|
3328
|
+
>
|
|
3329
|
+
<path
|
|
3330
|
+
stroke-linecap="round"
|
|
3331
|
+
stroke-linejoin="round"
|
|
3332
|
+
d="M15.75 19.5 8.25 12l7.5-7.5"
|
|
3333
|
+
/>
|
|
3334
|
+
</svg>
|
|
3335
|
+
</button>
|
|
3336
|
+
` : nothing}
|
|
2982
3337
|
<span class="drawer__title">${title}</span>
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
fill="none"
|
|
2991
|
-
viewBox="0 0 24 24"
|
|
2992
|
-
stroke-width="2"
|
|
2993
|
-
stroke="currentColor"
|
|
2994
|
-
class="header-action-icon"
|
|
3338
|
+
${this._drawerView === 'history' ? html `
|
|
3339
|
+
<kr-button
|
|
3340
|
+
class="history__new"
|
|
3341
|
+
variant="flat"
|
|
3342
|
+
color="primary"
|
|
3343
|
+
size="small"
|
|
3344
|
+
@click=${this._handleNewConversation}
|
|
2995
3345
|
>
|
|
2996
|
-
<
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3346
|
+
<svg
|
|
3347
|
+
slot="icon"
|
|
3348
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3349
|
+
fill="none"
|
|
3350
|
+
viewBox="0 0 24 24"
|
|
3351
|
+
stroke-width="1.75"
|
|
3352
|
+
stroke="currentColor"
|
|
3353
|
+
>
|
|
3354
|
+
<path
|
|
3355
|
+
stroke-linecap="round"
|
|
3356
|
+
stroke-linejoin="round"
|
|
3357
|
+
d="M12 4.5v15m7.5-7.5h-15"
|
|
3358
|
+
/>
|
|
3359
|
+
</svg>
|
|
3360
|
+
New conversation
|
|
3361
|
+
</kr-button>
|
|
3362
|
+
` : nothing}
|
|
3363
|
+
${fullPanel ? nothing : html `
|
|
3364
|
+
<button
|
|
3365
|
+
class="header-action"
|
|
3366
|
+
@click=${this._handleCloseDrawer}
|
|
3367
|
+
title="Close"
|
|
3368
|
+
>
|
|
3369
|
+
<svg
|
|
3370
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3371
|
+
fill="none"
|
|
3372
|
+
viewBox="0 0 24 24"
|
|
3373
|
+
stroke-width="2"
|
|
3374
|
+
stroke="currentColor"
|
|
3375
|
+
class="header-action-icon"
|
|
3376
|
+
>
|
|
3377
|
+
<path
|
|
3378
|
+
stroke-linecap="round"
|
|
3379
|
+
stroke-linejoin="round"
|
|
3380
|
+
d="M6 18 18 6M6 6l12 12"
|
|
3381
|
+
/>
|
|
3382
|
+
</svg>
|
|
3383
|
+
</button>
|
|
3384
|
+
`}
|
|
3003
3385
|
</div>
|
|
3004
3386
|
<div class="drawer__body">
|
|
3005
3387
|
${body}
|
|
@@ -3260,9 +3642,10 @@ export class KRChatbot extends LitElement {
|
|
|
3260
3642
|
const isDebugEnabled = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
3261
3643
|
// Push the content over by the open drawer's width so the drawer sits beside
|
|
3262
3644
|
// the content rather than overlaying it. One consistent (resizable) width for
|
|
3263
|
-
// both views. Only pushes while the drawer is open
|
|
3645
|
+
// both views. Only pushes while the drawer is open, and only inline — the
|
|
3646
|
+
// floating drawer fills the panel over the transcript, so it must not push.
|
|
3264
3647
|
const contentPadding = {};
|
|
3265
|
-
if (this._drawerOpen) {
|
|
3648
|
+
if (this._drawerOpen && this.variant === 'inline') {
|
|
3266
3649
|
contentPadding.paddingRight = `${this._drawerWidth}px`;
|
|
3267
3650
|
}
|
|
3268
3651
|
// Group the transcript into runs so an agent turn (its reasoning/tool/text/
|
|
@@ -3380,10 +3763,52 @@ export class KRChatbot extends LitElement {
|
|
|
3380
3763
|
<div class="header" @mousedown=${this._handleHeaderMouseDown}>
|
|
3381
3764
|
<div class="header-left">
|
|
3382
3765
|
<div class="header-info">
|
|
3383
|
-
<span class="header-title">${this.label}</span>
|
|
3766
|
+
<span class="header-title">${this._conversationTitle || this.label}</span>
|
|
3384
3767
|
</div>
|
|
3385
3768
|
</div>
|
|
3386
3769
|
<div class="header-actions">
|
|
3770
|
+
${this.variant === 'floating' ? html `
|
|
3771
|
+
<button
|
|
3772
|
+
class="header-action"
|
|
3773
|
+
@click=${this._handleNewConversation}
|
|
3774
|
+
title="New conversation"
|
|
3775
|
+
>
|
|
3776
|
+
<svg
|
|
3777
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3778
|
+
fill="none"
|
|
3779
|
+
viewBox="0 0 24 24"
|
|
3780
|
+
stroke-width="1.75"
|
|
3781
|
+
stroke="currentColor"
|
|
3782
|
+
class="header-action-icon"
|
|
3783
|
+
>
|
|
3784
|
+
<path
|
|
3785
|
+
stroke-linecap="round"
|
|
3786
|
+
stroke-linejoin="round"
|
|
3787
|
+
d="M12 4.5v15m7.5-7.5h-15"
|
|
3788
|
+
/>
|
|
3789
|
+
</svg>
|
|
3790
|
+
</button>
|
|
3791
|
+
<button
|
|
3792
|
+
class="header-action"
|
|
3793
|
+
@click=${() => this._toggleDrawer('history')}
|
|
3794
|
+
title="Conversations"
|
|
3795
|
+
>
|
|
3796
|
+
<svg
|
|
3797
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
3798
|
+
fill="none"
|
|
3799
|
+
viewBox="0 0 24 24"
|
|
3800
|
+
stroke-width="1.5"
|
|
3801
|
+
stroke="currentColor"
|
|
3802
|
+
class="header-action-icon"
|
|
3803
|
+
>
|
|
3804
|
+
<path
|
|
3805
|
+
stroke-linecap="round"
|
|
3806
|
+
stroke-linejoin="round"
|
|
3807
|
+
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
|
3808
|
+
/>
|
|
3809
|
+
</svg>
|
|
3810
|
+
</button>
|
|
3811
|
+
` : nothing}
|
|
3387
3812
|
<button
|
|
3388
3813
|
class="header-action"
|
|
3389
3814
|
@click=${() => this._toggleDrawer('settings')}
|
|
@@ -3461,6 +3886,17 @@ export class KRChatbot extends LitElement {
|
|
|
3461
3886
|
${this.messages.length === 0 ? html `
|
|
3462
3887
|
<div class="empty">
|
|
3463
3888
|
<span class="empty-text">What can I help you with?</span>
|
|
3889
|
+
${this._suggestedActions.length ? html `
|
|
3890
|
+
<div class="empty-suggestions">
|
|
3891
|
+
${this._suggestedActions.map((action) => html `
|
|
3892
|
+
<button
|
|
3893
|
+
type="button"
|
|
3894
|
+
class="empty-suggestions__chip"
|
|
3895
|
+
@click=${() => this._handleSuggestedActionClick(action)}
|
|
3896
|
+
>${action.label}</button>
|
|
3897
|
+
`)}
|
|
3898
|
+
</div>
|
|
3899
|
+
` : nothing}
|
|
3464
3900
|
</div>
|
|
3465
3901
|
` : nothing}
|
|
3466
3902
|
${messageGroups.map((group) => {
|
|
@@ -4127,22 +4563,28 @@ KRChatbot.styles = css `
|
|
|
4127
4563
|
display: flex;
|
|
4128
4564
|
align-items: center;
|
|
4129
4565
|
gap: 10px;
|
|
4566
|
+
min-width: 0;
|
|
4130
4567
|
}
|
|
4131
4568
|
|
|
4132
4569
|
.header-info {
|
|
4133
4570
|
display: flex;
|
|
4134
4571
|
flex-direction: column;
|
|
4572
|
+
min-width: 0;
|
|
4135
4573
|
}
|
|
4136
4574
|
|
|
4137
4575
|
.header-title {
|
|
4138
4576
|
font-size: 16px;
|
|
4139
4577
|
font-weight: 600;
|
|
4578
|
+
white-space: nowrap;
|
|
4579
|
+
overflow: hidden;
|
|
4580
|
+
text-overflow: ellipsis;
|
|
4140
4581
|
}
|
|
4141
4582
|
|
|
4142
4583
|
.header-actions {
|
|
4143
4584
|
display: flex;
|
|
4144
4585
|
align-items: center;
|
|
4145
4586
|
gap: 4px;
|
|
4587
|
+
flex-shrink: 0;
|
|
4146
4588
|
}
|
|
4147
4589
|
|
|
4148
4590
|
.header-action {
|
|
@@ -4316,7 +4758,7 @@ KRChatbot.styles = css `
|
|
|
4316
4758
|
align-items: center;
|
|
4317
4759
|
justify-content: center;
|
|
4318
4760
|
color: var(--kr-chatbot-text-secondary);
|
|
4319
|
-
gap:
|
|
4761
|
+
gap: 16px;
|
|
4320
4762
|
}
|
|
4321
4763
|
|
|
4322
4764
|
.empty-text {
|
|
@@ -4324,6 +4766,35 @@ KRChatbot.styles = css `
|
|
|
4324
4766
|
color: rgb(0 0 0 / 80%);
|
|
4325
4767
|
}
|
|
4326
4768
|
|
|
4769
|
+
/* Context-aware action chips under the empty-state prompt. A row of pills the user can click to
|
|
4770
|
+
prefill a skill — capped small server-side so this reads as a few suggestions, not a menu. */
|
|
4771
|
+
.empty-suggestions {
|
|
4772
|
+
display: flex;
|
|
4773
|
+
flex-wrap: wrap;
|
|
4774
|
+
justify-content: center;
|
|
4775
|
+
gap: 10px;
|
|
4776
|
+
max-width: 420px;
|
|
4777
|
+
margin-top: 4px;
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
.empty-suggestions__chip {
|
|
4781
|
+
padding: 8px 12px;
|
|
4782
|
+
background: rgb(0 0 0 / 6%);
|
|
4783
|
+
border: none;
|
|
4784
|
+
border-radius: 16px;
|
|
4785
|
+
color: #000000;
|
|
4786
|
+
font: inherit;
|
|
4787
|
+
font-size: 13px;
|
|
4788
|
+
line-height: 1.2;
|
|
4789
|
+
cursor: pointer;
|
|
4790
|
+
white-space: nowrap;
|
|
4791
|
+
}
|
|
4792
|
+
|
|
4793
|
+
.empty-suggestions__chip:hover {
|
|
4794
|
+
background: rgb(0 0 0 / 10%);
|
|
4795
|
+
color: #000000;
|
|
4796
|
+
}
|
|
4797
|
+
|
|
4327
4798
|
.message {
|
|
4328
4799
|
position: relative;
|
|
4329
4800
|
display: flex;
|
|
@@ -4968,6 +5439,13 @@ KRChatbot.styles = css `
|
|
|
4968
5439
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
4969
5440
|
}
|
|
4970
5441
|
|
|
5442
|
+
/* Full-panel (floating) header: back button then title, left-aligned. */
|
|
5443
|
+
.drawer--full .drawer__header {
|
|
5444
|
+
justify-content: flex-start;
|
|
5445
|
+
gap: 8px;
|
|
5446
|
+
padding: 0 12px;
|
|
5447
|
+
}
|
|
5448
|
+
|
|
4971
5449
|
.drawer__title {
|
|
4972
5450
|
font-size: 14px;
|
|
4973
5451
|
font-weight: 600;
|
|
@@ -4981,6 +5459,78 @@ KRChatbot.styles = css `
|
|
|
4981
5459
|
padding: 4px 0;
|
|
4982
5460
|
}
|
|
4983
5461
|
|
|
5462
|
+
/* Floating variant: the drawer fills the whole panel over the transcript (a
|
|
5463
|
+
full-panel view with a back button), rather than sliding in beside it. */
|
|
5464
|
+
.drawer--full {
|
|
5465
|
+
left: 0;
|
|
5466
|
+
width: 100%;
|
|
5467
|
+
max-width: 100%;
|
|
5468
|
+
z-index: 6;
|
|
5469
|
+
}
|
|
5470
|
+
|
|
5471
|
+
.drawer--full.drawer--open {
|
|
5472
|
+
box-shadow: none;
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
/* History (previous conversations) view. */
|
|
5476
|
+
.history {
|
|
5477
|
+
display: flex;
|
|
5478
|
+
flex-direction: column;
|
|
5479
|
+
}
|
|
5480
|
+
|
|
5481
|
+
/* "New conversation" action in the Conversations header — pushed to the right. */
|
|
5482
|
+
.history__new {
|
|
5483
|
+
margin-left: auto;
|
|
5484
|
+
}
|
|
5485
|
+
|
|
5486
|
+
.history__empty {
|
|
5487
|
+
padding: 24px 20px;
|
|
5488
|
+
text-align: center;
|
|
5489
|
+
font-size: 13px;
|
|
5490
|
+
color: rgb(0 0 0 / 50%);
|
|
5491
|
+
}
|
|
5492
|
+
|
|
5493
|
+
.history__list {
|
|
5494
|
+
display: flex;
|
|
5495
|
+
flex-direction: column;
|
|
5496
|
+
padding: 4px 0;
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
.history__item {
|
|
5500
|
+
display: flex;
|
|
5501
|
+
flex-direction: column;
|
|
5502
|
+
align-items: flex-start;
|
|
5503
|
+
gap: 5px;
|
|
5504
|
+
padding: 10px 20px;
|
|
5505
|
+
background: transparent;
|
|
5506
|
+
border: none;
|
|
5507
|
+
text-align: left;
|
|
5508
|
+
cursor: pointer;
|
|
5509
|
+
transition: background 0.15s;
|
|
5510
|
+
}
|
|
5511
|
+
|
|
5512
|
+
.history__item:hover {
|
|
5513
|
+
background: rgba(0, 0, 0, 0.04);
|
|
5514
|
+
}
|
|
5515
|
+
|
|
5516
|
+
.history__item--active {
|
|
5517
|
+
background: rgba(0, 0, 0, 0.06);
|
|
5518
|
+
}
|
|
5519
|
+
|
|
5520
|
+
.history__item-title {
|
|
5521
|
+
font-size: 14px;
|
|
5522
|
+
color: #000000;
|
|
5523
|
+
white-space: nowrap;
|
|
5524
|
+
overflow: hidden;
|
|
5525
|
+
text-overflow: ellipsis;
|
|
5526
|
+
max-width: 100%;
|
|
5527
|
+
}
|
|
5528
|
+
|
|
5529
|
+
.history__item-date {
|
|
5530
|
+
font-size: 12px;
|
|
5531
|
+
color: rgb(0 0 0 / 60%);
|
|
5532
|
+
}
|
|
5533
|
+
|
|
4984
5534
|
.debug-section {
|
|
4985
5535
|
padding: 14px 20px;
|
|
4986
5536
|
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
@@ -5915,6 +6465,9 @@ __decorate([
|
|
|
5915
6465
|
__decorate([
|
|
5916
6466
|
state()
|
|
5917
6467
|
], KRChatbot.prototype, "_error", void 0);
|
|
6468
|
+
__decorate([
|
|
6469
|
+
state()
|
|
6470
|
+
], KRChatbot.prototype, "_conversationTitle", void 0);
|
|
5918
6471
|
__decorate([
|
|
5919
6472
|
state()
|
|
5920
6473
|
], KRChatbot.prototype, "_contextWarning", void 0);
|
|
@@ -5948,6 +6501,12 @@ __decorate([
|
|
|
5948
6501
|
__decorate([
|
|
5949
6502
|
state()
|
|
5950
6503
|
], KRChatbot.prototype, "_drawerView", void 0);
|
|
6504
|
+
__decorate([
|
|
6505
|
+
state()
|
|
6506
|
+
], KRChatbot.prototype, "_conversations", void 0);
|
|
6507
|
+
__decorate([
|
|
6508
|
+
state()
|
|
6509
|
+
], KRChatbot.prototype, "_conversationsLoading", void 0);
|
|
5951
6510
|
__decorate([
|
|
5952
6511
|
state()
|
|
5953
6512
|
], KRChatbot.prototype, "_toolDetail", void 0);
|
|
@@ -5987,6 +6546,9 @@ __decorate([
|
|
|
5987
6546
|
__decorate([
|
|
5988
6547
|
state()
|
|
5989
6548
|
], KRChatbot.prototype, "_invocableSkills", void 0);
|
|
6549
|
+
__decorate([
|
|
6550
|
+
state()
|
|
6551
|
+
], KRChatbot.prototype, "_suggestedActions", void 0);
|
|
5990
6552
|
__decorate([
|
|
5991
6553
|
state()
|
|
5992
6554
|
], KRChatbot.prototype, "_aiFrameworkConfig", void 0);
|