@klevu/core
Version:
Typescript SDK that simplifies development on Klevu backend. Klevu provides advanced AI-powered search and discovery solutions for online retailers.
284 lines (283 loc) • 12.5 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { KlevuConfig, } from "../../index.js";
import { KlevuStorage } from "../../utils/storage.js";
import { post } from "../fetch.js";
const STORAGE_KEY = "klevu-moi-session";
const MAX_MESSAGES = 10;
const saveProductInfo = (config, productInfo, widgetId) => __awaiter(void 0, void 0, void 0, function* () {
return yield post(`${config.moiApiUrl}chat/${widgetId}/productInfo`, productInfo);
});
export function startMoi(options = {}) {
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () {
const config = ((_a = options.settings) === null || _a === void 0 ? void 0 : _a.configOverride) || KlevuConfig.getDefault();
let startingMessages = [];
let questions = [];
let ctx = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ klevuApiKey: config.apiKey, sessionId: "", visitorId: "", mode: options.mode, url: options.url, productId: options.productId, pqaWidgetId: options.pqaWidgetId, additionalData: options.additionalData }, (options.itemId && { itemId: options.itemId })), (options.itemGroupId && { itemGroupId: options.itemGroupId })), (options.itemVariantId && { itemVariantId: options.itemVariantId })), (options.channelId && { channelId: options.channelId })), (options.locale && { locale: options.locale }));
const storedSession = yield getStoredSession();
let menu;
let genericOptions;
let feedbacks = [];
let shouldSendMessage = (_b = options.settings) === null || _b === void 0 ? void 0 : _b.alwaysStartConversation;
const PQAKey = options.productId || options.url;
if (storedSession && storedSession.context) {
switch (options.mode) {
case undefined:
ctx.sessionId = storedSession.context.sessionId;
ctx.visitorId = storedSession.context.visitorId;
if (storedSession.MOI) {
startingMessages = storedSession.MOI.messages;
menu = storedSession.MOI.menu;
genericOptions = storedSession.MOI.genericOptions;
feedbacks = storedSession.MOI.feedbacks;
if ((_c = options.settings) === null || _c === void 0 ? void 0 : _c.alwaysStartConversation) {
shouldSendMessage = storedSession.MOI.messages.length === 0;
}
}
break;
case "PQA":
ctx.sessionId = storedSession.context.sessionId;
ctx.visitorId = storedSession.context.visitorId;
if (PQAKey && storedSession.PQA && storedSession.PQA[PQAKey]) {
startingMessages = storedSession.PQA[PQAKey].messages;
menu = storedSession.PQA[PQAKey].menu;
genericOptions = storedSession.PQA[PQAKey].genericOptions;
feedbacks = storedSession.PQA[PQAKey].feedbacks;
questions = storedSession.PQA[PQAKey].questions || [];
if ((_d = options.settings) === null || _d === void 0 ? void 0 : _d.alwaysStartConversation) {
shouldSendMessage = storedSession.PQA[PQAKey].messages.length === 0;
}
}
}
}
if (shouldSendMessage) {
try {
if (options.productInfo && options.mode === "PQA")
yield saveProductInfo(config, options.productInfo, options.pqaWidgetId || 'NA');
}
catch (err) {
console.warn("Failed to save product Info", err);
}
const result = yield queryMoi({
context: ctx,
}, config);
if (!(result === null || result === void 0 ? void 0 : result.data) || !((_e = result === null || result === void 0 ? void 0 : result.data[0]) === null || _e === void 0 ? void 0 : _e.context)) {
throw new Error("No context found");
}
const parsed = parseResponse(result);
ctx = Object.assign(Object.assign({}, ctx), parsed.context);
startingMessages = parsed.messages;
menu = parsed.menu;
genericOptions = parsed.genericOptions;
questions = parsed.questions || [];
}
return new MoiSession({
feedbacks,
messages: startingMessages,
questions,
menu,
genericOptions,
}, options, ctx, config);
});
}
export class MoiSession {
constructor(state, options, context, config) {
var _a;
this.messages = state.messages;
this.questions = state.questions;
this.menu = state.menu;
this.genericOptions = state.genericOptions;
this.feedbacks = (_a = state.feedbacks) !== null && _a !== void 0 ? _a : [];
this.options = options;
this.context = context;
this.config = config;
if (this.context.url && this.context.productId) {
throw new Error("Cannot set both url and productId for PQA");
}
this.save();
}
query(request, target) {
var _a, _b, _c, _d, _e, _f, _g;
return __awaiter(this, void 0, void 0, function* () {
if (request.message) {
this.messages = [
...((_a = this.messages) !== null && _a !== void 0 ? _a : []),
{ local: { message: request.message } },
];
(_c = (_b = this.options).onMessage) === null || _c === void 0 ? void 0 : _c.call(_b);
}
const res = yield queryMoi(Object.assign({ context: this.context }, request), this.config, target);
if (!res) {
throw new Error("No response from MOI");
}
// run actions first
for (const obj of res.data) {
if ("actions" in obj) {
for (const action of obj.actions.actions) {
//notify listeners if they want to do something
const response = (_e = (_d = this.options).onAction) === null || _e === void 0 ? void 0 : _e.call(_d, action);
// allow listeners to cancel the default actions
if (response === false) {
continue;
}
if (action.type === "purgeHistory") {
this.clear();
}
else if (action.type === "redirectToUrl" && action.context.link) {
if (this.options.onRedirect) {
this.options.onRedirect(action.context.link);
}
else {
window.location.href = action.context.link;
}
}
}
}
}
const { messages, genericOptions, menu, context, questions } = parseResponse(res);
this.messages = [...this.messages, ...messages];
if (this.messages.length > MAX_MESSAGES) {
this.messages.shift();
}
if (target === "send") {
this.questions = [...questions];
}
(_g = (_f = this.options).onMessage) === null || _g === void 0 ? void 0 : _g.call(_f);
this.genericOptions = genericOptions;
this.menu = menu;
this.context = Object.assign(Object.assign({}, this.context), context);
this.save();
return res;
});
}
clear() {
var _a, _b;
this.messages = [];
this.feedbacks = [];
this.save();
(_b = (_a = this.options).onMessage) === null || _b === void 0 ? void 0 : _b.call(_a);
}
save() {
const PQAkey = this.context.productId || this.context.url;
switch (this.context.mode) {
case undefined:
saveSession({
context: this.context,
MOI: {
menu: this.menu,
genericOptions: this.genericOptions,
messages: this.messages,
feedbacks: this.feedbacks,
},
});
break;
case "PQA":
if (!PQAkey) {
throw new Error("Cannot save PQA session without url or productId");
}
saveSession({
context: this.context,
PQA: {
[PQAkey]: {
menu: this.menu,
genericOptions: this.genericOptions,
messages: this.messages,
feedbacks: this.feedbacks,
questions: this.questions || [],
},
},
});
}
}
addFeedback(messageId, thumbs, reason) {
return __awaiter(this, void 0, void 0, function* () {
const oldFeedback = this.feedbacks.find((f) => f.id === messageId);
if (oldFeedback) {
oldFeedback.thumbs = thumbs;
oldFeedback.reason = reason;
}
else {
this.feedbacks.push({ id: messageId, thumbs, reason });
}
return this.query({
feedback: {
messageId,
thumbs: thumbs.toUpperCase(),
reason,
},
}, "feedback");
});
}
}
function getStoredSession() {
const storedSession = KlevuStorage.getItem(STORAGE_KEY);
if (!storedSession) {
return undefined;
}
return JSON.parse(storedSession);
}
function saveSession(session) {
const saved = KlevuStorage.getItem(STORAGE_KEY);
let parsed = {};
if (saved) {
parsed = JSON.parse(saved);
}
const key = session.context.productId || session.context.url;
switch (session.context.mode) {
case undefined:
parsed.context = session.context;
parsed.MOI = session.MOI;
break;
case "PQA":
parsed.context = session.context;
if (!parsed.PQA) {
parsed.PQA = {};
}
if (!key || !session.PQA) {
throw new Error("No url, productId or PQA session");
}
parsed.PQA[key] = session.PQA[key];
break;
}
KlevuStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
}
function queryMoi(request, config, target = "send") {
return __awaiter(this, void 0, void 0, function* () {
return yield post(`${config.moiApiUrl}chat/${target}`, request);
});
}
function parseResponse(response) {
var _a;
const messages = [];
const questions = [];
let genericOptions = undefined;
let menu = undefined;
const context = response.data[0].context;
for (const d of response.data) {
"message" in d && messages.push(d);
"filter" in d && messages.push(d);
"productData" in d && messages.push(d);
"questions" in d && questions.push(...(((_a = d.questions) === null || _a === void 0 ? void 0 : _a.options) || []));
if ("genericOptions" in d) {
genericOptions = d.genericOptions;
}
if ("menuOptions" in d) {
menu = d.menuOptions;
}
}
return {
context,
messages,
menu,
genericOptions,
questions,
};
}