@kodaris/krubble-app-components 1.0.106 → 1.0.108
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 +123 -38
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +212 -165
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +212 -165
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +178 -149
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +211 -164
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +44 -15
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KRContextMenu, KRDialog, KRFilePreview } from '@kodaris/krubble-components';
|
|
1
|
+
import { KRContextMenu, KRDialog, KRSnackbar, KRFilePreview } from '@kodaris/krubble-components';
|
|
2
2
|
import { KRHttp } from '@kodaris/krubble-http';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -5775,10 +5775,10 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
5775
5775
|
*/
|
|
5776
5776
|
this._globalSkills = [];
|
|
5777
5777
|
/**
|
|
5778
|
-
* Latest results for the `/` composer menu
|
|
5779
|
-
*
|
|
5780
|
-
* buffer from the two browse-tab lists
|
|
5781
|
-
* menu never
|
|
5778
|
+
* Latest results for the `/` composer menu — everything the current user can
|
|
5779
|
+
* actually invoke, merged and role-filtered server-side by
|
|
5780
|
+
* `/conversation/availableSkills`. A separate buffer from the two browse-tab lists
|
|
5781
|
+
* so installed globals in the menu never leak into the "Your Org" tab.
|
|
5782
5782
|
*/
|
|
5783
5783
|
this._invocableSkills = [];
|
|
5784
5784
|
/**
|
|
@@ -6356,77 +6356,30 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6356
6356
|
}
|
|
6357
6357
|
// --- Skills ---
|
|
6358
6358
|
/**
|
|
6359
|
-
*
|
|
6360
|
-
*
|
|
6361
|
-
*
|
|
6362
|
-
* over name^3/description^2/instructions, active-only) so the menu ranks skills
|
|
6363
|
-
* the way the model does. An empty query uses match_all — multi_match with an
|
|
6364
|
-
* empty string matches nothing, so the "show everything" case needs it.
|
|
6359
|
+
* Load the browse dialog's "Your Org" tab into `_orgSkills` from
|
|
6360
|
+
* `/conversation/localSkillList` — this instance's own active skills, already
|
|
6361
|
+
* role-filtered to what the caller may run and ranked by the server.
|
|
6365
6362
|
*
|
|
6366
|
-
*
|
|
6367
|
-
*
|
|
6368
|
-
*
|
|
6363
|
+
* Not the `aiskills` search endpoint: that requires a skill-authoring role
|
|
6364
|
+
* (`skillView`/`skillEdit`), so an ordinary chat user got a 403 and an empty
|
|
6365
|
+
* tab. This one only needs a logged-in employee. It also means the query
|
|
6366
|
+
* matching, active-only filter and "exclude globally-published" rule all live
|
|
6367
|
+
* server-side instead of being rebuilt as OpenSearch DSL here.
|
|
6368
|
+
*
|
|
6369
|
+
* EXCEPTION — on kodaris.com this list also carries that instance's own
|
|
6370
|
+
* globally-published skills (flagged `global: true`), because there is no
|
|
6371
|
+
* Kodaris tab there for them to appear on.
|
|
6372
|
+
*
|
|
6373
|
+
* Request-id guard drops responses that arrive out of order (kr-combo-box
|
|
6374
|
+
* `_fetch` pattern).
|
|
6369
6375
|
*/
|
|
6370
6376
|
_searchOrgSkills(query) {
|
|
6371
6377
|
this._orgSkillRequestId++;
|
|
6372
6378
|
const requestId = this._orgSkillRequestId;
|
|
6373
|
-
const trimmed = query.trim();
|
|
6374
|
-
// An empty query matches everything; otherwise substring-match the query
|
|
6375
|
-
// against name/code/description. We use case-insensitive wildcard queries on
|
|
6376
|
-
// the `.raw` keyword subfields so "ord" matches anywhere in "Create Order"
|
|
6377
|
-
// (a plain match/multi_match only matches whole tokens). Escape the wildcard
|
|
6378
|
-
// specials so user input is treated literally, then wrap with `*...*`.
|
|
6379
|
-
let must = { match_all: {} };
|
|
6380
|
-
if (trimmed) {
|
|
6381
|
-
must = {
|
|
6382
|
-
bool: {
|
|
6383
|
-
should: [
|
|
6384
|
-
{
|
|
6385
|
-
wildcard: {
|
|
6386
|
-
'name.raw': {
|
|
6387
|
-
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
6388
|
-
case_insensitive: true,
|
|
6389
|
-
}
|
|
6390
|
-
}
|
|
6391
|
-
},
|
|
6392
|
-
{
|
|
6393
|
-
wildcard: {
|
|
6394
|
-
'code.raw': {
|
|
6395
|
-
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
6396
|
-
case_insensitive: true,
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
},
|
|
6400
|
-
{
|
|
6401
|
-
wildcard: {
|
|
6402
|
-
'description.raw': {
|
|
6403
|
-
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
6404
|
-
case_insensitive: true,
|
|
6405
|
-
}
|
|
6406
|
-
}
|
|
6407
|
-
},
|
|
6408
|
-
],
|
|
6409
|
-
minimum_should_match: 1,
|
|
6410
|
-
},
|
|
6411
|
-
};
|
|
6412
|
-
}
|
|
6413
6379
|
KRHttp.fetch({
|
|
6414
|
-
url: '/api/system/
|
|
6380
|
+
url: '/api/system/conversation/localSkillList',
|
|
6415
6381
|
method: 'POST',
|
|
6416
|
-
body: {
|
|
6417
|
-
size: 100,
|
|
6418
|
-
query: {
|
|
6419
|
-
bool: {
|
|
6420
|
-
must: [must],
|
|
6421
|
-
filter: [{ term: { active: true } }],
|
|
6422
|
-
// The "Your Org" tab lists only this instance's own skills. Globally-
|
|
6423
|
-
// published skills belong to the "Kodaris" tab (fed by the registry),
|
|
6424
|
-
// so exclude them here. Documents without the field aren't matched by
|
|
6425
|
-
// the term, so ordinary org skills are kept.
|
|
6426
|
-
must_not: [{ term: { globalPublished: true } }],
|
|
6427
|
-
},
|
|
6428
|
-
},
|
|
6429
|
-
},
|
|
6382
|
+
body: { query: query.trim() },
|
|
6430
6383
|
})
|
|
6431
6384
|
.then((r) => r.json())
|
|
6432
6385
|
.then((res) => {
|
|
@@ -6434,114 +6387,63 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6434
6387
|
if (requestId !== this._orgSkillRequestId) {
|
|
6435
6388
|
return;
|
|
6436
6389
|
}
|
|
6437
|
-
this._orgSkills = (res?.data
|
|
6390
|
+
this._orgSkills = (res?.data || []);
|
|
6438
6391
|
})
|
|
6439
6392
|
.catch((error) => {
|
|
6440
6393
|
console.error('kr-chatbot: skill search failed', error);
|
|
6441
6394
|
});
|
|
6442
6395
|
}
|
|
6443
6396
|
/**
|
|
6444
|
-
*
|
|
6445
|
-
*
|
|
6446
|
-
*
|
|
6447
|
-
*
|
|
6448
|
-
*
|
|
6449
|
-
*
|
|
6450
|
-
*
|
|
6451
|
-
*
|
|
6397
|
+
* Load the skills for the `/` composer menu into `_invocableSkills` from
|
|
6398
|
+
* `/conversation/availableSkills` — this instance's own skills plus the installed
|
|
6399
|
+
* global ones, already merged, role-filtered and ranked by the server. That's the
|
|
6400
|
+
* same set the model sees via `list_skills`, so the menu can't offer a skill the
|
|
6401
|
+
* chat would then refuse.
|
|
6402
|
+
*
|
|
6403
|
+
* Not the `aiskills` endpoints the browse dialog's Org tab uses: those require a
|
|
6404
|
+
* skill-authoring role, which an ordinary chat user doesn't have.
|
|
6405
|
+
*
|
|
6406
|
+
* Request-id guard drops responses that arrive out of order (kr-combo-box `_fetch`
|
|
6407
|
+
* pattern).
|
|
6452
6408
|
*/
|
|
6453
6409
|
_searchInvocableSkills(query) {
|
|
6454
6410
|
this._invocableSkillRequestId++;
|
|
6455
6411
|
const requestId = this._invocableSkillRequestId;
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
if (query.trim()) {
|
|
6459
|
-
must = {
|
|
6460
|
-
bool: {
|
|
6461
|
-
should: [
|
|
6462
|
-
{
|
|
6463
|
-
wildcard: {
|
|
6464
|
-
'name.raw': {
|
|
6465
|
-
value: `*${query.trim().replace(/([*?\\])/g, '\\$1')}*`,
|
|
6466
|
-
case_insensitive: true,
|
|
6467
|
-
}
|
|
6468
|
-
}
|
|
6469
|
-
},
|
|
6470
|
-
{
|
|
6471
|
-
wildcard: {
|
|
6472
|
-
'code.raw': {
|
|
6473
|
-
value: `*${query.trim().replace(/([*?\\])/g, '\\$1')}*`,
|
|
6474
|
-
case_insensitive: true,
|
|
6475
|
-
}
|
|
6476
|
-
}
|
|
6477
|
-
},
|
|
6478
|
-
{
|
|
6479
|
-
wildcard: {
|
|
6480
|
-
'description.raw': {
|
|
6481
|
-
value: `*${query.trim().replace(/([*?\\])/g, '\\$1')}*`,
|
|
6482
|
-
case_insensitive: true,
|
|
6483
|
-
}
|
|
6484
|
-
}
|
|
6485
|
-
},
|
|
6486
|
-
],
|
|
6487
|
-
minimum_should_match: 1,
|
|
6488
|
-
},
|
|
6489
|
-
};
|
|
6490
|
-
}
|
|
6491
|
-
// Org skills — this instance's own catalog (globalPublished excluded).
|
|
6492
|
-
const orgSearch = KRHttp.fetch({
|
|
6493
|
-
url: '/api/system/opensearch/aiskills/search',
|
|
6494
|
-
method: 'POST',
|
|
6495
|
-
body: {
|
|
6496
|
-
size: 100,
|
|
6497
|
-
query: {
|
|
6498
|
-
bool: {
|
|
6499
|
-
must: [must],
|
|
6500
|
-
filter: [{ term: { active: true } }],
|
|
6501
|
-
must_not: [{ term: { globalPublished: true } }],
|
|
6502
|
-
},
|
|
6503
|
-
},
|
|
6504
|
-
},
|
|
6505
|
-
})
|
|
6506
|
-
.then((r) => r.json())
|
|
6507
|
-
.then((res) => (res?.data?.hits?.hits || []).map((hit) => hit._source))
|
|
6508
|
-
.catch((error) => {
|
|
6509
|
-
console.error('kr-chatbot: invocable skill search failed', error);
|
|
6510
|
-
return [];
|
|
6511
|
-
});
|
|
6512
|
-
// Installed globals — the registry catalog, kept only where enabled=true.
|
|
6513
|
-
const globalSearch = KRHttp.fetch({
|
|
6514
|
-
url: '/api/system/opensearch/aiskills/global/search',
|
|
6412
|
+
KRHttp.fetch({
|
|
6413
|
+
url: '/api/system/conversation/availableSkills',
|
|
6515
6414
|
method: 'POST',
|
|
6516
6415
|
body: { query: query.trim() },
|
|
6517
6416
|
})
|
|
6518
6417
|
.then((r) => r.json())
|
|
6519
|
-
.then((res) =>
|
|
6520
|
-
.catch((error) => {
|
|
6521
|
-
console.error('kr-chatbot: installed global skill search failed', error);
|
|
6522
|
-
return [];
|
|
6523
|
-
});
|
|
6524
|
-
Promise.all([orgSearch, globalSearch]).then(([orgSkills, globalSkills]) => {
|
|
6418
|
+
.then((res) => {
|
|
6525
6419
|
// Ignore a stale response superseded by a newer search.
|
|
6526
6420
|
if (requestId !== this._invocableSkillRequestId) {
|
|
6527
6421
|
return;
|
|
6528
6422
|
}
|
|
6529
|
-
this._invocableSkills = [
|
|
6423
|
+
this._invocableSkills = (res?.data || []);
|
|
6424
|
+
})
|
|
6425
|
+
.catch((error) => {
|
|
6426
|
+
console.error('kr-chatbot: invocable skill search failed', error);
|
|
6530
6427
|
});
|
|
6531
6428
|
}
|
|
6532
6429
|
/**
|
|
6533
|
-
*
|
|
6534
|
-
*
|
|
6535
|
-
*
|
|
6536
|
-
*
|
|
6537
|
-
*
|
|
6538
|
-
*
|
|
6430
|
+
* Load the browse dialog's "Kodaris" tab into `_globalSkills` — the whole
|
|
6431
|
+
* published Kodaris catalog for discovery, installed or not, runnable by this
|
|
6432
|
+
* user or not. Each row carries `enabled` (installed here), `canEnable` (may
|
|
6433
|
+
* install it) and `canRun` (roles allow invoking it).
|
|
6434
|
+
*
|
|
6435
|
+
* Uses `/conversation/globalSkillList` rather than the `/aiskills/global/search`
|
|
6436
|
+
* twin, which requires a skill-authoring role an ordinary chat user doesn't have.
|
|
6437
|
+
*
|
|
6438
|
+
* Returns `[]` — not an error — when there's no reachable registry, and always
|
|
6439
|
+
* on kodaris.com, which IS the catalog source and so has no remote to pull. The
|
|
6440
|
+
* tab itself is hidden there (see `_renderSkillBrowse`).
|
|
6539
6441
|
*/
|
|
6540
6442
|
_searchGlobalSkills(query) {
|
|
6541
6443
|
this._globalSkillRequestId++;
|
|
6542
6444
|
const requestId = this._globalSkillRequestId;
|
|
6543
6445
|
KRHttp.fetch({
|
|
6544
|
-
url: '/api/system/
|
|
6446
|
+
url: '/api/system/conversation/globalSkillList',
|
|
6545
6447
|
method: 'POST',
|
|
6546
6448
|
body: { query: query.trim() },
|
|
6547
6449
|
})
|
|
@@ -6565,6 +6467,17 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6565
6467
|
_handleGlobalSkillEnable(skill, e) {
|
|
6566
6468
|
// The card body inserts the skill; the install button must not also do that.
|
|
6567
6469
|
e.stopPropagation();
|
|
6470
|
+
// The button is shown to everyone so the catalog reads the same for all users,
|
|
6471
|
+
// but the enable endpoint requires administrator/superuser/skillEdit. Without
|
|
6472
|
+
// it, say who to ask instead of firing a request that comes back 403.
|
|
6473
|
+
if (!skill.canEnable) {
|
|
6474
|
+
KRSnackbar.show({
|
|
6475
|
+
title: 'Permission Required',
|
|
6476
|
+
message: 'You do not have permission to install skills. Please contact your site administrator.',
|
|
6477
|
+
type: 'error',
|
|
6478
|
+
});
|
|
6479
|
+
return;
|
|
6480
|
+
}
|
|
6568
6481
|
if (this._globalSkillPending.has(skill.code)) {
|
|
6569
6482
|
return;
|
|
6570
6483
|
}
|
|
@@ -6691,13 +6604,33 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6691
6604
|
recognition.start();
|
|
6692
6605
|
this._inputEl?.focus();
|
|
6693
6606
|
}
|
|
6607
|
+
/**
|
|
6694
6608
|
/** Insert `/code ` into the composer and focus it, ready for the message. */
|
|
6695
6609
|
_handleSkillSelect(skill) {
|
|
6696
|
-
//
|
|
6697
|
-
//
|
|
6698
|
-
//
|
|
6699
|
-
|
|
6700
|
-
|
|
6610
|
+
// The user's roles don't allow this skill — inserting /code would just fail in the
|
|
6611
|
+
// chat. Name the roles that would grant it, so the user knows what to ask for and
|
|
6612
|
+
// whoever grants it knows what to give.
|
|
6613
|
+
if (skill.canRun === false) {
|
|
6614
|
+
let message = `You do not have access to "${skill.name}". Ask your site administrator to grant it.`;
|
|
6615
|
+
if (skill.allowedRoles?.length) {
|
|
6616
|
+
message = `"${skill.name}" requires the ${skill.allowedRoles.join(' or ')} role. Ask your site administrator to grant it.`;
|
|
6617
|
+
}
|
|
6618
|
+
KRSnackbar.show({
|
|
6619
|
+
title: 'Access Required',
|
|
6620
|
+
message: message,
|
|
6621
|
+
type: 'error',
|
|
6622
|
+
});
|
|
6623
|
+
return;
|
|
6624
|
+
}
|
|
6625
|
+
// A global skill nobody has installed here yet isn't runnable either — point at the
|
|
6626
|
+
// Install button rather than appearing to ignore the click. Local skills carry no
|
|
6627
|
+
// install state, so this only applies to global ones.
|
|
6628
|
+
if (skill.global && !skill.enabled) {
|
|
6629
|
+
KRSnackbar.show({
|
|
6630
|
+
title: 'Not Installed',
|
|
6631
|
+
message: `"${skill.name}" has to be installed on this site before you can use it — use the Install button on the card.`,
|
|
6632
|
+
type: 'info',
|
|
6633
|
+
});
|
|
6701
6634
|
return;
|
|
6702
6635
|
}
|
|
6703
6636
|
this._inputValue = `/${skill.code} `;
|
|
@@ -6729,8 +6662,11 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6729
6662
|
this._skillBrowseQuery = '';
|
|
6730
6663
|
this._skillBrowseOpened = true;
|
|
6731
6664
|
// Fill both tabs up front so switching between them is instant, not stale.
|
|
6665
|
+
// The Kodaris tab isn't rendered on the registry server, so skip its fetch there.
|
|
6732
6666
|
this._searchOrgSkills('');
|
|
6733
|
-
this.
|
|
6667
|
+
if (this._aiFrameworkConfig.registryServer !== true) {
|
|
6668
|
+
this._searchGlobalSkills('');
|
|
6669
|
+
}
|
|
6734
6670
|
}
|
|
6735
6671
|
_handleSkillBrowseClose() {
|
|
6736
6672
|
this._skillBrowseOpened = false;
|
|
@@ -6739,7 +6675,9 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6739
6675
|
this._skillBrowseQuery = e.target.value;
|
|
6740
6676
|
// Filter both catalogs so whichever tab the user switches to is already current.
|
|
6741
6677
|
this._searchOrgSkills(this._skillBrowseQuery);
|
|
6742
|
-
|
|
6678
|
+
if (this._aiFrameworkConfig.registryServer !== true) {
|
|
6679
|
+
this._searchGlobalSkills(this._skillBrowseQuery);
|
|
6680
|
+
}
|
|
6743
6681
|
}
|
|
6744
6682
|
_handleSkillBrowseTabChange(e) {
|
|
6745
6683
|
// Both buffers are kept current by open/search, so switching just swaps which
|
|
@@ -7081,6 +7019,15 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7081
7019
|
userText: params.message,
|
|
7082
7020
|
fileIds: files.map((f) => f.conversationFileID),
|
|
7083
7021
|
};
|
|
7022
|
+
// Ground the model in where the user is and what they've highlighted.
|
|
7023
|
+
// Only sent when present so an empty selection / failed capture doesn't
|
|
7024
|
+
// add noise to the request.
|
|
7025
|
+
if (params.pageContext) {
|
|
7026
|
+
body.pageContext = params.pageContext;
|
|
7027
|
+
}
|
|
7028
|
+
if (params.selectionContext) {
|
|
7029
|
+
body.selectionContext = params.selectionContext;
|
|
7030
|
+
}
|
|
7084
7031
|
if (this.conversationId != null) {
|
|
7085
7032
|
body.conversationID = this.conversationId;
|
|
7086
7033
|
}
|
|
@@ -7125,10 +7072,16 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7125
7072
|
});
|
|
7126
7073
|
};
|
|
7127
7074
|
}
|
|
7075
|
+
// Capture where the user is and what they've highlighted at send time (not
|
|
7076
|
+
// earlier — selection is transient and the route may have changed since the
|
|
7077
|
+
// panel opened), so every turn grounds the model in the user's current spot.
|
|
7078
|
+
const context = this._captureContext();
|
|
7128
7079
|
this.send({
|
|
7129
7080
|
message: params.message,
|
|
7130
7081
|
messages: this.messages.slice(0, -1),
|
|
7131
7082
|
conversationFiles: params.conversationFiles,
|
|
7083
|
+
pageContext: context.pageContext,
|
|
7084
|
+
selectionContext: context.selectionContext,
|
|
7132
7085
|
}).then((response) => {
|
|
7133
7086
|
if (!response.ok) {
|
|
7134
7087
|
this._handleStreamError('Request failed: ' + response.status);
|
|
@@ -7148,6 +7101,56 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7148
7101
|
}
|
|
7149
7102
|
});
|
|
7150
7103
|
}
|
|
7104
|
+
/**
|
|
7105
|
+
* Capture ambient "where is the user / what are they looking at" context at
|
|
7106
|
+
* send time, so the AI is grounded without the host passing anything in.
|
|
7107
|
+
*
|
|
7108
|
+
* Two universal sources that exist on every page the chatbot mounts in:
|
|
7109
|
+
* - `pageContext` — the location (full url, which already carries the path and
|
|
7110
|
+
* query) and title, plus, when the page renders the server-side
|
|
7111
|
+
* `#kodaris-page` JSON island (storefront content pages do), its parsed
|
|
7112
|
+
* contents (page name/type/ids). A page without the island still yields url
|
|
7113
|
+
* + title, so context is never empty on a real page.
|
|
7114
|
+
* - `selectionContext` — the text the user currently has highlighted, read
|
|
7115
|
+
* live here because a selection is transient (it may be gone a moment later).
|
|
7116
|
+
*
|
|
7117
|
+
* Both are returned as strings because that's what the backend `ModelForText`
|
|
7118
|
+
* fields (`pageContext` / `selectionContext`) accept. Capture is best-effort:
|
|
7119
|
+
* any failure yields an empty string for that field rather than blocking send.
|
|
7120
|
+
*/
|
|
7121
|
+
_captureContext() {
|
|
7122
|
+
let pageContext = '';
|
|
7123
|
+
try {
|
|
7124
|
+
const page = {
|
|
7125
|
+
url: window.location.href,
|
|
7126
|
+
title: document.title,
|
|
7127
|
+
};
|
|
7128
|
+
// Structured page context is opt-in by convention: a page that renders the
|
|
7129
|
+
// `#kodaris-page` island gets its name/type/ids folded in for free. The
|
|
7130
|
+
// island's text is JSON; a malformed/absent island just leaves it out.
|
|
7131
|
+
const island = document.getElementById('kodaris-page');
|
|
7132
|
+
if (island && island.textContent) {
|
|
7133
|
+
try {
|
|
7134
|
+
page.page = JSON.parse(island.textContent);
|
|
7135
|
+
}
|
|
7136
|
+
catch { /* island present but not valid JSON — location context still stands */ }
|
|
7137
|
+
}
|
|
7138
|
+
pageContext = JSON.stringify(page);
|
|
7139
|
+
}
|
|
7140
|
+
catch { /* leave pageContext empty — never block a send on context capture */ }
|
|
7141
|
+
let selectionContext = '';
|
|
7142
|
+
try {
|
|
7143
|
+
const selection = window.getSelection();
|
|
7144
|
+
if (selection) {
|
|
7145
|
+
selectionContext = selection.toString().trim();
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
catch { /* leave selectionContext empty */ }
|
|
7149
|
+
return {
|
|
7150
|
+
pageContext,
|
|
7151
|
+
selectionContext,
|
|
7152
|
+
};
|
|
7153
|
+
}
|
|
7151
7154
|
_handleStop() {
|
|
7152
7155
|
if (this._reader) {
|
|
7153
7156
|
this._reader.cancel();
|
|
@@ -7421,7 +7424,14 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7421
7424
|
</div>
|
|
7422
7425
|
`;
|
|
7423
7426
|
}
|
|
7424
|
-
/**
|
|
7427
|
+
/**
|
|
7428
|
+
* The "Browse all skills" dialog — a searchable grid of Org / Kodaris cards.
|
|
7429
|
+
*
|
|
7430
|
+
* The Kodaris tab is omitted on the registry server (kodaris.com): that instance
|
|
7431
|
+
* IS the catalog source, so `globalSkillList` has no remote to pull and always
|
|
7432
|
+
* returns `[]`. Its own published skills appear on the Your Org tab instead,
|
|
7433
|
+
* carrying a "Global" badge.
|
|
7434
|
+
*/
|
|
7425
7435
|
_renderSkillBrowse() {
|
|
7426
7436
|
return b `
|
|
7427
7437
|
<kr-dialog
|
|
@@ -7443,9 +7453,11 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7443
7453
|
<kr-tab id="org" label="Your Org">
|
|
7444
7454
|
${this._renderSkillGrid()}
|
|
7445
7455
|
</kr-tab>
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7456
|
+
${this._aiFrameworkConfig.registryServer === true ? A : b `
|
|
7457
|
+
<kr-tab id="kodaris" label="Kodaris">
|
|
7458
|
+
${this._renderSkillGrid(true)}
|
|
7459
|
+
</kr-tab>
|
|
7460
|
+
`}
|
|
7449
7461
|
</kr-tab-group>
|
|
7450
7462
|
</div>
|
|
7451
7463
|
</kr-dialog>
|
|
@@ -7454,8 +7466,13 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7454
7466
|
/**
|
|
7455
7467
|
* The card grid of skill search results for the browse dialog. Clicking a card
|
|
7456
7468
|
* inserts `/code ` into the composer. In the shared-Kodaris (`global`) tab each
|
|
7457
|
-
* card also gets a footer row with an
|
|
7458
|
-
*
|
|
7469
|
+
* card also gets a footer row with an Install/Uninstall button.
|
|
7470
|
+
*
|
|
7471
|
+
* The Kodaris tab is the whole published catalog, deliberately NOT role-filtered so
|
|
7472
|
+
* users can discover skills they don't have yet. So a card there can be in one of two
|
|
7473
|
+
* non-runnable states — the user's roles don't allow it (`canRun: false`), or it isn't
|
|
7474
|
+
* installed here (not `enabled`). Neither looks clickable; clicking either explains
|
|
7475
|
+
* why via `_handleSkillSelect`.
|
|
7459
7476
|
*/
|
|
7460
7477
|
_renderSkillGrid(global = false) {
|
|
7461
7478
|
let skills = this._orgSkills;
|
|
@@ -7469,7 +7486,10 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7469
7486
|
<div class="skill-browse__grid">
|
|
7470
7487
|
${skills.map((skill) => b `
|
|
7471
7488
|
<div
|
|
7472
|
-
class
|
|
7489
|
+
class=${e({
|
|
7490
|
+
'skill-card': true,
|
|
7491
|
+
'skill-card--unavailable': skill.canRun === false || (!!skill.global && !skill.enabled),
|
|
7492
|
+
})}
|
|
7473
7493
|
role="button"
|
|
7474
7494
|
tabindex="0"
|
|
7475
7495
|
@click=${() => this._handleSkillSelect(skill)}
|
|
@@ -7479,6 +7499,9 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7479
7499
|
${skill.description ? b `
|
|
7480
7500
|
<span class="skill-card__desc">${skill.description}</span>
|
|
7481
7501
|
` : A}
|
|
7502
|
+
${!global && skill.global ? b `
|
|
7503
|
+
<span class="skill-card__badge">Global</span>
|
|
7504
|
+
` : A}
|
|
7482
7505
|
${global ? b `
|
|
7483
7506
|
<div class="skill-card__footer">
|
|
7484
7507
|
<span class="skill-card__price">Free</span>
|
|
@@ -9348,6 +9371,30 @@ KRChatbot.styles = i$6 `
|
|
|
9348
9371
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
9349
9372
|
}
|
|
9350
9373
|
|
|
9374
|
+
/* A Kodaris-tab card the user can't invoke — not in their roles, or not installed
|
|
9375
|
+
here yet. Clicking it does nothing, so it drops the pointer cursor and the hover
|
|
9376
|
+
highlight. The Install button inside keeps its own pointer cursor. */
|
|
9377
|
+
.skill-card--unavailable {
|
|
9378
|
+
cursor: default;
|
|
9379
|
+
}
|
|
9380
|
+
|
|
9381
|
+
.skill-card--unavailable:hover {
|
|
9382
|
+
border-color: #e5e7eb;
|
|
9383
|
+
box-shadow: none;
|
|
9384
|
+
}
|
|
9385
|
+
|
|
9386
|
+
/* Marks a globally-published skill listed on the Your Org tab — only happens on
|
|
9387
|
+
kodaris.com, which has no Kodaris tab for its own published skills. */
|
|
9388
|
+
.skill-card__badge {
|
|
9389
|
+
align-self: flex-start;
|
|
9390
|
+
padding: 2px 8px;
|
|
9391
|
+
border-radius: 10px;
|
|
9392
|
+
background: #f3f4f6;
|
|
9393
|
+
font-size: 11px;
|
|
9394
|
+
font-weight: 600;
|
|
9395
|
+
color: #000000;
|
|
9396
|
+
}
|
|
9397
|
+
|
|
9351
9398
|
/* Footer row on global skill cards: install button on the left, price on the
|
|
9352
9399
|
right. margin-top:auto pins it to the bottom so cards in a row align their
|
|
9353
9400
|
footers regardless of description length. Negative horizontal margins
|