diff --git a/Conversation Design/Conversation Flows/publishToTestHarness.js b/Conversation Design/Conversation Flows/publishToTestHarness.js new file mode 100644 index 0000000..42f1771 --- /dev/null +++ b/Conversation Design/Conversation Flows/publishToTestHarness.js @@ -0,0 +1,21 @@ +(async () => { + webdis().publish(recognizedObject.workspaceId, { + conversationId: recognizedObject.conversationId, + input: recognizedObject.input, + answers: recognizedObject.answers, + }); +})() + .catch((error) => { + console.log(error.message); + recognizedObject.answers.push(""); + recognizedObject.errorInfo = { + ...recognizedObject.errorInfo, + label: { + data: error.toJSON ? error.toJSON() : {}, + message: error.message, + }, + }; + }) + .finally(() => { + next(); + }); diff --git a/Integrations/Global Variables/latencySpan.js b/Integrations/Global Variables/latencySpan.js new file mode 100644 index 0000000..7bb1c76 --- /dev/null +++ b/Integrations/Global Variables/latencySpan.js @@ -0,0 +1,111 @@ +const traces = axios.create({ + baseURL: opentelemetry_settings.baseUrl, + timeout: 16000, +}); +return { + endSpan(conversationId) { + return new Promise(async (resolve, reject) => { + try { + const latencySpanKey = `latency-span-${conversationId}`; + const span = JSON.parse(await redis.get(latencySpanKey)); + if (!span) { + resolve(); + return; + } + redis.del(latencySpanKey); + + span.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = + "" + Date.now() * 1000000; + + console.log("latencySpan span:"); + console.log(span); + + const response = await traces.post(`/v1/traces`, span, { + headers: { + "Content-Type": "application/json", + }, + }); + resolve(span); + } catch (error) { + reject(error); + } + }); + }, + + startSpan(conversationId, startTime) { + return new Promise(async (resolve, reject) => { + try { + const latencySpanKey = `latency-span-${conversationId}`; + const traceId = conversationId.replace(/-/gi, ""); + const span = { + resourceSpans: [ + { + resource: { + attributes: [ + { + key: "service.name", + value: { + stringValue: "end-of-utterance-latency", + }, + }, + ], + }, + scopeSpans: [ + { + scope: { + name: "scope-name", + version: "1.0.0", + attributes: [ + { + key: "my.scope.attribute", + value: { + stringValue: "some scope attribute", + }, + }, + ], + }, + spans: [ + { + traceId: traceId, + spanId: getIdGenerator(8)(), + name: "end-of-utterance-latency", + startTimeUnixNano: startTime, + kind: 2, + }, + ], + }, + ], + }, + ], + }; + + await redis.set(latencySpanKey, JSON.stringify(span)); + await redis.expire(latencySpanKey, 300); + resolve(span); + } catch (error) { + reject(error); + } + }); + }, +}; + +function getIdGenerator(bytes) { + return function generateId() { + const SHARED_BUFFER = buffer.Buffer.allocUnsafe(16); + + for (let i = 0; i < bytes / 4; i++) { + // unsigned right shift drops decimal part of the number + // it is required because if a number between 2**32 and 2**32 - 1 is generated, an out of range error is thrown by writeUInt32BE + SHARED_BUFFER.writeUInt32BE((Math.random() * 2 ** 32) >>> 0, i * 4); + } + // If buffer is all 0, set the last byte to 1 to guarantee a valid w3c id is generated + for (let i = 0; i < bytes; i++) { + if (SHARED_BUFFER[i] > 0) { + break; + } else if (i === bytes - 1) { + SHARED_BUFFER[bytes - 1] = 1; + } + } + return SHARED_BUFFER.toString("hex", 0, bytes); + }; +} \ No newline at end of file diff --git a/Integrations/Global Variables/opentelemetry.js b/Integrations/Global Variables/opentelemetry.js new file mode 100644 index 0000000..09c0feb --- /dev/null +++ b/Integrations/Global Variables/opentelemetry.js @@ -0,0 +1,105 @@ +const traces = axios.create({ + baseURL: opentelemetry_settings.baseUrl, + timeout: 16000, +}) +return { + async startSpan(name) { + try { + if (!conversationData.spans) { + conversationData.spans = [] + } + const spanName = name ? name : 'default' + + const span = { + resourceSpans: [ + { + resource: { + attributes: [ + { + key: 'service.name', + value: { + stringValue: 'ivastudio.verint.live', + }, + }, + ], + }, + scopeSpans: [ + { + scope: { + name: recognizedObject.conversationId, + version: '1.0.0', + attributes: [ + { + key: 'my.scope.attribute', + value: { + stringValue: 'some scope attribute', + }, + }, + ], + }, + spans: [ + { + // traceId: recognizedObject.req.headers['x-b3-traceid'], + traceId: recognizedObject.conversationId.replace(/-/gi, ''), + spanId: recognizedObject.req.headers['x-b3-spanid'], + // parentSpanId: recognizedObject.req.headers['x-b3-parentspanid'], + name: spanName, + startTimeUnixNano: '' + Date.now() * 1000000, + kind: 2, + attributes: [ + { + key: 'conversationId', + value: { + stringValue: recognizedObject.conversationId, + }, + }, + { + key: 'workspaceId', + value: { + stringValue: recognizedObject.workspaceId, + }, + }, + { + key: 'input', + value: { + stringValue: recognizedObject.input, + }, + }, + ], + }, + ], + }, + ], + }, + ], + } + conversationData.spans.push(span) + return span + } catch (e) { + throw e + } + }, + async endSpan() { + try { + const data = conversationData.spans.pop() + + data.resourceSpans[0].scopeSpans[0].spans[0].endTimeUnixNano = '' + Date.now() * 1000000 + + data.resourceSpans[0].scopeSpans[0].spans[0].attributes.push({ + key: 'answers', + value: { + stringValue: JSON.stringify(recognizedObject.answers), + }, + }) + + const response = await traces.post(`/v1/traces`, data, { + headers: { + 'Content-Type': 'application/json', + }, + }) + return response.data + } catch (e) { + throw e + } + }, +} \ No newline at end of file diff --git a/Integrations/Global Variables/opentelemetry_settings.json b/Integrations/Global Variables/opentelemetry_settings.json new file mode 100644 index 0000000..d0fc950 --- /dev/null +++ b/Integrations/Global Variables/opentelemetry_settings.json @@ -0,0 +1,3 @@ +{ + "baseUrl": "https://iva.mortons.site/tracing" +} diff --git a/Integrations/Global Variables/webdis.js b/Integrations/Global Variables/webdis.js new file mode 100644 index 0000000..47e0c80 --- /dev/null +++ b/Integrations/Global Variables/webdis.js @@ -0,0 +1,17 @@ +const webdisBaseURL = "https://iva.mortons.site/webdis/"; + +return { + async publish(channel, message) { + const str = JSON.stringify(message); + const encodedStr = buffer.Buffer.from(str).toString("base64"); + let escapedStr = encodedStr.replace(/\//g, "%2f"); + escapedStr = escapedStr.replace(/\+/g, "%2B"); + + console.log(`webdis:PUBLISH/${channel}/${escapedStr}`); + axios + .post(webdisBaseURL, `PUBLISH/${channel}/${escapedStr}`) + .then(function (response) { + console.log(response.data); + }); + }, +}; \ No newline at end of file diff --git a/Integrations/Proxy Scripts/latency_span.js b/Integrations/Proxy Scripts/latency_span.js new file mode 100644 index 0000000..faa8600 --- /dev/null +++ b/Integrations/Proxy Scripts/latency_span.js @@ -0,0 +1,15 @@ +const conversationId = req.body.conversationId; +const startTime = req.body.startTime; + +(async () => { + try { + const span = await latencySpan().startSpan(conversationId, startTime); + + res.send(span); + } catch (error) { + console.log(error.message); + res.send({ + error: error.message, + }); + } +})(); diff --git a/Integrations/Proxy Scripts/salesforce_mock.js b/Integrations/Proxy Scripts/salesforce_mock.js new file mode 100644 index 0000000..387c135 --- /dev/null +++ b/Integrations/Proxy Scripts/salesforce_mock.js @@ -0,0 +1,24 @@ +(async () => { + const caseId = "003abc000123xyzabc"; + + try { + console.log({ URL: req.url, body: req.body }); + if (req.url.endsWith("token")) { + res.json({ access_token: "TOKEN" }); + } else if (req.url.endsWith(caseId)) { + res.json({ + id: caseId, + CaseNumber: 1024, + }); + } else { + // create a case + res.json({ id: caseId }); + } + } catch (error) { + res.json({ + error: { + message: error.message, + }, + }); + } +})();