@kodaris/krubble-app-components 1.0.76 → 1.0.78
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 +199 -8
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +863 -40
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +923 -95
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +591 -120
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +923 -95
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +548 -77
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { LitElement, html, css, nothing } from 'lit';
|
|
8
8
|
import { customElement, property, state, query } from 'lit/decorators.js';
|
|
9
9
|
import { classMap } from 'lit/directives/class-map.js';
|
|
10
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
|
10
11
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
11
12
|
import { repeat } from 'lit/directives/repeat.js';
|
|
12
13
|
import { marked } from 'marked';
|
|
@@ -71,15 +72,27 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
71
72
|
this._dragStartTop = 0;
|
|
72
73
|
this._resizeStartW = 0;
|
|
73
74
|
this._resizeStartH = 0;
|
|
75
|
+
/** Cursor X at debug-drawer resize start. */
|
|
76
|
+
this._debugResizeStartX = 0;
|
|
77
|
+
/** Debug-drawer width at resize start. */
|
|
78
|
+
this._debugResizeStartW = 0;
|
|
74
79
|
/** Counter for depth-aware dragenter/dragleave tracking. */
|
|
75
80
|
this._dragDepth = 0;
|
|
76
81
|
/** Monotonic id source for pending attachments. */
|
|
77
82
|
this._pendingSeq = 0;
|
|
78
83
|
/**
|
|
79
|
-
* Request-id
|
|
80
|
-
* responses are dropped (mirrors the kr-combo-box `_requestId` pattern).
|
|
84
|
+
* Request-id guards for skill searches — bumped on each search so out-of-order
|
|
85
|
+
* responses are dropped (mirrors the kr-combo-box `_requestId` pattern). Org and
|
|
86
|
+
* global searches run concurrently into separate buffers, so each has its own.
|
|
81
87
|
*/
|
|
82
88
|
this._skillRequestId = 0;
|
|
89
|
+
this._globalSkillRequestId = 0;
|
|
90
|
+
/**
|
|
91
|
+
* Codes of global skills with an in-flight enable/disable request. The `+`
|
|
92
|
+
* button reads this to show a pending state and ignore repeat clicks. Mutated
|
|
93
|
+
* in place; call requestUpdate() after changing it to re-render.
|
|
94
|
+
*/
|
|
95
|
+
this._globalSkillPending = new Set();
|
|
83
96
|
// --- @property (public API) ---
|
|
84
97
|
/**
|
|
85
98
|
* Callback function for sending messages. Receives the user's message and
|
|
@@ -143,6 +156,39 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
143
156
|
this._inputValue = '';
|
|
144
157
|
this._isStreaming = false;
|
|
145
158
|
this._error = '';
|
|
159
|
+
/**
|
|
160
|
+
* User-facing context warning text (`CONVERSATION_TOKEN_WARNING`). Shown as a
|
|
161
|
+
* dismissible banner in all variants. Carries only friendly guidance, no numbers.
|
|
162
|
+
*/
|
|
163
|
+
this._contextWarning = '';
|
|
164
|
+
/**
|
|
165
|
+
* True once the conversation hit the context-window limit
|
|
166
|
+
* (`CONVERSATION_LIMIT_REACHED`, or `contextExceeded` on load). Drives a
|
|
167
|
+
* blocking "start a new conversation" notice. User-facing, all variants.
|
|
168
|
+
*/
|
|
169
|
+
this._contextLimitReached = false;
|
|
170
|
+
/** Server-provided limit text (`CONVERSATION_LIMIT_REACHED`), if any. */
|
|
171
|
+
this._contextLimitMessage = '';
|
|
172
|
+
/**
|
|
173
|
+
* INTERNAL-ONLY latest telemetry snapshot (last-value-wins). Populated from
|
|
174
|
+
* `TOKEN_USAGE` events during streaming and hydrated from the conversation
|
|
175
|
+
* DTO on load. Only surfaced in the `inline` variant's debug panel.
|
|
176
|
+
*/
|
|
177
|
+
this._telemetry = null;
|
|
178
|
+
/**
|
|
179
|
+
* INTERNAL-ONLY per-iteration telemetry log for the debug panel's verbose
|
|
180
|
+
* table. Append-only within a turn; reset at the start of each send. Each row
|
|
181
|
+
* may be augmented with its `CACHE_CHECKPOINT` info.
|
|
182
|
+
*/
|
|
183
|
+
this._iterationLog = [];
|
|
184
|
+
/** Latest cache-checkpoint info keyed by iteration, for the verbose table. */
|
|
185
|
+
this._cacheCheckpoints = {};
|
|
186
|
+
/** Whether the internal debug panel is expanded (off by default). */
|
|
187
|
+
this._debugPanelOpen = false;
|
|
188
|
+
/** Current width of the debug drawer in px (user-resizable, persisted). */
|
|
189
|
+
this._debugDrawerWidth = 430;
|
|
190
|
+
/** True while the user is dragging the debug drawer's resize handle. */
|
|
191
|
+
this._isDebugResizing = false;
|
|
146
192
|
/** Images staged in the composer, before the message is sent. */
|
|
147
193
|
this._pendingInternalFiles = [];
|
|
148
194
|
/** True while a file is being dragged over the panel (shows the drop overlay). */
|
|
@@ -152,10 +198,24 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
152
198
|
/** Current `/` query (text after the slash), sent to `_searchSkills`. */
|
|
153
199
|
this._skillQuery = '';
|
|
154
200
|
/**
|
|
155
|
-
* Latest skill search results
|
|
156
|
-
*
|
|
201
|
+
* Latest org skill search results (this instance's own skills). Backs the `/`
|
|
202
|
+
* menu and the browse dialog's "Your Org" tab.
|
|
157
203
|
*/
|
|
158
204
|
this._skillResults = [];
|
|
205
|
+
/**
|
|
206
|
+
* Latest shared-Kodaris (global) skill results, for the browse dialog's
|
|
207
|
+
* "Kodaris" tab. Kept separate from `_skillResults` so switching tabs shows
|
|
208
|
+
* fresh results immediately instead of the other tab's stale list — the browse
|
|
209
|
+
* search fills both buffers at once.
|
|
210
|
+
*/
|
|
211
|
+
this._globalSkillResults = [];
|
|
212
|
+
/**
|
|
213
|
+
* AI framework UI config flags fetched from `aiFrameworkConfig` on connect.
|
|
214
|
+
* `debugTelemetryEnabled` (same server-side gate as TOKEN_USAGE emission) turns
|
|
215
|
+
* on the debug drawer toggle and the `/test-warning` / `/test-limit` composer
|
|
216
|
+
* options — known up front, before any message.
|
|
217
|
+
*/
|
|
218
|
+
this._aiFrameworkConfig = {};
|
|
159
219
|
/** Highlighted item in the skill menu for keyboard navigation (-1 = none). */
|
|
160
220
|
this._skillHighlighted = 0;
|
|
161
221
|
/** Index of the message whose copy button just flashed "Copied" (-1 = none). */
|
|
@@ -163,11 +223,11 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
163
223
|
/** Whether the "Browse all skills" dialog is open. */
|
|
164
224
|
this._skillBrowseOpened = false;
|
|
165
225
|
/**
|
|
166
|
-
* Active tab in the browse dialog. `org`
|
|
167
|
-
*
|
|
226
|
+
* Active tab in the browse dialog. `org` shows `_skillResults` (this instance's
|
|
227
|
+
* skills); `kodaris` shows `_globalSkillResults` (the shared global catalog).
|
|
168
228
|
*/
|
|
169
229
|
this._skillBrowseSource = 'org';
|
|
170
|
-
/** Search text in the browse dialog,
|
|
230
|
+
/** Search text in the browse dialog, routed to the active tab's search. */
|
|
171
231
|
this._skillBrowseQuery = '';
|
|
172
232
|
this._handleMouseMove = (e) => {
|
|
173
233
|
if (this._isDragging) {
|
|
@@ -226,6 +286,25 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
226
286
|
document.removeEventListener('mouseup', this._handleMouseUp);
|
|
227
287
|
this._saveState();
|
|
228
288
|
};
|
|
289
|
+
this._handleDebugResizeMouseMove = (e) => {
|
|
290
|
+
if (!this._isDebugResizing) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
// Handle is on the drawer's left edge; dragging left (negative dx) widens it.
|
|
294
|
+
const dx = e.clientX - this._debugResizeStartX;
|
|
295
|
+
const minW = 300;
|
|
296
|
+
// Never let the chat column shrink below 300px — cap the drawer at the
|
|
297
|
+
// container width minus that reserved chat width.
|
|
298
|
+
const minChatWidth = 300;
|
|
299
|
+
const maxW = Math.max(minW, this.offsetWidth - minChatWidth);
|
|
300
|
+
this._debugDrawerWidth = Math.max(minW, Math.min(this._debugResizeStartW - dx, maxW));
|
|
301
|
+
};
|
|
302
|
+
this._handleDebugResizeMouseUp = () => {
|
|
303
|
+
this._isDebugResizing = false;
|
|
304
|
+
document.removeEventListener('mousemove', this._handleDebugResizeMouseMove);
|
|
305
|
+
document.removeEventListener('mouseup', this._handleDebugResizeMouseUp);
|
|
306
|
+
this._saveState();
|
|
307
|
+
};
|
|
229
308
|
}
|
|
230
309
|
// --- Lifecycle ---
|
|
231
310
|
connectedCallback() {
|
|
@@ -246,11 +325,22 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
246
325
|
this.messages = messages;
|
|
247
326
|
}
|
|
248
327
|
}).catch(() => { });
|
|
328
|
+
// Fetch the framework config on load (debugTelemetryEnabled, v2ChatEnabled,
|
|
329
|
+
// registryServer). debugTelemetryEnabled gates the debug affordances and is
|
|
330
|
+
// known before any message.
|
|
331
|
+
KRHttp.fetch({
|
|
332
|
+
url: '/api/system/integration/aws/bedrock/aiFrameworkConfig',
|
|
333
|
+
})
|
|
334
|
+
.then((r) => r.json())
|
|
335
|
+
.then((res) => {
|
|
336
|
+
this._aiFrameworkConfig = res?.data || {};
|
|
337
|
+
})
|
|
338
|
+
.catch(() => { });
|
|
249
339
|
// Inline mode fills its container and is always open — the floating widget's
|
|
250
|
-
// toggle/drag/resize state
|
|
340
|
+
// toggle/drag/resize state doesn't apply, but the composer message and debug
|
|
341
|
+
// drawer state (restored inside _restoreState) do.
|
|
251
342
|
if (this.variant === 'inline') {
|
|
252
343
|
this.opened = true;
|
|
253
|
-
return;
|
|
254
344
|
}
|
|
255
345
|
this._restoreState();
|
|
256
346
|
}
|
|
@@ -440,7 +530,12 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
440
530
|
}
|
|
441
531
|
_saveState() {
|
|
442
532
|
try {
|
|
443
|
-
const state = {
|
|
533
|
+
const state = {
|
|
534
|
+
opened: String(this.opened),
|
|
535
|
+
message: this._inputValue,
|
|
536
|
+
debugPanelOpen: String(this._debugPanelOpen),
|
|
537
|
+
debugDrawerWidth: String(this._debugDrawerWidth),
|
|
538
|
+
};
|
|
444
539
|
if (this._panelEl) {
|
|
445
540
|
const s = this._panelEl.style;
|
|
446
541
|
if (s.left) {
|
|
@@ -470,7 +565,17 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
470
565
|
return;
|
|
471
566
|
}
|
|
472
567
|
const state = JSON.parse(raw);
|
|
473
|
-
|
|
568
|
+
// Draft text and the debug drawer are persisted for all variants.
|
|
569
|
+
if (typeof state.message === 'string') {
|
|
570
|
+
this._inputValue = state.message;
|
|
571
|
+
}
|
|
572
|
+
this._debugPanelOpen = state.debugPanelOpen === 'true';
|
|
573
|
+
const w = Number(state.debugDrawerWidth);
|
|
574
|
+
if (w >= 300) {
|
|
575
|
+
this._debugDrawerWidth = w;
|
|
576
|
+
}
|
|
577
|
+
// The floating widget's open/layout state doesn't apply to inline mode.
|
|
578
|
+
if (this.variant !== 'inline' && state.opened === 'true') {
|
|
474
579
|
this.opened = true;
|
|
475
580
|
// Apply layout after first render
|
|
476
581
|
this.updateComplete.then(() => this._applyStoredLayout());
|
|
@@ -505,6 +610,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
505
610
|
}
|
|
506
611
|
_handleInputChange(e) {
|
|
507
612
|
this._inputValue = e.target.value;
|
|
613
|
+
this._saveState();
|
|
508
614
|
// Auto-resize textarea
|
|
509
615
|
const textarea = e.target;
|
|
510
616
|
textarea.style.height = 'auto';
|
|
@@ -594,17 +700,33 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
594
700
|
// specials so user input is treated literally, then wrap with `*...*`.
|
|
595
701
|
let must = { match_all: {} };
|
|
596
702
|
if (trimmed) {
|
|
597
|
-
const pattern = `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`;
|
|
598
|
-
const value = {
|
|
599
|
-
value: pattern,
|
|
600
|
-
case_insensitive: true,
|
|
601
|
-
};
|
|
602
703
|
must = {
|
|
603
704
|
bool: {
|
|
604
705
|
should: [
|
|
605
|
-
{
|
|
606
|
-
|
|
607
|
-
|
|
706
|
+
{
|
|
707
|
+
wildcard: {
|
|
708
|
+
'name.raw': {
|
|
709
|
+
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
710
|
+
case_insensitive: true,
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
wildcard: {
|
|
716
|
+
'code.raw': {
|
|
717
|
+
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
718
|
+
case_insensitive: true,
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
wildcard: {
|
|
724
|
+
'description.raw': {
|
|
725
|
+
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
726
|
+
case_insensitive: true,
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
},
|
|
608
730
|
],
|
|
609
731
|
minimum_should_match: 1,
|
|
610
732
|
},
|
|
@@ -635,6 +757,66 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
635
757
|
console.error('kr-chatbot: skill search failed', error);
|
|
636
758
|
});
|
|
637
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Search the shared Kodaris global-skill registry into `_globalSkillResults`
|
|
762
|
+
* (the browse dialog's "Kodaris" tab). Unlike `/aiskills/search`,
|
|
763
|
+
* `/aiskills/global/search` returns a digested list
|
|
764
|
+
* (`data: [{ code, name, description, enabled }]`) already in `KRChatbotSkill`
|
|
765
|
+
* shape, so no `_source` lifting is needed. Only the browse dialog calls this
|
|
766
|
+
* (the `/` menu is org-only), so it isn't run per slash keystroke.
|
|
767
|
+
*/
|
|
768
|
+
_searchGlobalSkills(query) {
|
|
769
|
+
this._globalSkillRequestId++;
|
|
770
|
+
const requestId = this._globalSkillRequestId;
|
|
771
|
+
KRHttp.fetch({
|
|
772
|
+
url: '/api/system/opensearch/aiskills/global/search',
|
|
773
|
+
method: 'POST',
|
|
774
|
+
body: { query: query.trim() },
|
|
775
|
+
})
|
|
776
|
+
.then((r) => r.json())
|
|
777
|
+
.then((res) => {
|
|
778
|
+
// Ignore a stale response superseded by a newer search.
|
|
779
|
+
if (requestId !== this._globalSkillRequestId) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
this._globalSkillResults = (res?.data || []);
|
|
783
|
+
})
|
|
784
|
+
.catch((error) => {
|
|
785
|
+
console.error('kr-chatbot: global skill search failed', error);
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Enable or disable a global skill on this instance via
|
|
790
|
+
* `/aiskills/global/{code}/enabled`, then reflect the server's result on the
|
|
791
|
+
* matching card. Ignores clicks while a request for the same code is pending.
|
|
792
|
+
*/
|
|
793
|
+
_handleGlobalSkillEnable(skill, e) {
|
|
794
|
+
// The card body inserts the skill; the `+` button must not also do that.
|
|
795
|
+
e.stopPropagation();
|
|
796
|
+
if (this._globalSkillPending.has(skill.code)) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
this._globalSkillPending.add(skill.code);
|
|
800
|
+
this.requestUpdate();
|
|
801
|
+
KRHttp.fetch({
|
|
802
|
+
url: `/api/system/opensearch/aiskills/global/${encodeURIComponent(skill.code)}/enabled`,
|
|
803
|
+
method: 'POST',
|
|
804
|
+
body: { enabled: !skill.enabled },
|
|
805
|
+
})
|
|
806
|
+
.then((r) => r.json())
|
|
807
|
+
.then((res) => {
|
|
808
|
+
// Reflect the server's returned flag on the card.
|
|
809
|
+
skill.enabled = res?.data?.enabled;
|
|
810
|
+
this.requestUpdate();
|
|
811
|
+
})
|
|
812
|
+
.catch((error) => {
|
|
813
|
+
console.error('kr-chatbot: global skill enable failed', error);
|
|
814
|
+
})
|
|
815
|
+
.finally(() => {
|
|
816
|
+
this._globalSkillPending.delete(skill.code);
|
|
817
|
+
this.requestUpdate();
|
|
818
|
+
});
|
|
819
|
+
}
|
|
638
820
|
/** The `+` button toggles the skill/attachment menu open (showing all skills). */
|
|
639
821
|
_handlePlusClick() {
|
|
640
822
|
if (this._skillMenuOpened) {
|
|
@@ -649,10 +831,18 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
649
831
|
/** Insert `/code ` into the composer and focus it, ready for the message. */
|
|
650
832
|
_handleSkillSelect(skill) {
|
|
651
833
|
this._inputValue = `/${skill.code} `;
|
|
834
|
+
this._saveState();
|
|
652
835
|
this._skillMenuOpened = false;
|
|
653
836
|
this._skillBrowseOpened = false;
|
|
654
837
|
this._inputEl?.focus();
|
|
655
838
|
}
|
|
839
|
+
/** Activate a skill card via keyboard (Enter/Space), since it's a div role=button. */
|
|
840
|
+
_handleSkillCardKeydown(e, skill) {
|
|
841
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
842
|
+
e.preventDefault();
|
|
843
|
+
this._handleSkillSelect(skill);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
656
846
|
/** Copy a message's text to the clipboard and briefly flash "Copied". */
|
|
657
847
|
_handleCopyMessage(content, index) {
|
|
658
848
|
navigator.clipboard.writeText(content).then(() => {
|
|
@@ -668,16 +858,22 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
668
858
|
this._skillMenuOpened = false;
|
|
669
859
|
this._skillBrowseQuery = '';
|
|
670
860
|
this._skillBrowseOpened = true;
|
|
861
|
+
// Fill both tabs up front so switching between them is instant, not stale.
|
|
671
862
|
this._searchSkills('');
|
|
863
|
+
this._searchGlobalSkills('');
|
|
672
864
|
}
|
|
673
865
|
_handleSkillBrowseClose() {
|
|
674
866
|
this._skillBrowseOpened = false;
|
|
675
867
|
}
|
|
676
868
|
_handleSkillBrowseSearch(e) {
|
|
677
869
|
this._skillBrowseQuery = e.target.value;
|
|
870
|
+
// Filter both catalogs so whichever tab the user switches to is already current.
|
|
678
871
|
this._searchSkills(this._skillBrowseQuery);
|
|
872
|
+
this._searchGlobalSkills(this._skillBrowseQuery);
|
|
679
873
|
}
|
|
680
874
|
_handleSkillBrowseTabChange(e) {
|
|
875
|
+
// Both buffers are kept current by open/search, so switching just swaps which
|
|
876
|
+
// one is shown — no re-search needed.
|
|
681
877
|
this._skillBrowseSource = e.detail.activeTabId;
|
|
682
878
|
}
|
|
683
879
|
// --- Attachments ---
|
|
@@ -821,6 +1017,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
821
1017
|
};
|
|
822
1018
|
this.messages = [...this.messages, userMessage];
|
|
823
1019
|
this._inputValue = '';
|
|
1020
|
+
this._saveState();
|
|
824
1021
|
this._error = '';
|
|
825
1022
|
this._userHasScrolledUp = false;
|
|
826
1023
|
// Clear the composer and release its preview object URLs.
|
|
@@ -930,6 +1127,14 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
930
1127
|
this._error = '';
|
|
931
1128
|
this._inputValue = '';
|
|
932
1129
|
this._userHasScrolledUp = false;
|
|
1130
|
+
// Reset conversation-scoped context + telemetry state for the new convo.
|
|
1131
|
+
this._contextWarning = '';
|
|
1132
|
+
this._contextLimitReached = false;
|
|
1133
|
+
this._contextLimitMessage = '';
|
|
1134
|
+
this._telemetry = null;
|
|
1135
|
+
this._iterationLog = [];
|
|
1136
|
+
this._cacheCheckpoints = {};
|
|
1137
|
+
this._saveState();
|
|
933
1138
|
});
|
|
934
1139
|
}
|
|
935
1140
|
_handleMessagesScroll(e) {
|
|
@@ -945,6 +1150,22 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
945
1150
|
_handleErrorDismiss() {
|
|
946
1151
|
this._error = '';
|
|
947
1152
|
}
|
|
1153
|
+
_handleContextWarningDismiss() {
|
|
1154
|
+
this._contextWarning = '';
|
|
1155
|
+
}
|
|
1156
|
+
_handleToggleDebugPanel() {
|
|
1157
|
+
this._debugPanelOpen = !this._debugPanelOpen;
|
|
1158
|
+
this._saveState();
|
|
1159
|
+
}
|
|
1160
|
+
_handleDebugResizeMouseDown(e) {
|
|
1161
|
+
this._isDebugResizing = true;
|
|
1162
|
+
this._debugResizeStartX = e.clientX;
|
|
1163
|
+
this._debugResizeStartW = this._debugDrawerWidth;
|
|
1164
|
+
e.preventDefault();
|
|
1165
|
+
e.stopPropagation();
|
|
1166
|
+
document.addEventListener('mousemove', this._handleDebugResizeMouseMove);
|
|
1167
|
+
document.addEventListener('mouseup', this._handleDebugResizeMouseUp);
|
|
1168
|
+
}
|
|
948
1169
|
_readStreamChunk() {
|
|
949
1170
|
if (!this._reader) {
|
|
950
1171
|
return;
|
|
@@ -1008,6 +1229,47 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1008
1229
|
else if (data.type === 'ERROR') {
|
|
1009
1230
|
this._handleStreamError(String(data.message || 'Unknown error'));
|
|
1010
1231
|
}
|
|
1232
|
+
else if (data.type === 'CONVERSATION_TOKEN_WARNING') {
|
|
1233
|
+
// User-facing: friendly "conversation is getting long" guidance.
|
|
1234
|
+
// Text only, never sent to the model — render verbatim as a banner.
|
|
1235
|
+
this._contextWarning = String(data.message || '');
|
|
1236
|
+
}
|
|
1237
|
+
else if (data.type === 'CONVERSATION_LIMIT_REACHED') {
|
|
1238
|
+
// User-facing hard stop. Carries finished:true (handled below).
|
|
1239
|
+
// Clear any soft warning so we don't double-message the user.
|
|
1240
|
+
this._contextLimitReached = true;
|
|
1241
|
+
this._contextLimitMessage = String(data.message || '');
|
|
1242
|
+
this._contextWarning = '';
|
|
1243
|
+
}
|
|
1244
|
+
else if (data.type === 'TOKEN_USAGE') {
|
|
1245
|
+
// INTERNAL-ONLY debug telemetry. Update the panel snapshot and the
|
|
1246
|
+
// per-iteration log; never render as a chat bubble.
|
|
1247
|
+
if (data.data) {
|
|
1248
|
+
const usage = data.data;
|
|
1249
|
+
this._telemetry = usage;
|
|
1250
|
+
// A new turn starts at iteration 1 — clear the prior turn's log
|
|
1251
|
+
// only now (not at send), so it stays visible until fresh data
|
|
1252
|
+
// actually arrives rather than blanking out on submit.
|
|
1253
|
+
if (usage.iteration === 1) {
|
|
1254
|
+
this._iterationLog = [usage];
|
|
1255
|
+
this._cacheCheckpoints = {};
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
this._iterationLog = [...this._iterationLog, usage];
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
else if (data.type === 'CACHE_CHECKPOINT') {
|
|
1263
|
+
// INTERNAL-ONLY debug telemetry. Records when/under which mode the
|
|
1264
|
+
// history checkpoint was placed, keyed by iteration for the table.
|
|
1265
|
+
if (data.data) {
|
|
1266
|
+
const checkpoint = data.data;
|
|
1267
|
+
this._cacheCheckpoints = {
|
|
1268
|
+
...this._cacheCheckpoints,
|
|
1269
|
+
[checkpoint.iteration]: checkpoint,
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1011
1273
|
if (data.finished) {
|
|
1012
1274
|
this._isStreaming = false;
|
|
1013
1275
|
this._reader = null;
|
|
@@ -1108,37 +1370,81 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1108
1370
|
${this._renderSkillGrid()}
|
|
1109
1371
|
</kr-tab>
|
|
1110
1372
|
<kr-tab id="kodaris" label="Kodaris">
|
|
1111
|
-
|
|
1112
|
-
Shared Kodaris skills are coming soon.
|
|
1113
|
-
</div>
|
|
1373
|
+
${this._renderSkillGrid(true)}
|
|
1114
1374
|
</kr-tab>
|
|
1115
1375
|
</kr-tab-group>
|
|
1116
1376
|
</div>
|
|
1117
1377
|
</kr-dialog>
|
|
1118
1378
|
`;
|
|
1119
1379
|
}
|
|
1120
|
-
/**
|
|
1121
|
-
|
|
1122
|
-
|
|
1380
|
+
/**
|
|
1381
|
+
* The card grid of skill search results for the browse dialog. Clicking a card
|
|
1382
|
+
* inserts `/code ` into the composer. In the shared-Kodaris (`global`) tab each
|
|
1383
|
+
* card also gets a `+` install button that enables the skill on this instance
|
|
1384
|
+
* (turning into a ✓ once installed).
|
|
1385
|
+
*/
|
|
1386
|
+
_renderSkillGrid(global = false) {
|
|
1387
|
+
let skills = this._skillResults;
|
|
1388
|
+
if (global) {
|
|
1389
|
+
skills = this._globalSkillResults;
|
|
1390
|
+
}
|
|
1123
1391
|
if (!skills.length) {
|
|
1124
1392
|
return nothing;
|
|
1125
1393
|
}
|
|
1126
1394
|
return html `
|
|
1127
1395
|
<div class="skill-browse__grid">
|
|
1128
1396
|
${skills.map((skill) => html `
|
|
1129
|
-
<
|
|
1397
|
+
<div
|
|
1130
1398
|
class="skill-card"
|
|
1399
|
+
role="button"
|
|
1400
|
+
tabindex="0"
|
|
1131
1401
|
@click=${() => this._handleSkillSelect(skill)}
|
|
1402
|
+
@keydown=${(e) => this._handleSkillCardKeydown(e, skill)}
|
|
1132
1403
|
>
|
|
1133
1404
|
<span class="skill-card__name">${skill.name}</span>
|
|
1134
1405
|
${skill.description ? html `
|
|
1135
1406
|
<span class="skill-card__desc">${skill.description}</span>
|
|
1136
1407
|
` : nothing}
|
|
1137
|
-
|
|
1408
|
+
${global ? this._renderSkillInstall(skill) : nothing}
|
|
1409
|
+
</div>
|
|
1138
1410
|
`)}
|
|
1139
1411
|
</div>
|
|
1140
1412
|
`;
|
|
1141
1413
|
}
|
|
1414
|
+
/**
|
|
1415
|
+
* The install affordance on a global skill card: a `+` button that enables the
|
|
1416
|
+
* skill on this instance, showing a ✓ once installed and a disabled state
|
|
1417
|
+
* while the enable/disable request is in flight.
|
|
1418
|
+
*/
|
|
1419
|
+
_renderSkillInstall(skill) {
|
|
1420
|
+
return html `
|
|
1421
|
+
<button
|
|
1422
|
+
class=${classMap({
|
|
1423
|
+
'skill-card__install': true,
|
|
1424
|
+
'skill-card__install--installed': !!skill.enabled,
|
|
1425
|
+
})}
|
|
1426
|
+
?disabled=${this._globalSkillPending.has(skill.code)}
|
|
1427
|
+
title=${!!skill.enabled ? 'Installed — click to remove' : 'Install this skill'}
|
|
1428
|
+
aria-label=${!!skill.enabled ? 'Remove skill' : 'Install skill'}
|
|
1429
|
+
@click=${(e) => this._handleGlobalSkillEnable(skill, e)}
|
|
1430
|
+
>
|
|
1431
|
+
<svg
|
|
1432
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1433
|
+
fill="none"
|
|
1434
|
+
viewBox="0 0 24 24"
|
|
1435
|
+
stroke-width="2"
|
|
1436
|
+
stroke="currentColor"
|
|
1437
|
+
class="skill-card__install-icon"
|
|
1438
|
+
>
|
|
1439
|
+
<path
|
|
1440
|
+
stroke-linecap="round"
|
|
1441
|
+
stroke-linejoin="round"
|
|
1442
|
+
d=${!!skill.enabled ? 'm4.5 12.75 6 6 9-13.5' : 'M12 4.5v15m7.5-7.5h-15'}
|
|
1443
|
+
/>
|
|
1444
|
+
</svg>
|
|
1445
|
+
</button>
|
|
1446
|
+
`;
|
|
1447
|
+
}
|
|
1142
1448
|
/**
|
|
1143
1449
|
* Render a user message, highlighting a leading `/skill-code` token (like the
|
|
1144
1450
|
* way Claude highlights slash commands). The composer only inserts valid skill
|
|
@@ -1151,7 +1457,162 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1151
1457
|
}
|
|
1152
1458
|
return content;
|
|
1153
1459
|
}
|
|
1460
|
+
/** Compact integer formatting for the debug panel (e.g. 88928 → "88,928"). */
|
|
1461
|
+
_fmtTokens(n) {
|
|
1462
|
+
if (n == null || isNaN(n)) {
|
|
1463
|
+
return '—';
|
|
1464
|
+
}
|
|
1465
|
+
return n.toLocaleString('en-US');
|
|
1466
|
+
}
|
|
1467
|
+
/** Approximate USD formatting for the debug panel (e.g. 0.1434 → "~$0.1434"). */
|
|
1468
|
+
_fmtCost(n) {
|
|
1469
|
+
if (n == null || isNaN(n)) {
|
|
1470
|
+
return '—';
|
|
1471
|
+
}
|
|
1472
|
+
return '~$' + n.toFixed(4);
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* INTERNAL-ONLY debug telemetry panel. Rendered only in the inline variant
|
|
1476
|
+
* (guarded by the caller) and only when the user has toggled it open. Shows
|
|
1477
|
+
* three scopes from the latest TOKEN_USAGE event — context gauge, this turn,
|
|
1478
|
+
* lifetime — plus a verbose per-iteration table. Never customer-facing.
|
|
1479
|
+
*/
|
|
1480
|
+
_renderDebugPanel() {
|
|
1481
|
+
return html `
|
|
1482
|
+
<div
|
|
1483
|
+
class=${classMap({
|
|
1484
|
+
'debug-drawer': true,
|
|
1485
|
+
'debug-drawer--open': this._debugPanelOpen,
|
|
1486
|
+
'debug-drawer--resizing': this._isDebugResizing,
|
|
1487
|
+
})}
|
|
1488
|
+
style=${styleMap({ width: `${this._debugDrawerWidth}px` })}
|
|
1489
|
+
>
|
|
1490
|
+
<div
|
|
1491
|
+
class="debug-drawer__resize"
|
|
1492
|
+
@mousedown=${this._handleDebugResizeMouseDown}
|
|
1493
|
+
></div>
|
|
1494
|
+
<div class="debug-drawer__header">
|
|
1495
|
+
<span class="debug-drawer__title">Debug</span>
|
|
1496
|
+
<button
|
|
1497
|
+
class="header-action"
|
|
1498
|
+
@click=${this._handleToggleDebugPanel}
|
|
1499
|
+
title="Close debug panel"
|
|
1500
|
+
>
|
|
1501
|
+
<svg
|
|
1502
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1503
|
+
fill="none"
|
|
1504
|
+
viewBox="0 0 24 24"
|
|
1505
|
+
stroke-width="2"
|
|
1506
|
+
stroke="currentColor"
|
|
1507
|
+
class="header-action-icon"
|
|
1508
|
+
>
|
|
1509
|
+
<path
|
|
1510
|
+
stroke-linecap="round"
|
|
1511
|
+
stroke-linejoin="round"
|
|
1512
|
+
d="M6 18 18 6M6 6l12 12"
|
|
1513
|
+
/>
|
|
1514
|
+
</svg>
|
|
1515
|
+
</button>
|
|
1516
|
+
</div>
|
|
1517
|
+
<div class="debug-drawer__body">
|
|
1518
|
+
${this._telemetry ? html `
|
|
1519
|
+
<div class="debug-section">
|
|
1520
|
+
<div class="debug-section__label">Context</div>
|
|
1521
|
+
<div class="debug-gauge">
|
|
1522
|
+
<div class="debug-gauge__bar">
|
|
1523
|
+
<div
|
|
1524
|
+
class="debug-gauge__fill"
|
|
1525
|
+
style=${styleMap({ width: `${Math.min(100, Math.max(0, this._telemetry.contextUsedPercent || 0))}%` })}
|
|
1526
|
+
></div>
|
|
1527
|
+
</div>
|
|
1528
|
+
<span class="debug-gauge__text">
|
|
1529
|
+
${this._fmtTokens(this._telemetry.currentContextTokens)} / ${this._fmtTokens(this._telemetry.contextWindowTokens)}
|
|
1530
|
+
· ${this._telemetry.contextUsedPercent ?? 0}%
|
|
1531
|
+
</span>
|
|
1532
|
+
</div>
|
|
1533
|
+
</div>
|
|
1534
|
+
|
|
1535
|
+
<div class="debug-section">
|
|
1536
|
+
<div class="debug-section__label">This turn</div>
|
|
1537
|
+
<div class="debug-metrics">
|
|
1538
|
+
<span class="debug-metrics__key">Input</span>
|
|
1539
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnInputTokens)}</span>
|
|
1540
|
+
<span class="debug-metrics__key">Output</span>
|
|
1541
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnOutputTokens)}</span>
|
|
1542
|
+
<span class="debug-metrics__key">Cache read</span>
|
|
1543
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.turnCacheReadTokens)}</span>
|
|
1544
|
+
<span class="debug-metrics__key">Hit rate</span>
|
|
1545
|
+
<span class="debug-metrics__val">${this._telemetry.turnHitRate ?? '—'}%</span>
|
|
1546
|
+
<span class="debug-metrics__key">Cost</span>
|
|
1547
|
+
<span class="debug-metrics__val debug-cost">${this._fmtCost(this._telemetry.turnCostUsd)}</span>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
|
|
1551
|
+
<div class="debug-section">
|
|
1552
|
+
<div class="debug-section__label">Lifetime</div>
|
|
1553
|
+
<div class="debug-metrics">
|
|
1554
|
+
<span class="debug-metrics__key">Input</span>
|
|
1555
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationInputTokens)}</span>
|
|
1556
|
+
<span class="debug-metrics__key">Output</span>
|
|
1557
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationOutputTokens)}</span>
|
|
1558
|
+
<span class="debug-metrics__key">Cache read</span>
|
|
1559
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationCacheReadTokens)}</span>
|
|
1560
|
+
<span class="debug-metrics__key">Cache write</span>
|
|
1561
|
+
<span class="debug-metrics__val">${this._fmtTokens(this._telemetry.conversationCacheWriteTokens)}</span>
|
|
1562
|
+
<span class="debug-metrics__key">Cost so far</span>
|
|
1563
|
+
<span class="debug-metrics__val debug-cost">${this._fmtCost(this._telemetry.conversationCostUsd)}</span>
|
|
1564
|
+
</div>
|
|
1565
|
+
</div>
|
|
1566
|
+
` : html `
|
|
1567
|
+
<div class="debug-empty">No telemetry yet — send a message to populate.</div>
|
|
1568
|
+
`}
|
|
1569
|
+
|
|
1570
|
+
${this._iterationLog.length ? html `
|
|
1571
|
+
<div class="debug-section">
|
|
1572
|
+
<div class="debug-section__label">Iterations (this turn)</div>
|
|
1573
|
+
<div class="debug-iterations-scroll">
|
|
1574
|
+
<table class="debug-iterations">
|
|
1575
|
+
<thead>
|
|
1576
|
+
<tr>
|
|
1577
|
+
<th>#</th>
|
|
1578
|
+
<th>In</th>
|
|
1579
|
+
<th>Out</th>
|
|
1580
|
+
<th>Cache R</th>
|
|
1581
|
+
<th>Cache W</th>
|
|
1582
|
+
<th>Hit</th>
|
|
1583
|
+
<th>Cost</th>
|
|
1584
|
+
<th>Ckpt</th>
|
|
1585
|
+
</tr>
|
|
1586
|
+
</thead>
|
|
1587
|
+
<tbody>
|
|
1588
|
+
${this._iterationLog.map((it) => {
|
|
1589
|
+
const ckpt = this._cacheCheckpoints[it.iteration];
|
|
1590
|
+
return html `
|
|
1591
|
+
<tr>
|
|
1592
|
+
<td>${it.iteration}</td>
|
|
1593
|
+
<td>${this._fmtTokens(it.iterationInput)}</td>
|
|
1594
|
+
<td>${this._fmtTokens(it.iterationOutput)}</td>
|
|
1595
|
+
<td>${this._fmtTokens(it.iterationCacheRead)}</td>
|
|
1596
|
+
<td>${this._fmtTokens(it.iterationCacheWrite)}</td>
|
|
1597
|
+
<td>${it.iterationHitRate ?? '—'}%</td>
|
|
1598
|
+
<td class="debug-cost">${this._fmtCost(it.iterationCostUsd)}</td>
|
|
1599
|
+
<td>${ckpt ? ckpt.mode : '—'}</td>
|
|
1600
|
+
</tr>
|
|
1601
|
+
`;
|
|
1602
|
+
})}
|
|
1603
|
+
</tbody>
|
|
1604
|
+
</table>
|
|
1605
|
+
</div>
|
|
1606
|
+
</div>
|
|
1607
|
+
` : nothing}
|
|
1608
|
+
</div>
|
|
1609
|
+
</div>
|
|
1610
|
+
`;
|
|
1611
|
+
}
|
|
1154
1612
|
render() {
|
|
1613
|
+
// Debug affordances (drawer toggle, drawer, /test-* options) require the
|
|
1614
|
+
// inline variant AND the server-side debug flag from aiFrameworkConfig.
|
|
1615
|
+
const isDebugEnabled = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
1155
1616
|
return html `
|
|
1156
1617
|
${(this.hideToggle || this.variant === 'inline') ? nothing : html `
|
|
1157
1618
|
<button
|
|
@@ -1180,7 +1641,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1180
1641
|
class=${classMap({
|
|
1181
1642
|
'panel': true,
|
|
1182
1643
|
'panel--opened': this.opened,
|
|
1644
|
+
'panel--resizing-debug': this._isDebugResizing,
|
|
1183
1645
|
})}
|
|
1646
|
+
style=${styleMap(this._debugPanelOpen ? { paddingRight: `${this._debugDrawerWidth}px` } : {})}
|
|
1184
1647
|
@dragenter=${this._handleDragEnter}
|
|
1185
1648
|
@dragover=${this._handleDragOver}
|
|
1186
1649
|
@dragleave=${this._handleDragLeave}
|
|
@@ -1253,6 +1716,31 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1253
1716
|
</div>
|
|
1254
1717
|
</div>
|
|
1255
1718
|
<div class="header-actions">
|
|
1719
|
+
${isDebugEnabled ? html `
|
|
1720
|
+
<button
|
|
1721
|
+
class=${classMap({
|
|
1722
|
+
'header-action': true,
|
|
1723
|
+
'header-action--active': this._debugPanelOpen,
|
|
1724
|
+
})}
|
|
1725
|
+
@click=${this._handleToggleDebugPanel}
|
|
1726
|
+
title="Debug telemetry"
|
|
1727
|
+
>
|
|
1728
|
+
<svg
|
|
1729
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1730
|
+
fill="none"
|
|
1731
|
+
viewBox="0 0 24 24"
|
|
1732
|
+
stroke-width="1.5"
|
|
1733
|
+
stroke="currentColor"
|
|
1734
|
+
class="header-action-icon"
|
|
1735
|
+
>
|
|
1736
|
+
<path
|
|
1737
|
+
stroke-linecap="round"
|
|
1738
|
+
stroke-linejoin="round"
|
|
1739
|
+
d="M3.75 3v11.25A2.25 2.25 0 0 0 6 16.5h2.25M3.75 3h-1.5m1.5 0h16.5m0 0h1.5m-1.5 0v11.25A2.25 2.25 0 0 1 18 16.5h-2.25m-7.5 0h7.5m-7.5 0-1 3m8.5-3 1 3m0 0 .5 1.5m-.5-1.5h-9.5m0 0-.5 1.5M9 11.25v1.5M12 9v3.75m3-6v6"
|
|
1740
|
+
/>
|
|
1741
|
+
</svg>
|
|
1742
|
+
</button>
|
|
1743
|
+
` : nothing}
|
|
1256
1744
|
${this.messages.length > 0 ? html `
|
|
1257
1745
|
<button
|
|
1258
1746
|
class="header-action"
|
|
@@ -1417,6 +1905,37 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1417
1905
|
</div>
|
|
1418
1906
|
` : nothing}
|
|
1419
1907
|
|
|
1908
|
+
${this._contextLimitReached ? html `
|
|
1909
|
+
<div class="context-limit" role="alert">
|
|
1910
|
+
${this._contextLimitMessage
|
|
1911
|
+
|| 'This conversation has reached its limit. Please start a new one to continue.'}
|
|
1912
|
+
</div>
|
|
1913
|
+
` : this._contextWarning ? html `
|
|
1914
|
+
<div class="context-warning" role="status">
|
|
1915
|
+
<span>${this._contextWarning}</span>
|
|
1916
|
+
<button
|
|
1917
|
+
class="context-warning-dismiss"
|
|
1918
|
+
title="Dismiss"
|
|
1919
|
+
@click=${this._handleContextWarningDismiss}
|
|
1920
|
+
>
|
|
1921
|
+
<svg
|
|
1922
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1923
|
+
fill="none"
|
|
1924
|
+
viewBox="0 0 24 24"
|
|
1925
|
+
stroke-width="2"
|
|
1926
|
+
stroke="currentColor"
|
|
1927
|
+
class="context-warning-dismiss-icon"
|
|
1928
|
+
>
|
|
1929
|
+
<path
|
|
1930
|
+
stroke-linecap="round"
|
|
1931
|
+
stroke-linejoin="round"
|
|
1932
|
+
d="M6 18 18 6M6 6l12 12"
|
|
1933
|
+
/>
|
|
1934
|
+
</svg>
|
|
1935
|
+
</button>
|
|
1936
|
+
</div>
|
|
1937
|
+
` : nothing}
|
|
1938
|
+
|
|
1420
1939
|
<div class="footer">
|
|
1421
1940
|
${this._pendingInternalFiles.length ? html `
|
|
1422
1941
|
<div class="composer-attachments">
|
|
@@ -1516,6 +2035,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1516
2035
|
</div>
|
|
1517
2036
|
</div>
|
|
1518
2037
|
${this._skillBrowseOpened ? this._renderSkillBrowse() : nothing}
|
|
2038
|
+
${isDebugEnabled ? this._renderDebugPanel() : nothing}
|
|
1519
2039
|
</div>
|
|
1520
2040
|
`;
|
|
1521
2041
|
}
|
|
@@ -1697,6 +2217,11 @@ KRChatbot.styles = css `
|
|
|
1697
2217
|
color: #000000;
|
|
1698
2218
|
}
|
|
1699
2219
|
|
|
2220
|
+
.header-action--active {
|
|
2221
|
+
background: rgba(0, 0, 0, 0.08);
|
|
2222
|
+
color: #000000;
|
|
2223
|
+
}
|
|
2224
|
+
|
|
1700
2225
|
.header-action-icon {
|
|
1701
2226
|
width: 20px;
|
|
1702
2227
|
height: 20px;
|
|
@@ -1726,19 +2251,6 @@ KRChatbot.styles = css `
|
|
|
1726
2251
|
margin-right: auto;
|
|
1727
2252
|
}
|
|
1728
2253
|
|
|
1729
|
-
.messages::-webkit-scrollbar {
|
|
1730
|
-
width: 4px;
|
|
1731
|
-
}
|
|
1732
|
-
|
|
1733
|
-
.messages::-webkit-scrollbar-track {
|
|
1734
|
-
background: transparent;
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
.messages::-webkit-scrollbar-thumb {
|
|
1738
|
-
background: rgba(0, 0, 0, 0.08);
|
|
1739
|
-
border-radius: 2px;
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
2254
|
.empty {
|
|
1743
2255
|
flex: 1;
|
|
1744
2256
|
display: flex;
|
|
@@ -1977,6 +2489,238 @@ KRChatbot.styles = css `
|
|
|
1977
2489
|
height: 14px;
|
|
1978
2490
|
}
|
|
1979
2491
|
|
|
2492
|
+
/* Context warning banner (user-facing, dismissible, non-blocking) */
|
|
2493
|
+
.context-warning {
|
|
2494
|
+
padding: 8px 16px;
|
|
2495
|
+
background: #fffbeb;
|
|
2496
|
+
color: #92400e;
|
|
2497
|
+
font-size: 13px;
|
|
2498
|
+
display: flex;
|
|
2499
|
+
align-items: center;
|
|
2500
|
+
justify-content: space-between;
|
|
2501
|
+
gap: 8px;
|
|
2502
|
+
flex-shrink: 0;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
.context-warning-dismiss {
|
|
2506
|
+
background: none;
|
|
2507
|
+
border: none;
|
|
2508
|
+
color: #92400e;
|
|
2509
|
+
cursor: pointer;
|
|
2510
|
+
padding: 2px;
|
|
2511
|
+
display: flex;
|
|
2512
|
+
align-items: center;
|
|
2513
|
+
flex-shrink: 0;
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
.context-warning-dismiss-icon {
|
|
2517
|
+
width: 14px;
|
|
2518
|
+
height: 14px;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
/* Context-limit notice (user-facing, blocking — steers to a new conversation) */
|
|
2522
|
+
.context-limit {
|
|
2523
|
+
padding: 10px 16px;
|
|
2524
|
+
background: var(--kr-chatbot-error-bg);
|
|
2525
|
+
color: var(--kr-chatbot-error-text);
|
|
2526
|
+
font-size: 13px;
|
|
2527
|
+
line-height: 1.5;
|
|
2528
|
+
flex-shrink: 0;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
/*
|
|
2532
|
+
* INTERNAL-ONLY debug telemetry drawer. A layout drawer docked to the right
|
|
2533
|
+
* of the inline chat — the chat column shrinks to make room; it does not
|
|
2534
|
+
* overlay. Closed by default (width 0), animates open. Not customer-facing —
|
|
2535
|
+
* token counts, cache internals, and approximate cost estimates for internal
|
|
2536
|
+
* debugging. Styled to match the chatbot's light theme (Inter, design tokens).
|
|
2537
|
+
*/
|
|
2538
|
+
/*
|
|
2539
|
+
* The chat column reserves space for the drawer by animating padding-right,
|
|
2540
|
+
* so opening the drawer shrinks the chat (header/messages/composer) rather
|
|
2541
|
+
* than overlaying it. Only applies in inline mode.
|
|
2542
|
+
*/
|
|
2543
|
+
:host([variant='inline']) .panel {
|
|
2544
|
+
transition: padding-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
/* Track the cursor 1:1 while dragging the drawer's resize handle. */
|
|
2548
|
+
:host([variant='inline']) .panel--resizing-debug {
|
|
2549
|
+
transition: none;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
.debug-drawer {
|
|
2553
|
+
position: absolute;
|
|
2554
|
+
top: 0;
|
|
2555
|
+
right: 0;
|
|
2556
|
+
bottom: 0;
|
|
2557
|
+
background: var(--kr-chatbot-bg);
|
|
2558
|
+
display: flex;
|
|
2559
|
+
flex-direction: column;
|
|
2560
|
+
transform: translateX(100%);
|
|
2561
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
2562
|
+
color: #000000;
|
|
2563
|
+
z-index: 5;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
/* While dragging the resize handle, suppress the slide transition so the
|
|
2567
|
+
drawer tracks the cursor 1:1. */
|
|
2568
|
+
.debug-drawer--resizing {
|
|
2569
|
+
transition: none;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
.debug-drawer--open {
|
|
2573
|
+
transform: translateX(0);
|
|
2574
|
+
box-shadow: -4px 0 20px rgba(10, 26, 46, 0.07);
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
/* Left-edge grab handle for resizing the drawer. */
|
|
2578
|
+
.debug-drawer__resize {
|
|
2579
|
+
position: absolute;
|
|
2580
|
+
top: 0;
|
|
2581
|
+
left: 0;
|
|
2582
|
+
bottom: 0;
|
|
2583
|
+
width: 10px;
|
|
2584
|
+
cursor: ew-resize;
|
|
2585
|
+
z-index: 10;
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
.debug-drawer__header {
|
|
2589
|
+
display: flex;
|
|
2590
|
+
align-items: center;
|
|
2591
|
+
justify-content: space-between;
|
|
2592
|
+
padding: 0 12px 0 20px;
|
|
2593
|
+
height: 64px;
|
|
2594
|
+
flex-shrink: 0;
|
|
2595
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
.debug-drawer__title {
|
|
2599
|
+
font-size: 14px;
|
|
2600
|
+
font-weight: 600;
|
|
2601
|
+
color: #000000;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
.debug-drawer__body {
|
|
2605
|
+
flex: 1;
|
|
2606
|
+
overflow-y: auto;
|
|
2607
|
+
overflow-x: hidden;
|
|
2608
|
+
padding: 4px 0;
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
.debug-section {
|
|
2612
|
+
padding: 14px 20px;
|
|
2613
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
.debug-section:last-child {
|
|
2617
|
+
border-bottom: none;
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
.debug-section__label {
|
|
2621
|
+
text-transform: uppercase;
|
|
2622
|
+
letter-spacing: 0.06em;
|
|
2623
|
+
color: #000000;
|
|
2624
|
+
font-size: 11px;
|
|
2625
|
+
font-weight: 600;
|
|
2626
|
+
margin-bottom: 10px;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
/* Context-fullness gauge */
|
|
2630
|
+
.debug-gauge {
|
|
2631
|
+
display: flex;
|
|
2632
|
+
align-items: center;
|
|
2633
|
+
gap: 10px;
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
.debug-gauge__bar {
|
|
2637
|
+
flex: 1;
|
|
2638
|
+
height: 8px;
|
|
2639
|
+
border-radius: 4px;
|
|
2640
|
+
background: rgb(244 244 244 / 100%);
|
|
2641
|
+
overflow: hidden;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
.debug-gauge__fill {
|
|
2645
|
+
height: 100%;
|
|
2646
|
+
border-radius: 4px;
|
|
2647
|
+
background: var(--kr-chatbot-primary);
|
|
2648
|
+
transition: width 0.3s ease;
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
.debug-gauge__text {
|
|
2652
|
+
color: #000000;
|
|
2653
|
+
font-size: 12px;
|
|
2654
|
+
white-space: nowrap;
|
|
2655
|
+
font-variant-numeric: tabular-nums;
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
/* Metric rows */
|
|
2659
|
+
.debug-metrics {
|
|
2660
|
+
display: grid;
|
|
2661
|
+
grid-template-columns: auto 1fr;
|
|
2662
|
+
column-gap: 12px;
|
|
2663
|
+
row-gap: 7px;
|
|
2664
|
+
font-size: 13px;
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
.debug-metrics__key {
|
|
2668
|
+
color: #000000;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
.debug-metrics__val {
|
|
2672
|
+
color: #000000;
|
|
2673
|
+
text-align: right;
|
|
2674
|
+
font-variant-numeric: tabular-nums;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
.debug-cost {
|
|
2678
|
+
font-weight: 600;
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
/* Per-iteration table (verbose). The wrapper scrolls horizontally on its own
|
|
2682
|
+
so a wide table never forces the whole drawer to scroll sideways. */
|
|
2683
|
+
.debug-iterations-scroll {
|
|
2684
|
+
overflow-x: auto;
|
|
2685
|
+
max-width: 100%;
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
.debug-iterations {
|
|
2689
|
+
width: 100%;
|
|
2690
|
+
border-collapse: collapse;
|
|
2691
|
+
font-size: 12px;
|
|
2692
|
+
font-variant-numeric: tabular-nums;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
.debug-iterations th,
|
|
2696
|
+
.debug-iterations td {
|
|
2697
|
+
padding: 5px 6px;
|
|
2698
|
+
text-align: right;
|
|
2699
|
+
white-space: nowrap;
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
.debug-iterations th {
|
|
2703
|
+
color: #000000;
|
|
2704
|
+
font-weight: 600;
|
|
2705
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.10);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
.debug-iterations tbody tr:not(:last-child) td {
|
|
2709
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
.debug-iterations td:first-child,
|
|
2713
|
+
.debug-iterations th:first-child {
|
|
2714
|
+
text-align: left;
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
.debug-empty {
|
|
2718
|
+
color: #000000;
|
|
2719
|
+
padding: 20px;
|
|
2720
|
+
font-size: 13px;
|
|
2721
|
+
text-align: center;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
1980
2724
|
/* Footer / Input */
|
|
1981
2725
|
.footer {
|
|
1982
2726
|
padding: 16px 16px 16px;
|
|
@@ -2316,6 +3060,7 @@ KRChatbot.styles = css `
|
|
|
2316
3060
|
}
|
|
2317
3061
|
|
|
2318
3062
|
.skill-card {
|
|
3063
|
+
position: relative;
|
|
2319
3064
|
display: flex;
|
|
2320
3065
|
flex-direction: column;
|
|
2321
3066
|
gap: 6px;
|
|
@@ -2335,8 +3080,53 @@ KRChatbot.styles = css `
|
|
|
2335
3080
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
2336
3081
|
}
|
|
2337
3082
|
|
|
3083
|
+
/* Install button on global skill cards. Absolutely positioned in the corner
|
|
3084
|
+
so it sits above the card's own click target; leave room via the name's
|
|
3085
|
+
padding-right below. */
|
|
3086
|
+
.skill-card__install {
|
|
3087
|
+
position: absolute;
|
|
3088
|
+
top: 10px;
|
|
3089
|
+
right: 10px;
|
|
3090
|
+
display: inline-flex;
|
|
3091
|
+
align-items: center;
|
|
3092
|
+
justify-content: center;
|
|
3093
|
+
width: 24px;
|
|
3094
|
+
height: 24px;
|
|
3095
|
+
padding: 0;
|
|
3096
|
+
border: 1px solid #d1d5db;
|
|
3097
|
+
border-radius: 6px;
|
|
3098
|
+
background: white;
|
|
3099
|
+
color: #374151;
|
|
3100
|
+
cursor: pointer;
|
|
3101
|
+
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
3102
|
+
}
|
|
3103
|
+
|
|
3104
|
+
.skill-card__install:hover:not(:disabled) {
|
|
3105
|
+
border-color: var(--kr-chatbot-primary);
|
|
3106
|
+
color: var(--kr-chatbot-primary);
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
.skill-card__install:disabled {
|
|
3110
|
+
opacity: 0.5;
|
|
3111
|
+
cursor: default;
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
/* Installed state: filled check. */
|
|
3115
|
+
.skill-card__install--installed {
|
|
3116
|
+
background: var(--kr-chatbot-primary);
|
|
3117
|
+
border-color: var(--kr-chatbot-primary);
|
|
3118
|
+
color: white;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
.skill-card__install-icon {
|
|
3122
|
+
width: 15px;
|
|
3123
|
+
height: 15px;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
2338
3126
|
.skill-card__name {
|
|
2339
3127
|
font-weight: 600;
|
|
3128
|
+
/* Leave room for the corner install button on global cards. */
|
|
3129
|
+
padding-right: 28px;
|
|
2340
3130
|
}
|
|
2341
3131
|
|
|
2342
3132
|
.skill-card__desc {
|
|
@@ -2484,6 +3274,33 @@ __decorate([
|
|
|
2484
3274
|
__decorate([
|
|
2485
3275
|
state()
|
|
2486
3276
|
], KRChatbot.prototype, "_error", void 0);
|
|
3277
|
+
__decorate([
|
|
3278
|
+
state()
|
|
3279
|
+
], KRChatbot.prototype, "_contextWarning", void 0);
|
|
3280
|
+
__decorate([
|
|
3281
|
+
state()
|
|
3282
|
+
], KRChatbot.prototype, "_contextLimitReached", void 0);
|
|
3283
|
+
__decorate([
|
|
3284
|
+
state()
|
|
3285
|
+
], KRChatbot.prototype, "_contextLimitMessage", void 0);
|
|
3286
|
+
__decorate([
|
|
3287
|
+
state()
|
|
3288
|
+
], KRChatbot.prototype, "_telemetry", void 0);
|
|
3289
|
+
__decorate([
|
|
3290
|
+
state()
|
|
3291
|
+
], KRChatbot.prototype, "_iterationLog", void 0);
|
|
3292
|
+
__decorate([
|
|
3293
|
+
state()
|
|
3294
|
+
], KRChatbot.prototype, "_cacheCheckpoints", void 0);
|
|
3295
|
+
__decorate([
|
|
3296
|
+
state()
|
|
3297
|
+
], KRChatbot.prototype, "_debugPanelOpen", void 0);
|
|
3298
|
+
__decorate([
|
|
3299
|
+
state()
|
|
3300
|
+
], KRChatbot.prototype, "_debugDrawerWidth", void 0);
|
|
3301
|
+
__decorate([
|
|
3302
|
+
state()
|
|
3303
|
+
], KRChatbot.prototype, "_isDebugResizing", void 0);
|
|
2487
3304
|
__decorate([
|
|
2488
3305
|
state()
|
|
2489
3306
|
], KRChatbot.prototype, "_pendingInternalFiles", void 0);
|
|
@@ -2499,6 +3316,12 @@ __decorate([
|
|
|
2499
3316
|
__decorate([
|
|
2500
3317
|
state()
|
|
2501
3318
|
], KRChatbot.prototype, "_skillResults", void 0);
|
|
3319
|
+
__decorate([
|
|
3320
|
+
state()
|
|
3321
|
+
], KRChatbot.prototype, "_globalSkillResults", void 0);
|
|
3322
|
+
__decorate([
|
|
3323
|
+
state()
|
|
3324
|
+
], KRChatbot.prototype, "_aiFrameworkConfig", void 0);
|
|
2502
3325
|
__decorate([
|
|
2503
3326
|
state()
|
|
2504
3327
|
], KRChatbot.prototype, "_skillHighlighted", void 0);
|