@ahmedramez-org/nexus-chat 1.0.0-beta.16 → 1.0.0-beta.18
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/contract/conformance.js +24 -1
- package/contract/index.d.ts +36 -4
- package/contract/index.js +2 -0
- package/package.json +1 -1
- package/react.js +1 -1
- package/window.js +1 -1
package/contract/conformance.js
CHANGED
|
@@ -267,14 +267,37 @@ export async function checkHostContract(options, checkOptions = {}) {
|
|
|
267
267
|
});
|
|
268
268
|
ensure(new Set(kinds).size === kinds.length, 'a context kind is listed twice (a source replaces its bare kind: list only the source).');
|
|
269
269
|
});
|
|
270
|
-
await checks.run('chat.triggers', 'each trigger is @ or
|
|
270
|
+
await checks.run('chat.triggers', 'each trigger is a bare @ or #, or one with a search', () => {
|
|
271
271
|
const chars = (chat?.triggers ?? []).map((trigger) => {
|
|
272
|
+
if (typeof trigger === 'string') {
|
|
273
|
+
ensure(TRIGGER_CHARS.includes(trigger), `unknown trigger ${JSON.stringify(trigger)}; a bare string is one of: ${TRIGGER_CHARS.join(', ')}.`);
|
|
274
|
+
ensure(connection?.incortaApi?.type === 'bff', `the bare ${trigger} trigger needs a connection.incortaApi of type 'bff'.`);
|
|
275
|
+
return trigger;
|
|
276
|
+
}
|
|
272
277
|
ensure(TRIGGER_CHARS.includes(trigger.char), `unknown trigger ${JSON.stringify(trigger.char)}.`);
|
|
273
278
|
ensure(typeof trigger.search === 'function', `the ${trigger.char} trigger has no search().`);
|
|
274
279
|
return trigger.char;
|
|
275
280
|
});
|
|
276
281
|
ensure(new Set(chars).size === chars.length, 'a trigger is listed twice.');
|
|
277
282
|
});
|
|
283
|
+
const sessionLink = chat?.sessionLink;
|
|
284
|
+
if (!sessionLink) {
|
|
285
|
+
checks.skip('chat.sessionLink', 'links reopen a conversation', 'No session link declared.');
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
await checks.run('chat.sessionLink', 'links reopen a conversation', () => {
|
|
289
|
+
const link = sessionLink('contract-check-session');
|
|
290
|
+
let absolute = false;
|
|
291
|
+
try {
|
|
292
|
+
absolute = typeof link === 'string' && /^https?:$/.test(new URL(link).protocol);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// A relative path throws: the link must carry the host's origin.
|
|
296
|
+
}
|
|
297
|
+
ensure(absolute, `chat.sessionLink must return an absolute URL; received ${JSON.stringify(link)}.`);
|
|
298
|
+
ensure(link.includes('contract-check-session'), 'chat.sessionLink must carry the conversation id.');
|
|
299
|
+
});
|
|
300
|
+
}
|
|
278
301
|
await checks.run('actions', 'each action has can() and run()', () => {
|
|
279
302
|
Object.entries(actions ?? {}).forEach(([name, action]) => {
|
|
280
303
|
if (action === undefined)
|
package/contract/index.d.ts
CHANGED
|
@@ -133,15 +133,18 @@ export interface ValueMatch {
|
|
|
133
133
|
dataType?: string;
|
|
134
134
|
complete?: boolean;
|
|
135
135
|
}
|
|
136
|
+
/** The composer trigger characters the chat can serve itself. */
|
|
137
|
+
export declare const BUILT_IN_TRIGGERS: readonly ["@", "#"];
|
|
138
|
+
export type BuiltInTrigger = (typeof BUILT_IN_TRIGGERS)[number];
|
|
136
139
|
/**
|
|
137
|
-
* A composer trigger
|
|
138
|
-
* view (`viewId` is that context's `id`); with none or several
|
|
139
|
-
* character stays plain text.
|
|
140
|
+
* A composer trigger the host serves. Both search inside the one attached
|
|
141
|
+
* business view (`viewId` is that context's `id`); with none or several
|
|
142
|
+
* attached the character stays plain text.
|
|
140
143
|
*
|
|
141
144
|
* - `@` mentions a column. Asked once per view; the chat filters as you type.
|
|
142
145
|
* - `#` searches the view's values for the typed text.
|
|
143
146
|
*/
|
|
144
|
-
export type
|
|
147
|
+
export type TriggerSource = {
|
|
145
148
|
char: '@';
|
|
146
149
|
search(request: {
|
|
147
150
|
viewId: string;
|
|
@@ -154,6 +157,21 @@ export type TriggerSpec = {
|
|
|
154
157
|
limit: number;
|
|
155
158
|
}, signal: AbortSignal): Promise<readonly ValueMatch[]>;
|
|
156
159
|
};
|
|
160
|
+
/**
|
|
161
|
+
* One composer trigger; `chat.triggers` lists them.
|
|
162
|
+
*
|
|
163
|
+
* - A bare `'@'` or `'#'`: the chat serves it itself through a BFF
|
|
164
|
+
* `connection.incortaApi`, across every attached business view and physical
|
|
165
|
+
* table. The user can narrow to one view, then one column, and `#` searches
|
|
166
|
+
* text columns only. Without a BFF `incortaApi` the character is left out
|
|
167
|
+
* with a console warning.
|
|
168
|
+
* - A `TriggerSource`: the host serves it, for one attached view.
|
|
169
|
+
*
|
|
170
|
+
* List each character once. Migrating: code that read `trigger.char` must
|
|
171
|
+
* narrow with `typeof trigger === 'string'` first. Pass bare characters only
|
|
172
|
+
* to a chat that has them (this release or later): an older chat ignores them.
|
|
173
|
+
*/
|
|
174
|
+
export type TriggerSpec = BuiltInTrigger | TriggerSource;
|
|
157
175
|
/** Switches for things the chat builds itself. Each one defaults to `true`. */
|
|
158
176
|
export interface NexusChatFeatures {
|
|
159
177
|
/**
|
|
@@ -274,6 +292,13 @@ export interface NexusChatChat {
|
|
|
274
292
|
beforeSend?(request: {
|
|
275
293
|
prompt: string;
|
|
276
294
|
}): SendAddition | Promise<SendAddition>;
|
|
295
|
+
/**
|
|
296
|
+
* The absolute URL that reopens a saved conversation in this host (the
|
|
297
|
+
* instance root plus the conversation id, for example
|
|
298
|
+
* `https://host/incorta/?chat-session-id=<id>`, in the page query before any hash route, which in-app redirects never drop). The host reads that URL
|
|
299
|
+
* back and calls `handle.openSession`. Absent: no Copy link.
|
|
300
|
+
*/
|
|
301
|
+
sessionLink?(sessionId: string): string;
|
|
277
302
|
}
|
|
278
303
|
export type OpenIntent = {
|
|
279
304
|
type: 'open-dashboard';
|
|
@@ -438,6 +463,13 @@ export interface NexusChatHandle {
|
|
|
438
463
|
* gates: the agent must support the kind; `false` when refused).
|
|
439
464
|
*/
|
|
440
465
|
attach(kind: Exclude<ContextKind, 'file'>, item: ContextItem): boolean;
|
|
466
|
+
/**
|
|
467
|
+
* Opens a saved conversation by its id (a `chat.sessionLink` the host read
|
|
468
|
+
* back from its URL). A conversation with no messages, or one the backend
|
|
469
|
+
* refuses (deleted, someone else's), stays closed and the chat says so.
|
|
470
|
+
* While an unsent question is still being recovered nothing opens.
|
|
471
|
+
*/
|
|
472
|
+
openSession(sessionId: string): void;
|
|
441
473
|
}
|
|
442
474
|
/** The federated entry point `nexusChat/mount`. */
|
|
443
475
|
export type NexusChatMount = (element: HTMLElement, options: NexusChatOptions) => NexusChatHandle;
|
package/contract/index.js
CHANGED
|
@@ -11,6 +11,8 @@ export const BUILT_IN_CONTEXT_KINDS = [
|
|
|
11
11
|
'ai-container',
|
|
12
12
|
'knowledge-store',
|
|
13
13
|
];
|
|
14
|
+
/** The composer trigger characters the chat can serve itself. */
|
|
15
|
+
export const BUILT_IN_TRIGGERS = ['@', '#'];
|
|
14
16
|
/**
|
|
15
17
|
* Reads the namespaced-kind convention. Returns null for bare legacy kinds.
|
|
16
18
|
* Framework-free on purpose: hosts use this inside `canRender` to route by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahmedramez-org/nexus-chat",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.18",
|
|
4
4
|
"description": "NexusChat for any app: a connector and React component that load the prebuilt chat from IncortaCopilot (or a pinned copy), and the host contract types and checks.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
package/react.js
CHANGED
|
@@ -304,7 +304,7 @@ function EmbeddedChat({ embedUrl, modes, defaultMode, theme = 'light', locale })
|
|
|
304
304
|
ref: frame,
|
|
305
305
|
title: NAME,
|
|
306
306
|
src,
|
|
307
|
-
allow: 'storage-access; microphone',
|
|
307
|
+
allow: 'storage-access; microphone; clipboard-write',
|
|
308
308
|
style: { display: 'block', width: '100%', height: '100%', border: 0 },
|
|
309
309
|
}),
|
|
310
310
|
),
|
package/window.js
CHANGED
|
@@ -357,7 +357,7 @@ export function createEmbeddedChat({
|
|
|
357
357
|
if (embedded || !win.opened) return;
|
|
358
358
|
const iframe = document.createElement('iframe');
|
|
359
359
|
iframe.title = NAME;
|
|
360
|
-
iframe.allow = 'storage-access; microphone';
|
|
360
|
+
iframe.allow = 'storage-access; microphone; clipboard-write';
|
|
361
361
|
iframe.src = embedSrc(embedUrl, theme, locale);
|
|
362
362
|
iframe.style.cssText = 'display:block;width:100%;height:100%;border:0';
|
|
363
363
|
win.panel.prepend(iframe);
|