@nlxai/core 1.2.3 → 1.2.4-alpha.3

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/lib/index.cjs CHANGED
@@ -5,75 +5,9 @@ var ramda = require('ramda');
5
5
  var ReconnectingWebSocket = require('reconnecting-websocket');
6
6
  var uuid = require('uuid');
7
7
 
8
- var name = "@nlxai/core";
9
- var version$1 = "1.2.3";
10
- var description = "Low-level SDK for building NLX experiences";
11
- var type = "module";
12
- var main = "lib/index.cjs";
13
- var module$1 = "lib/index.esm.js";
14
- var browser = "lib/index.umd.js";
15
- var types = "lib/index.d.ts";
16
- var exports$1 = {
17
- ".": {
18
- types: "./lib/index.d.ts",
19
- "import": "./lib/index.esm.js",
20
- require: "./lib/index.cjs"
21
- }
22
- };
23
- var scripts = {
24
- build: "rm -rf lib && rollup -c --configPlugin typescript --configImportAttributesKey with",
25
- "lint:check": "eslint src/ --ext .ts,.tsx,.js,.jsx --max-warnings 0",
26
- lint: "eslint src/ --ext .ts,.tsx,.js,.jsx --fix",
27
- prepublish: "npm run build",
28
- test: "typedoc --emit none",
29
- tsc: "tsc",
30
- "update-readme:docs": "rm -rf docs/ && typedoc",
31
- "update-readme:merge": "../../scripts/transclude-markdown.js",
32
- "update-readme": "npm run update-readme:docs && npm run update-readme:merge"
33
- };
34
- var author = "Peter Szerzo <peter@nlx.ai>";
35
- var license = "MIT";
36
- var devDependencies = {
37
- "@types/isomorphic-fetch": "^0.0.39",
38
- "@types/node": "^24.10.1",
39
- "@types/ramda": "0.31.1",
40
- "@types/uuid": "^9.0.7",
41
- "concat-md": "^0.5.1",
42
- "eslint-config-nlx": "*",
43
- prettier: "^3.1.0",
44
- "rollup-config-nlx": "*",
45
- typedoc: "^0.28.14",
46
- "typedoc-plugin-markdown": "^4.9.0",
47
- typescript: "^5.5.4"
48
- };
49
- var dependencies = {
50
- "isomorphic-fetch": "^3.0.0",
51
- ramda: "^0.32.0",
52
- "reconnecting-websocket": "^4.4.0",
53
- uuid: "^9.0.1"
54
- };
55
- var publishConfig = {
56
- access: "public"
57
- };
58
- var gitHead = "3902161e95745dd4a9cfb68bb469755a62b421f5";
8
+ var version$1 = "1.2.4-alpha.3";
59
9
  var packageJson = {
60
- name: name,
61
- version: version$1,
62
- description: description,
63
- type: type,
64
- main: main,
65
- module: module$1,
66
- browser: browser,
67
- types: types,
68
- exports: exports$1,
69
- scripts: scripts,
70
- author: author,
71
- license: license,
72
- devDependencies: devDependencies,
73
- dependencies: dependencies,
74
- publishConfig: publishConfig,
75
- gitHead: gitHead
76
- };
10
+ version: version$1};
77
11
 
