@kodaris/krubble-app-components 1.0.107 → 1.0.109
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 +91 -24
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +188 -85
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +188 -85
- 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 +187 -84
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +47 -18
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/krubble-app.umd.js
CHANGED
|
@@ -6359,76 +6359,30 @@
|
|
|
6359
6359
|
}
|
|
6360
6360
|
// --- Skills ---
|
|
6361
6361
|
/**
|
|
6362
|
-
*
|
|
6363
|
-
*
|
|
6364
|
-
*
|
|
6365
|
-
* match_all — multi_match with an empty string matches nothing, so the "show
|
|
6366
|
-
* everything" case needs it.
|
|
6362
|
+
* Load the browse dialog's "Your Org" tab into `_orgSkills` from
|
|
6363
|
+
* `/conversation/localSkillList` — this instance's own active skills, already
|
|
6364
|
+
* role-filtered to what the caller may run and ranked by the server.
|
|
6367
6365
|
*
|
|
6368
|
-
*
|
|
6369
|
-
*
|
|
6370
|
-
*
|
|
6366
|
+
* Not the `aiskills` search endpoint: that requires a skill-authoring role
|
|
6367
|
+
* (`skillView`/`skillEdit`), so an ordinary chat user got a 403 and an empty
|
|
6368
|
+
* tab. This one only needs a logged-in employee. It also means the query
|
|
6369
|
+
* matching, active-only filter and "exclude globally-published" rule all live
|
|
6370
|
+
* server-side instead of being rebuilt as OpenSearch DSL here.
|
|
6371
|
+
*
|
|
6372
|
+
* EXCEPTION — on kodaris.com this list also carries that instance's own
|
|
6373
|
+
* globally-published skills (flagged `global: true`), because there is no
|
|
6374
|
+
* Kodaris tab there for them to appear on.
|
|
6375
|
+
*
|
|
6376
|
+
* Request-id guard drops responses that arrive out of order (kr-combo-box
|
|
6377
|
+
* `_fetch` pattern).
|
|
6371
6378
|
*/
|
|
6372
6379
|
_searchOrgSkills(query) {
|
|
6373
6380
|
this._orgSkillRequestId++;
|
|
6374
6381
|
const requestId = this._orgSkillRequestId;
|
|
6375
|
-
const trimmed = query.trim();
|
|
6376
|
-
// An empty query matches everything; otherwise substring-match the query
|
|
6377
|
-
// against name/code/description. We use case-insensitive wildcard queries on
|
|
6378
|
-
// the `.raw` keyword subfields so "ord" matches anywhere in "Create Order"
|
|
6379
|
-
// (a plain match/multi_match only matches whole tokens). Escape the wildcard
|
|
6380
|
-
// specials so user input is treated literally, then wrap with `*...*`.
|
|
6381
|
-
let must = { match_all: {} };
|
|
6382
|
-
if (trimmed) {
|
|
6383
|
-
must = {
|
|
6384
|
-
bool: {
|
|
6385
|
-
should: [
|
|
6386
|
-
{
|
|
6387
|
-
wildcard: {
|
|
6388
|
-
'name.raw': {
|
|
6389
|
-
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
6390
|
-
case_insensitive: true,
|
|
6391
|
-
}
|
|
6392
|
-
}
|
|
6393
|
-
},
|
|
6394
|
-
{
|
|
6395
|
-
wildcard: {
|
|
6396
|
-
'code.raw': {
|
|
6397
|
-
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
6398
|
-
case_insensitive: true,
|
|
6399
|
-
}
|
|
6400
|
-
}
|
|
6401
|
-
},
|
|
6402
|
-
{
|
|
6403
|
-
wildcard: {
|
|
6404
|
-
'description.raw': {
|
|
6405
|
-
value: `*${trimmed.replace(/([*?\\])/g, '\\$1')}*`,
|
|
6406
|
-
case_insensitive: true,
|
|
6407
|
-
}
|
|
6408
|
-
}
|
|
6409
|
-
},
|
|
6410
|
-
],
|
|
6411
|
-
minimum_should_match: 1,
|
|
6412
|
-
},
|
|
6413
|
-
};
|
|
6414
|
-
}
|
|
6415
6382
|
krubbleHttp.Http.fetch({
|
|
6416
|
-
url: '/api/system/
|
|
6383
|
+
url: '/api/system/conversation/localSkillList',
|
|
6417
6384
|
method: 'POST',
|
|
6418
|
-
body: {
|
|
6419
|
-
size: 100,
|
|
6420
|
-
query: {
|
|
6421
|
-
bool: {
|
|
6422
|
-
must: [must],
|
|
6423
|
-
filter: [{ term: { active: true } }],
|
|
6424
|
-
// The "Your Org" tab lists only this instance's own skills. Globally-
|
|
6425
|
-
// published skills belong to the "Kodaris" tab (fed by the registry),
|
|
6426
|
-
// so exclude them here. Documents without the field aren't matched by
|
|
6427
|
-
// the term, so ordinary org skills are kept.
|
|
6428
|
-
must_not: [{ term: { globalPublished: true } }],
|
|
6429
|
-
},
|
|
6430
|
-
},
|
|
6431
|
-
},
|
|
6385
|
+
body: { query: query.trim() },
|
|
6432
6386
|
})
|
|
6433
6387
|
.then((r) => r.json())
|
|
6434
6388
|
.then((res) => {
|
|
@@ -6436,7 +6390,7 @@
|
|
|
6436
6390
|
if (requestId !== this._orgSkillRequestId) {
|
|
6437
6391
|
return;
|
|
6438
6392
|
}
|
|
6439
|
-
this._orgSkills = (res?.data
|
|
6393
|
+
this._orgSkills = (res?.data || []);
|
|
6440
6394
|
})
|
|
6441
6395
|
.catch((error) => {
|
|
6442
6396
|
console.error('kr-chatbot: skill search failed', error);
|
|
@@ -6476,19 +6430,23 @@
|
|
|
6476
6430
|
});
|
|
6477
6431
|
}
|
|
6478
6432
|
/**
|
|
6479
|
-
* Load the browse dialog's "Kodaris" tab into `_globalSkills` — the
|
|
6480
|
-
* catalog, installed or not,
|
|
6433
|
+
* Load the browse dialog's "Kodaris" tab into `_globalSkills` — the whole
|
|
6434
|
+
* published Kodaris catalog for discovery, installed or not, runnable by this
|
|
6435
|
+
* user or not. Each row carries `enabled` (installed here), `canEnable` (may
|
|
6436
|
+
* install it) and `canRun` (roles allow invoking it).
|
|
6481
6437
|
*
|
|
6482
|
-
* Uses `/conversation/
|
|
6438
|
+
* Uses `/conversation/globalSkillList` rather than the `/aiskills/global/search`
|
|
6483
6439
|
* twin, which requires a skill-authoring role an ordinary chat user doesn't have.
|
|
6484
|
-
*
|
|
6485
|
-
*
|
|
6440
|
+
*
|
|
6441
|
+
* Returns `[]` — not an error — when there's no reachable registry, and always
|
|
6442
|
+
* on kodaris.com, which IS the catalog source and so has no remote to pull. The
|
|
6443
|
+
* tab itself is hidden there (see `_renderSkillBrowse`).
|
|
6486
6444
|
*/
|
|
6487
6445
|
_searchGlobalSkills(query) {
|
|
6488
6446
|
this._globalSkillRequestId++;
|
|
6489
6447
|
const requestId = this._globalSkillRequestId;
|
|
6490
6448
|
krubbleHttp.Http.fetch({
|
|
6491
|
-
url: '/api/system/conversation/
|
|
6449
|
+
url: '/api/system/conversation/globalSkillList',
|
|
6492
6450
|
method: 'POST',
|
|
6493
6451
|
body: { query: query.trim() },
|
|
6494
6452
|
})
|
|
@@ -6512,6 +6470,17 @@
|
|
|
6512
6470
|
_handleGlobalSkillEnable(skill, e) {
|
|
6513
6471
|
// The card body inserts the skill; the install button must not also do that.
|
|
6514
6472
|
e.stopPropagation();
|
|
6473
|
+
// The button is shown to everyone so the catalog reads the same for all users,
|
|
6474
|
+
// but the enable endpoint requires administrator/superuser/skillEdit. Without
|
|
6475
|
+
// it, say who to ask instead of firing a request that comes back 403.
|
|
6476
|
+
if (!skill.canEnable) {
|
|
6477
|
+
krubbleComponents.Snackbar.show({
|
|
6478
|
+
title: 'Permission Required',
|
|
6479
|
+
message: 'You do not have permission to install skills. Please contact your site administrator.',
|
|
6480
|
+
type: 'error',
|
|
6481
|
+
});
|
|
6482
|
+
return;
|
|
6483
|
+
}
|
|
6515
6484
|
if (this._globalSkillPending.has(skill.code)) {
|
|
6516
6485
|
return;
|
|
6517
6486
|
}
|
|
@@ -6638,13 +6607,33 @@
|
|
|
6638
6607
|
recognition.start();
|
|
6639
6608
|
this._inputEl?.focus();
|
|
6640
6609
|
}
|
|
6610
|
+
/**
|
|
6641
6611
|
/** Insert `/code ` into the composer and focus it, ready for the message. */
|
|
6642
6612
|
_handleSkillSelect(skill) {
|
|
6643
|
-
//
|
|
6644
|
-
//
|
|
6645
|
-
//
|
|
6646
|
-
|
|
6647
|
-
|
|
6613
|
+
// The user's roles don't allow this skill — inserting /code would just fail in the
|
|
6614
|
+
// chat. Name the roles that would grant it, so the user knows what to ask for and
|
|
6615
|
+
// whoever grants it knows what to give.
|
|
6616
|
+
if (skill.canRun === false) {
|
|
6617
|
+
let message = `You do not have access to "${skill.name}". Ask your site administrator to grant it.`;
|
|
6618
|
+
if (skill.allowedRoles?.length) {
|
|
6619
|
+
message = `"${skill.name}" requires the ${skill.allowedRoles.join(' or ')} role. Ask your site administrator to grant it.`;
|
|
6620
|
+
}
|
|
6621
|
+
krubbleComponents.Snackbar.show({
|
|
6622
|
+
title: 'Access Required',
|
|
6623
|
+
message: message,
|
|
6624
|
+
type: 'error',
|
|
6625
|
+
});
|
|
6626
|
+
return;
|
|
6627
|
+
}
|
|
6628
|
+
// A global skill nobody has installed here yet isn't runnable either — point at the
|
|
6629
|
+
// Install button rather than appearing to ignore the click. Local skills carry no
|
|
6630
|
+
// install state, so this only applies to global ones.
|
|
6631
|
+
if (skill.global && !skill.enabled) {
|
|
6632
|
+
krubbleComponents.Snackbar.show({
|
|
6633
|
+
title: 'Not Installed',
|
|
6634
|
+
message: `"${skill.name}" has to be installed on this site before you can use it — use the Install button on the card.`,
|
|
6635
|
+
type: 'info',
|
|
6636
|
+
});
|
|
6648
6637
|
return;
|
|
6649
6638
|
}
|
|
6650
6639
|
this._inputValue = `/${skill.code} `;
|
|
@@ -6676,8 +6665,11 @@
|
|
|
6676
6665
|
this._skillBrowseQuery = '';
|
|
6677
6666
|
this._skillBrowseOpened = true;
|
|
6678
6667
|
// Fill both tabs up front so switching between them is instant, not stale.
|
|
6668
|
+
// The Kodaris tab isn't rendered on the registry server, so skip its fetch there.
|
|
6679
6669
|
this._searchOrgSkills('');
|
|
6680
|
-
this.
|
|
6670
|
+
if (this._aiFrameworkConfig.registryServer !== true) {
|
|
6671
|
+
this._searchGlobalSkills('');
|
|
6672
|
+
}
|
|
6681
6673
|
}
|
|
6682
6674
|
_handleSkillBrowseClose() {
|
|
6683
6675
|
this._skillBrowseOpened = false;
|
|
@@ -6686,7 +6678,9 @@
|
|
|
6686
6678
|
this._skillBrowseQuery = e.target.value;
|
|
6687
6679
|
// Filter both catalogs so whichever tab the user switches to is already current.
|
|
6688
6680
|
this._searchOrgSkills(this._skillBrowseQuery);
|
|
6689
|
-
|
|
6681
|
+
if (this._aiFrameworkConfig.registryServer !== true) {
|
|
6682
|
+
this._searchGlobalSkills(this._skillBrowseQuery);
|
|
6683
|
+
}
|
|
6690
6684
|
}
|
|
6691
6685
|
_handleSkillBrowseTabChange(e) {
|
|
6692
6686
|
// Both buffers are kept current by open/search, so switching just swaps which
|
|
@@ -7028,6 +7022,15 @@
|
|
|
7028
7022
|
userText: params.message,
|
|
7029
7023
|
fileIds: files.map((f) => f.conversationFileID),
|
|
7030
7024
|
};
|
|
7025
|
+
// Ground the model in where the user is and what they've highlighted.
|
|
7026
|
+
// Only sent when present so an empty selection / failed capture doesn't
|
|
7027
|
+
// add noise to the request.
|
|
7028
|
+
if (params.pageContext) {
|
|
7029
|
+
body.pageContext = params.pageContext;
|
|
7030
|
+
}
|
|
7031
|
+
if (params.selectionContext) {
|
|
7032
|
+
body.selectionContext = params.selectionContext;
|
|
7033
|
+
}
|
|
7031
7034
|
if (this.conversationId != null) {
|
|
7032
7035
|
body.conversationID = this.conversationId;
|
|
7033
7036
|
}
|
|
@@ -7072,10 +7075,16 @@
|
|
|
7072
7075
|
});
|
|
7073
7076
|
};
|
|
7074
7077
|
}
|
|
7078
|
+
// Capture where the user is and what they've highlighted at send time (not
|
|
7079
|
+
// earlier — selection is transient and the route may have changed since the
|
|
7080
|
+
// panel opened), so every turn grounds the model in the user's current spot.
|
|
7081
|
+
const context = this._captureContext();
|
|
7075
7082
|
this.send({
|
|
7076
7083
|
message: params.message,
|
|
7077
7084
|
messages: this.messages.slice(0, -1),
|
|
7078
7085
|
conversationFiles: params.conversationFiles,
|
|
7086
|
+
pageContext: context.pageContext,
|
|
7087
|
+
selectionContext: context.selectionContext,
|
|
7079
7088
|
}).then((response) => {
|
|
7080
7089
|
if (!response.ok) {
|
|
7081
7090
|
this._handleStreamError('Request failed: ' + response.status);
|
|
@@ -7095,6 +7104,56 @@
|
|
|
7095
7104
|
}
|
|
7096
7105
|
});
|
|
7097
7106
|
}
|
|
7107
|
+
/**
|
|
7108
|
+
* Capture ambient "where is the user / what are they looking at" context at
|
|
7109
|
+
* send time, so the AI is grounded without the host passing anything in.
|
|
7110
|
+
*
|
|
7111
|
+
* Two universal sources that exist on every page the chatbot mounts in:
|
|
7112
|
+
* - `pageContext` — the location (full url, which already carries the path and
|
|
7113
|
+
* query) and title, plus, when the page renders the server-side
|
|
7114
|
+
* `#kodaris-page` JSON island (storefront content pages do), its parsed
|
|
7115
|
+
* contents (page name/type/ids). A page without the island still yields url
|
|
7116
|
+
* + title, so context is never empty on a real page.
|
|
7117
|
+
* - `selectionContext` — the text the user currently has highlighted, read
|
|
7118
|
+
* live here because a selection is transient (it may be gone a moment later).
|
|
7119
|
+
*
|
|
7120
|
+
* Both are returned as strings because that's what the backend `ModelForText`
|
|
7121
|
+
* fields (`pageContext` / `selectionContext`) accept. Capture is best-effort:
|
|
7122
|
+
* any failure yields an empty string for that field rather than blocking send.
|
|
7123
|
+
*/
|
|
7124
|
+
_captureContext() {
|
|
7125
|
+
let pageContext = '';
|
|
7126
|
+
try {
|
|
7127
|
+
const page = {
|
|
7128
|
+
url: window.location.href,
|
|
7129
|
+
title: document.title,
|
|
7130
|
+
};
|
|
7131
|
+
// Structured page context is opt-in by convention: a page that renders the
|
|
7132
|
+
// `#kodaris-page` island gets its name/type/ids folded in for free. The
|
|
7133
|
+
// island's text is JSON; a malformed/absent island just leaves it out.
|
|
7134
|
+
const island = document.getElementById('kodaris-page');
|
|
7135
|
+
if (island && island.textContent) {
|
|
7136
|
+
try {
|
|
7137
|
+
page.page = JSON.parse(island.textContent);
|
|
7138
|
+
}
|
|
7139
|
+
catch { /* island present but not valid JSON — location context still stands */ }
|
|
7140
|
+
}
|
|
7141
|
+
pageContext = JSON.stringify(page);
|
|
7142
|
+
}
|
|
7143
|
+
catch { /* leave pageContext empty — never block a send on context capture */ }
|
|
7144
|
+
let selectionContext = '';
|
|
7145
|
+
try {
|
|
7146
|
+
const selection = window.getSelection();
|
|
7147
|
+
if (selection) {
|
|
7148
|
+
selectionContext = selection.toString().trim();
|
|
7149
|
+
}
|
|
7150
|
+
}
|
|
7151
|
+
catch { /* leave selectionContext empty */ }
|
|
7152
|
+
return {
|
|
7153
|
+
pageContext,
|
|
7154
|
+
selectionContext,
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7098
7157
|
_handleStop() {
|
|
7099
7158
|
if (this._reader) {
|
|
7100
7159
|
this._reader.cancel();
|
|
@@ -7368,7 +7427,14 @@
|
|
|
7368
7427
|
</div>
|
|
7369
7428
|
`;
|
|
7370
7429
|
}
|
|
7371
|
-
/**
|
|
7430
|
+
/**
|
|
7431
|
+
* The "Browse all skills" dialog — a searchable grid of Org / Kodaris cards.
|
|
7432
|
+
*
|
|
7433
|
+
* The Kodaris tab is omitted on the registry server (kodaris.com): that instance
|
|
7434
|
+
* IS the catalog source, so `globalSkillList` has no remote to pull and always
|
|
7435
|
+
* returns `[]`. Its own published skills appear on the Your Org tab instead,
|
|
7436
|
+
* carrying a "Global" badge.
|
|
7437
|
+
*/
|
|
7372
7438
|
_renderSkillBrowse() {
|
|
7373
7439
|
return b `
|
|
7374
7440
|
<kr-dialog
|
|
@@ -7390,9 +7456,11 @@
|
|
|
7390
7456
|
<kr-tab id="org" label="Your Org">
|
|
7391
7457
|
${this._renderSkillGrid()}
|
|
7392
7458
|
</kr-tab>
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7459
|
+
${this._aiFrameworkConfig.registryServer === true ? A : b `
|
|
7460
|
+
<kr-tab id="kodaris" label="Kodaris">
|
|
7461
|
+
${this._renderSkillGrid(true)}
|
|
7462
|
+
</kr-tab>
|
|
7463
|
+
`}
|
|
7396
7464
|
</kr-tab-group>
|
|
7397
7465
|
</div>
|
|
7398
7466
|
</kr-dialog>
|
|
@@ -7401,8 +7469,13 @@
|
|
|
7401
7469
|
/**
|
|
7402
7470
|
* The card grid of skill search results for the browse dialog. Clicking a card
|
|
7403
7471
|
* inserts `/code ` into the composer. In the shared-Kodaris (`global`) tab each
|
|
7404
|
-
* card also gets a footer row with an
|
|
7405
|
-
*
|
|
7472
|
+
* card also gets a footer row with an Install/Uninstall button.
|
|
7473
|
+
*
|
|
7474
|
+
* The Kodaris tab is the whole published catalog, deliberately NOT role-filtered so
|
|
7475
|
+
* users can discover skills they don't have yet. So a card there can be in one of two
|
|
7476
|
+
* non-runnable states — the user's roles don't allow it (`canRun: false`), or it isn't
|
|
7477
|
+
* installed here (not `enabled`). Neither looks clickable; clicking either explains
|
|
7478
|
+
* why via `_handleSkillSelect`.
|
|
7406
7479
|
*/
|
|
7407
7480
|
_renderSkillGrid(global = false) {
|
|
7408
7481
|
let skills = this._orgSkills;
|
|
@@ -7416,7 +7489,10 @@
|
|
|
7416
7489
|
<div class="skill-browse__grid">
|
|
7417
7490
|
${skills.map((skill) => b `
|
|
7418
7491
|
<div
|
|
7419
|
-
class
|
|
7492
|
+
class=${e({
|
|
7493
|
+
'skill-card': true,
|
|
7494
|
+
'skill-card--unavailable': skill.canRun === false || (!!skill.global && !skill.enabled),
|
|
7495
|
+
})}
|
|
7420
7496
|
role="button"
|
|
7421
7497
|
tabindex="0"
|
|
7422
7498
|
@click=${() => this._handleSkillSelect(skill)}
|
|
@@ -7426,6 +7502,9 @@
|
|
|
7426
7502
|
${skill.description ? b `
|
|
7427
7503
|
<span class="skill-card__desc">${skill.description}</span>
|
|
7428
7504
|
` : A}
|
|
7505
|
+
${!global && skill.global ? b `
|
|
7506
|
+
<span class="skill-card__badge">Global</span>
|
|
7507
|
+
` : A}
|
|
7429
7508
|
${global ? b `
|
|
7430
7509
|
<div class="skill-card__footer">
|
|
7431
7510
|
<span class="skill-card__price">Free</span>
|
|
@@ -9295,6 +9374,30 @@
|
|
|
9295
9374
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
9296
9375
|
}
|
|
9297
9376
|
|
|
9377
|
+
/* A Kodaris-tab card the user can't invoke — not in their roles, or not installed
|
|
9378
|
+
here yet. Clicking it does nothing, so it drops the pointer cursor and the hover
|
|
9379
|
+
highlight. The Install button inside keeps its own pointer cursor. */
|
|
9380
|
+
.skill-card--unavailable {
|
|
9381
|
+
cursor: default;
|
|
9382
|
+
}
|
|
9383
|
+
|
|
9384
|
+
.skill-card--unavailable:hover {
|
|
9385
|
+
border-color: #e5e7eb;
|
|
9386
|
+
box-shadow: none;
|
|
9387
|
+
}
|
|
9388
|
+
|
|
9389
|
+
/* Marks a globally-published skill listed on the Your Org tab — only happens on
|
|
9390
|
+
kodaris.com, which has no Kodaris tab for its own published skills. */
|
|
9391
|
+
.skill-card__badge {
|
|
9392
|
+
align-self: flex-start;
|
|
9393
|
+
padding: 2px 8px;
|
|
9394
|
+
border-radius: 10px;
|
|
9395
|
+
background: #f3f4f6;
|
|
9396
|
+
font-size: 11px;
|
|
9397
|
+
font-weight: 600;
|
|
9398
|
+
color: #000000;
|
|
9399
|
+
}
|
|
9400
|
+
|
|
9298
9401
|
/* Footer row on global skill cards: install button on the left, price on the
|
|
9299
9402
|
right. margin-top:auto pins it to the bottom so cards in a row align their
|
|
9300
9403
|
footers regardless of description length. Negative horizontal margins
|