@kodaris/krubble-app-components 1.0.77 → 1.0.79
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 +73 -9
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +323 -36
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +323 -36
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +122 -37
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +323 -36
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +107 -22
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/chatbot.js
CHANGED
|
@@ -81,10 +81,18 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
81
81
|
/** Monotonic id source for pending attachments. */
|
|
82
82
|
this._pendingSeq = 0;
|
|
83
83
|
/**
|
|
84
|
-
* Request-id
|
|
85
|
-
* 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.
|
|
86
87
|
*/
|
|
87
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();
|
|
88
96
|
// --- @property (public API) ---
|
|
89
97
|
/**
|
|
90
98
|
* Callback function for sending messages. Receives the user's message and
|
|
@@ -190,10 +198,24 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
190
198
|
/** Current `/` query (text after the slash), sent to `_searchSkills`. */
|
|
191
199
|
this._skillQuery = '';
|
|
192
200
|
/**
|
|
193
|
-
* Latest skill search results
|
|
194
|
-
*
|
|
201
|
+
* Latest org skill search results (this instance's own skills). Backs the `/`
|
|
202
|
+
* menu and the browse dialog's "Your Org" tab.
|
|
195
203
|
*/
|
|
196
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 = {};
|
|
197
219
|
/** Highlighted item in the skill menu for keyboard navigation (-1 = none). */
|
|
198
220
|
this._skillHighlighted = 0;
|
|
199
221
|
/** Index of the message whose copy button just flashed "Copied" (-1 = none). */
|
|
@@ -201,11 +223,11 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
201
223
|
/** Whether the "Browse all skills" dialog is open. */
|
|
202
224
|
this._skillBrowseOpened = false;
|
|
203
225
|
/**
|
|
204
|
-
* Active tab in the browse dialog. `org`
|
|
205
|
-
*
|
|
226
|
+
* Active tab in the browse dialog. `org` shows `_skillResults` (this instance's
|
|
227
|
+
* skills); `kodaris` shows `_globalSkillResults` (the shared global catalog).
|
|
206
228
|
*/
|
|
207
229
|
this._skillBrowseSource = 'org';
|
|
208
|
-
/** Search text in the browse dialog,
|
|
230
|
+
/** Search text in the browse dialog, routed to the active tab's search. */
|
|
209
231
|
this._skillBrowseQuery = '';
|
|
210
232
|
this._handleMouseMove = (e) => {
|
|
211
233
|
if (this._isDragging) {
|
|
@@ -303,6 +325,17 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
303
325
|
this.messages = messages;
|
|
304
326
|
}
|
|
305
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(() => { });
|
|
306
339
|
// Inline mode fills its container and is always open — the floating widget's
|
|
307
340
|
// toggle/drag/resize state doesn't apply, but the composer message and debug
|
|
308
341
|
// drawer state (restored inside _restoreState) do.
|
|
@@ -597,11 +630,16 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
597
630
|
}
|
|
598
631
|
}
|
|
599
632
|
_handleKeyDown(e) {
|
|
600
|
-
// When the skill menu is open, arrow keys / Enter / Escape drive it. The
|
|
601
|
-
//
|
|
602
|
-
// filtered skills
|
|
633
|
+
// When the skill menu is open, arrow keys / Enter / Escape drive it. The top
|
|
634
|
+
// actions (Attach Files, Browse All Skills, plus the two debug test triggers
|
|
635
|
+
// when enabled) come first, then the filtered skills — all one navigable list.
|
|
603
636
|
if (this._skillMenuOpened) {
|
|
604
|
-
const
|
|
637
|
+
const debugActions = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
638
|
+
let actionCount = 2;
|
|
639
|
+
if (debugActions) {
|
|
640
|
+
actionCount = 4;
|
|
641
|
+
}
|
|
642
|
+
const count = actionCount + this._skillResults.length;
|
|
605
643
|
if (e.key === 'ArrowDown') {
|
|
606
644
|
e.preventDefault();
|
|
607
645
|
this._skillHighlighted = (this._skillHighlighted + 1) % count;
|
|
@@ -628,8 +666,13 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
628
666
|
this._handleSubmit();
|
|
629
667
|
}
|
|
630
668
|
}
|
|
631
|
-
/** Run the skill-menu item at the given flat index (
|
|
669
|
+
/** Run the skill-menu item at the given flat index (actions first, then skills). */
|
|
632
670
|
_activateSkillMenuItem(index) {
|
|
671
|
+
const debugActions = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
672
|
+
let actionCount = 2;
|
|
673
|
+
if (debugActions) {
|
|
674
|
+
actionCount = 4;
|
|
675
|
+
}
|
|
633
676
|
if (index === 0) {
|
|
634
677
|
this._openFilePicker();
|
|
635
678
|
return;
|
|
@@ -638,7 +681,23 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
638
681
|
this._handleSkillBrowseOpen();
|
|
639
682
|
return;
|
|
640
683
|
}
|
|
641
|
-
|
|
684
|
+
if (debugActions && index === 2) {
|
|
685
|
+
this._skillMenuOpened = false;
|
|
686
|
+
this._send({
|
|
687
|
+
message: '/test-warning',
|
|
688
|
+
internalFiles: [],
|
|
689
|
+
});
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (debugActions && index === 3) {
|
|
693
|
+
this._skillMenuOpened = false;
|
|
694
|
+
this._send({
|
|
695
|
+
message: '/test-limit',
|
|
696
|
+
internalFiles: [],
|
|
697
|
+
});
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const skill = this._skillResults[index - actionCount];
|
|
642
701
|
if (skill) {
|
|
643
702
|
this._handleSkillSelect(skill);
|
|
644
703
|
}
|
|
@@ -667,17 +726,33 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
667
726
|
// specials so user input is treated literally, then wrap with `*...*`.
|
|
668
727
|
let must = { match_all: {} };
|
|
669
728
|
if (trimmed) {
|
|
670
|
-
const pattern = `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`;
|
|
671
|
-
const value = {
|
|
672
|
-
value: pattern,
|
|
673
|
-
case_insensitive: true,
|
|
674
|
-
};
|
|
675
729
|
must = {
|
|
676
730
|
bool: {
|
|
677
731
|
should: [
|
|
678
|
-
{
|
|
679
|
-
|
|
680
|
-
|
|
732
|
+
{
|
|
733
|
+
wildcard: {
|
|
734
|
+
'name.raw': {
|
|
735
|
+
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
736
|
+
case_insensitive: true,
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
wildcard: {
|
|
742
|
+
'code.raw': {
|
|
743
|
+
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
744
|
+
case_insensitive: true,
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
wildcard: {
|
|
750
|
+
'description.raw': {
|
|
751
|
+
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
752
|
+
case_insensitive: true,
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
},
|
|
681
756
|
],
|
|
682
757
|
minimum_should_match: 1,
|
|
683
758
|
},
|
|
@@ -708,6 +783,66 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
708
783
|
console.error('kr-chatbot: skill search failed', error);
|
|
709
784
|
});
|
|
710
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Search the shared Kodaris global-skill registry into `_globalSkillResults`
|
|
788
|
+
* (the browse dialog's "Kodaris" tab). Unlike `/aiskills/search`,
|
|
789
|
+
* `/aiskills/global/search` returns a digested list
|
|
790
|
+
* (`data: [{ code, name, description, enabled }]`) already in `KRChatbotSkill`
|
|
791
|
+
* shape, so no `_source` lifting is needed. Only the browse dialog calls this
|
|
792
|
+
* (the `/` menu is org-only), so it isn't run per slash keystroke.
|
|
793
|
+
*/
|
|
794
|
+
_searchGlobalSkills(query) {
|
|
795
|
+
this._globalSkillRequestId++;
|
|
796
|
+
const requestId = this._globalSkillRequestId;
|
|
797
|
+
KRHttp.fetch({
|
|
798
|
+
url: '/api/system/opensearch/aiskills/global/search',
|
|
799
|
+
method: 'POST',
|
|
800
|
+
body: { query: query.trim() },
|
|
801
|
+
})
|
|
802
|
+
.then((r) => r.json())
|
|
803
|
+
.then((res) => {
|
|
804
|
+
// Ignore a stale response superseded by a newer search.
|
|
805
|
+
if (requestId !== this._globalSkillRequestId) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
this._globalSkillResults = (res?.data || []);
|
|
809
|
+
})
|
|
810
|
+
.catch((error) => {
|
|
811
|
+
console.error('kr-chatbot: global skill search failed', error);
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Enable or disable a global skill on this instance via
|
|
816
|
+
* `/aiskills/global/{code}/enabled`, then reflect the server's result on the
|
|
817
|
+
* matching card. Ignores clicks while a request for the same code is pending.
|
|
818
|
+
*/
|
|
819
|
+
_handleGlobalSkillEnable(skill, e) {
|
|
820
|
+
// The card body inserts the skill; the `+` button must not also do that.
|
|
821
|
+
e.stopPropagation();
|
|
822
|
+
if (this._globalSkillPending.has(skill.code)) {
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
this._globalSkillPending.add(skill.code);
|
|
826
|
+
this.requestUpdate();
|
|
827
|
+
KRHttp.fetch({
|
|
828
|
+
url: `/api/system/opensearch/aiskills/global/${encodeURIComponent(skill.code)}/enabled`,
|
|
829
|
+
method: 'POST',
|
|
830
|
+
body: { enabled: !skill.enabled },
|
|
831
|
+
})
|
|
832
|
+
.then((r) => r.json())
|
|
833
|
+
.then((res) => {
|
|
834
|
+
// Reflect the server's returned flag on the card.
|
|
835
|
+
skill.enabled = res?.data?.enabled;
|
|
836
|
+
this.requestUpdate();
|
|
837
|
+
})
|
|
838
|
+
.catch((error) => {
|
|
839
|
+
console.error('kr-chatbot: global skill enable failed', error);
|
|
840
|
+
})
|
|
841
|
+
.finally(() => {
|
|
842
|
+
this._globalSkillPending.delete(skill.code);
|
|
843
|
+
this.requestUpdate();
|
|
844
|
+
});
|
|
845
|
+
}
|
|
711
846
|
/** The `+` button toggles the skill/attachment menu open (showing all skills). */
|
|
712
847
|
_handlePlusClick() {
|
|
713
848
|
if (this._skillMenuOpened) {
|
|
@@ -727,6 +862,13 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
727
862
|
this._skillBrowseOpened = false;
|
|
728
863
|
this._inputEl?.focus();
|
|
729
864
|
}
|
|
865
|
+
/** Activate a skill card via keyboard (Enter/Space), since it's a div role=button. */
|
|
866
|
+
_handleSkillCardKeydown(e, skill) {
|
|
867
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
868
|
+
e.preventDefault();
|
|
869
|
+
this._handleSkillSelect(skill);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
730
872
|
/** Copy a message's text to the clipboard and briefly flash "Copied". */
|
|
731
873
|
_handleCopyMessage(content, index) {
|
|
732
874
|
navigator.clipboard.writeText(content).then(() => {
|
|
@@ -742,16 +884,22 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
742
884
|
this._skillMenuOpened = false;
|
|
743
885
|
this._skillBrowseQuery = '';
|
|
744
886
|
this._skillBrowseOpened = true;
|
|
887
|
+
// Fill both tabs up front so switching between them is instant, not stale.
|
|
745
888
|
this._searchSkills('');
|
|
889
|
+
this._searchGlobalSkills('');
|
|
746
890
|
}
|
|
747
891
|
_handleSkillBrowseClose() {
|
|
748
892
|
this._skillBrowseOpened = false;
|
|
749
893
|
}
|
|
750
894
|
_handleSkillBrowseSearch(e) {
|
|
751
895
|
this._skillBrowseQuery = e.target.value;
|
|
896
|
+
// Filter both catalogs so whichever tab the user switches to is already current.
|
|
752
897
|
this._searchSkills(this._skillBrowseQuery);
|
|
898
|
+
this._searchGlobalSkills(this._skillBrowseQuery);
|
|
753
899
|
}
|
|
754
900
|
_handleSkillBrowseTabChange(e) {
|
|
901
|
+
// Both buffers are kept current by open/search, so switching just swaps which
|
|
902
|
+
// one is shown — no re-search needed.
|
|
755
903
|
this._skillBrowseSource = e.detail.activeTabId;
|
|
756
904
|
}
|
|
757
905
|
// --- Attachments ---
|
|
@@ -889,9 +1037,21 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
889
1037
|
if (submitEvent.defaultPrevented) {
|
|
890
1038
|
return;
|
|
891
1039
|
}
|
|
1040
|
+
this._send({
|
|
1041
|
+
message: messageText,
|
|
1042
|
+
internalFiles
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Append the user message and stream the response. Split out of `_handleSubmit`
|
|
1047
|
+
* so debug test triggers can send a specific message without going through the
|
|
1048
|
+
* composer input. `_handleSubmit` owns reading/validating the composer and
|
|
1049
|
+
* firing the cancelable `message-submit` event; this owns the send + stream.
|
|
1050
|
+
*/
|
|
1051
|
+
_send(params) {
|
|
892
1052
|
const userMessage = {
|
|
893
1053
|
type: 'user',
|
|
894
|
-
content:
|
|
1054
|
+
content: params.message,
|
|
895
1055
|
};
|
|
896
1056
|
this.messages = [...this.messages, userMessage];
|
|
897
1057
|
this._inputValue = '';
|
|
@@ -957,9 +1117,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
957
1117
|
};
|
|
958
1118
|
}
|
|
959
1119
|
this.send({
|
|
960
|
-
message:
|
|
1120
|
+
message: params.message,
|
|
961
1121
|
messages: this.messages.slice(0, -1),
|
|
962
|
-
internalFiles,
|
|
1122
|
+
internalFiles: params.internalFiles,
|
|
963
1123
|
}).then((response) => {
|
|
964
1124
|
if (!response.ok) {
|
|
965
1125
|
this._handleStreamError('Request failed: ' + response.status);
|
|
@@ -1180,6 +1340,14 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1180
1340
|
/** The `/` skill menu shown above the composer while typing a slash command. */
|
|
1181
1341
|
_renderSkillMenu() {
|
|
1182
1342
|
const skills = this._skillResults;
|
|
1343
|
+
// Debug test triggers are extra actions after Attach Files / Browse. The
|
|
1344
|
+
// flat keyboard index counts actions first, then skills, so the skills'
|
|
1345
|
+
// index offset is the number of actions shown.
|
|
1346
|
+
const debugActions = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
1347
|
+
let actionCount = 2;
|
|
1348
|
+
if (debugActions) {
|
|
1349
|
+
actionCount = 4;
|
|
1350
|
+
}
|
|
1183
1351
|
return html `
|
|
1184
1352
|
<div class="skill-menu">
|
|
1185
1353
|
<div class="skill-menu__actions">
|
|
@@ -1201,6 +1369,26 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1201
1369
|
>
|
|
1202
1370
|
Browse All Skills
|
|
1203
1371
|
</button>
|
|
1372
|
+
${debugActions ? html `
|
|
1373
|
+
<button
|
|
1374
|
+
class=${classMap({
|
|
1375
|
+
'skill-menu__action': true,
|
|
1376
|
+
'skill-menu__item--highlighted': this._skillHighlighted === 2,
|
|
1377
|
+
})}
|
|
1378
|
+
@click=${() => this._activateSkillMenuItem(2)}
|
|
1379
|
+
>
|
|
1380
|
+
Test: context warning
|
|
1381
|
+
</button>
|
|
1382
|
+
<button
|
|
1383
|
+
class=${classMap({
|
|
1384
|
+
'skill-menu__action': true,
|
|
1385
|
+
'skill-menu__item--highlighted': this._skillHighlighted === 3,
|
|
1386
|
+
})}
|
|
1387
|
+
@click=${() => this._activateSkillMenuItem(3)}
|
|
1388
|
+
>
|
|
1389
|
+
Test: context limit
|
|
1390
|
+
</button>
|
|
1391
|
+
` : nothing}
|
|
1204
1392
|
</div>
|
|
1205
1393
|
${skills.length ? html `
|
|
1206
1394
|
<ul class="skill-menu__list">
|
|
@@ -1209,7 +1397,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1209
1397
|
<button
|
|
1210
1398
|
class=${classMap({
|
|
1211
1399
|
'skill-menu__item': true,
|
|
1212
|
-
'skill-menu__item--highlighted': index +
|
|
1400
|
+
'skill-menu__item--highlighted': index + actionCount === this._skillHighlighted,
|
|
1213
1401
|
})}
|
|
1214
1402
|
@click=${() => this._handleSkillSelect(skill)}
|
|
1215
1403
|
>
|
|
@@ -1248,37 +1436,81 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1248
1436
|
${this._renderSkillGrid()}
|
|
1249
1437
|
</kr-tab>
|
|
1250
1438
|
<kr-tab id="kodaris" label="Kodaris">
|
|
1251
|
-
|
|
1252
|
-
Shared Kodaris skills are coming soon.
|
|
1253
|
-
</div>
|
|
1439
|
+
${this._renderSkillGrid(true)}
|
|
1254
1440
|
</kr-tab>
|
|
1255
1441
|
</kr-tab-group>
|
|
1256
1442
|
</div>
|
|
1257
1443
|
</kr-dialog>
|
|
1258
1444
|
`;
|
|
1259
1445
|
}
|
|
1260
|
-
/**
|
|
1261
|
-
|
|
1262
|
-
|
|
1446
|
+
/**
|
|
1447
|
+
* The card grid of skill search results for the browse dialog. Clicking a card
|
|
1448
|
+
* inserts `/code ` into the composer. In the shared-Kodaris (`global`) tab each
|
|
1449
|
+
* card also gets a `+` install button that enables the skill on this instance
|
|
1450
|
+
* (turning into a ✓ once installed).
|
|
1451
|
+
*/
|
|
1452
|
+
_renderSkillGrid(global = false) {
|
|
1453
|
+
let skills = this._skillResults;
|
|
1454
|
+
if (global) {
|
|
1455
|
+
skills = this._globalSkillResults;
|
|
1456
|
+
}
|
|
1263
1457
|
if (!skills.length) {
|
|
1264
1458
|
return nothing;
|
|
1265
1459
|
}
|
|
1266
1460
|
return html `
|
|
1267
1461
|
<div class="skill-browse__grid">
|
|
1268
1462
|
${skills.map((skill) => html `
|
|
1269
|
-
<
|
|
1463
|
+
<div
|
|
1270
1464
|
class="skill-card"
|
|
1465
|
+
role="button"
|
|
1466
|
+
tabindex="0"
|
|
1271
1467
|
@click=${() => this._handleSkillSelect(skill)}
|
|
1468
|
+
@keydown=${(e) => this._handleSkillCardKeydown(e, skill)}
|
|
1272
1469
|
>
|
|
1273
1470
|
<span class="skill-card__name">${skill.name}</span>
|
|
1274
1471
|
${skill.description ? html `
|
|
1275
1472
|
<span class="skill-card__desc">${skill.description}</span>
|
|
1276
1473
|
` : nothing}
|
|
1277
|
-
|
|
1474
|
+
${global ? this._renderSkillInstall(skill) : nothing}
|
|
1475
|
+
</div>
|
|
1278
1476
|
`)}
|
|
1279
1477
|
</div>
|
|
1280
1478
|
`;
|
|
1281
1479
|
}
|
|
1480
|
+
/**
|
|
1481
|
+
* The install affordance on a global skill card: a `+` button that enables the
|
|
1482
|
+
* skill on this instance, showing a ✓ once installed and a disabled state
|
|
1483
|
+
* while the enable/disable request is in flight.
|
|
1484
|
+
*/
|
|
1485
|
+
_renderSkillInstall(skill) {
|
|
1486
|
+
return html `
|
|
1487
|
+
<button
|
|
1488
|
+
class=${classMap({
|
|
1489
|
+
'skill-card__install': true,
|
|
1490
|
+
'skill-card__install--installed': !!skill.enabled,
|
|
1491
|
+
})}
|
|
1492
|
+
?disabled=${this._globalSkillPending.has(skill.code)}
|
|
1493
|
+
title=${!!skill.enabled ? 'Installed — click to remove' : 'Install this skill'}
|
|
1494
|
+
aria-label=${!!skill.enabled ? 'Remove skill' : 'Install skill'}
|
|
1495
|
+
@click=${(e) => this._handleGlobalSkillEnable(skill, e)}
|
|
1496
|
+
>
|
|
1497
|
+
<svg
|
|
1498
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
1499
|
+
fill="none"
|
|
1500
|
+
viewBox="0 0 24 24"
|
|
1501
|
+
stroke-width="2"
|
|
1502
|
+
stroke="currentColor"
|
|
1503
|
+
class="skill-card__install-icon"
|
|
1504
|
+
>
|
|
1505
|
+
<path
|
|
1506
|
+
stroke-linecap="round"
|
|
1507
|
+
stroke-linejoin="round"
|
|
1508
|
+
d=${!!skill.enabled ? 'm4.5 12.75 6 6 9-13.5' : 'M12 4.5v15m7.5-7.5h-15'}
|
|
1509
|
+
/>
|
|
1510
|
+
</svg>
|
|
1511
|
+
</button>
|
|
1512
|
+
`;
|
|
1513
|
+
}
|
|
1282
1514
|
/**
|
|
1283
1515
|
* Render a user message, highlighting a leading `/skill-code` token (like the
|
|
1284
1516
|
* way Claude highlights slash commands). The composer only inserts valid skill
|
|
@@ -1444,6 +1676,9 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1444
1676
|
`;
|
|
1445
1677
|
}
|
|
1446
1678
|
render() {
|
|
1679
|
+
// Debug affordances (drawer toggle, drawer, /test-* options) require the
|
|
1680
|
+
// inline variant AND the server-side debug flag from aiFrameworkConfig.
|
|
1681
|
+
const isDebugEnabled = this.variant === 'inline' && this._aiFrameworkConfig.debugTelemetryEnabled === true;
|
|
1447
1682
|
return html `
|
|
1448
1683
|
${(this.hideToggle || this.variant === 'inline') ? nothing : html `
|
|
1449
1684
|
<button
|
|
@@ -1474,7 +1709,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1474
1709
|
'panel--opened': this.opened,
|
|
1475
1710
|
'panel--resizing-debug': this._isDebugResizing,
|
|
1476
1711
|
})}
|
|
1477
|
-
style=${styleMap(
|
|
1712
|
+
style=${styleMap(isDebugEnabled && this._debugPanelOpen ? { paddingRight: `${this._debugDrawerWidth}px` } : {})}
|
|
1478
1713
|
@dragenter=${this._handleDragEnter}
|
|
1479
1714
|
@dragover=${this._handleDragOver}
|
|
1480
1715
|
@dragleave=${this._handleDragLeave}
|
|
@@ -1547,7 +1782,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1547
1782
|
</div>
|
|
1548
1783
|
</div>
|
|
1549
1784
|
<div class="header-actions">
|
|
1550
|
-
${
|
|
1785
|
+
${isDebugEnabled ? html `
|
|
1551
1786
|
<button
|
|
1552
1787
|
class=${classMap({
|
|
1553
1788
|
'header-action': true,
|
|
@@ -1866,7 +2101,7 @@ let KRChatbot = class KRChatbot extends LitElement {
|
|
|
1866
2101
|
</div>
|
|
1867
2102
|
</div>
|
|
1868
2103
|
${this._skillBrowseOpened ? this._renderSkillBrowse() : nothing}
|
|
1869
|
-
${
|
|
2104
|
+
${isDebugEnabled ? this._renderDebugPanel() : nothing}
|
|
1870
2105
|
</div>
|
|
1871
2106
|
`;
|
|
1872
2107
|
}
|
|
@@ -2891,6 +3126,7 @@ KRChatbot.styles = css `
|
|
|
2891
3126
|
}
|
|
2892
3127
|
|
|
2893
3128
|
.skill-card {
|
|
3129
|
+
position: relative;
|
|
2894
3130
|
display: flex;
|
|
2895
3131
|
flex-direction: column;
|
|
2896
3132
|
gap: 6px;
|
|
@@ -2910,8 +3146,53 @@ KRChatbot.styles = css `
|
|
|
2910
3146
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
2911
3147
|
}
|
|
2912
3148
|
|
|
3149
|
+
/* Install button on global skill cards. Absolutely positioned in the corner
|
|
3150
|
+
so it sits above the card's own click target; leave room via the name's
|
|
3151
|
+
padding-right below. */
|
|
3152
|
+
.skill-card__install {
|
|
3153
|
+
position: absolute;
|
|
3154
|
+
top: 10px;
|
|
3155
|
+
right: 10px;
|
|
3156
|
+
display: inline-flex;
|
|
3157
|
+
align-items: center;
|
|
3158
|
+
justify-content: center;
|
|
3159
|
+
width: 24px;
|
|
3160
|
+
height: 24px;
|
|
3161
|
+
padding: 0;
|
|
3162
|
+
border: 1px solid #d1d5db;
|
|
3163
|
+
border-radius: 6px;
|
|
3164
|
+
background: white;
|
|
3165
|
+
color: #374151;
|
|
3166
|
+
cursor: pointer;
|
|
3167
|
+
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
3168
|
+
}
|
|
3169
|
+
|
|
3170
|
+
.skill-card__install:hover:not(:disabled) {
|
|
3171
|
+
border-color: var(--kr-chatbot-primary);
|
|
3172
|
+
color: var(--kr-chatbot-primary);
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
.skill-card__install:disabled {
|
|
3176
|
+
opacity: 0.5;
|
|
3177
|
+
cursor: default;
|
|
3178
|
+
}
|
|
3179
|
+
|
|
3180
|
+
/* Installed state: filled check. */
|
|
3181
|
+
.skill-card__install--installed {
|
|
3182
|
+
background: var(--kr-chatbot-primary);
|
|
3183
|
+
border-color: var(--kr-chatbot-primary);
|
|
3184
|
+
color: white;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
.skill-card__install-icon {
|
|
3188
|
+
width: 15px;
|
|
3189
|
+
height: 15px;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
2913
3192
|
.skill-card__name {
|
|
2914
3193
|
font-weight: 600;
|
|
3194
|
+
/* Leave room for the corner install button on global cards. */
|
|
3195
|
+
padding-right: 28px;
|
|
2915
3196
|
}
|
|
2916
3197
|
|
|
2917
3198
|
.skill-card__desc {
|
|
@@ -3101,6 +3382,12 @@ __decorate([
|
|
|
3101
3382
|
__decorate([
|
|
3102
3383
|
state()
|
|
3103
3384
|
], KRChatbot.prototype, "_skillResults", void 0);
|
|
3385
|
+
__decorate([
|
|
3386
|
+
state()
|
|
3387
|
+
], KRChatbot.prototype, "_globalSkillResults", void 0);
|
|
3388
|
+
__decorate([
|
|
3389
|
+
state()
|
|
3390
|
+
], KRChatbot.prototype, "_aiFrameworkConfig", void 0);
|
|
3104
3391
|
__decorate([
|
|
3105
3392
|
state()
|
|
3106
3393
|
], KRChatbot.prototype, "_skillHighlighted", void 0);
|