78
12
  /**
79
13
  * Package version
@@ -163,6 +97,96 @@ const normalizeToHttp = (applicationUrl) => {
163
97
  const isWebsocketUrl = (url) => {
164
98
  return url.indexOf("wss://") === 0;
165
99
  };
100
+ const fetchUserMessage = async ({ fullApplicationUrl, headers, body, stream, eventListeners, }) => {
101
+ const streamRequest = async (body) => {
102
+ const response = await fetch(fullApplicationUrl, {
103
+ method: "POST",
104
+ headers: {
105
+ ...headers,
106
+ "Content-Type": "application/json",
107
+ "nlx-sdk-version": packageJson.version,
108
+ },
109
+ body: JSON.stringify({ ...body, stream: true }),
110
+ });
111
+ if (!response.ok || response.body == null)
112
+ throw new Error(`HTTP Error: ${response.status}`);
113
+ const reader = response.body.getReader();
114
+ const decoder = new TextDecoder();
115
+ let buffer = "";
116
+ const messages = [];
117
+ let finalResponse = {};
118
+ while (true) {
119
+ const { done, value } = await reader.read();
120
+ if (done)
121
+ break;
122
+ buffer += decoder.decode(value, { stream: true });
123
+ while (true) {
124
+ const openBrace = buffer.indexOf("{");
125
+ if (openBrace === -1)
126
+ break;
127
+ let foundObject = false;
128
+ for (let i = 0; i < buffer.length; i++) {
129
+ if (buffer[i] === "}") {
130
+ const candidate = buffer.substring(openBrace, i + 1);
131
+ try {
132
+ const json = JSON.parse(candidate);
133
+ if (json.type === "interim") {
134
+ const text = json.text;
135
+ if (typeof text === "string") {
136
+ eventListeners.interimMessage.forEach((listener) => {
137
+ listener(text);
138
+ });
139
+ }
140
+ }
141
+ else if (json.type === "message") {
142
+ messages.push({
143
+ text: json.text,
144
+ choices: json.choices ?? [],
145
+ messageId: json.messageId,
146
+ metadata: json.metadata,
147
+ });
148
+ }
149
+ else if (json.type === "final_response") {
150
+ finalResponse = json.data;
151
+ }
152
+ buffer = buffer.substring(i + 1);
153
+ foundObject = true;
154
+ break;
155
+ }
156
+ catch (e) {
157
+ /* keep scanning */
158
+ }
159
+ }
160
+ }
161
+ if (!foundObject)
162
+ break;
163
+ }
164
+ }
165
+ eventListeners.interimMessage.forEach((listener) => {
166
+ listener(undefined);
167
+ });
168
+ return { ...finalResponse, messages };
169
+ };
170
+ if (stream) {
171
+ return await streamRequest(body);
172
+ }
173
+ else {
174
+ const response = await fetch(fullApplicationUrl, {
175
+ method: "POST",
176
+ headers: {
177
+ ...(headers ?? {}),
178
+ Accept: "application/json",
179
+ "Content-Type": "application/json",
180
+ "nlx-sdk-version": packageJson.version,
181
+ },
182
+ body: JSON.stringify(body),
183
+ });
184
+ if (!response.ok || response.body == null)
185
+ throw new Error(`HTTP Error: ${response.status}`);
186
+ const json = await response.json();
187
+ return json;
188
+ }
189
+ };
166
190
  /**
167
191
  * Call this to create a conversation handler.
168
192
  * @param configuration - The necessary configuration to create the conversation.
@@ -193,7 +217,10 @@ function createConversation(configuration) {
193
217
  if (/[-|_][a-z]{2,}[-|_][A-Z]{2,}$/.test(applicationUrl)) {
194
218
  Console.warn("Since v1.0.0, the language code is no longer added at the end of the application URL. Please remove the modifier (e.g. '-en-US') from the URL, and specify it in the `languageCode` parameter instead.");
195
219
  }
196
- const eventListeners = { voicePlusCommand: [] };
220
+ const eventListeners = {
221
+ voicePlusCommand: [],
222
+ interimMessage: [],
223
+ };
197
224
  const initialConversationId = configuration.conversationId ?? uuid.v4();
198
225
  let state = {
199
226
  responses: configuration.responses ?? [],
@@ -305,20 +332,13 @@ function createConversation(configuration) {
305
332
  }
306
333
  else {
307
334
  try {
308
- const res = await fetch(fullApplicationHttpUrl(), {
309
- method: "POST",
310
- headers: {
311
- ...(configuration.headers ?? {}),
312
- Accept: "application/json",
313
- "Content-Type": "application/json",
314
- "nlx-sdk-version": packageJson.version,
315
- },
316
- body: JSON.stringify(bodyWithContext),
335
+ const json = await fetchUserMessage({
336
+ fullApplicationUrl: fullApplicationHttpUrl(),
337
+ headers: configuration.headers ?? {},
338
+ stream: configuration.experimental?.streamHttp ?? false,
339
+ eventListeners,
340
+ body: bodyWithContext,
317
341
  });
318
- if (res.status >= 400) {
319
- throw new Error(`Responded with ${res.status}`);
320
- }
321
- const json = await res.json();
322
342
  messageResponseHandler(json);
323
343
  }
324
344
  catch (err) {
@@ -591,6 +611,23 @@ function createConversation(configuration) {
591
611
  sendFlow(welcomeIntent, context);
592
612
  },
593
613
  sendChoice,
614
+ submitFeedback: async (feedbackUrl, feedback) => {
615
+ const res = await fetch(feedbackUrl, {
616
+ method: "POST",
617
+ headers: {
618
+ "Content-Type": "application/json",
619
+ },
620
+ body: JSON.stringify({
621
+ languageCode: state.languageCode,
622
+ conversationId: state.conversationId,
623
+ userId: state.userId,
624
+ ...feedback,
625
+ }),
626
+ });
627
+ if (res.status >= 400) {
628
+ throw new Error(`Responded with ${res.status}`);
629
+ }
630
+ },
594
631
  currentConversationId: () => {
595
632
  return state.conversationId;
596
633
  },
package/lib/index.d.ts CHANGED
@@ -55,6 +55,10 @@ export interface Config {
55
55
  * @internal
56
56
  */
