From 2e20b28c4dd5ec6a1bcf40cf992944e2e091ae75 Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Tue, 14 Mar 2023 15:42:59 -0400 Subject: [PATCH] chore: refactor progressCB to one place, fix sydney, and sanitize html --- api/app/bingai.js | 7 +- api/app/chatgpt-browser.js | 7 +- api/app/chatgpt-client.js | 7 +- api/app/sydney.js | 6 +- api/package-lock.json | 1 + api/package.json | 1 + api/server/routes/ask.js | 33 +--- api/server/routes/askBing.js | 13 +- api/server/routes/askSydney.js | 13 +- api/server/routes/handlers.js | 34 +++- package-lock.json | 293 +++++++++++++++++++++++++++++++++ package.json | 5 + 12 files changed, 351 insertions(+), 69 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json 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/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..5f94d7d4c3 100644 --- a/api/server/routes/ask.js +++ b/api/server/routes/ask.js @@ -11,7 +11,7 @@ const { detectCode } = require('../../app/'); const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models'); -const { handleError, sendMessage } = require('./handlers'); +const { handleError, sendMessage, createOnProgress } = 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, diff --git a/api/server/routes/askBing.js b/api/server/routes/askBing.js index 1871e96f40..add804a43e 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 } = 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, diff --git a/api/server/routes/askSydney.js b/api/server/routes/askSydney.js index 3a663184b6..1f04ad4166 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 } = 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, diff --git a/api/server/routes/handlers.js b/api/server/routes/handlers.js index e727cacfa3..7672ef41a5 100644 --- a/api/server/routes/handlers.js +++ b/api/server/routes/handlers.js @@ -1,3 +1,7 @@ +const { citeText } = 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,32 @@ 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.trim(); + tokens = tokens.replaceAll('[DONE]', ''); + 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; +}; + +module.exports = { handleError, sendMessage, createOnProgress }; \ 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" + } +}