@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.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 guard for skill searches — bumped on each search so out-of-order
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. Shared by the `/` menu and the browse dialog —
194
- * they're mutually exclusive (opening browse closes the menu), so one buffer.
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` lists this org's skills (searched via
205
- * `_searchSkills`); `kodaris` is the shared global catalog, added later.
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, sent to `_searchSkills`. */
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 two
601
- // top actions (Attach Files, Browse All Skills) are index 0 and 1, then the
602
- // filtered skills follow — all navigable as one list.
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 count = 2 + this._skillResults.length;
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 (0/1 = actions, then skills). */
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
- const skill = this._skillResults[index - 2];
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
- { wildcard: { 'name.raw': value } },
679
- { wildcard: { 'code.raw': value } },
680
- { wildcard: { 'description.raw': value } },
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: messageText,
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: messageText,
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 + 2 === this._skillHighlighted,
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
- <div class="skill-browse__empty">
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
- /** The card grid of this org's skill search results for the browse dialog. */
1261
- _renderSkillGrid() {
1262
- const skills = this._skillResults;
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
- <button
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
- </button>
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(this.variant === 'inline' && this._debugPanelOpen ? { paddingRight: `${this._debugDrawerWidth}px` } : {})}
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
- ${this.variant === 'inline' ? html `
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
- ${this.variant === 'inline' ? this._renderDebugPanel() : nothing}
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);