57
57
  experimental?: {
58
+ /**
59
+ * Check whether HTTP streaming should be enabled
60
+ */
61
+ streamHttp?: boolean;
58
62
  /**
59
63
  * Simulate alternative channel types
60
64
  */
@@ -145,6 +149,15 @@ export interface ConversationHandler {
145
149
  * @param context - [Context](https://docs.studio.nlx.ai/workspacesettings/documentation-settings/settings-context-attributes) for usage later in the intent.
146
150
  */
147
151
  sendStructured: (request: StructuredRequest, context?: Context) => void;
152
+ /**
153
+ * Submit feedback about a response.
154
+ * @param url - The URL comming from the Application response `metadata.feedbackURL` field.
155
+ * @param feedback - Either a numerical rating or a textual comment.
156
+ */
157
+ submitFeedback: (url: string, feedback: {
158
+ rating?: number;
159
+ comment?: string;
160
+ }) => Promise<void>;
148
161
  /**
149
162
  * Subscribe a callback to the conversation. On subscribe, the subscriber will receive all of the Responses that the conversation has already received.
150
163
  * @param subscriber - The callback to subscribe
@@ -351,6 +364,16 @@ export interface ApplicationResponseMetadata {
351
364
  * Knowledge base sources
352
365
  */
353
366
  sources?: KnowledgeBaseResponseSource[];
367
+ /**
368
+ * URL to use for submitting feedback about this response. See `feedbackConfig` for what the expected feedback type is.
369
+ *
370
+ * You can pass this as the first argument to `submitFeedback`.
371
+ */
372
+ feedbackUrl?: string;
373
+ /**
374
+ * If present, the application would like to collect feedback from the user.
375
+ */
376
+ feedbackConfig?: FeedbackConfiguration;
354
377
  }
355
378
  /**
356
379
  * Response for knowlege base sources
@@ -541,6 +564,52 @@ export type Response = ApplicationResponse | UserResponse | FailureMessage;
541
564
  * The time value in milliseconds since midnight, January 1, 1970 UTC.
542
565
  */
543
566
  export type Time = number;
567
+ /**
568
+ * Configuration for feedback collection. You can use this to render an appropriate feedback widget in your application.
569
+ */
570
+ export interface FeedbackConfiguration {
571
+ /** Unique identifier for the feedback collection. */
572
+ feedbackId: string;
573
+ /** Human readable name of this feedback collection. */
574
+ feedbackName: string;
575
+ /**
576
+ * Type of feedback being collected.
577
+ * At the moment only binary feedback is supported, but we plan to introduce more types in the future.
578
+ * Hence your code should make sure to check the `type` attribute to make sure the expected feedback type is handled.
579
+ */
580
+ feedbackType: FeedbackType;
581
+ /** Whether comments are enabled for this feedback collection. */
582
+ commentsEnabled: boolean;
583
+ /** Optional question to show to the user when collecting feedback. */
584
+ question?: string;
585
+ /** Labels for individual feedback UI elements as customised by the builder. */
586
+ labels: {
587
+ /** Label for positive feedback */
588
+ positive?: string;
589
+ /** Label for negative feedback */
590
+ negative?: string;
591
+ /** Label for comment */
592
+ comment?: string;
593
+ };
594
+ }
595
+ /**
596
+ * @inline @hidden
597
+ */
598
+ export type FeedbackType = {
599
+ /** A binary feedback type is a thumbs up/down sort of choice. */
600
+ type: "binary";
601
+ /** Configuration specific to binary feedback. */
602
+ config: BinaryFeedbackConfig;
603
+ };
604
+ /**
605
+ * @inline @hidden
606
+ */
607
+ export interface BinaryFeedbackConfig {
608
+ /** Value to send for positive feedback. Default `1`. */
609
+ positiveValue: number;
610
+ /** Value to send for negative feedback. Default `-1`. */
611
+ negativeValue: number;
612
+ }
544
613
  /**
545
614
  * @hidden
546
615
  */
@@ -709,7 +778,15 @@ export interface VoicePlusMessage {
709
778
  * Handler events
710
779
  * @event voicePlusCommand
711
780
  */
712
- export type ConversationHandlerEvent = "voicePlusCommand";
781
+ export type ConversationHandlerEvent = "voicePlusCommand" | "interimMessage";
782
+ /**
783
+ * Voice+ command listener
784
+ */
785
+ export type VoicePlusCommandListener = (payload: any) => void;
786
+ /**
787
+ * Interim message listener
788
+ */
789
+ export type InterimMessageListener = (message?: string) => void;
713
790
  /**
714
791
  * Dictionary of handler methods per event
715
792
  */
@@ -717,7 +794,11 @@ export interface EventHandlers {
717
794
  /**
718
795
  * Voice+ command event handler
719
796
  */
720
- voicePlusCommand: (payload: any) => void;
797
+ voicePlusCommand: VoicePlusCommandListener;
798
+ /**
799
+ * Interim message event handler
800
+ */
801
+ interimMessage: InterimMessageListener;
721
802
  }
722
803
  /**
723
804
  * The callback function for listening to all responses.
package/lib/index.esm.js CHANGED
@@ -3,75 +3,9 @@ import { equals, adjust } from 'ramda';
3
3
  import ReconnectingWebSocket from 'reconnecting-websocket';
4
4
  import { v4 } from 'uuid';
5
5
 
6
- var name = "@nlxai/core";
7
- var version$1 = "1.2.3";
8
- var description = "Low-level SDK for building NLX experiences";
9
- var type = "module";
10
- var main = "lib/index.cjs";
11
- var module = "lib/index.esm.js";
12
- var browser = "lib/index.umd.js";
13
- var types = "lib/index.d.ts";
14
- var exports = {
15
- ".": {
16
- types: "./lib/index.d.ts",
17
- "import": "./lib/index.esm.js",
18
- require: "./lib/index.cjs"
19
- }
20
- };
21
- var scripts = {
22
- build: "rm -rf lib && rollup -c --configPlugin typescript --configImportAttributesKey with",
23
- "lint:check": "eslint src/ --ext .ts,.tsx,.js,.jsx --max-warnings 0",
24
- lint: "eslint src/ --ext .ts,.tsx,.js,.jsx --fix",
25
- prepublish: "npm run build",
26
- test: "typedoc --emit none",
27
- tsc: "tsc",
28
- "update-readme:docs": "rm -rf docs/ && typedoc",
29
- "update-readme:merge": "../../scripts/transclude-markdown.js",
30
- "update-readme": "npm run update-readme:docs && npm run update-readme:merge"
31
- };
32
- var author = "Peter Szerzo <peter@nlx.ai>";
33
- var license = "MIT";
34
- var devDependencies = {
35
- "@types/isomorphic-fetch": "^0.0.39",
36
- "@types/node": "^24.10.1",
37
- "@types/ramda": "0.31.1",
38
- "@types/uuid": "^9.0.7",
39
- "concat-md": "^0.5.1",
40
- "eslint-config-nlx": "*",
41
- prettier: "^3.1.0",
42
- "rollup-config-nlx": "*",
43
- typedoc: "^0.28.14",
44
- "typedoc-plugin-markdown": "^4.9.0",
45
- typescript: "^5.5.4"
46
- };
47
- var dependencies = {
48
- "isomorphic-fetch": "^3.0.0",
49
- ramda: "^0.32.0",
50
- "reconnecting-websocket": "^4.4.0",
51
- uuid: "^9.0.1"
52
- };
53
- var publishConfig = {
54
- access: "public"
55
- };
56
- var gitHead = "3902161e95745dd4a9cfb68bb469755a62b421f5";
6
+ var version$1 = "1.2.4-alpha.3";
57
7
  var packageJson = {
58
- name: name,
59
- version: version$1,
60
- description: description,
61
- type: type,
62
- main: main,
63
- module: module,
64
- browser: browser,
65
- types: types,
66
- exports: exports,
67
- scripts: scripts,
68
- author: author,
69
- license: license,
70
- devDependencies: devDependencies,
71
- dependencies: dependencies,
72
- publishConfig: publishConfig,
73
- gitHead: gitHead
74
- };
8
+ version: version$1};
75
9
 
76
10
  /**
77
11
  * Package version
@@ -161,6 +95,96 @@ const normalizeToHttp = (applicationUrl) => {
161
95
  const isWebsocketUrl = (url) => {
162
96
  return url.indexOf("wss://") === 0;
163
97
  };
98
+ const fetchUserMessage = async ({ fullApplicationUrl, headers, body, stream, eventListeners, }) => {
99
+ const streamRequest = async (body) => {
100
+ const response = await fetch(fullApplicationUrl, {
101
+ method: "POST",
102
+ headers: {
103
+ ...headers,
104
+ "Content-Type": "application/json",
105
+ "nlx-sdk-version": packageJson.version,
106
+ },
107
+ body: JSON.stringify({ ...body, stream: true }),
108
+ });
109
+ if (!response.ok || response.body == null)
110
+ throw new Error(`HTTP Error: ${response.status}`);
111
+ const reader = response.body.getReader();
112
+ const decoder = new TextDecoder();
113
+ let buffer = "";
114
+ const messages = [];
115
+ let finalResponse = {};
116
+ while (true) {
117
+ const { done, value } = await reader.read();
118
+ if (done)
119
+ break;
120
+ buffer += decoder.decode(value, { stream: true });
121
+ while (true) {
122
+ const openBrace = buffer.indexOf("{");
123
+ if (openBrace === -1)
124
+ break;
125
+ let foundObject = false;
126
+ for (let i = 0; i < buffer.length; i++) {
127
+ if (buffer[i] === "}") {
128
+ const candidate = buffer.substring(openBrace, i + 1);
129
+ try {
130
+ const json = JSON.parse(candidate);
131
+ if (json.type === "interim") {
132
+ const text = json.text;
133
+ if (typeof text === "string") {
134
+ eventListeners.interimMessage.forEach((listener) => {
135
+ listener(text);
136
+ });
137
+ }
138
+ }
139
+ else if (json.type === "message") {
140
+ messages.push({
141
+ text: json.text,
142
+ choices: json.choices ?? [],
143
+ messageId: json.messageId,
144
+ metadata: json.metadata,
145
+ });
146
+ }
147
+ else if (json.type === "final_response") {
148
+ finalResponse = json.data;
149
+ }
150
+ buffer = buffer.substring(i + 1);
151
+ foundObject = true;
152
+ break;
153
+ }
154
+ catch (e) {
155
+ /* keep scanning */
156
+ }
157
+ }
158
+ }
159
+ if (!foundObject)
160
+ break;
161
+ }
162
+ }
163
+ eventListeners.interimMessage.forEach((listener) => {
164
+ listener(undefined);
165
+ });
166
+ return { ...finalResponse, messages };
167
+ };
168
+ if (stream) {
169
+ return await streamRequest(body);
170
+ }
171
+ else {
172
+ const response = await fetch(fullApplicationUrl, {
173
+ method: "POST",
174
+ headers: {
175
+ ...(headers ?? {}),
176
+ Accept: "application/json",
177
+ "Content-Type": "application/json",
178
+ "nlx-sdk-version": packageJson.version,
179
+ },
180
+ body: JSON.stringify(body),
181
+ });
182
+ if (!response.ok || response.body == null)
183
+ throw new Error(`HTTP Error: ${response.status}`);
184
+ const json = await response.json();
185
+ return json;
186
+ }
187
+ };
164
188
  /**
165
189
  * Call this to create a conversation handler.
166
190
  * @param configuration - The necessary configuration to create the conversation.
@@ -191,7 +215,10 @@ function createConversation(configuration) {
191
215
  if (/[-|_][a-z]{2,}[-|_][A-Z]{2,}$/.test(applicationUrl)) {
192
216
  Console.warn("Since v1.0.0, the language code is no longer added at the end of the application URL. Please remove the modifier (e.g. '-en-US') from the URL, and specify it in the `languageCode` parameter instead.");
193
217
  }
194
- const eventListeners = { voicePlusCommand: [] };
218
+ const eventListeners = {
219
+ voicePlusCommand: [],
220
+ interimMessage: [],
221
+ };
195
222
  const initialConversationId = configuration.conversationId ?? v4();
196
223
  let state = {
197
224
  responses: configuration.responses ?? [],
@@ -303,20 +330,13 @@ function createConversation(configuration) {
303
330
  }
304
331
  else {
305
332
  try {
306
- const res = await fetch(fullApplicationHttpUrl(), {
307
- method: "POST",
308
- headers: {
309
- ...(configuration.headers ?? {}),
310
- Accept: "application/json",
311
- "Content-Type": "application/json",
312
- "nlx-sdk-version": packageJson.version,
313
- },
314
- body: JSON.stringify(bodyWithContext),
333
+ const json = await fetchUserMessage({
334
+ fullApplicationUrl: fullApplicationHttpUrl(),
335
+ headers: configuration.headers ?? {},
336
+ stream: configuration.experimental?.streamHttp ?? false,
337
+ eventListeners,
338
+ body: bodyWithContext,
315
339
  });
316
- if (res.status >= 400) {
317
- throw new Error(`Responded with ${res.status}`);
318
- }
319
- const json = await res.json();
320
340
  messageResponseHandler(json);
321
341
  }
322
342
  catch (err) {
@@ -589,6 +609,23 @@ function createConversation(configuration) {
589
609
  sendFlow(welcomeIntent, context);
590
610
  },
591
611
  sendChoice,
612
+ submitFeedback: async (feedbackUrl, feedback) => {
613
+ const res = await fetch(feedbackUrl, {
614
+ method: "POST",
615
+ headers: {
616
+ "Content-Type": "application/json",
617
+ },
618
+ body: JSON.stringify({
619
+ languageCode: state.languageCode,
620
+ conversationId: state.conversationId,
621
+ userId: state.userId,
622
+ ...feedback,
623
+ }),
624
+ });
625
+ if (res.status >= 400) {
626
+ throw new Error(`Responded with ${res.status}`);
627
+ }
628
+ },
592
629
  currentConversationId: () => {
593
630
  return state.conversationId;
594
631
  },