diff --git a/api/app/bingai.js b/api/app/bingai.js
index 2ccbc2b356..dcf150d1cf 100644
--- a/api/app/bingai.js
+++ b/api/app/bingai.js
@@ -1,7 +1,7 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
-const askBing = async ({ text, progressCallback, convo }) => {
+const askBing = async ({ text, onProgress, convo }) => {
const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api'));
const bingAIClient = new BingAIClient({
@@ -14,10 +14,7 @@ const askBing = async ({ text, progressCallback, convo }) => {
proxy: process.env.PROXY || null,
});
- let options = {
- onProgress: async (partialRes) => await progressCallback(partialRes),
- };
-
+ let options = { onProgress };
if (convo) {
options = { ...options, ...convo };
}
diff --git a/api/app/chatgpt-browser.js b/api/app/chatgpt-browser.js
index 8a3c903641..a5bea22729 100644
--- a/api/app/chatgpt-browser.js
+++ b/api/app/chatgpt-browser.js
@@ -10,7 +10,7 @@ const clientOptions = {
proxy: process.env.PROXY || null,
};
-const browserClient = async ({ text, progressCallback, convo }) => {
+const browserClient = async ({ text, onProgress, convo }) => {
const { ChatGPTBrowserClient } = await import('@waylaidwanderer/chatgpt-api');
const store = {
@@ -18,10 +18,7 @@ const browserClient = async ({ text, progressCallback, convo }) => {
};
const client = new ChatGPTBrowserClient(clientOptions, store);
-
- let options = {
- onProgress: async (partialRes) => await progressCallback(partialRes)
- };
+ let options = { onProgress };
if (!!convo.parentMessageId && !!convo.conversationId) {
options = { ...options, ...convo };
diff --git a/api/app/chatgpt-client.js b/api/app/chatgpt-client.js
index afd31e0a81..350d1210ce 100644
--- a/api/app/chatgpt-client.js
+++ b/api/app/chatgpt-client.js
@@ -9,17 +9,14 @@ const clientOptions = {
debug: false
};
-const askClient = async ({ text, progressCallback, convo }) => {
+const askClient = async ({ text, onProgress, convo }) => {
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
const store = {
store: new KeyvFile({ filename: './data/cache.json' })
};
const client = new ChatGPTClient(process.env.OPENAI_KEY, clientOptions, store);
-
- let options = {
- onProgress: async (partialRes) => await progressCallback(partialRes)
- };
+ let options = { onProgress };
if (!!convo.parentMessageId && !!convo.conversationId) {
options = { ...options, ...convo };
diff --git a/api/app/sydney.js b/api/app/sydney.js
index fe47c74f57..3466f71c17 100644
--- a/api/app/sydney.js
+++ b/api/app/sydney.js
@@ -1,7 +1,7 @@
require('dotenv').config();
const { KeyvFile } = require('keyv-file');
-const askSydney = async ({ text, progressCallback, convo }) => {
+const askSydney = async ({ text, onProgress, convo }) => {
const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api'));
const sydneyClient = new BingAIClient({
@@ -15,10 +15,10 @@ const askSydney = async ({ text, progressCallback, convo }) => {
let options = {
jailbreakConversationId: true,
- onProgress: async (partialRes) => await progressCallback(partialRes),
+ onProgress,
};
- if (convo.parentMessageId) {
+ if (convo.jailbreakConversationId) {
options = { ...options, jailbreakConversationId: convo.jailbreakConversationId, parentMessageId: convo.parentMessageId };
}
diff --git a/api/models/Conversation.js b/api/models/Conversation.js
index 6e3c646c64..622744d27a 100644
--- a/api/models/Conversation.js
+++ b/api/models/Conversation.js
@@ -96,7 +96,7 @@ module.exports = {
const totalConvos = await Conversation.countDocuments();
const totalPages = Math.ceil(totalConvos / pageSize);
const convos = await Conversation.find()
- .sort({ createdAt: -1 })
+ .sort({ createdAt: -1, created: -1 })
.skip((pageNumber - 1) * pageSize)
.limit(pageSize)
.exec();
@@ -127,7 +127,7 @@ module.exports = {
const conversations = await Conversation.find({ model: null }).exec();
if (!conversations || conversations.length === 0)
- return { message: 'No conversations to migrate' };
+ return { message: '[Migrate] No conversations to migrate' };
for (let convo of conversations) {
const messages = await getMessages({
@@ -135,22 +135,20 @@ module.exports = {
messageId: { $exists: false }
});
- const promises = [];
let model;
let oldId;
+ const promises = [];
messages.forEach((message, i) => {
const msgObj = message.toObject();
const newId = msgObj.id;
if (i === 0) {
message.parentMessageId = '00000000-0000-0000-0000-000000000000';
- oldId = newId;
} else {
message.parentMessageId = oldId;
- oldId = newId;
}
+ oldId = newId;
message.messageId = newId;
- message.createdAt = message.created;
if (message.sender.toLowerCase() !== 'user' && !model) {
model = message.sender.toLowerCase();
}
@@ -164,7 +162,7 @@ module.exports = {
await Conversation.findOneAndUpdate(
{ conversationId: convo.conversationId },
- { model, createdAt: convo.created },
+ { model },
{ new: true }
).exec();
}
@@ -172,11 +170,11 @@ module.exports = {
try {
await mongoose.connection.db.collection('messages').dropIndex('id_1');
} catch (error) {
- console.log("Index doesn't exist or already dropped");
+ console.log("[Migrate] Index doesn't exist or already dropped");
}
} catch (error) {
console.log(error);
- return { message: 'Error migrating conversations' };
+ return { message: '[Migrate] Error migrating conversations' };
}
}
};
diff --git a/api/package-lock.json b/api/package-lock.json
index 911fda953e..daa36e69e4 100644
--- a/api/package-lock.json
+++ b/api/package-lock.json
@@ -17,6 +17,7 @@
"express": "^4.18.2",
"keyv": "^4.5.2",
"keyv-file": "^0.2.0",
+ "lodash": "^4.17.21",
"mongoose": "^6.9.0",
"openai": "^3.1.0"
},
diff --git a/api/package.json b/api/package.json
index 28bd6769d3..6dcf344ea0 100644
--- a/api/package.json
+++ b/api/package.json
@@ -27,6 +27,7 @@
"express": "^4.18.2",
"keyv": "^4.5.2",
"keyv-file": "^0.2.0",
+ "lodash": "^4.17.21",
"mongoose": "^6.9.0",
"openai": "^3.1.0"
},
diff --git a/api/server/routes/ask.js b/api/server/routes/ask.js
index d12ee17b4e..8d20b808dc 100644
--- a/api/server/routes/ask.js
+++ b/api/server/routes/ask.js
@@ -8,10 +8,10 @@ const {
askClient,
browserClient,
customClient,
- detectCode
+ // detectCode
} = require('../../app/');
const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models');
-const { handleError, sendMessage } = require('./handlers');
+const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const { getMessages } = require('../../models/Message');
router.use('/bing', askBing);
@@ -138,37 +138,10 @@ const ask = async ({
sendMessage(res, { message: userMessage, created: true });
try {
- let i = 0;
- let tokens = '';
- const progressCallback = async (partial) => {
- if (i === 0 && typeof partial === 'object') {
- userMessage.conversationId = conversationId ? conversationId : partial.conversationId;
- await saveMessage(userMessage);
- sendMessage(res, { ...partial, parentMessageId: overrideParentMessageId || userMessageId, initial: true });
- i++;
- }
-
- if (typeof partial === 'object') {
- sendMessage(res, { ...partial, parentMessageId: overrideParentMessageId || userMessageId, message: true });
- } else {
- tokens += partial === text ? '' : partial;
- if (tokens.match(/^\n/)) {
- tokens = tokens.replace(/^\n/, '');
- }
-
- if (tokens.includes('[DONE]')) {
- tokens = tokens.replace('[DONE]', '');
- }
-
- // tokens = await detectCode(tokens);
- sendMessage(res, { text: tokens, message: true, initial: i === 0 ? true : false });
- i++;
- }
- };
-
+ const progressCallback = createOnProgress();
let gptResponse = await client({
text,
- progressCallback,
+ onProgress: progressCallback.call(null, model, {res, text }),
convo: {
parentMessageId: userParentMessageId,
conversationId,
@@ -204,7 +177,8 @@ const ask = async ({
gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model;
// gptResponse.final = true;
- gptResponse.text = await detectCode(gptResponse.text);
+ // gptResponse.text = await detectCode(gptResponse.text);
+ gptResponse.text = await handleText(gptResponse.text);
if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') {
gptResponse.chatGptLabel = convo.chatGptLabel;
diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js
index 1871e96f40..5bb8abd4f0 100644
--- a/api/server/routes/askBing.js
+++ b/api/server/routes/askBing.js
@@ -3,7 +3,7 @@ const crypto = require('crypto');
const router = express.Router();
const { titleConvo, getCitations, citeText, askBing } = require('../../app/');
const { saveMessage, getConvoTitle, saveConvo } = require('../../models');
-const { handleError, sendMessage } = require('./handlers');
+const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const citationRegex = /\[\^\d+?\^]/g;
router.post('/', async (req, res) => {
@@ -68,17 +68,10 @@ const ask = async ({
sendMessage(res, { message: userMessage, created: true });
try {
- let tokens = '';
- const progressCallback = async (partial) => {
- tokens += partial === text ? '' : partial;
- // tokens = appendCode(tokens);
- tokens = citeText(tokens, true);
- sendMessage(res, { text: tokens, message: true, parentMessageId: overrideParentMessageId || userMessageId });
- };
-
+ const progressCallback = createOnProgress();
let response = await askBing({
text,
- progressCallback,
+ onProgress: progressCallback.call(null, model, {res, text, parentMessageId: overrideParentMessageId || userMessageId }),
convo: {
...convo,
parentMessageId: userParentMessageId,
@@ -120,6 +113,7 @@ const ask = async ({
response.text =
citeText(response) +
(links?.length > 0 && hasCitations ? `\n${links}` : '');
+ response.text = await handleText(response.text);
await saveMessage(response);
await saveConvo({...response, model, ...convo});
diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js
index 3a663184b6..a0a3bd32df 100644
--- a/api/server/routes/askSydney.js
+++ b/api/server/routes/askSydney.js
@@ -3,7 +3,7 @@ const crypto = require('crypto');
const router = express.Router();
const { titleConvo, getCitations, citeText, askSydney } = require('../../app/');
const { saveMessage, saveConvo, getConvoTitle } = require('../../models');
-const { handleError, sendMessage } = require('./handlers');
+const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const citationRegex = /\[\^\d+?\^]/g;
router.post('/', async (req, res) => {
@@ -68,17 +68,10 @@ const ask = async ({
sendMessage(res, { message: userMessage, created: true });
try {
- let tokens = '';
- const progressCallback = async (partial) => {
- tokens += partial === text ? '' : partial;
- // tokens = appendCode(tokens);
- tokens = citeText(tokens, true);
- sendMessage(res, { text: tokens, message: true, parentMessageId: overrideParentMessageId || userMessageId });
- };
-
+ const progressCallback = createOnProgress();
let response = await askSydney({
text,
- progressCallback,
+ onProgress: progressCallback.call(null, model, {res, text, parentMessageId: overrideParentMessageId || userMessageId }),
convo: {
parentMessageId: userParentMessageId,
conversationId,
@@ -121,6 +114,7 @@ const ask = async ({
response.text =
citeText(response) +
(links?.length > 0 && hasCitations ? `\n${links}` : '');
+ response.text = await handleText(response.text);
// Save user message
userMessage.conversationId = response.conversationId || conversationId;
diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js
index e727cacfa3..d3e613bccc 100644
--- a/api/server/routes/handlers.js
+++ b/api/server/routes/handlers.js
@@ -1,3 +1,7 @@
+const { citeText, detectCode } = require('../../app/');
+const _ = require('lodash');
+const sanitizeHtml = require('sanitize-html');
+
const handleError = (res, message) => {
res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`);
res.end();
@@ -10,4 +14,46 @@ const sendMessage = (res, message) => {
res.write(`event: message\ndata: ${JSON.stringify(message)}\n\n`);
};
-module.exports = { handleError, sendMessage };
+const createOnProgress = () => {
+ let i = 0;
+ let tokens = '';
+
+ const progressCallback = async (partial, { res, text, bing = false, ...rest }) => {
+ tokens += partial === text ? '' : partial;
+ tokens = tokens.replaceAll('[DONE]', '');
+
+ if (tokens.match(/^\n/)) {
+ tokens = tokens.replace(/^\n/, '');
+ }
+ // if (tokens.includes('```')) {
+ // tokens = sanitizeHtml(tokens);
+ // }
+
+ if (bing) {
+ tokens = citeText(tokens, true);
+ }
+
+ sendMessage(res, { text: tokens, message: true, initial: i === 0, ...rest });
+ i++;
+ };
+
+ const onProgress = (model, opts) => {
+ const bingModels = new Set(['bingai', 'sydney']);
+ return _.partialRight(progressCallback, { ...opts, bing: bingModels.has(model) });
+ };
+
+ return onProgress;
+};
+
+const handleText = async (input) => {
+ let text = input;
+ text = await detectCode(text);
+ // if (text.includes('```')) {
+ // text = sanitizeHtml(text);
+ // text = text.replaceAll(') =>', ') =>');
+ // }
+
+ return text;
+};
+
+module.exports = { handleError, sendMessage, createOnProgress, handleText };
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000..bd60daac40
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,293 @@
+{
+ "name": "chatgpt-clone",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "sanitize-html": "^2.10.0"
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+ "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ]
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
+ "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
+ "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz",
+ "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "entities": "^4.3.0"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/parse-srcset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/postcss": {
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/sanitize-html": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz",
+ "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==",
+ "dependencies": {
+ "deepmerge": "^4.2.2",
+ "escape-string-regexp": "^4.0.0",
+ "htmlparser2": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "parse-srcset": "^1.0.2",
+ "postcss": "^8.3.11"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ }
+ },
+ "dependencies": {
+ "deepmerge": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+ "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
+ },
+ "dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "requires": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ }
+ },
+ "domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
+ },
+ "domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "requires": {
+ "domelementtype": "^2.3.0"
+ }
+ },
+ "domutils": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
+ "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+ "requires": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.1"
+ }
+ },
+ "entities": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
+ "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA=="
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "htmlparser2": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz",
+ "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==",
+ "requires": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "entities": "^4.3.0"
+ }
+ },
+ "is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
+ },
+ "nanoid": {
+ "version": "3.3.4",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
+ "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+ },
+ "parse-srcset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "postcss": {
+ "version": "8.4.21",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
+ "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "requires": {
+ "nanoid": "^3.3.4",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "sanitize-html": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.10.0.tgz",
+ "integrity": "sha512-JqdovUd81dG4k87vZt6uA6YhDfWkUGruUu/aPmXLxXi45gZExnt9Bnw/qeQU8oGf82vPyaE0vO4aH0PbobB9JQ==",
+ "requires": {
+ "deepmerge": "^4.2.2",
+ "escape-string-regexp": "^4.0.0",
+ "htmlparser2": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "parse-srcset": "^1.0.2",
+ "postcss": "^8.3.11"
+ }
+ },
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..4da6b6e8c4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "sanitize-html": "^2.10.0"
+ }
+}