361 lines
11 KiB
JavaScript
361 lines
11 KiB
JavaScript
console.log({ query: req.query, params: req.params, body: req.body });
|
|
|
|
const msgrSettings = settings_667ef98d3ee8930a5debcbdb;
|
|
const paSettings = proactive_agent_settings;
|
|
const studioToken = msgrSettings
|
|
? {
|
|
Authorization: `Bearer ${msgrSettings.messenger.token}`,
|
|
}
|
|
: {};
|
|
const channel = req.body?.metadata?.channel | "copilot";
|
|
|
|
let bot_user = {
|
|
...paSettings.messenger.user,
|
|
};
|
|
|
|
const stripHTMLTags = (input) => {
|
|
if (typeof input !== "string") {
|
|
throw new Error("Input must be a string");
|
|
}
|
|
return input.replace(/<\/?[^>]+(>|$)/g, ""); // Matches and removes HTML tags
|
|
}
|
|
|
|
const app = {
|
|
routes: [],
|
|
route: function (regexp, fn) {
|
|
this.routes.push({ regexp: new RegExp(regexp), function: fn });
|
|
},
|
|
run: function (req, res) {
|
|
const path =
|
|
req.params[0].split(
|
|
`/ProxyScript/run/${req.params.workspaceId}/${req.params.branch}/${req.params.route}`
|
|
)[1] || "/";
|
|
|
|
for (var route of this.routes) {
|
|
console.log(`Checking route: ${route.regexp}`);
|
|
console.log(`Against path: ${path}`);
|
|
if (route.regexp.test(path)) {
|
|
console.log(`Matched route: ${route.regexp}`);
|
|
route.function(req, res, route.regexp.exec(path));
|
|
return;
|
|
}
|
|
}
|
|
// If no route matches, return a 404 status
|
|
const status = { path: req.params[0], status: 404 };
|
|
res.status(404).send(status);
|
|
},
|
|
};
|
|
|
|
const redisKey = (userId) => {
|
|
return `user-map-${userId}`;
|
|
};
|
|
|
|
const axioshelper = (message, config, callback, retries = 3, delay = 100) => {
|
|
console.log(config);
|
|
return new Promise(async (resolve, reject) => {
|
|
for (let i = 0; i < retries; i++) {
|
|
try {
|
|
const response = await axios(config);
|
|
|
|
if (response.status < 200 || response.status > 299) {
|
|
if (callback) {
|
|
await callback();
|
|
} else {
|
|
throw new Error(
|
|
`Axios Request failed! ${JSON.stringify(response)}`
|
|
);
|
|
}
|
|
}
|
|
resolve(response);
|
|
break;
|
|
} catch (error) {
|
|
if (error.response) {
|
|
// The request was made and the server responded with a status code
|
|
// that falls out of the range of 2xx
|
|
console.log(error.response.data);
|
|
console.log(error.response.status);
|
|
console.log(error.response.headers);
|
|
} else if (error.request) {
|
|
// The request was made but no response was received
|
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
// http.ClientRequest in node.js
|
|
console.log(error.request);
|
|
} else {
|
|
// Something happened in setting up the request that triggered an Error
|
|
console.log("Error", error.message);
|
|
}
|
|
console.log(error.config);
|
|
if (i === retries - 1) {
|
|
if (callback) {
|
|
await callback();
|
|
}
|
|
console.log({
|
|
retry: i + 1,
|
|
error: "out of retries",
|
|
message,
|
|
url: config.url,
|
|
});
|
|
reject(error);
|
|
} else {
|
|
console.log({
|
|
level: "warn",
|
|
message: `Attempt ${i + 1} failed.`,
|
|
url: config.url,
|
|
});
|
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
}
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
async function createSessionMap(umStr, sessionMap) {
|
|
console.log({ method: "createSessionMap", sessionMap });
|
|
let keys = Object.keys(sessionMap);
|
|
keys.forEach(async (key) => {
|
|
await redis.hSet(umStr, key, sessionMap[key]);
|
|
});
|
|
await redis.expire(umStr, 3600);
|
|
}
|
|
|
|
async function m2mAuthentication() {
|
|
let data = new URLSearchParams({
|
|
grant_type: paSettings.copilot.oauth2.grant_type,
|
|
client_id: paSettings.copilot.oauth2.clientId,
|
|
client_secret: paSettings.copilot.oauth2.clientSecret,
|
|
audience: paSettings.copilot.oauth2.audience,
|
|
});
|
|
|
|
let token = await axioshelper("m2m authentication", {
|
|
method: "post",
|
|
url: paSettings.copilot.tokenUrl,
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
},
|
|
data,
|
|
})
|
|
.then((response) => {
|
|
return response.data.access_token;
|
|
})
|
|
.catch((error) => {
|
|
console.error("Error during M2M authentication:", error);
|
|
throw error;
|
|
});
|
|
|
|
return token;
|
|
}
|
|
|
|
app.route(
|
|
"^(?:/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/contexts/([^/]+))(?:/$)?$",
|
|
async (req, res, match) => {
|
|
try {
|
|
console.log(`Received request for contexts: ${match}`);
|
|
|
|
const modelName =
|
|
req.params?.modelName || context_updater_settings.modelName;
|
|
|
|
// expect 2 matches
|
|
if (match.length > 2) {
|
|
const tenantId = match[1];
|
|
const contextOwner = match[2];
|
|
const smStr = redisKey(contextOwner);
|
|
let sessionMap = await redis.hGetAll(smStr);
|
|
|
|
if (!sessionMap || !sessionMap.conversationId) {
|
|
// no session data is okay for copilot, create one now
|
|
sessionMap = {
|
|
customerId: paSettings.copilot.customerId,
|
|
upn: contextOwner,
|
|
tenantId,
|
|
};
|
|
await createSessionMap(smStr, sessionMap);
|
|
}
|
|
console.log({ sessionMap });
|
|
|
|
const metadata = {
|
|
userId: bot_user.userId,
|
|
channel,
|
|
};
|
|
|
|
let input = undefined;
|
|
req.body["vcx:fields"].forEach((field) => {
|
|
if (field["vcx:field"] == "searchTerm") {
|
|
input = "Search for " + field["vcx:typedValue"]["vcx:stringValue"];
|
|
}
|
|
});
|
|
if (input) {
|
|
const recognizedData = await axioshelper("run model", {
|
|
method: "post",
|
|
url: `${settings_667ef98d3ee8930a5debcbdb.nlu.apiBaseURL}Model/run/${req.params.workspaceId}/${req.params.branch}/${modelName}`,
|
|
headers: studioToken,
|
|
data: {
|
|
input: input,
|
|
conversationId: sessionMap.conversationId,
|
|
settings: settings_667ef98d3ee8930a5debcbdb.nlu.settings,
|
|
metadata: metadata,
|
|
},
|
|
})
|
|
.then((response) => {
|
|
return response.data;
|
|
})
|
|
.catch((error) => {
|
|
//If we have error handling turned off, don't record the error
|
|
if (settings_667ef98d3ee8930a5debcbdb.errorHandling === false) {
|
|
throw error;
|
|
} else {
|
|
// Conversation ID is generated by the NLU
|
|
// Must make sure Conversation ID is created properly for an error transaction
|
|
// Get it from session map if it exists, or generate it if we got the error at the very first transaction.
|
|
const conversationId = sessionMap.conversationId || uuidv4();
|
|
let errorPayload = {
|
|
id: uuidv4(),
|
|
workspaceId: req.params.workspaceId,
|
|
errorInfo: {
|
|
name: error.name,
|
|
message: error.message,
|
|
stack: error.stack,
|
|
},
|
|
conversationId,
|
|
input: req.body.input,
|
|
answers: [
|
|
settings_667ef98d3ee8930a5debcbdb.responses.modelError,
|
|
],
|
|
req: {
|
|
params: req.params,
|
|
query: req.query,
|
|
body: req.body,
|
|
headers: req.headers,
|
|
},
|
|
metadata,
|
|
classificationResults: [
|
|
{
|
|
label: "Model Error",
|
|
value: 0.0,
|
|
},
|
|
],
|
|
nerResults: {
|
|
entities: [],
|
|
sourceEntities: [],
|
|
},
|
|
// Reporting tags to match GlobalSupport function reporting
|
|
tag: "ERROR",
|
|
reporting: {
|
|
tag: "ERROR",
|
|
modelResponse: "ERROR",
|
|
},
|
|
};
|
|
|
|
// Add model name
|
|
errorPayload.req.params.route = modelName;
|
|
|
|
//add any other details you wish to see in transaction data
|
|
|
|
return errorPayload;
|
|
}
|
|
});
|
|
|
|
if (recognizedData.conversationId) {
|
|
await redis.hSet(
|
|
smStr,
|
|
"conversationId",
|
|
recognizedData.conversationId
|
|
);
|
|
await redis.expire(smStr, 3600);
|
|
if (sessionMap.conversationId) {
|
|
} else {
|
|
db.analytics.addConversation({
|
|
id: recognizedData.conversationId,
|
|
});
|
|
}
|
|
}
|
|
recognizedData._id = (
|
|
await db.analytics.addTransaction(recognizedData)
|
|
).insertedId;
|
|
|
|
// Send Answers to Messenger
|
|
new Promise(async (resolve) => {
|
|
for (const [index, answer] of recognizedData.answers.entries()) {
|
|
let bearerAuth = await m2mAuthentication();
|
|
if (!bearerAuth) {
|
|
throw Error("Unable to Authenticate M2M");
|
|
}
|
|
|
|
let body = answer;
|
|
|
|
if (typeof answer === "string" || answer instanceof String) {
|
|
|
|
body = stripHTMLTags(body);
|
|
|
|
body = {
|
|
source: "//wa/us-east-1/int/demo/plain",
|
|
type: "verint.ui_messages.text.v1",
|
|
id: "ca5057b8-2c1d-4758-89d6-0370d6f28cc2",
|
|
time: "2020-07-30T14:44:00+00:00",
|
|
data: {
|
|
version: "1.0.0",
|
|
format: "plain",
|
|
persona_icon: {
|
|
id: "Verint-Bot-Head",
|
|
color: "#007ACC",
|
|
},
|
|
title_icon: {
|
|
id: "message-status-info",
|
|
color: "#FF5722",
|
|
},
|
|
body: body,
|
|
},
|
|
};
|
|
}
|
|
|
|
console.log({
|
|
data: {
|
|
customerId: sessionMap.customerId,
|
|
upn: sessionMap.upn,
|
|
...body,
|
|
},
|
|
});
|
|
|
|
let cloudEventResponse = await axioshelper(
|
|
"copilot cloud-event",
|
|
{
|
|
method: "post",
|
|
url: `https://apigw.us-east-1.wrk-1.aws.hydra.verint.com/int/wa/v1/cloud-event`,
|
|
headers: {
|
|
Authorization: `Bearer ${bearerAuth}`,
|
|
},
|
|
data: {
|
|
customerId: sessionMap.customerId,
|
|
upn: sessionMap.upn,
|
|
...body,
|
|
},
|
|
}
|
|
);
|
|
console.log({ cloudEventResponse: cloudEventResponse.data });
|
|
}
|
|
|
|
resolve(true);
|
|
});
|
|
|
|
// scrub response before sending
|
|
delete recognizedData.req;
|
|
|
|
res.send(recognizedData);
|
|
} else {
|
|
res.status(400).send({ error: "Bad Request no search term found." });
|
|
}
|
|
} else {
|
|
res
|
|
.status(400)
|
|
.send({ error: "Bad Request no context owner or tenantId found." });
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
return res.status(500).send({
|
|
status: 500,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.run(req, res);
|