Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
346c63617b | ||
|
|
ffcfb69dee | ||
|
|
c91ce36227 | ||
|
|
d06e58f043 | ||
|
|
d052d221dc | ||
|
|
ff45511011 | ||
|
|
8c6340aed0 | ||
|
|
84b104e65f | ||
|
|
a0c94715ce | ||
|
|
a8aad30fc8 | ||
|
|
2fd50c99b8 | ||
|
|
96ca783517 | ||
|
|
45ca0a8713 | ||
|
|
5d0b849930 | ||
|
|
54aa9debb4 | ||
|
|
4e91437049 | ||
|
|
918f2fecb6 | ||
|
|
796d8031e8 | ||
|
|
6e32f71565 | ||
|
|
6192c2964e | ||
|
|
626a8fbd8e | ||
|
|
a8344ec5bf | ||
|
|
c230fe41f4 | ||
|
|
d0ef0f84c8 | ||
|
|
8289558d94 | ||
|
|
2e20b28c4d | ||
|
|
9a17e94f8f | ||
|
|
71fc86b9a6 | ||
|
|
8882432210 | ||
|
|
644f3f716f | ||
|
|
8b00805d24 | ||
|
|
d73375958b | ||
|
|
7168498543 | ||
|
|
27515cb00a | ||
|
|
3e7ce67609 | ||
|
|
4fd05e15b4 | ||
|
|
0fa19bb6ad | ||
|
|
d9e5464b3b | ||
|
|
953c5fc970 | ||
|
|
2afbc5883f | ||
|
|
953f846958 | ||
|
|
a4d5f6a3f2 | ||
|
|
4a39965b22 | ||
|
|
90dc171b34 | ||
|
|
0e98cb4206 | ||
|
|
9f8e9cb091 | ||
|
|
8773878be2 | ||
|
|
5a409ccfa6 | ||
|
|
0ed8a40a41 | ||
|
|
be71140dd4 | ||
|
|
6d51ec3e37 | ||
|
|
bdfc895800 | ||
|
|
b9975ac283 | ||
|
|
dd1f74da72 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,7 +47,6 @@ bower_components/
|
||||
.env
|
||||
cache.json
|
||||
api/data/
|
||||
.eslintrc.js
|
||||
owner.yml
|
||||
archive
|
||||
.vscode/settings.json
|
||||
|
||||
@@ -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,16 +14,15 @@ 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 };
|
||||
}
|
||||
|
||||
const res = await bingAIClient.sendMessage(text, options
|
||||
);
|
||||
if (options?.jailbreakConversationId == 'false')
|
||||
options.jailbreakConversationId = false
|
||||
|
||||
const res = await bingAIClient.sendMessage(text, options);
|
||||
|
||||
return res;
|
||||
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -9,7 +9,7 @@ const clientOptions = {
|
||||
debug: false
|
||||
};
|
||||
|
||||
const customClient = async ({ text, progressCallback, convo, promptPrefix, chatGptLabel }) => {
|
||||
const customClient = async ({ text, onProgress, convo, promptPrefix, chatGptLabel }) => {
|
||||
const ChatGPTClient = (await import('@waylaidwanderer/chatgpt-api')).default;
|
||||
const store = {
|
||||
store: new KeyvFile({ filename: './data/cache.json' })
|
||||
@@ -23,10 +23,7 @@ const customClient = async ({ text, progressCallback, convo, promptPrefix, chatG
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
require('dotenv').config();
|
||||
const Keyv = require('keyv');
|
||||
const { Configuration, OpenAIApi } = require('openai');
|
||||
const messageStore = new Keyv(process.env.MONGODB_URI, { namespace: 'chatgpt' });
|
||||
|
||||
const ask = async (question, progressCallback, convo) => {
|
||||
const { ChatGPTAPI } = await import('chatgpt');
|
||||
const api = new ChatGPTAPI({ apiKey: process.env.OPENAI_KEY, messageStore });
|
||||
let options = {
|
||||
onProgress: async (partialRes) => {
|
||||
if (partialRes.text.length > 0) {
|
||||
await progressCallback(partialRes);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!!convo.parentMessageId && !!convo.conversationId) {
|
||||
options = { ...options, ...convo };
|
||||
}
|
||||
|
||||
const res = await api.sendMessage(question, options);
|
||||
return res;
|
||||
};
|
||||
|
||||
const titleConvo = async (message, response, model) => {
|
||||
const configuration = new Configuration({
|
||||
apiKey: process.env.OPENAI_KEY
|
||||
});
|
||||
const openai = new OpenAIApi(configuration);
|
||||
const completion = await openai.createCompletion({
|
||||
model: 'text-davinci-002',
|
||||
prompt: `Write a short title in title case, ideally in 5 words or less, and do not refer to the user or ${model}, that summarizes this conversation:\nUser:"${message}"\n${model}:"${response}"\nTitle: `
|
||||
});
|
||||
|
||||
return completion.data.choices[0].text.replace(/\n/g, '');
|
||||
};
|
||||
|
||||
module.exports = { ask, titleConvo };
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,59 @@
|
||||
const { Configuration, OpenAIApi } = require('openai');
|
||||
const _ = require('lodash');
|
||||
|
||||
const titleConvo = async ({ message, response, model }) => {
|
||||
const configuration = new Configuration({
|
||||
apiKey: process.env.OPENAI_KEY
|
||||
});
|
||||
const openai = new OpenAIApi(configuration);
|
||||
const completion = await openai.createChatCompletion({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.'
|
||||
},
|
||||
{ role: 'user', content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${message}"\n\n${model}: "${response}"\n\nTitle: ` },
|
||||
]
|
||||
});
|
||||
const proxyEnvToAxiosProxy = (proxyString) => {
|
||||
if (!proxyString) return null;
|
||||
|
||||
//eslint-disable-next-line
|
||||
return completion.data.choices[0].message.content.replace(/["\.]/g, '');
|
||||
const regex = /^([^:]+):\/\/(?:([^:@]*):?([^:@]*)@)?([^:]+)(?::(\d+))?/;
|
||||
const [, protocol, username, password, host, port] = proxyString.match(regex);
|
||||
const proxyConfig = {
|
||||
protocol,
|
||||
host,
|
||||
port: port ? parseInt(port) : undefined,
|
||||
auth: username && password ? { username, password } : undefined
|
||||
};
|
||||
|
||||
return proxyConfig;
|
||||
};
|
||||
|
||||
module.exports = titleConvo;
|
||||
const titleConvo = async ({ model, text, response }) => {
|
||||
let title = 'New Chat';
|
||||
try {
|
||||
const configuration = new Configuration({
|
||||
apiKey: process.env.OPENAI_KEY
|
||||
});
|
||||
const openai = new OpenAIApi(configuration);
|
||||
const completion = await openai.createChatCompletion(
|
||||
{
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content:
|
||||
'You are a title-generator with one job: giving a conversation, detect the language and titling the conversation provided by a user in title case, using the same language.'
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: `In 5 words or less, summarize the conversation below with a title in title case using the language the user writes in. Don't refer to the participants of the conversation by name. Do not include punctuation or quotation marks. Your response should be in title case, exclusively containing the title. Conversation:\n\nUser: "${text}"\n\n${model}: "${JSON.stringify(
|
||||
response?.text
|
||||
)}"\n\nTitle: `
|
||||
}
|
||||
]
|
||||
},
|
||||
{ proxy: proxyEnvToAxiosProxy(process.env.PROXY || null) }
|
||||
);
|
||||
|
||||
//eslint-disable-next-line
|
||||
title = completion.data.choices[0].message.content.replace(/["\.]/g, '');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
console.log('There was an issue generating title, see error above');
|
||||
}
|
||||
|
||||
console.log('CONVERSATION TITLE', title);
|
||||
return title;
|
||||
};
|
||||
|
||||
const throttledTitleConvo = _.throttle(titleConvo, 1000);
|
||||
|
||||
module.exports = throttledTitleConvo;
|
||||
|
||||
@@ -1,48 +1,53 @@
|
||||
const mongoose = require('mongoose');
|
||||
const crypto = require('crypto');
|
||||
const { getMessages, deleteMessages } = require('./Message');
|
||||
|
||||
const convoSchema = mongoose.Schema({
|
||||
conversationId: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true
|
||||
const convoSchema = mongoose.Schema(
|
||||
{
|
||||
conversationId: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true
|
||||
},
|
||||
parentMessageId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'New Chat'
|
||||
},
|
||||
jailbreakConversationId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
conversationSignature: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
clientId: {
|
||||
type: String
|
||||
},
|
||||
invocationId: {
|
||||
type: String
|
||||
},
|
||||
chatGptLabel: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
promptPrefix: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
model: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
suggestions: [{ type: String }],
|
||||
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }]
|
||||
},
|
||||
parentMessageId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'New conversation'
|
||||
},
|
||||
jailbreakConversationId: {
|
||||
type: String
|
||||
},
|
||||
conversationSignature: {
|
||||
type: String
|
||||
},
|
||||
clientId: {
|
||||
type: String
|
||||
},
|
||||
invocationId: {
|
||||
type: String
|
||||
},
|
||||
chatGptLabel: {
|
||||
type: String
|
||||
},
|
||||
promptPrefix: {
|
||||
type: String
|
||||
},
|
||||
model: {
|
||||
type: String
|
||||
},
|
||||
suggestions: [{ type: String }],
|
||||
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
const Conversation =
|
||||
mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
|
||||
@@ -57,13 +62,24 @@ const getConvo = async (conversationId) => {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
saveConvo: async ({ conversationId, title, ...convo }) => {
|
||||
saveConvo: async ({ conversationId, newConversationId, title, ...convo }) => {
|
||||
try {
|
||||
const messages = await getMessages({ conversationId });
|
||||
const update = { ...convo, messages };
|
||||
if (title) {
|
||||
update.title = title;
|
||||
}
|
||||
if (newConversationId) {
|
||||
update.conversationId = newConversationId;
|
||||
}
|
||||
if (!update.jailbreakConversationId) {
|
||||
update.jailbreakConversationId = null;
|
||||
}
|
||||
if (update.model !== 'chatgptCustom' && update.chatGptLabel && update.promptPrefix) {
|
||||
console.log('Validation error: resetting chatgptCustom fields', update);
|
||||
update.chatGptLabel = null;
|
||||
update.promptPrefix = null;
|
||||
}
|
||||
|
||||
return await Conversation.findOneAndUpdate(
|
||||
{ conversationId },
|
||||
@@ -85,20 +101,18 @@ module.exports = {
|
||||
return { message: 'Error updating conversation' };
|
||||
}
|
||||
},
|
||||
// getConvos: async () => await Conversation.find({}).sort({ created: -1 }).exec(),
|
||||
getConvos: async (pageNumber = 1, pageSize = 12) => {
|
||||
// getConvos: async () => await Conversation.find({}).sort({ createdAt: -1 }).exec(),
|
||||
getConvosByPage: async (pageNumber = 1, pageSize = 12) => {
|
||||
try {
|
||||
const skip = (pageNumber - 1) * pageSize;
|
||||
// const limit = pageNumber * pageSize;
|
||||
|
||||
const conversations = await Conversation.find({})
|
||||
.sort({ created: -1 })
|
||||
.skip(skip)
|
||||
// .limit(limit)
|
||||
const totalConvos = (await Conversation.countDocuments()) || 1;
|
||||
const totalPages = Math.ceil(totalConvos / pageSize);
|
||||
const convos = await Conversation.find()
|
||||
.sort({ createdAt: -1, created: -1 })
|
||||
.skip((pageNumber - 1) * pageSize)
|
||||
.limit(pageSize)
|
||||
.exec();
|
||||
|
||||
return conversations;
|
||||
return { conversations: convos, pages: totalPages, pageNumber, pageSize };
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return { message: 'Error getting conversations' };
|
||||
@@ -118,5 +132,60 @@ module.exports = {
|
||||
let deleteCount = await Conversation.deleteMany(filter).exec();
|
||||
deleteCount.messages = await deleteMessages(filter);
|
||||
return deleteCount;
|
||||
},
|
||||
migrateDb: async () => {
|
||||
try {
|
||||
const conversations = await Conversation.find({ model: null }).exec();
|
||||
|
||||
if (!conversations || conversations.length === 0)
|
||||
return { message: '[Migrate] No conversations to migrate' };
|
||||
|
||||
for (let convo of conversations) {
|
||||
const messages = await getMessages({
|
||||
conversationId: convo.conversationId,
|
||||
messageId: { $exists: false }
|
||||
});
|
||||
|
||||
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';
|
||||
} else {
|
||||
message.parentMessageId = oldId;
|
||||
}
|
||||
|
||||
oldId = newId;
|
||||
message.messageId = newId;
|
||||
if (message.sender.toLowerCase() !== 'user' && !model) {
|
||||
model = message.sender.toLowerCase();
|
||||
}
|
||||
|
||||
if (message.sender.toLowerCase() === 'user') {
|
||||
message.isCreatedByUser = true;
|
||||
}
|
||||
promises.push(message.save());
|
||||
});
|
||||
await Promise.all(promises);
|
||||
|
||||
await Conversation.findOneAndUpdate(
|
||||
{ conversationId: convo.conversationId },
|
||||
{ model },
|
||||
{ new: true }
|
||||
).exec();
|
||||
}
|
||||
|
||||
try {
|
||||
await mongoose.connection.db.collection('messages').dropIndex('id_1');
|
||||
} catch (error) {
|
||||
console.log("[Migrate] Index doesn't exist or already dropped");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return { message: '[Migrate] Error migrating conversations' };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,11 +12,7 @@ const customGptSchema = mongoose.Schema({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
}, { timestamps: true });
|
||||
|
||||
const CustomGpt = mongoose.models.CustomGpt || mongoose.model('CustomGpt', customGptSchema);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const messageSchema = mongoose.Schema({
|
||||
id: {
|
||||
messageId: {
|
||||
type: String,
|
||||
unique: true,
|
||||
required: true
|
||||
@@ -32,33 +32,50 @@ const messageSchema = mongoose.Schema({
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
isCreatedByUser: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
}, { timestamps: true });
|
||||
|
||||
const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
|
||||
|
||||
module.exports = {
|
||||
saveMessage: async ({ id, conversationId, parentMessageId, sender, text }) => {
|
||||
saveMessage: async ({ messageId, conversationId, parentMessageId, sender, text, isCreatedByUser=false, error }) => {
|
||||
try {
|
||||
await Message.create({
|
||||
id,
|
||||
await Message.findOneAndUpdate({ messageId }, {
|
||||
conversationId,
|
||||
parentMessageId,
|
||||
sender,
|
||||
text
|
||||
});
|
||||
return { id, conversationId, parentMessageId, sender, text };
|
||||
text,
|
||||
isCreatedByUser,
|
||||
error
|
||||
}, { upsert: true, new: true });
|
||||
return { messageId, conversationId, parentMessageId, sender, text, isCreatedByUser };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { message: 'Error saving message' };
|
||||
}
|
||||
},
|
||||
deleteMessagesSince: async ({ messageId, conversationId }) => {
|
||||
try {
|
||||
const message = await Message.findOne({ messageId }).exec()
|
||||
|
||||
if (message)
|
||||
return await Message.find({ conversationId }).deleteMany({ createdAt: { $gt: message.createdAt } }).exec();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { message: 'Error deleting messages' };
|
||||
}
|
||||
},
|
||||
getMessages: async (filter) => {
|
||||
try {
|
||||
return await Message.find(filter).exec()
|
||||
return await Message.find(filter).sort({createdAt: 1}).exec()
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return { message: 'Error getting messages' };
|
||||
|
||||
@@ -12,11 +12,7 @@ const promptSchema = mongoose.Schema({
|
||||
category: {
|
||||
type: String,
|
||||
},
|
||||
created: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
});
|
||||
}, { timestamps: true });
|
||||
|
||||
const Prompt = mongoose.models.Prompt || mongoose.model('Prompt', promptSchema);
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
const { saveMessage, deleteMessages } = require('./Message');
|
||||
const { saveMessage, deleteMessagesSince, deleteMessages } = require('./Message');
|
||||
const { getCustomGpts, updateCustomGpt, updateByLabel, deleteCustomGpts } = require('./CustomGpt');
|
||||
const { getConvoTitle, getConvo, saveConvo } = require('./Conversation');
|
||||
const { getConvoTitle, getConvo, saveConvo, migrateDb } = require('./Conversation');
|
||||
|
||||
module.exports = {
|
||||
saveMessage,
|
||||
deleteMessagesSince,
|
||||
deleteMessages,
|
||||
getConvoTitle,
|
||||
getConvo,
|
||||
saveConvo,
|
||||
migrateDb,
|
||||
getCustomGpts,
|
||||
updateCustomGpt,
|
||||
updateByLabel,
|
||||
|
||||
275
api/package-lock.json
generated
275
api/package-lock.json
generated
@@ -17,8 +17,10 @@
|
||||
"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"
|
||||
"openai": "^3.1.0",
|
||||
"sanitize-html": "^2.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.20",
|
||||
@@ -2210,6 +2212,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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/defaults": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||
@@ -2246,6 +2256,57 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"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/dotenv": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||
@@ -2277,6 +2338,17 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"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-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@@ -2750,6 +2822,24 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"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/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
@@ -2957,6 +3047,14 @@
|
||||
"node": ">=0.12.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/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
@@ -3272,6 +3370,17 @@
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"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/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@@ -3468,6 +3577,11 @@
|
||||
"p-defer": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"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/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@@ -3576,6 +3690,29 @@
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz",
|
||||
"integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g=="
|
||||
},
|
||||
"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/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
@@ -3795,6 +3932,30 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"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/sanitize-html/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/saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
@@ -3984,6 +4145,14 @@
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"node_modules/sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
@@ -6274,6 +6443,11 @@
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
|
||||
"integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
|
||||
},
|
||||
"defaults": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
|
||||
@@ -6297,6 +6471,39 @@
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "16.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
|
||||
@@ -6322,6 +6529,11 @@
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
|
||||
},
|
||||
"entities": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
|
||||
"integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA=="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@@ -6686,6 +6898,17 @@
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
@@ -6825,6 +7048,11 @@
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
@@ -7073,6 +7301,11 @@
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
@@ -7216,6 +7449,11 @@
|
||||
"p-defer": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"parse-srcset": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
|
||||
"integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
@@ -7302,6 +7540,16 @@
|
||||
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.1.0.tgz",
|
||||
"integrity": "sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
@@ -7457,6 +7705,26 @@
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"saslprep": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
|
||||
@@ -7614,6 +7882,11 @@
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"sparse-bitfield": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
"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"
|
||||
"openai": "^3.1.0",
|
||||
"sanitize-html": "^2.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.20",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const dbConnect = require('../models/dbConnect');
|
||||
const { migrateDb } = require('../models');
|
||||
const path = require('path');
|
||||
const cors = require('cors');
|
||||
const routes = require('./routes');
|
||||
@@ -7,7 +8,10 @@ const app = express();
|
||||
const port = process.env.PORT || 3080;
|
||||
const host = process.env.HOST || 'localhost'
|
||||
const projectPath = path.join(__dirname, '..', '..', 'client');
|
||||
dbConnect().then(() => console.log('Connected to MongoDB'));
|
||||
dbConnect().then(() => {
|
||||
console.log('Connected to MongoDB');
|
||||
migrateDb();
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
@@ -3,37 +3,100 @@ const crypto = require('crypto');
|
||||
const router = express.Router();
|
||||
const askBing = require('./askBing');
|
||||
const askSydney = require('./askSydney');
|
||||
const {
|
||||
titleConvo,
|
||||
askClient,
|
||||
browserClient,
|
||||
customClient,
|
||||
detectCode
|
||||
} = require('../../app/');
|
||||
const { getConvo, saveMessage, deleteMessages, saveConvo } = require('../../models');
|
||||
const { handleError, sendMessage } = require('./handlers');
|
||||
const { titleConvo, askClient, browserClient, customClient } = require('../../app/');
|
||||
const { getConvo, saveMessage, getConvoTitle, saveConvo } = require('../../models');
|
||||
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
||||
const { getMessages } = require('../../models/Message');
|
||||
|
||||
router.use('/bing', askBing);
|
||||
router.use('/sydney', askSydney);
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
let { model, text, parentMessageId, conversationId, chatGptLabel, promptPrefix } = req.body;
|
||||
let { model, text, parentMessageId, conversationId: oldConversationId, ...convo } = req.body;
|
||||
if (text.length === 0) {
|
||||
return handleError(res, 'Prompt empty or too short');
|
||||
return handleError(res, { text: 'Prompt empty or too short' });
|
||||
}
|
||||
|
||||
const conversationId = oldConversationId || crypto.randomUUID();
|
||||
|
||||
const userMessageId = crypto.randomUUID();
|
||||
let userMessage = { id: userMessageId, sender: 'User', text };
|
||||
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
|
||||
let userMessage = {
|
||||
messageId: userMessageId,
|
||||
sender: 'User',
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
isCreatedByUser: true
|
||||
};
|
||||
|
||||
console.log('ask log', {
|
||||
model,
|
||||
...userMessage,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
chatGptLabel,
|
||||
promptPrefix
|
||||
...convo
|
||||
});
|
||||
|
||||
await saveMessage(userMessage);
|
||||
await saveConvo({ ...userMessage, model, ...convo });
|
||||
|
||||
return await ask({
|
||||
userMessage,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest: true,
|
||||
req,
|
||||
res
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/regenerate', async (req, res) => {
|
||||
const { model } = req.body;
|
||||
|
||||
const oldUserMessage = await getMessages({ messageId: req.body });
|
||||
|
||||
if (oldUserMessage) {
|
||||
const convo = await getConvo(userMessage?.conversationId);
|
||||
|
||||
const userMessageId = crypto.randomUUID();
|
||||
|
||||
let userMessage = {
|
||||
...userMessage,
|
||||
messageId: userMessageId
|
||||
};
|
||||
|
||||
console.log('ask log for regeneration', {
|
||||
model,
|
||||
...userMessage,
|
||||
...convo
|
||||
});
|
||||
|
||||
return await ask({
|
||||
userMessage,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest: false,
|
||||
req,
|
||||
res
|
||||
});
|
||||
} else return handleError(res, { text: 'Parent message not found' });
|
||||
});
|
||||
|
||||
const ask = async ({
|
||||
userMessage,
|
||||
overrideParentMessageId = null,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest = true,
|
||||
req,
|
||||
res
|
||||
}) => {
|
||||
let {
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
messageId: userMessageId
|
||||
} = userMessage;
|
||||
|
||||
let client;
|
||||
|
||||
if (model === 'chatgpt') {
|
||||
@@ -44,15 +107,6 @@ router.post('/', async (req, res) => {
|
||||
client = browserClient;
|
||||
}
|
||||
|
||||
if (model === 'chatgptCustom' && !chatGptLabel && conversationId) {
|
||||
const convo = await getConvo({ conversationId });
|
||||
if (convo) {
|
||||
console.log('found convo for custom gpt', { convo })
|
||||
chatGptLabel = convo.chatGptLabel;
|
||||
promptPrefix = convo.promptPrefix;
|
||||
}
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
Connection: 'keep-alive',
|
||||
'Content-Type': 'text/event-stream',
|
||||
@@ -61,54 +115,27 @@ router.post('/', async (req, res) => {
|
||||
'X-Accel-Buffering': 'no'
|
||||
});
|
||||
|
||||
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
|
||||
|
||||
try {
|
||||
let i = 0;
|
||||
let tokens = '';
|
||||
const progressCallback = async (partial) => {
|
||||
if (i === 0 && typeof partial === 'object') {
|
||||
userMessage.parentMessageId = parentMessageId ? parentMessageId : partial.id;
|
||||
userMessage.conversationId = conversationId ? conversationId : partial.conversationId;
|
||||
await saveMessage(userMessage);
|
||||
sendMessage(res, { ...partial, initial: true });
|
||||
i++;
|
||||
}
|
||||
|
||||
if (typeof partial === 'object') {
|
||||
sendMessage(res, { ...partial, 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,
|
||||
conversationId
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
...convo
|
||||
},
|
||||
chatGptLabel,
|
||||
promptPrefix
|
||||
...convo
|
||||
});
|
||||
|
||||
console.log('CLIENT RESPONSE', gptResponse);
|
||||
|
||||
if (!gptResponse.parentMessageId) {
|
||||
gptResponse.text = gptResponse.response;
|
||||
gptResponse.id = gptResponse.messageId;
|
||||
gptResponse.parentMessageId = gptResponse.messageId;
|
||||
userMessage.parentMessageId = parentMessageId ? parentMessageId : gptResponse.messageId;
|
||||
// gptResponse.id = gptResponse.messageId;
|
||||
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
|
||||
userMessage.conversationId = conversationId
|
||||
? conversationId
|
||||
: gptResponse.conversationId;
|
||||
@@ -121,37 +148,65 @@ router.post('/', async (req, res) => {
|
||||
gptResponse.text.toLowerCase().includes('no response') ||
|
||||
gptResponse.text.toLowerCase().includes('no answer')
|
||||
) {
|
||||
return handleError(res, 'Prompt empty or too short');
|
||||
}
|
||||
|
||||
if (!parentMessageId) {
|
||||
gptResponse.title = await titleConvo({
|
||||
model,
|
||||
message: text,
|
||||
response: JSON.stringify(gptResponse.text)
|
||||
await saveMessage({
|
||||
messageId: crypto.randomUUID(),
|
||||
sender: model,
|
||||
conversationId,
|
||||
parentMessageId: overrideParentMessageId || userMessageId,
|
||||
error: true,
|
||||
text: 'Prompt empty or too short'
|
||||
});
|
||||
}
|
||||
gptResponse.sender = model === 'chatgptCustom' ? chatGptLabel : model;
|
||||
gptResponse.final = true;
|
||||
gptResponse.text = await detectCode(gptResponse.text);
|
||||
|
||||
if (chatGptLabel?.length > 0 && model === 'chatgptCustom') {
|
||||
gptResponse.chatGptLabel = chatGptLabel;
|
||||
return handleError(res, { text: 'Prompt empty or too short' });
|
||||
}
|
||||
|
||||
if (promptPrefix?.length > 0 && model === 'chatgptCustom') {
|
||||
gptResponse.promptPrefix = promptPrefix;
|
||||
gptResponse.sender = model === 'chatgptCustom' ? convo.chatGptLabel : model;
|
||||
gptResponse.model = model;
|
||||
// gptResponse.final = true;
|
||||
gptResponse.text = await handleText(gptResponse);
|
||||
|
||||
if (convo.chatGptLabel?.length > 0 && model === 'chatgptCustom') {
|
||||
gptResponse.chatGptLabel = convo.chatGptLabel;
|
||||
}
|
||||
|
||||
if (convo.promptPrefix?.length > 0 && model === 'chatgptCustom') {
|
||||
gptResponse.promptPrefix = convo.promptPrefix;
|
||||
}
|
||||
|
||||
// override the parentMessageId, for the regeneration.
|
||||
gptResponse.parentMessageId = overrideParentMessageId || userMessageId;
|
||||
|
||||
await saveMessage(gptResponse);
|
||||
await saveConvo(gptResponse);
|
||||
sendMessage(res, gptResponse);
|
||||
sendMessage(res, {
|
||||
title: await getConvoTitle(conversationId),
|
||||
final: true,
|
||||
requestMessage: userMessage,
|
||||
responseMessage: gptResponse
|
||||
});
|
||||
res.end();
|
||||
|
||||
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||
const title = await titleConvo({ model, text, response: gptResponse });
|
||||
|
||||
await saveConvo({
|
||||
conversationId,
|
||||
title
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await deleteMessages({ id: userMessageId });
|
||||
handleError(res, error.message);
|
||||
// await deleteMessages({ messageId: userMessageId });
|
||||
const errorMessage = {
|
||||
messageId: crypto.randomUUID(),
|
||||
sender: model,
|
||||
conversationId,
|
||||
parentMessageId: overrideParentMessageId || userMessageId,
|
||||
error: true,
|
||||
text: error.message
|
||||
};
|
||||
await saveMessage(errorMessage);
|
||||
handleError(res, errorMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,21 +1,72 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const router = express.Router();
|
||||
const { titleConvo, getCitations, citeText, askBing } = require('../../app/');
|
||||
const { saveMessage, deleteMessages, saveConvo } = require('../../models');
|
||||
const { handleError, sendMessage } = require('./handlers');
|
||||
const citationRegex = /\[\^\d+?\^]/g;
|
||||
const { titleConvo, askBing } = require('../../app/');
|
||||
const { saveMessage, getConvoTitle, saveConvo } = require('../../models');
|
||||
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const { model, text, ...convo } = req.body;
|
||||
const {
|
||||
model,
|
||||
text,
|
||||
parentMessageId,
|
||||
conversationId: oldConversationId,
|
||||
...convo
|
||||
} = req.body;
|
||||
if (text.length === 0) {
|
||||
return handleError(res, 'Prompt empty or too short');
|
||||
return handleError(res, { text: 'Prompt empty or too short' });
|
||||
}
|
||||
|
||||
const userMessageId = crypto.randomUUID();
|
||||
let userMessage = { id: userMessageId, sender: 'User', text };
|
||||
const conversationId = oldConversationId || crypto.randomUUID();
|
||||
const isNewConversation = !oldConversationId;
|
||||
|
||||
console.log('ask log', { model, ...userMessage, ...convo });
|
||||
const userMessageId = crypto.randomUUID();
|
||||
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
|
||||
let userMessage = {
|
||||
messageId: userMessageId,
|
||||
sender: 'User',
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
isCreatedByUser: true
|
||||
};
|
||||
|
||||
console.log('ask log', {
|
||||
model,
|
||||
...userMessage,
|
||||
...convo
|
||||
});
|
||||
|
||||
await saveMessage(userMessage);
|
||||
await saveConvo({ ...userMessage, model, ...convo });
|
||||
|
||||
return await ask({
|
||||
isNewConversation,
|
||||
userMessage,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest: true,
|
||||
req,
|
||||
res
|
||||
});
|
||||
});
|
||||
|
||||
const ask = async ({
|
||||
isNewConversation,
|
||||
overrideParentMessageId = null,
|
||||
userMessage,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest = true,
|
||||
req,
|
||||
res
|
||||
}) => {
|
||||
let {
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
messageId: userMessageId
|
||||
} = userMessage;
|
||||
|
||||
res.writeHead(200, {
|
||||
Connection: 'keep-alive',
|
||||
@@ -25,62 +76,90 @@ router.post('/', async (req, res) => {
|
||||
'X-Accel-Buffering': 'no'
|
||||
});
|
||||
|
||||
try {
|
||||
let tokens = '';
|
||||
const progressCallback = async (partial) => {
|
||||
tokens += partial === text ? '' : partial;
|
||||
// tokens = appendCode(tokens);
|
||||
tokens = citeText(tokens, true);
|
||||
sendMessage(res, { text: tokens, message: true });
|
||||
};
|
||||
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
|
||||
|
||||
try {
|
||||
const progressCallback = createOnProgress();
|
||||
let response = await askBing({
|
||||
text,
|
||||
progressCallback,
|
||||
convo
|
||||
onProgress: progressCallback.call(null, model, {
|
||||
res,
|
||||
text,
|
||||
parentMessageId: overrideParentMessageId || userMessageId
|
||||
}),
|
||||
convo: {
|
||||
...convo,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId
|
||||
}
|
||||
});
|
||||
|
||||
console.log('BING RESPONSE');
|
||||
console.log('BING RESPONSE', response);
|
||||
// console.dir(response, { depth: null });
|
||||
const hasCitations = response.response.match(citationRegex)?.length > 0;
|
||||
|
||||
userMessage.conversationSignature =
|
||||
convo.conversationSignature || response.conversationSignature;
|
||||
userMessage.conversationId = convo.conversationId || response.conversationId;
|
||||
userMessage.conversationId = response.conversationId || conversationId;
|
||||
userMessage.invocationId = response.invocationId;
|
||||
await saveMessage(userMessage);
|
||||
|
||||
if (!convo.conversationSignature) {
|
||||
response.title = await titleConvo({
|
||||
model,
|
||||
message: text,
|
||||
response: JSON.stringify(response.response)
|
||||
// Bing API will not use our conversationId at the first time,
|
||||
// so change the placeholder conversationId to the real one.
|
||||
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
|
||||
// but in this situation, don't change the conversationId, but create new convo.
|
||||
if (conversationId != userMessage.conversationId && isNewConversation)
|
||||
await saveConvo({
|
||||
conversationId: conversationId,
|
||||
newConversationId: userMessage.conversationId
|
||||
});
|
||||
}
|
||||
conversationId = userMessage.conversationId;
|
||||
|
||||
response.text = response.response;
|
||||
delete response.response;
|
||||
response.id = response.details.messageId;
|
||||
// response.id = response.details.messageId;
|
||||
response.suggestions =
|
||||
response.details.suggestedResponses &&
|
||||
response.details.suggestedResponses.map((s) => s.text);
|
||||
response.sender = model;
|
||||
response.final = true;
|
||||
// response.final = true;
|
||||
|
||||
const links = getCitations(response);
|
||||
response.text =
|
||||
citeText(response) +
|
||||
(links?.length > 0 && hasCitations ? `\n<small>${links}</small>` : '');
|
||||
// override the parentMessageId, for the regeneration.
|
||||
response.parentMessageId =
|
||||
overrideParentMessageId || response.parentMessageId || userMessageId;
|
||||
|
||||
response.text = await handleText(response, true);
|
||||
await saveMessage(response);
|
||||
await saveConvo(response);
|
||||
sendMessage(res, response);
|
||||
await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
|
||||
sendMessage(res, {
|
||||
title: await getConvoTitle(conversationId),
|
||||
final: true,
|
||||
requestMessage: userMessage,
|
||||
responseMessage: response
|
||||
});
|
||||
res.end();
|
||||
|
||||
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||
const title = await titleConvo({ model, text, response });
|
||||
|
||||
await saveConvo({
|
||||
conversationId,
|
||||
title
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await deleteMessages({ id: userMessageId });
|
||||
handleError(res, error.message);
|
||||
// await deleteMessages({ messageId: userMessageId });
|
||||
const errorMessage = {
|
||||
messageId: crypto.randomUUID(),
|
||||
sender: model,
|
||||
conversationId,
|
||||
parentMessageId: overrideParentMessageId || userMessageId,
|
||||
error: true,
|
||||
text: error.message
|
||||
};
|
||||
await saveMessage(errorMessage);
|
||||
handleError(res, errorMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,21 +1,72 @@
|
||||
const express = require('express');
|
||||
const crypto = require('crypto');
|
||||
const router = express.Router();
|
||||
const { titleConvo, getCitations, citeText, askSydney } = require('../../app/');
|
||||
const { saveMessage, deleteMessages, saveConvo, getConvoTitle } = require('../../models');
|
||||
const { handleError, sendMessage } = require('./handlers');
|
||||
const citationRegex = /\[\^\d+?\^]/g;
|
||||
const { titleConvo, askSydney } = require('../../app/');
|
||||
const { saveMessage, saveConvo, getConvoTitle } = require('../../models');
|
||||
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
const { model, text, ...convo } = req.body;
|
||||
const {
|
||||
model,
|
||||
text,
|
||||
parentMessageId,
|
||||
conversationId: oldConversationId,
|
||||
...convo
|
||||
} = req.body;
|
||||
if (text.length === 0) {
|
||||
return handleError(res, 'Prompt empty or too short');
|
||||
return handleError(res, { text: 'Prompt empty or too short' });
|
||||
}
|
||||
|
||||
const userMessageId = crypto.randomUUID();
|
||||
let userMessage = { id: userMessageId, sender: 'User', text };
|
||||
const conversationId = oldConversationId || crypto.randomUUID();
|
||||
const isNewConversation = !oldConversationId;
|
||||
|
||||
console.log('ask log', { model, ...userMessage, ...convo });
|
||||
const userMessageId = crypto.randomUUID();
|
||||
const userParentMessageId = parentMessageId || '00000000-0000-0000-0000-000000000000';
|
||||
let userMessage = {
|
||||
messageId: userMessageId,
|
||||
sender: 'User',
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
isCreatedByUser: true
|
||||
};
|
||||
|
||||
console.log('ask log', {
|
||||
model,
|
||||
...userMessage,
|
||||
...convo
|
||||
});
|
||||
|
||||
await saveMessage(userMessage);
|
||||
await saveConvo({ ...userMessage, model, ...convo });
|
||||
|
||||
return await ask({
|
||||
isNewConversation,
|
||||
userMessage,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest: true,
|
||||
req,
|
||||
res
|
||||
});
|
||||
});
|
||||
|
||||
const ask = async ({
|
||||
isNewConversation,
|
||||
overrideParentMessageId = null,
|
||||
userMessage,
|
||||
model,
|
||||
convo,
|
||||
preSendRequest = true,
|
||||
req,
|
||||
res
|
||||
}) => {
|
||||
let {
|
||||
text,
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
messageId: userMessageId
|
||||
} = userMessage;
|
||||
|
||||
res.writeHead(200, {
|
||||
Connection: 'keep-alive',
|
||||
@@ -25,41 +76,38 @@ router.post('/', async (req, res) => {
|
||||
'X-Accel-Buffering': 'no'
|
||||
});
|
||||
|
||||
try {
|
||||
let tokens = '';
|
||||
const progressCallback = async (partial) => {
|
||||
tokens += partial === text ? '' : partial;
|
||||
// tokens = appendCode(tokens);
|
||||
tokens = citeText(tokens, true);
|
||||
sendMessage(res, { text: tokens, message: true });
|
||||
};
|
||||
if (preSendRequest) sendMessage(res, { message: userMessage, created: true });
|
||||
|
||||
try {
|
||||
const progressCallback = createOnProgress();
|
||||
let response = await askSydney({
|
||||
text,
|
||||
progressCallback,
|
||||
convo
|
||||
onProgress: progressCallback.call(null, model, {
|
||||
res,
|
||||
text,
|
||||
parentMessageId: overrideParentMessageId || userMessageId
|
||||
}),
|
||||
convo: {
|
||||
parentMessageId: userParentMessageId,
|
||||
conversationId,
|
||||
...convo
|
||||
}
|
||||
});
|
||||
|
||||
console.log('SYDNEY RESPONSE');
|
||||
console.log(response.response);
|
||||
console.log('SYDNEY RESPONSE', response);
|
||||
// console.dir(response, { depth: null });
|
||||
const hasCitations = response.response.match(citationRegex)?.length > 0;
|
||||
|
||||
userMessage.conversationSignature =
|
||||
convo.conversationSignature || response.conversationSignature;
|
||||
userMessage.conversationId = response.conversationId || conversationId;
|
||||
userMessage.invocationId = response.invocationId;
|
||||
// Unlike gpt and bing, Sydney will never accept our given userMessage.messageId, it will generate its own one.
|
||||
await saveMessage(userMessage);
|
||||
|
||||
// Save sydney response
|
||||
response.id = response.messageId;
|
||||
// response.parentMessageId = convo.parentMessageId ? convo.parentMessageId : response.messageId;
|
||||
response.parentMessageId = response.messageId;
|
||||
// response.id = response.messageId;
|
||||
response.invocationId = convo.invocationId ? convo.invocationId + 1 : 1;
|
||||
response.title = convo.jailbreakConversationId
|
||||
? await getConvoTitle(convo.conversationId)
|
||||
: await titleConvo({
|
||||
model,
|
||||
message: text,
|
||||
response: JSON.stringify(response.response)
|
||||
});
|
||||
response.conversationId = convo.conversationId
|
||||
? convo.conversationId
|
||||
: crypto.randomUUID();
|
||||
response.conversationId = conversationId ? conversationId : crypto.randomUUID();
|
||||
response.conversationSignature = convo.conversationSignature
|
||||
? convo.conversationSignature
|
||||
: crypto.randomUUID();
|
||||
@@ -69,28 +117,61 @@ router.post('/', async (req, res) => {
|
||||
response.details.suggestedResponses &&
|
||||
response.details.suggestedResponses.map((s) => s.text);
|
||||
response.sender = model;
|
||||
response.final = true;
|
||||
// response.final = true;
|
||||
|
||||
const links = getCitations(response);
|
||||
response.text =
|
||||
citeText(response) +
|
||||
(links?.length > 0 && hasCitations ? `\n<small>${links}</small>` : '');
|
||||
// override the parentMessageId, for the regeneration.
|
||||
response.parentMessageId =
|
||||
overrideParentMessageId || response.parentMessageId || userMessageId;
|
||||
|
||||
// Save user message
|
||||
userMessage.conversationId = response.conversationId;
|
||||
userMessage.parentMessageId = response.parentMessageId;
|
||||
userMessage.conversationId = response.conversationId || conversationId;
|
||||
await saveMessage(userMessage);
|
||||
|
||||
// Bing API will not use our conversationId at the first time,
|
||||
// so change the placeholder conversationId to the real one.
|
||||
// Attition: the api will also create new conversationId while using invalid userMessage.parentMessageId,
|
||||
// but in this situation, don't change the conversationId, but create new convo.
|
||||
if (conversationId != userMessage.conversationId && isNewConversation)
|
||||
await saveConvo({
|
||||
conversationId: conversationId,
|
||||
newConversationId: userMessage.conversationId
|
||||
});
|
||||
conversationId = userMessage.conversationId;
|
||||
|
||||
response.text = await handleText(response, true);
|
||||
// Save sydney response & convo, then send
|
||||
await saveMessage(response);
|
||||
await saveConvo(response);
|
||||
sendMessage(res, response);
|
||||
await saveConvo({ ...response, model, chatGptLabel: null, promptPrefix: null, ...convo });
|
||||
sendMessage(res, {
|
||||
title: await getConvoTitle(conversationId),
|
||||
final: true,
|
||||
requestMessage: userMessage,
|
||||
responseMessage: response
|
||||
});
|
||||
res.end();
|
||||
|
||||
if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||
const title = await titleConvo({ model, text, response });
|
||||
|
||||
await saveConvo({
|
||||
conversationId,
|
||||
title
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
await deleteMessages({ id: userMessageId });
|
||||
handleError(res, error.message);
|
||||
// await deleteMessages({ messageId: userMessageId });
|
||||
const errorMessage = {
|
||||
messageId: crypto.randomUUID(),
|
||||
sender: model,
|
||||
conversationId,
|
||||
parentMessageId: overrideParentMessageId || userMessageId,
|
||||
error: true,
|
||||
text: error.message
|
||||
};
|
||||
await saveMessage(errorMessage);
|
||||
handleError(res, errorMessage);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { getConvos, deleteConvos, updateConvo } = require('../../models/Conversation');
|
||||
const { getConvosByPage, deleteConvos, updateConvo } = require('../../models/Conversation');
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
const pageNumber = req.query.pageNumber || 1;
|
||||
res.status(200).send(await getConvos(pageNumber));
|
||||
res.status(200).send(await getConvosByPage(pageNumber));
|
||||
});
|
||||
|
||||
router.post('/clear', async (req, res) => {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
const handleError = (res, errorMessage) => {
|
||||
res.status(500).write(`event: error\ndata: ${errorMessage}`);
|
||||
const _ = require('lodash');
|
||||
const sanitizeHtml = require('sanitize-html');
|
||||
const citationRegex = /\[\^\d+?\^]/g;
|
||||
const { getCitations, citeText, detectCode } = require('../../app/');
|
||||
// const htmlTagRegex = /(<\/?\s*[a-zA-Z]*\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?)>|<\s*[a-zA-Z]+\s*(?:\s+[a-zA-Z]+\s*=\s*(?:"[^"]*"|'[^']*'))*\s*(?:\/?>|<\/?>))/g;
|
||||
|
||||
const handleError = (res, message) => {
|
||||
res.write(`event: error\ndata: ${JSON.stringify(message)}\n\n`);
|
||||
res.end();
|
||||
};
|
||||
|
||||
@@ -10,4 +16,65 @@ 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/, '');
|
||||
}
|
||||
|
||||
// const htmlTags = tokens.match(htmlTagRegex);
|
||||
// if (tokens.includes('```') && htmlTags && htmlTags.length > 0) {
|
||||
// htmlTags.forEach((tag) => {
|
||||
// const sanitizedTag = sanitizeHtml(tag);
|
||||
// tokens = tokens.replaceAll(tag, sanitizedTag);
|
||||
// });
|
||||
// }
|
||||
|
||||
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 (response, bing = false) => {
|
||||
let { text } = response;
|
||||
text = await detectCode(text);
|
||||
response.text = text;
|
||||
|
||||
if (bing) {
|
||||
// const hasCitations = response.response.match(citationRegex)?.length > 0;
|
||||
const links = getCitations(response);
|
||||
if (response.text.match(citationRegex)?.length > 0) {
|
||||
text = citeText(response);
|
||||
}
|
||||
text += links?.length > 0 ? `\n<small>${links}</small>` : '';
|
||||
}
|
||||
|
||||
// const htmlTags = text.match(htmlTagRegex);
|
||||
// if (text.includes('```') && htmlTags && htmlTags.length > 0) {
|
||||
// htmlTags.forEach((tag) => {
|
||||
// const sanitizedTag = sanitizeHtml(tag);
|
||||
// text = text.replaceAll(tag, sanitizedTag);
|
||||
// });
|
||||
// }
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
module.exports = { handleError, sendMessage, createOnProgress, handleText };
|
||||
|
||||
12
client/package-lock.json
generated
12
client/package-lock.json
generated
@@ -11125,9 +11125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"version": "5.76.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
|
||||
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
@@ -19431,9 +19431,9 @@
|
||||
}
|
||||
},
|
||||
"webpack": {
|
||||
"version": "5.75.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
|
||||
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
|
||||
"version": "5.76.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.1.tgz",
|
||||
"integrity": "sha512-4+YIK4Abzv8172/SGqObnUjaIHjLEuUasz9EwQj/9xmPPkYJy2Mh03Q/lJfSD3YLzbxy5FeTq5Uw0323Oh6SJQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
|
||||
@@ -8,7 +8,7 @@ import useDocumentTitle from '~/hooks/useDocumentTitle';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
const App = () => {
|
||||
const { messages } = useSelector((state) => state.messages);
|
||||
const { messages, messageTree } = useSelector((state) => state.messages);
|
||||
const { title } = useSelector((state) => state.convo);
|
||||
const { conversationId } = useSelector((state) => state.convo);
|
||||
const [ navVisible, setNavVisible ]= useState(false)
|
||||
@@ -25,6 +25,7 @@ const App = () => {
|
||||
) : (
|
||||
<Messages
|
||||
messages={messages}
|
||||
messageTree={messageTree}
|
||||
/>
|
||||
)}
|
||||
<TextChat messages={messages} />
|
||||
|
||||
@@ -8,12 +8,14 @@ import { setMessages, setEmptyMessage } from '~/store/messageSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import manualSWR from '~/utils/fetchers';
|
||||
import ConvoIcon from '../svg/ConvoIcon';
|
||||
import { refreshConversation } from '../../store/convoSlice';
|
||||
|
||||
export default function Conversation({
|
||||
id,
|
||||
model,
|
||||
parentMessageId,
|
||||
conversationId,
|
||||
title = 'New conversation',
|
||||
title,
|
||||
chatGptLabel = null,
|
||||
promptPrefix = null,
|
||||
bingData,
|
||||
@@ -75,17 +77,19 @@ export default function Conversation({
|
||||
|
||||
if (chatGptLabel) {
|
||||
dispatch(setModel('chatgptCustom'));
|
||||
dispatch(setCustomModel(chatGptLabel.toLowerCase()));
|
||||
} else {
|
||||
dispatch(setModel(data[1].sender));
|
||||
}
|
||||
|
||||
if (modelMap[data[1].sender.toLowerCase()]) {
|
||||
console.log('sender', data[1].sender);
|
||||
dispatch(setCustomModel(data[1].sender.toLowerCase()));
|
||||
} else {
|
||||
dispatch(setModel(model));
|
||||
dispatch(setCustomModel(null));
|
||||
}
|
||||
|
||||
// if (modelMap[chatGptLabel.toLowerCase()]) {
|
||||
// console.log('custom model', chatGptLabel);
|
||||
// dispatch(setCustomModel(chatGptLabel.toLowerCase()));
|
||||
// } else {
|
||||
// dispatch(setCustomModel(null));
|
||||
// }
|
||||
|
||||
dispatch(setMessages(data));
|
||||
dispatch(setCustomGpt(convo));
|
||||
dispatch(setText(''));
|
||||
@@ -94,6 +98,7 @@ export default function Conversation({
|
||||
|
||||
const renameHandler = (e) => {
|
||||
e.preventDefault();
|
||||
setTitleInput(title);
|
||||
setRenaming(true);
|
||||
setTimeout(() => {
|
||||
inputRef.current.focus();
|
||||
@@ -111,7 +116,10 @@ export default function Conversation({
|
||||
if (titleInput === title) {
|
||||
return;
|
||||
}
|
||||
rename.trigger({ conversationId, title: titleInput });
|
||||
rename.trigger({ conversationId, title: titleInput })
|
||||
.then(() => {
|
||||
dispatch(refreshConversation())
|
||||
});
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
@@ -148,7 +156,7 @@ export default function Conversation({
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
) : (
|
||||
titleInput
|
||||
title
|
||||
)}
|
||||
</div>
|
||||
{conversationId === id ? (
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import Conversation from './Conversation';
|
||||
|
||||
export default function Conversations({ conversations, conversationId, showMore }) {
|
||||
const clickHandler = async (e) => {
|
||||
export default function Conversations({ conversations, conversationId, pageNumber, pages, nextPage, previousPage, moveToTop }) {
|
||||
const clickHandler = (func) => async (e) => {
|
||||
e.preventDefault();
|
||||
await showMore();
|
||||
await func();
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -26,24 +26,36 @@ export default function Conversations({ conversations, conversationId, showMore
|
||||
<Conversation
|
||||
key={convo.conversationId}
|
||||
id={convo.conversationId}
|
||||
model={convo.model}
|
||||
parentMessageId={convo.parentMessageId}
|
||||
title={convo.title}
|
||||
conversationId={conversationId}
|
||||
chatGptLabel={convo.chatGptLabel}
|
||||
promptPrefix={convo.promptPrefix}
|
||||
bingData={bingData}
|
||||
retainView={showMore.bind(null, false)}
|
||||
retainView={moveToTop}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{conversations?.length >= 12 && (
|
||||
<button
|
||||
onClick={clickHandler}
|
||||
className="btn btn-dark btn-small m-auto mb-2 flex justify-center gap-2"
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
)}
|
||||
<div className="m-auto mt-4 mb-2 flex justify-center items-center gap-2">
|
||||
<button
|
||||
onClick={clickHandler(previousPage)}
|
||||
className={"flex btn btn-small transition bg-transition dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400 m-auto gap-2 hover:bg-gray-800" + (pageNumber<=1?" hidden-visibility":"")}
|
||||
disabled={pageNumber<=1}
|
||||
>
|
||||
<<
|
||||
</button>
|
||||
<span className="flex-none text-gray-400">
|
||||
{pageNumber} / {pages}
|
||||
</span>
|
||||
<button
|
||||
onClick={clickHandler(nextPage)}
|
||||
className={"flex btn btn-small transition bg-transition dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400 m-auto gap-2 hover:bg-gray-800" + (pageNumber>=pages?" hidden-visibility":"")}
|
||||
disabled={pageNumber>=pages}
|
||||
>
|
||||
>>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function SubmitButton({ submitMessage }) {
|
||||
|
||||
if (isSubmitting) {
|
||||
return (
|
||||
<button className="absolute bottom-1.5 right-1 rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:bottom-0.5 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:bottom-2.5 md:right-2 md:disabled:bottom-1">
|
||||
<button className="absolute bottom-0 h-[50px] w-[30px] right-1 rounded-md p-1 text-gray-500 hover:bg-gray-100disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:right-2" disabled>
|
||||
<div className="text-2xl">
|
||||
<span >·</span>
|
||||
<span className="blink">·</span>
|
||||
@@ -23,7 +23,7 @@ export default function SubmitButton({ submitMessage }) {
|
||||
<button
|
||||
onClick={clickHandler}
|
||||
disabled={disabled}
|
||||
className="absolute bottom-1.5 right-1 rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent md:bottom-2.5 md:right-2"
|
||||
className="absolute bottom-0 flex justify-center items-center h-[50px] w-[50px] right-0 rounded-md p-1 text-gray-500 hover:bg-gray-100 disabled:hover:bg-transparent dark:hover:bg-gray-900 dark:hover:text-gray-400 dark:disabled:hover:bg-transparent"
|
||||
>
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -5,9 +5,10 @@ import Regenerate from './Regenerate';
|
||||
import ModelMenu from '../Models/ModelMenu';
|
||||
import Footer from './Footer';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import handleSubmit from '~/utils/handleSubmit';
|
||||
import createPayload from '~/utils/createPayload';
|
||||
import resetConvo from '~/utils/resetConvo';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { setConversation, setError } from '~/store/convoSlice';
|
||||
import { setConversation, setNewConvo, setError, refreshConversation } from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setSubmitState, setSubmission } from '~/store/submitSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
@@ -15,6 +16,7 @@ import { setText } from '~/store/textSlice';
|
||||
export default function TextChat({ messages }) {
|
||||
const [errorMessage, setErrorMessage] = useState('');
|
||||
const inputRef = useRef(null)
|
||||
const isComposing = useRef(false);
|
||||
const dispatch = useDispatch();
|
||||
const convo = useSelector((state) => state.convo);
|
||||
const { initial } = useSelector((state) => state.models);
|
||||
@@ -26,29 +28,54 @@ export default function TextChat({ messages }) {
|
||||
// auto focus to input, when enter a conversation.
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, [convo?.conversationId, ])
|
||||
}, [convo?.conversationId,])
|
||||
|
||||
const messageHandler = (data, currentState) => {
|
||||
const { messages, currentMsg, sender } = currentState;
|
||||
dispatch(setMessages([...messages, currentMsg, { sender, text: data }]));
|
||||
const messageHandler = (data, currentState, currentMsg) => {
|
||||
const { messages, _currentMsg, message, sender } = currentState;
|
||||
|
||||
dispatch(setMessages([...messages, currentMsg, { sender, text: data, parentMessageId: currentMsg?.messageId, messageId: currentMsg?.messageId + '_', submitting: true }]));
|
||||
};
|
||||
|
||||
const convoHandler = (data, currentState) => {
|
||||
const { messages, currentMsg, sender, isCustomModel, model, chatGptLabel, promptPrefix } =
|
||||
currentState;
|
||||
const createdHandler = (data, currentState, currentMsg) => {
|
||||
const { conversationId } = currentMsg;
|
||||
dispatch(
|
||||
setMessages([...messages, currentMsg, { sender, text: data.text || data.response }])
|
||||
setConversation({
|
||||
conversationId,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const convoHandler = (data, currentState, currentMsg) => {
|
||||
const { requestMessage, responseMessage } = data;
|
||||
const { conversationId } = requestMessage;
|
||||
const { messages, _currentMsg, message, isCustomModel, sender } =
|
||||
currentState;
|
||||
const { model, chatGptLabel, promptPrefix } = message;
|
||||
dispatch(
|
||||
setMessages([...messages, requestMessage, responseMessage,])
|
||||
);
|
||||
|
||||
const isBing = model === 'bingai' || model === 'sydney';
|
||||
|
||||
if (requestMessage.parentMessageId == '00000000-0000-0000-0000-000000000000') {
|
||||
setTimeout(() => {
|
||||
dispatch(refreshConversation());
|
||||
}, 2000);
|
||||
|
||||
// in case it takes too long.
|
||||
setTimeout(() => {
|
||||
dispatch(refreshConversation());
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
if (!isBing && convo.conversationId === null && convo.parentMessageId === null) {
|
||||
const { title, conversationId, id } = data;
|
||||
const { title } = data;
|
||||
const { conversationId, messageId } = responseMessage;
|
||||
dispatch(
|
||||
setConversation({
|
||||
title,
|
||||
conversationId,
|
||||
parentMessageId: id,
|
||||
parentMessageId: messageId,
|
||||
jailbreakConversationId: null,
|
||||
conversationSignature: null,
|
||||
clientId: null,
|
||||
@@ -58,12 +85,11 @@ export default function TextChat({ messages }) {
|
||||
})
|
||||
);
|
||||
} else if (
|
||||
model === 'bingai' &&
|
||||
convo.conversationId === null &&
|
||||
convo.invocationId === null
|
||||
model === 'bingai'
|
||||
) {
|
||||
console.log('Bing data:', data);
|
||||
const { title, conversationSignature, clientId, conversationId, invocationId } = data;
|
||||
const { title } = data;
|
||||
const { conversationSignature, clientId, conversationId, invocationId } = responseMessage;
|
||||
dispatch(
|
||||
setConversation({
|
||||
title,
|
||||
@@ -75,15 +101,15 @@ export default function TextChat({ messages }) {
|
||||
})
|
||||
);
|
||||
} else if (model === 'sydney') {
|
||||
const { title } = data;
|
||||
const {
|
||||
title,
|
||||
jailbreakConversationId,
|
||||
parentMessageId,
|
||||
conversationSignature,
|
||||
clientId,
|
||||
conversationId,
|
||||
invocationId
|
||||
} = data;
|
||||
} = responseMessage;
|
||||
dispatch(
|
||||
setConversation({
|
||||
title,
|
||||
@@ -100,18 +126,18 @@ export default function TextChat({ messages }) {
|
||||
dispatch(setSubmitState(false));
|
||||
};
|
||||
|
||||
const errorHandler = (event, currentState) => {
|
||||
const { initialResponse, messages, currentMsg, message } = currentState;
|
||||
console.log('Error:', event);
|
||||
const errorHandler = (data, currentState, currentMsg) => {
|
||||
const { initialResponse, messages, _currentMsg, message } = currentState;
|
||||
console.log('Error:', data);
|
||||
const errorResponse = {
|
||||
...initialResponse,
|
||||
text: `An error occurred. Please try again in a few moments.\n\nError message: ${event.data}`,
|
||||
error: true
|
||||
...data,
|
||||
error: true,
|
||||
parentMessageId: currentMsg?.messageId,
|
||||
};
|
||||
setErrorMessage(event.data);
|
||||
setErrorMessage(data?.text);
|
||||
dispatch(setSubmitState(false));
|
||||
dispatch(setMessages([...messages.slice(0, -2), currentMsg, errorResponse]));
|
||||
dispatch(setText(message));
|
||||
dispatch(setMessages([...messages, currentMsg, errorResponse]));
|
||||
dispatch(setText(message?.text));
|
||||
dispatch(setError(true));
|
||||
return;
|
||||
};
|
||||
@@ -125,69 +151,51 @@ export default function TextChat({ messages }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this is not a real messageId, it is used as placeholder before real messageId returned
|
||||
const fakeMessageId = crypto.randomUUID();
|
||||
const isCustomModel = model === 'chatgptCustom' || !initial[model];
|
||||
const message = text.trim();
|
||||
const currentMsg = { sender: 'User', text: message, current: true };
|
||||
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
|
||||
const initialResponse = { sender, text: '' };
|
||||
let parentMessageId = convo.parentMessageId || '00000000-0000-0000-0000-000000000000';
|
||||
let currentMessages = messages;
|
||||
if (resetConvo(currentMessages, sender)) {
|
||||
parentMessageId = '00000000-0000-0000-0000-000000000000';
|
||||
dispatch(setNewConvo());
|
||||
currentMessages = [];
|
||||
}
|
||||
const currentMsg = { sender: 'User', text: message, current: true, isCreatedByUser: true, parentMessageId , messageId: fakeMessageId };
|
||||
const initialResponse = { sender, text: '', parentMessageId: fakeMessageId, submitting: true };
|
||||
|
||||
dispatch(setSubmitState(true));
|
||||
dispatch(setMessages([...messages, currentMsg, initialResponse]));
|
||||
dispatch(setMessages([...currentMessages, currentMsg, initialResponse]));
|
||||
dispatch(setText(''));
|
||||
|
||||
const submission = {
|
||||
model,
|
||||
text: message,
|
||||
convo,
|
||||
chatGptLabel,
|
||||
promptPrefix,
|
||||
isCustomModel,
|
||||
message,
|
||||
messages,
|
||||
message: {
|
||||
...currentMsg,
|
||||
model,
|
||||
chatGptLabel,
|
||||
promptPrefix,
|
||||
},
|
||||
messages: currentMessages,
|
||||
currentMsg,
|
||||
initialResponse,
|
||||
sender,
|
||||
initialResponse
|
||||
};
|
||||
console.log('User Input:', message);
|
||||
// handleSubmit(submission);
|
||||
dispatch(setSubmission(submission));
|
||||
};
|
||||
|
||||
const createPayload = ({ model, text, convo, chatGptLabel, promptPrefix }) => {
|
||||
const endpoint = `/api/ask`;
|
||||
let payload = { model, text, chatGptLabel, promptPrefix };
|
||||
if (convo.conversationId && convo.parentMessageId) {
|
||||
payload = {
|
||||
...payload,
|
||||
conversationId: convo.conversationId,
|
||||
parentMessageId: convo.parentMessageId
|
||||
};
|
||||
}
|
||||
|
||||
const isBing = model === 'bingai' || model === 'sydney';
|
||||
if (isBing && convo.conversationId) {
|
||||
payload = {
|
||||
...payload,
|
||||
jailbreakConversationId: convo.jailbreakConversationId,
|
||||
conversationId: convo.conversationId,
|
||||
conversationSignature: convo.conversationSignature,
|
||||
clientId: convo.clientId,
|
||||
invocationId: convo.invocationId
|
||||
};
|
||||
}
|
||||
|
||||
let server = endpoint;
|
||||
server = model === 'bingai' ? server + '/bing' : server;
|
||||
server = model === 'sydney' ? server + '/sydney' : server;
|
||||
return { server, payload };
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
if (Object.keys(submission).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentState = submission;
|
||||
let currentMsg = currentState.currentMsg;
|
||||
const { server, payload } = createPayload(submission);
|
||||
const onMessage = (e) => {
|
||||
if (stopStream) {
|
||||
@@ -195,15 +203,22 @@ export default function TextChat({ messages }) {
|
||||
}
|
||||
|
||||
const data = JSON.parse(e.data);
|
||||
let text = data.text || data.response;
|
||||
if (data.message) {
|
||||
messageHandler(text, currentState);
|
||||
}
|
||||
|
||||
// if (data.message) {
|
||||
// messageHandler(text, currentState);
|
||||
// }
|
||||
|
||||
if (data.final) {
|
||||
convoHandler(data, currentState);
|
||||
convoHandler(data, currentState, currentMsg);
|
||||
console.log('final', data);
|
||||
} if (data.created) {
|
||||
currentMsg = data.message;
|
||||
createdHandler(data, currentState, currentMsg);
|
||||
} else {
|
||||
let text = data.text || data.response;
|
||||
if (data.message) {
|
||||
messageHandler(text, currentState, currentMsg);
|
||||
}
|
||||
// console.log('dataStream', data);
|
||||
}
|
||||
};
|
||||
@@ -222,7 +237,10 @@ export default function TextChat({ messages }) {
|
||||
events.onerror = function (e) {
|
||||
console.log('error in opening conn.');
|
||||
events.close();
|
||||
errorHandler(e, currentState);
|
||||
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
errorHandler(data, currentState, currentMsg);
|
||||
};
|
||||
|
||||
events.stream();
|
||||
@@ -237,6 +255,11 @@ export default function TextChat({ messages }) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
if (!isComposing.current)
|
||||
submitMessage();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyUp = (e) => {
|
||||
@@ -247,14 +270,19 @@ export default function TextChat({ messages }) {
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
submitMessage();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCompositionStart = (e) => {
|
||||
isComposing.current = true
|
||||
}
|
||||
|
||||
const handleCompositionEnd = (e) => {
|
||||
isComposing.current = false;
|
||||
}
|
||||
|
||||
const changeHandler = (e) => {
|
||||
const { value } = e.target;
|
||||
|
||||
if (isSubmitting && (value === '' || value === '\n')) {
|
||||
return;
|
||||
}
|
||||
@@ -296,6 +324,8 @@ export default function TextChat({ messages }) {
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
onChange={changeHandler}
|
||||
onCompositionStart={handleCompositionStart}
|
||||
onCompositionEnd={handleCompositionEnd}
|
||||
placeholder={disabled ? 'Choose another model or customize GPT again' : ''}
|
||||
disabled={disabled}
|
||||
className="m-0 h-auto max-h-52 resize-none overflow-auto border-0 bg-transparent p-0 pl-9 pr-8 leading-6 focus:outline-none focus:ring-0 focus-visible:ring-0 dark:bg-transparent md:pl-8"
|
||||
|
||||
@@ -2,15 +2,21 @@ import React from 'react';
|
||||
// import Clipboard from '../svg/Clipboard';
|
||||
import EditIcon from '../svg/EditIcon';
|
||||
|
||||
export default function HoverButtons({ user }) {
|
||||
export default function HoverButtons({ visible, onClick, model }) {
|
||||
const isBing = model === 'bingai' || model === 'sydney';
|
||||
const enabled = !isBing;
|
||||
|
||||
return (
|
||||
<div className="visible mt-2 flex justify-center gap-3 self-end text-gray-400 md:gap-4 lg:absolute lg:top-0 lg:right-0 lg:mt-0 lg:translate-x-full lg:gap-1 lg:self-center lg:pl-2">
|
||||
{user && (
|
||||
<button className="rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible">
|
||||
{/* <button className="rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"> */}
|
||||
<EditIcon />
|
||||
</button>
|
||||
)}
|
||||
{(visible&&enabled)?(
|
||||
<>
|
||||
<button className="resubmit-edit-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible"
|
||||
onClick={onClick}>
|
||||
{/* <button className="rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"> */}
|
||||
<EditIcon />
|
||||
</button>
|
||||
</>
|
||||
):null}
|
||||
{/* <button className="rounded-md p-1 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400">
|
||||
<Clipboard />
|
||||
</button> */}
|
||||
|
||||
@@ -1,32 +1,60 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import TextWrapper from './TextWrapper';
|
||||
import { useSelector } from 'react-redux';
|
||||
import GPTIcon from '../svg/GPTIcon';
|
||||
import BingIcon from '../svg/BingIcon';
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import HoverButtons from './HoverButtons';
|
||||
import Spinner from '../svg/Spinner';
|
||||
import SiblingSwitch from './SiblingSwitch';
|
||||
import { setError } from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setSubmitState, setSubmission } from '~/store/submitSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import { setConversation } from '../../store/convoSlice';
|
||||
import { getIconOfModel } from '../../utils';
|
||||
|
||||
export default function Message({
|
||||
sender,
|
||||
text,
|
||||
last = false,
|
||||
error = false,
|
||||
scrollToBottom
|
||||
message,
|
||||
messages,
|
||||
scrollToBottom,
|
||||
currentEditId,
|
||||
setCurrentEditId,
|
||||
siblingIdx,
|
||||
siblingCount,
|
||||
setSiblingIdx
|
||||
}) {
|
||||
const { isSubmitting } = useSelector((state) => state.submit);
|
||||
const { isSubmitting, model, chatGptLabel, promptPrefix } = useSelector(
|
||||
(state) => state.submit
|
||||
);
|
||||
const [abortScroll, setAbort] = useState(false);
|
||||
const notUser = sender.toLowerCase() !== 'user';
|
||||
const blinker = isSubmitting && last && notUser;
|
||||
const { sender, text, isCreatedByUser, error, submitting } = message;
|
||||
const textEditor = useRef(null);
|
||||
const convo = useSelector((state) => state.convo);
|
||||
const { initial } = useSelector((state) => state.models);
|
||||
const { error: convoError } = convo;
|
||||
const last = !message?.children?.length;
|
||||
const edit = message.messageId == currentEditId;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// const notUser = !isCreatedByUser; // sender.toLowerCase() !== 'user';
|
||||
const blinker = submitting && isSubmitting && last && !isCreatedByUser;
|
||||
const generateCursor = useCallback(() => {
|
||||
if (!blinker) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return <span className="result-streaming">█</span>;
|
||||
}, [blinker]);
|
||||
|
||||
useEffect(() => {
|
||||
if (blinker && !abortScroll) {
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [isSubmitting, text, blinker, scrollToBottom, abortScroll]);
|
||||
|
||||
if (sender === '') {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (last) dispatch(setConversation({ parentMessageId: message?.messageId }));
|
||||
}, [last]);
|
||||
|
||||
const enterEdit = (cancel) => setCurrentEditId(cancel ? -1 : message.messageId);
|
||||
|
||||
const handleWheel = () => {
|
||||
if (blinker) {
|
||||
@@ -41,79 +69,175 @@ export default function Message({
|
||||
'w-full border-b border-black/10 dark:border-gray-900/50 text-gray-800 bg-white dark:text-gray-100 group dark:bg-gray-800'
|
||||
};
|
||||
|
||||
const bgColors = {
|
||||
chatgpt: 'rgb(16, 163, 127)',
|
||||
chatgptBrowser: 'rgb(25, 207, 207)',
|
||||
bingai: '',
|
||||
sydney: ''
|
||||
};
|
||||
const icon = getIconOfModel({
|
||||
sender,
|
||||
isCreatedByUser,
|
||||
model,
|
||||
chatGptLabel,
|
||||
promptPrefix,
|
||||
error
|
||||
});
|
||||
|
||||
const isBing = sender === 'bingai' || sender === 'sydney';
|
||||
|
||||
let icon = `${sender}:`;
|
||||
let backgroundColor = bgColors[sender];
|
||||
|
||||
if (notUser) {
|
||||
if (!isCreatedByUser)
|
||||
props.className =
|
||||
'w-full border-b border-black/10 bg-gray-50 dark:border-gray-900/50 text-gray-800 dark:text-gray-100 group bg-gray-100 dark:bg-[#444654]';
|
||||
}
|
||||
|
||||
if ((notUser && backgroundColor) || isBing) {
|
||||
icon = (
|
||||
<div
|
||||
style={
|
||||
isBing
|
||||
? { background: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)' }
|
||||
: { backgroundColor }
|
||||
}
|
||||
className="relative flex h-[30px] w-[30px] items-center justify-center rounded-sm p-1 text-white"
|
||||
>
|
||||
{isBing ? <BingIcon /> : <GPTIcon />}
|
||||
{error && (
|
||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
||||
!
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// const wrapText = (text) => <TextWrapper text={text} generateCursor={generateCursor}/>;
|
||||
|
||||
const wrapText = (text) => <TextWrapper text={text} />;
|
||||
const resubmitMessage = () => {
|
||||
const text = textEditor.current.innerText;
|
||||
|
||||
if (convoError) {
|
||||
dispatch(setError(false));
|
||||
}
|
||||
|
||||
if (!!isSubmitting || text.trim() === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// this is not a real messageId, it is used as placeholder before real messageId returned
|
||||
const fakeMessageId = crypto.randomUUID();
|
||||
const isCustomModel = model === 'chatgptCustom' || !initial[model];
|
||||
const currentMsg = {
|
||||
sender: 'User',
|
||||
text: text.trim(),
|
||||
current: true,
|
||||
isCreatedByUser: true,
|
||||
parentMessageId: message?.parentMessageId,
|
||||
conversationId: message?.conversationId,
|
||||
messageId: fakeMessageId
|
||||
};
|
||||
const sender = model === 'chatgptCustom' ? chatGptLabel : model;
|
||||
|
||||
const initialResponse = {
|
||||
sender,
|
||||
text: '',
|
||||
parentMessageId: fakeMessageId,
|
||||
submitting: true
|
||||
};
|
||||
|
||||
dispatch(setSubmitState(true));
|
||||
dispatch(setMessages([...messages, currentMsg, initialResponse]));
|
||||
dispatch(setText(''));
|
||||
|
||||
const submission = {
|
||||
isCustomModel,
|
||||
message: {
|
||||
...currentMsg,
|
||||
model,
|
||||
chatGptLabel,
|
||||
promptPrefix
|
||||
},
|
||||
messages: messages,
|
||||
currentMsg,
|
||||
initialResponse,
|
||||
sender
|
||||
};
|
||||
console.log('User Input:', currentMsg?.text);
|
||||
// handleSubmit(submission);
|
||||
dispatch(setSubmission(submission));
|
||||
|
||||
setSiblingIdx(siblingCount - 1);
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||
<strong className="relative flex w-[30px] flex-col items-end text-right text-xs md:text-sm">
|
||||
{typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? (
|
||||
<span className=" direction-rtl w-40 overflow-x-scroll">{icon}</span>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
</strong>
|
||||
<div className="relative flex w-[calc(100%-50px)] flex-col gap-1 whitespace-pre-wrap md:gap-3 lg:w-[calc(100%-115px)]">
|
||||
<div className="flex flex-grow flex-col gap-3">
|
||||
{error ? (
|
||||
<div className="flex flex min-h-[20px] flex-row flex-col items-start gap-4 gap-2 whitespace-pre-wrap text-red-500">
|
||||
<div className="rounded-md border border-red-500 bg-red-500/10 py-2 px-3 text-sm text-gray-600 dark:text-gray-100">
|
||||
{text}
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<div
|
||||
{...props}
|
||||
onWheel={handleWheel}
|
||||
>
|
||||
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
|
||||
{typeof icon === 'string' && icon.match(/[^\u0000-\u007F]+/) ? (
|
||||
<span className=" direction-rtl w-40 overflow-x-scroll">{icon}</span>
|
||||
) : (
|
||||
<div className="flex min-h-[20px] flex-col items-start gap-4 whitespace-pre-wrap">
|
||||
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
||||
<div className="markdown prose dark:prose-invert light w-full break-words">
|
||||
{notUser ? wrapText(text) : text}
|
||||
{blinker && <span className="result-streaming">█</span>}
|
||||
</div>
|
||||
</div>
|
||||
icon
|
||||
)}
|
||||
<div className="sibling-switch invisible absolute left-0 top-2 -ml-4 flex -translate-x-full items-center justify-center gap-1 text-xs group-hover:visible">
|
||||
<SiblingSwitch
|
||||
siblingIdx={siblingIdx}
|
||||
siblingCount={siblingCount}
|
||||
setSiblingIdx={setSiblingIdx}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex w-[calc(100%-50px)] flex-col gap-1 whitespace-pre-wrap md:gap-3 lg:w-[calc(100%-115px)]">
|
||||
<div className="flex flex-grow flex-col gap-3">
|
||||
{error ? (
|
||||
<div className="flex flex min-h-[20px] flex-grow flex-col items-start gap-4 gap-2 whitespace-pre-wrap text-red-500">
|
||||
<div className="rounded-md border border-red-500 bg-red-500/10 py-2 px-3 text-sm text-gray-600 dark:text-gray-100">
|
||||
{`An error occurred. Please try again in a few moments.\n\nError message: ${text}`}
|
||||
</div>
|
||||
</div>
|
||||
) : edit ? (
|
||||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
|
||||
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
||||
|
||||
<div
|
||||
className="markdown prose dark:prose-invert light w-full break-words border-none focus:outline-none"
|
||||
contentEditable={true}
|
||||
ref={textEditor}
|
||||
suppressContentEditableWarning={true}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
<button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={resubmitMessage}
|
||||
>
|
||||
Save & Submit
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-neutral relative"
|
||||
onClick={() => enterEdit(true)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-[20px] flex-grow flex-col items-start gap-4 whitespace-pre-wrap">
|
||||
{/* <div className={`${blinker ? 'result-streaming' : ''} markdown prose dark:prose-invert light w-full break-words`}> */}
|
||||
<div className="markdown prose dark:prose-invert light w-full break-words">
|
||||
{!isCreatedByUser ? (
|
||||
<TextWrapper
|
||||
text={text}
|
||||
generateCursor={generateCursor}
|
||||
/>
|
||||
) : (
|
||||
text
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<HoverButtons
|
||||
model={model}
|
||||
visible={!error && isCreatedByUser && !edit}
|
||||
onClick={() => enterEdit()}
|
||||
/>
|
||||
<div className="sibling-switch-container flex justify-between">
|
||||
<div className="flex items-center justify-center gap-1 self-center pt-2 text-xs">
|
||||
<SiblingSwitch
|
||||
siblingIdx={siblingIdx}
|
||||
siblingCount={siblingCount}
|
||||
setSiblingIdx={setSiblingIdx}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HoverButtons user={!notUser} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<MultiMessage
|
||||
messageList={message.children}
|
||||
messages={messages}
|
||||
scrollToBottom={scrollToBottom}
|
||||
currentEditId={currentEditId}
|
||||
setCurrentEditId={setCurrentEditId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
40
client/src/components/Messages/MultiMessage.jsx
Normal file
40
client/src/components/Messages/MultiMessage.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import Message from './Message';
|
||||
|
||||
export default function MultiMessage({
|
||||
messageList,
|
||||
messages,
|
||||
scrollToBottom,
|
||||
currentEditId,
|
||||
setCurrentEditId
|
||||
}) {
|
||||
const [siblingIdx, setSiblingIdx] = useState(0);
|
||||
|
||||
const setSiblingIdxRev = (value) => {
|
||||
setSiblingIdx(messageList?.length - value - 1);
|
||||
};
|
||||
|
||||
// if (!messageList?.length) return null;
|
||||
if (!(messageList && messageList.length)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (siblingIdx >= messageList?.length) {
|
||||
setSiblingIdx(0);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Message
|
||||
key={messageList[messageList.length - siblingIdx - 1].messageId}
|
||||
message={messageList[messageList.length - siblingIdx - 1]}
|
||||
messages={messages}
|
||||
scrollToBottom={scrollToBottom}
|
||||
currentEditId={currentEditId}
|
||||
setCurrentEditId={setCurrentEditId}
|
||||
siblingIdx={messageList.length - siblingIdx - 1}
|
||||
siblingCount={messageList.length}
|
||||
setSiblingIdx={setSiblingIdxRev}
|
||||
/>
|
||||
);
|
||||
}
|
||||
26
client/src/components/Messages/SiblingSwitch.jsx
Normal file
26
client/src/components/Messages/SiblingSwitch.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function SiblingSwitch({
|
||||
siblingIdx,
|
||||
siblingCount,
|
||||
setSiblingIdx
|
||||
}) {
|
||||
const previous = () => {
|
||||
setSiblingIdx(siblingIdx - 1);
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
setSiblingIdx(siblingIdx + 1);
|
||||
}
|
||||
return siblingCount > 1 ? (
|
||||
<>
|
||||
<button className="dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400" onClick={previous} disabled={siblingIdx==0}>
|
||||
<svg stroke="currentColor" fill="none" strokeWidth="1.5" viewBox="0 0 24 24" strokeLinecap="round" strokeLinejoin="round" className="h-3 w-3" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="15 18 9 12 15 6"></polyline></svg>
|
||||
</button>
|
||||
<span className="flex-grow flex-shrink-0">{siblingIdx + 1}/{siblingCount}</span>
|
||||
<button className="dark:text-white disabled:text-gray-300 dark:disabled:text-gray-400" onClick={next} disabled={siblingIdx==siblingCount-1}>
|
||||
<svg stroke="currentColor" fill="none" strokeWidth="1.5" viewBox="0 0 24 24" strokeLinecap="round" strokeLinejoin="round" className="h-3 w-3" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="9 18 15 12 9 6"></polyline></svg>
|
||||
</button>
|
||||
</>
|
||||
):null;
|
||||
}
|
||||
@@ -46,8 +46,9 @@ const inLineWrap = (parts) => {
|
||||
});
|
||||
};
|
||||
|
||||
export default function TextWrapper({ text }) {
|
||||
export default function TextWrapper({ text, generateCursor }) {
|
||||
let embedTest = false;
|
||||
let result = null;
|
||||
|
||||
// to match unenclosed code blocks
|
||||
if (text.match(/```/g)?.length === 1) {
|
||||
@@ -137,13 +138,23 @@ export default function TextWrapper({ text }) {
|
||||
}
|
||||
});
|
||||
|
||||
return <>{codeParts}</>; // return the wrapped text
|
||||
// return <>{codeParts}</>; // return the wrapped text
|
||||
result = <>{codeParts}</>;
|
||||
} else if (text.match(markupRegex)) {
|
||||
// map over the parts and wrap any text between tildes with <code> tags
|
||||
const parts = text.split(markupRegex);
|
||||
const codeParts = inLineWrap(parts);
|
||||
return <>{codeParts}</>; // return the wrapped text
|
||||
// return <>{codeParts}</>; // return the wrapped text
|
||||
result = <>{codeParts}</>;
|
||||
} else {
|
||||
return <Markdown options={mdOptions}>{text}</Markdown>;
|
||||
// return <Markdown options={mdOptions}>{text}</Markdown>;
|
||||
result = <Markdown options={mdOptions}>{text}</Markdown>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{result}
|
||||
{generateCursor()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import React, { useEffect, useState, useRef, useMemo } from 'react';
|
||||
import Spinner from '../svg/Spinner';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import ScrollToBottom from './ScrollToBottom';
|
||||
import Message from './Message';
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
const Messages = ({ messages }) => {
|
||||
const Messages = ({ messages, messageTree }) => {
|
||||
const [currentEditId, setCurrentEditId] = useState(-1);
|
||||
const { conversationId } = useSelector((state) => state.convo);
|
||||
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||
const scrollableRef = useRef(null);
|
||||
const messagesEndRef = useRef(null);
|
||||
@@ -55,30 +59,33 @@ const Messages = ({ messages }) => {
|
||||
onScroll={debouncedHandleScroll}
|
||||
>
|
||||
{/* <div className="flex-1 overflow-hidden"> */}
|
||||
<div className="h-full dark:gpt-dark-gray">
|
||||
<div className="flex h-full flex-col items-center text-sm dark:gpt-dark-gray">
|
||||
{messages.map((message, i) => (
|
||||
<Message
|
||||
key={i}
|
||||
sender={message.sender}
|
||||
text={message.text}
|
||||
last={i === messages.length - 1}
|
||||
error={message.error ? true : false}
|
||||
scrollToBottom={i === messages.length - 1 ? scrollToBottom : null}
|
||||
/>
|
||||
))}
|
||||
<CSSTransition
|
||||
in={showScrollButton}
|
||||
timeout={400}
|
||||
classNames="scroll-down"
|
||||
unmountOnExit={false}
|
||||
// appear
|
||||
>
|
||||
{() => showScrollButton && <ScrollToBottom scrollHandler={scrollHandler} />}
|
||||
</CSSTransition>
|
||||
|
||||
<div className="dark:gpt-dark-gray h-full">
|
||||
<div className="dark:gpt-dark-gray flex h-full flex-col items-center text-sm">
|
||||
{messageTree.length === 0 ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
<MultiMessage
|
||||
key={conversationId} // avoid internal state mixture
|
||||
messageList={messageTree}
|
||||
messages={messages}
|
||||
scrollToBottom={scrollToBottom}
|
||||
currentEditId={currentEditId}
|
||||
setCurrentEditId={setCurrentEditId}
|
||||
/>
|
||||
<CSSTransition
|
||||
in={showScrollButton}
|
||||
timeout={400}
|
||||
classNames="scroll-down"
|
||||
unmountOnExit={false}
|
||||
// appear
|
||||
>
|
||||
{() => showScrollButton && <ScrollToBottom scrollHandler={scrollHandler} />}
|
||||
</CSSTransition>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className="group h-32 w-full flex-shrink-0 dark:border-gray-900/50 dark:gpt-dark-gray md:h-48"
|
||||
className="dark:gpt-dark-gray group h-32 w-full flex-shrink-0 dark:border-gray-900/50 md:h-48"
|
||||
ref={messagesEndRef}
|
||||
/>
|
||||
</div>
|
||||
@@ -88,4 +95,4 @@ const Messages = ({ messages }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Messages;
|
||||
export default React.memo(Messages);
|
||||
|
||||
@@ -10,7 +10,10 @@ export default function MenuItems({ models, onSelect }) {
|
||||
id={modelItem._id}
|
||||
modelName={modelItem.name}
|
||||
value={modelItem.value}
|
||||
model={modelItem.model || 'chatgptCustom'}
|
||||
onSelect={onSelect}
|
||||
chatGptLabel={modelItem.chatGptLabel}
|
||||
promptPrefix={modelItem.promptPrefix}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
|
||||
@@ -7,8 +7,9 @@ import { DialogTrigger } from '../ui/Dialog.tsx';
|
||||
import RenameButton from '../Conversations/RenameButton';
|
||||
import TrashIcon from '../svg/TrashIcon';
|
||||
import manualSWR from '~/utils/fetchers';
|
||||
import { getIconOfModel } from '../../utils';
|
||||
|
||||
export default function ModelItem({ modelName, value, onSelect, id }) {
|
||||
export default function ModelItem({ modelName, value, model, onSelect, id, chatGptLabel, promptPrefix }) {
|
||||
const dispatch = useDispatch();
|
||||
const { customModel } = useSelector((state) => state.submit);
|
||||
const { initial } = useSelector((state) => state.models);
|
||||
@@ -27,6 +28,8 @@ export default function ModelItem({ modelName, value, onSelect, id }) {
|
||||
dispatch(setModels(fetchedModels));
|
||||
});
|
||||
|
||||
const icon = getIconOfModel({ size: 16, sender: modelName, isCreatedByUser: false, model, chatGptLabel, promptPrefix, error: false, className: "mr-2" });
|
||||
|
||||
if (value === 'chatgptCustom') {
|
||||
return (
|
||||
<DialogTrigger className="w-full">
|
||||
@@ -34,6 +37,7 @@ export default function ModelItem({ modelName, value, onSelect, id }) {
|
||||
value={value}
|
||||
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
{icon}
|
||||
{modelName}
|
||||
<sup>$</sup>
|
||||
</DropdownMenuRadioItem>
|
||||
@@ -47,6 +51,7 @@ export default function ModelItem({ modelName, value, onSelect, id }) {
|
||||
value={value}
|
||||
className="dark:font-semibold dark:text-gray-100 dark:hover:bg-gray-800"
|
||||
>
|
||||
{icon}
|
||||
{modelName}
|
||||
{value === 'chatgpt' && <sup>$</sup>}
|
||||
</DropdownMenuRadioItem>
|
||||
@@ -122,6 +127,9 @@ export default function ModelItem({ modelName, value, onSelect, id }) {
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</span>
|
||||
)}
|
||||
|
||||
{icon}
|
||||
|
||||
{renaming === true ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
|
||||
@@ -12,6 +12,8 @@ import ModelDialog from './ModelDialog';
|
||||
import MenuItems from './MenuItems';
|
||||
import { swr } from '~/utils/fetchers';
|
||||
import { setModels } from '~/store/modelSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
import GPTIcon from '../svg/GPTIcon';
|
||||
import BingIcon from '../svg/BingIcon';
|
||||
import { Button } from '../ui/Button.tsx';
|
||||
@@ -44,9 +46,16 @@ export default function ModelMenu() {
|
||||
|
||||
useEffect(() => {
|
||||
mutate();
|
||||
const lastSelected = JSON.parse(localStorage.getItem('model'));
|
||||
if (lastSelected && lastSelected !== 'chatgptCustom' && initial[lastSelected]) {
|
||||
dispatch(setModel(lastSelected));
|
||||
try {
|
||||
const lastSelected = JSON.parse(localStorage.getItem('model'));
|
||||
|
||||
if (lastSelected === 'chatgptCustom') {
|
||||
return;
|
||||
} else if (initial[lastSelected]) {
|
||||
dispatch(setModel(lastSelected));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -56,32 +65,32 @@ export default function ModelMenu() {
|
||||
localStorage.setItem('model', JSON.stringify(model));
|
||||
}, [model]);
|
||||
|
||||
const onChange = (value, custom = false) => {
|
||||
// if (custom) {
|
||||
// mutate();
|
||||
// }
|
||||
const onChange = (value) => {
|
||||
if (!value) {
|
||||
return;
|
||||
} else if (value === model) {
|
||||
return;
|
||||
} else if (value === 'chatgptCustom') {
|
||||
// dispatch(setMessages([]));
|
||||
// return;
|
||||
} else if (initial[value]) {
|
||||
dispatch(setModel(value));
|
||||
dispatch(setDisabled(false));
|
||||
dispatch(setCustomModel(null));
|
||||
dispatch(setCustomGpt({ chatGptLabel: null, promptPrefix: null }));
|
||||
} else if (!initial[value]) {
|
||||
const chatGptLabel = modelMap[value]?.chatGptLabel;
|
||||
const promptPrefix = modelMap[value]?.promptPrefix;
|
||||
dispatch(setCustomGpt({ chatGptLabel, promptPrefix }));
|
||||
dispatch(setModel('chatgptCustom'));
|
||||
dispatch(setCustomModel(value));
|
||||
// if (custom) {
|
||||
// setMenuOpen((prevOpen) => !prevOpen);
|
||||
// }
|
||||
setMenuOpen(false);
|
||||
} else if (!modelMap[value]) {
|
||||
dispatch(setCustomModel(null));
|
||||
}
|
||||
|
||||
// Set new conversation
|
||||
dispatch(setText(''));
|
||||
dispatch(setMessages([]));
|
||||
dispatch(setNewConvo());
|
||||
dispatch(setSubmission({}));
|
||||
};
|
||||
@@ -148,7 +157,7 @@ export default function ModelMenu() {
|
||||
{icon}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56 dark:bg-gray-700">
|
||||
<DropdownMenuContent className="w-56 dark:bg-gray-700" onCloseAutoFocus={(event) => event.preventDefault()}>
|
||||
<DropdownMenuLabel className="dark:text-gray-300">Select a Model</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuRadioGroup
|
||||
|
||||
@@ -5,6 +5,7 @@ import manualSWR from '~/utils/fetchers';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { setNewConvo, removeAll } from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setSubmission } from '~/store/submitSlice';
|
||||
|
||||
export default function ClearConvos() {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -2,11 +2,12 @@ import React from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { setNewConvo } from '~/store/convoSlice';
|
||||
import { setMessages } from '~/store/messageSlice';
|
||||
import { setSubmission } from '~/store/submitSlice';
|
||||
import { setText } from '~/store/textSlice';
|
||||
|
||||
export default function MobileNav({ setNavVisible }) {
|
||||
const dispatch = useDispatch();
|
||||
const { conversationId, convos } = useSelector((state) => state.convo);
|
||||
const { conversationId, convos, title } = useSelector((state) => state.convo);
|
||||
|
||||
const toggleNavVisible = () => {
|
||||
setNavVisible((prev) => {
|
||||
@@ -21,8 +22,6 @@ export default function MobileNav({ setNavVisible }) {
|
||||
dispatch(setSubmission({}));
|
||||
}
|
||||
|
||||
const title = convos?.find(element => element?.conversationId == conversationId)?.title || 'New Chat';
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 z-10 flex items-center border-b border-white/20 bg-gray-800 pl-1 pt-1 text-gray-200 sm:pl-3 md:hidden">
|
||||
<button
|
||||
@@ -63,7 +62,7 @@ export default function MobileNav({ setNavVisible }) {
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<h1 className="flex-1 text-center text-base font-normal">{title}</h1>
|
||||
<h1 className="flex-1 text-center text-base font-normal">{title || 'New Chat'}</h1>
|
||||
<button
|
||||
type="button"
|
||||
className="px-3"
|
||||
|
||||
@@ -6,36 +6,56 @@ import NavLinks from './NavLinks';
|
||||
import useDidMountEffect from '~/hooks/useDidMountEffect';
|
||||
import { swr } from '~/utils/fetchers';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { incrementPage, setConvos } from '~/store/convoSlice';
|
||||
import { increasePage, decreasePage, setPage, setConvos, setPages } from '~/store/convoSlice';
|
||||
|
||||
export default function Nav({ navVisible, setNavVisible }) {
|
||||
const dispatch = useDispatch();
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const { conversationId, convos, pageNumber } = useSelector((state) => state.convo);
|
||||
const { conversationId, convos, pages, pageNumber, refreshConvoHint } = useSelector(
|
||||
(state) => state.convo
|
||||
);
|
||||
const onSuccess = (data) => {
|
||||
dispatch(setConvos(data));
|
||||
const { conversations, pages } = data;
|
||||
|
||||
if (pageNumber > pages) {
|
||||
dispatch(setPage(pages));
|
||||
} else {
|
||||
dispatch(setConvos(conversations));
|
||||
dispatch(setPages(pages));
|
||||
}
|
||||
};
|
||||
|
||||
const { data, isLoading, mutate } = swr(
|
||||
`/api/convos?pageNumber=${pageNumber}`,
|
||||
onSuccess
|
||||
);
|
||||
const { data, isLoading, mutate } = swr(`/api/convos?pageNumber=${pageNumber}`, onSuccess, {
|
||||
revalidateOnMount: false
|
||||
});
|
||||
|
||||
const containerRef = useRef(null);
|
||||
const scrollPositionRef = useRef(null);
|
||||
|
||||
const showMore = async (increment = true) => {
|
||||
const moveToTop = () => {
|
||||
const container = containerRef.current;
|
||||
if (container) {
|
||||
scrollPositionRef.current = container.scrollTop;
|
||||
}
|
||||
|
||||
if (increment) {
|
||||
dispatch(incrementPage());
|
||||
await mutate();
|
||||
}
|
||||
};
|
||||
|
||||
useDidMountEffect(() => mutate(), [conversationId]);
|
||||
const nextPage = async () => {
|
||||
moveToTop();
|
||||
|
||||
dispatch(increasePage());
|
||||
await mutate();
|
||||
};
|
||||
|
||||
const previousPage = async () => {
|
||||
moveToTop();
|
||||
|
||||
dispatch(decreasePage());
|
||||
await mutate();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
mutate();
|
||||
}, [pageNumber, conversationId, refreshConvoHint]);
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current;
|
||||
@@ -49,14 +69,14 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
setNavVisible(false)
|
||||
}, [conversationId, ])
|
||||
setNavVisible(false);
|
||||
}, [conversationId]);
|
||||
|
||||
const toggleNavVisible = () => {
|
||||
setNavVisible((prev) => {
|
||||
return !prev
|
||||
})
|
||||
}
|
||||
return !prev;
|
||||
});
|
||||
};
|
||||
|
||||
const containerClasses =
|
||||
isLoading && pageNumber === 1
|
||||
@@ -65,7 +85,12 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={"dark nav bg-gray-900 md:fixed md:inset-y-0 md:flex md:w-[260px] md:flex-col" + (navVisible?' active':'')}>
|
||||
<div
|
||||
className={
|
||||
'nav dark bg-gray-900 md:fixed md:inset-y-0 md:flex md:w-[260px] md:flex-col' +
|
||||
(navVisible ? ' active' : '')
|
||||
}
|
||||
>
|
||||
<div className="flex h-full min-h-0 flex-col ">
|
||||
<div className="scrollbar-trigger flex h-full w-full flex-1 items-start border-white/20">
|
||||
<nav className="flex h-full flex-1 flex-col space-y-1 p-2">
|
||||
@@ -85,8 +110,11 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||
<Conversations
|
||||
conversations={convos}
|
||||
conversationId={conversationId}
|
||||
showMore={showMore}
|
||||
nextPage={nextPage}
|
||||
previousPage={previousPage}
|
||||
moveToTop={moveToTop}
|
||||
pageNumber={pageNumber}
|
||||
pages={pages}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -97,7 +125,7 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="nav-close-button -ml-0.5 -mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-900 focus:outline-none focus:ring-white hover:text-white text-white"
|
||||
className="nav-close-button -ml-0.5 -mt-0.5 inline-flex h-10 w-10 items-center justify-center rounded-md text-white hover:text-gray-900 hover:text-white focus:outline-none focus:ring-white"
|
||||
onClick={toggleNavVisible}
|
||||
>
|
||||
<span className="sr-only">Open sidebar</span>
|
||||
@@ -128,8 +156,10 @@ export default function Nav({ navVisible, setNavVisible }) {
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className={"nav-mask" + (navVisible?' active':'')} onClick={toggleNavVisible}>
|
||||
</div>
|
||||
<div
|
||||
className={'nav-mask' + (navVisible ? ' active' : '')}
|
||||
onClick={toggleNavVisible}
|
||||
></div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function BingIcon() {
|
||||
export default function BingIcon({ size=25 }) {
|
||||
return (
|
||||
<svg
|
||||
width="25"
|
||||
height="25"
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 56 56"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function GPTIcon({ button = false, menu = false }) {
|
||||
export default function GPTIcon({ button = false, menu = false, size=25 }) {
|
||||
let unit = '41';
|
||||
let height = unit;
|
||||
let width = unit;
|
||||
let height = size;
|
||||
let width = size;
|
||||
let boxSize = '6';
|
||||
if (button) {
|
||||
// unit = '45';
|
||||
@@ -20,7 +20,6 @@ export default function GPTIcon({ button = false, menu = false }) {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
strokeWidth="1.5"
|
||||
className={`h-${boxSize} w-${boxSize}`}
|
||||
>
|
||||
<path
|
||||
d="M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z"
|
||||
|
||||
@@ -1,55 +1,77 @@
|
||||
.nav-mask {
|
||||
position: fixed;
|
||||
z-index: 998;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(86, 88, 105, .75);
|
||||
padding-left: 420px;
|
||||
padding-top: 12px;
|
||||
opacity: 0;
|
||||
transition: all .5s;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
z-index: 998;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(86, 88, 105, 0.75);
|
||||
padding-left: 420px;
|
||||
padding-top: 12px;
|
||||
opacity: 0;
|
||||
transition: all 0.5s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nav {
|
||||
transition: all .5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.nav-close-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.sibling-switch-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
/* .sibling-switch {
|
||||
left: 114px;
|
||||
top: unset;
|
||||
bottom: 4px;
|
||||
visibility: visible;
|
||||
z-index: 2;
|
||||
} */
|
||||
.sibling-switch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.resubmit-edit-button {
|
||||
display: block;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.nav-close-button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 12px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.nav-close-button {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 12px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
left: calc(-100%);;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
max-width: 320px;
|
||||
width: calc(100% - 60px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nav.active {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
.nav {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
left: calc(-100%);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
max-width: 320px;
|
||||
width: calc(100% - 60px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nav-mask.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.nav.active {
|
||||
left: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.nav-mask.active {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,25 +13,36 @@ const initialState = {
|
||||
promptPrefix: null,
|
||||
convosLoading: false,
|
||||
pageNumber: 1,
|
||||
convos: []
|
||||
pages: 1,
|
||||
refreshConvoHint: 0,
|
||||
convos: [],
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
name: 'convo',
|
||||
initialState,
|
||||
reducers: {
|
||||
refreshConversation: (state, action) => {
|
||||
state.refreshConvoHint = state.refreshConvoHint + 1;
|
||||
},
|
||||
setConversation: (state, action) => {
|
||||
return { ...state, ...action.payload };
|
||||
},
|
||||
setError: (state, action) => {
|
||||
state.error = action.payload;
|
||||
},
|
||||
incrementPage: (state) => {
|
||||
increasePage: (state) => {
|
||||
state.pageNumber = state.pageNumber + 1;
|
||||
},
|
||||
decreasePage: (state) => {
|
||||
state.pageNumber = state.pageNumber - 1;
|
||||
},
|
||||
setPage: (state, action) => {
|
||||
state.pageNumber = action.payload;
|
||||
},
|
||||
setNewConvo: (state) => {
|
||||
state.error = false;
|
||||
state.title = 'New Chat';
|
||||
state.title = 'ChatGPT Clone';
|
||||
state.conversationId = null;
|
||||
state.parentMessageId = null;
|
||||
state.jailbreakConversationId = null;
|
||||
@@ -41,16 +52,15 @@ const currentSlice = createSlice({
|
||||
state.chatGptLabel = null;
|
||||
state.promptPrefix = null;
|
||||
state.convosLoading = false;
|
||||
state.pageNumber = 1;
|
||||
},
|
||||
setConvos: (state, action) => {
|
||||
const newConvos = action.payload.filter((convo) => {
|
||||
return !state.convos.some((c) => c.conversationId === convo.conversationId);
|
||||
});
|
||||
state.convos = [...state.convos, ...newConvos].sort(
|
||||
(a, b) => new Date(b.created) - new Date(a.created)
|
||||
state.convos = action.payload.sort(
|
||||
(a, b) => new Date(b.createdAt) - new Date(a.createdAt)
|
||||
);
|
||||
},
|
||||
setPages: (state, action) => {
|
||||
state.pages = action.payload;
|
||||
},
|
||||
removeConvo: (state, action) => {
|
||||
state.convos = state.convos.filter((convo) => convo.conversationId !== action.payload);
|
||||
},
|
||||
@@ -60,7 +70,7 @@ const currentSlice = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
export const { setConversation, setConvos, setNewConvo, setError, incrementPage, removeConvo, removeAll } =
|
||||
export const { refreshConversation, setConversation, setPages, setConvos, setNewConvo, setError, increasePage, decreasePage, setPage, removeConvo, removeAll } =
|
||||
currentSlice.actions;
|
||||
|
||||
export default currentSlice.reducer;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import buildTree from '~/utils/buildTree';
|
||||
|
||||
const initialState = {
|
||||
messages: [],
|
||||
messageTree: []
|
||||
};
|
||||
|
||||
const currentSlice = createSlice({
|
||||
@@ -10,11 +12,12 @@ const currentSlice = createSlice({
|
||||
reducers: {
|
||||
setMessages: (state, action) => {
|
||||
state.messages = action.payload;
|
||||
state.messageTree = buildTree(action.payload);
|
||||
},
|
||||
setEmptyMessage: (state) => {
|
||||
state.messages = [
|
||||
{
|
||||
id: '1',
|
||||
messageId: '1',
|
||||
conversationId: '1',
|
||||
parentMessageId: '1',
|
||||
sender: '',
|
||||
|
||||
@@ -5,27 +5,32 @@ const initialState = {
|
||||
{
|
||||
_id: '0',
|
||||
name: 'ChatGPT',
|
||||
value: 'chatgpt'
|
||||
value: 'chatgpt',
|
||||
model: 'chatgpt'
|
||||
},
|
||||
{
|
||||
_id: '1',
|
||||
name: 'CustomGPT',
|
||||
value: 'chatgptCustom'
|
||||
value: 'chatgptCustom',
|
||||
model: 'chatgptCustom'
|
||||
},
|
||||
{
|
||||
_id: '2',
|
||||
name: 'BingAI',
|
||||
value: 'bingai'
|
||||
value: 'bingai',
|
||||
model: 'bingai'
|
||||
},
|
||||
{
|
||||
_id: '3',
|
||||
name: 'Sydney',
|
||||
value: 'sydney'
|
||||
value: 'sydney',
|
||||
model: 'sydney'
|
||||
},
|
||||
{
|
||||
_id: '4',
|
||||
name: 'ChatGPT',
|
||||
value: 'chatgptBrowser'
|
||||
value: 'chatgptBrowser',
|
||||
model: 'chatgptBrowser'
|
||||
},
|
||||
],
|
||||
modelMap: {},
|
||||
@@ -45,7 +50,8 @@ const currentSlice = createSlice({
|
||||
models.slice(initialState.models.length).forEach((modelItem) => {
|
||||
modelMap[modelItem.value] = {
|
||||
chatGptLabel: modelItem.chatGptLabel,
|
||||
promptPrefix: modelItem.promptPrefix
|
||||
promptPrefix: modelItem.promptPrefix,
|
||||
model: 'chatgptCustom'
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ const initialState = {
|
||||
stopStream: false,
|
||||
disabled: false,
|
||||
model: 'chatgpt',
|
||||
promptPrefix: '',
|
||||
chatGptLabel: '',
|
||||
promptPrefix: null,
|
||||
chatGptLabel: null,
|
||||
customModel: null,
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ const currentSlice = createSlice({
|
||||
state.model = action.payload;
|
||||
},
|
||||
setCustomGpt: (state, action) => {
|
||||
console.log('setCustomGpt', action.payload);
|
||||
state.promptPrefix = action.payload.promptPrefix;
|
||||
state.chatGptLabel = action.payload.chatGptLabel;
|
||||
},
|
||||
|
||||
@@ -1251,7 +1251,6 @@ html {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
|
||||
/* .result-streaming>:not(ol):not(ul):not(pre):last-child:after,
|
||||
.result-streaming>ol:last-child li:last-child:after,
|
||||
.result-streaming>pre:last-child code:after,
|
||||
@@ -1876,3 +1875,6 @@ button.scroll-convo {
|
||||
background-color:hsla(0,0%,100%,.4)
|
||||
}
|
||||
}
|
||||
.hidden-visibility {
|
||||
visibility: hidden;
|
||||
}
|
||||
17
client/src/utils/buildTree.js
Normal file
17
client/src/utils/buildTree.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export default function buildTree(messages) {
|
||||
let messageMap = {};
|
||||
let rootMessages = [];
|
||||
|
||||
// Traverse the messages array and store each element in messageMap.
|
||||
messages.forEach(message => {
|
||||
messageMap[message.messageId] = {...message, children: []};
|
||||
|
||||
const parentMessage = messageMap[message.parentMessageId];
|
||||
if (parentMessage)
|
||||
parentMessage.children.push(messageMap[message.messageId]);
|
||||
else
|
||||
rootMessages.push(messageMap[message.messageId]);
|
||||
});
|
||||
|
||||
return rootMessages;
|
||||
}
|
||||
31
client/src/utils/createPayload.js
Normal file
31
client/src/utils/createPayload.js
Normal file
@@ -0,0 +1,31 @@
|
||||
export default function createPayload({ convo, message }) {
|
||||
const endpoint = `/api/ask`;
|
||||
let payload = { ...message };
|
||||
const { model } = message;
|
||||
|
||||
if (!payload.conversationId)
|
||||
if (convo?.conversationId && convo?.parentMessageId) {
|
||||
payload = {
|
||||
...payload,
|
||||
conversationId: convo.conversationId,
|
||||
parentMessageId: convo.parentMessageId || '00000000-0000-0000-0000-000000000000'
|
||||
};
|
||||
}
|
||||
|
||||
const isBing = model === 'bingai' || model === 'sydney';
|
||||
if (isBing && convo?.conversationId) {
|
||||
payload = {
|
||||
...payload,
|
||||
jailbreakConversationId: convo.jailbreakConversationId,
|
||||
conversationId: convo.conversationId,
|
||||
conversationSignature: convo.conversationSignature,
|
||||
clientId: convo.clientId,
|
||||
invocationId: convo.invocationId
|
||||
};
|
||||
}
|
||||
|
||||
let server = endpoint;
|
||||
server = model === 'bingai' ? server + '/bing' : server;
|
||||
server = model === 'sydney' ? server + '/sydney' : server;
|
||||
return { server, payload };
|
||||
};
|
||||
@@ -9,14 +9,14 @@ const postRequest = async (url, { arg }) => {
|
||||
return await axios.post(url, { arg });
|
||||
};
|
||||
|
||||
export const swr = (path, successCallback) => {
|
||||
const options = {};
|
||||
export const swr = (path, successCallback, options) => {
|
||||
const _options = {...options};
|
||||
|
||||
if (successCallback) {
|
||||
options.onSuccess = successCallback;
|
||||
_options.onSuccess = successCallback;
|
||||
}
|
||||
|
||||
return useSWR(path, fetcher, options);
|
||||
return useSWR(path, fetcher, _options);
|
||||
}
|
||||
|
||||
export default function manualSWR(path, type, successCallback) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { clsx } from 'clsx';
|
||||
import React from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import GPTIcon from '../components/svg/GPTIcon';
|
||||
import BingIcon from '../components/svg/BingIcon';
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
@@ -42,3 +45,56 @@ export const wrapperRegex = {
|
||||
languageMatch: /^```(\w+)/,
|
||||
newLineMatch: /^```(\n+)/
|
||||
};
|
||||
|
||||
export const getIconOfModel = ({ size=30, sender, isCreatedByUser, model, chatGptLabel, error, ...props }) => {
|
||||
const bgColors = {
|
||||
chatgpt: 'rgb(16, 163, 127)',
|
||||
chatgptBrowser: 'rgb(25, 207, 207)',
|
||||
bingai: 'transparent',
|
||||
sydney: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)',
|
||||
chatgptCustom: 'rgb(0, 163, 255)',
|
||||
};
|
||||
|
||||
if (isCreatedByUser)
|
||||
return (
|
||||
<div
|
||||
title='User'
|
||||
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12 }}
|
||||
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
||||
>
|
||||
User
|
||||
</div>
|
||||
)
|
||||
else if (!isCreatedByUser) {
|
||||
// TODO: use model from convo, rather than submit
|
||||
// const { model, chatGptLabel, promptPrefix } = convo;
|
||||
let background = bgColors[model];
|
||||
const isBing = model === 'bingai' || model === 'sydney';
|
||||
|
||||
return (
|
||||
<div
|
||||
title={chatGptLabel || model}
|
||||
style={
|
||||
{ background } || { background: 'radial-gradient(circle at 90% 110%, #F0F0FA, #D0E0F9)' }
|
||||
}
|
||||
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
||||
>
|
||||
{isBing ? <BingIcon size={size} /> : <GPTIcon size={size} />}
|
||||
{error && (
|
||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
||||
!
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else
|
||||
return (
|
||||
<div
|
||||
title='User'
|
||||
style={{ background: 'radial-gradient(circle at 90% 110%, rgb(1 43 128), rgb(17, 139, 161))', color: 'white', fontSize: 12 }}
|
||||
className={`relative flex h-[${size}px] w-[${size}px] items-center justify-center rounded-sm p-1 text-white ` + props?.className}
|
||||
>
|
||||
{chatGptLabel}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
22
client/src/utils/resetConvo.js
Normal file
22
client/src/utils/resetConvo.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export default function resetConvo(messages, sender) {
|
||||
if (messages.length === 0) {
|
||||
return false;
|
||||
}
|
||||
let modelMessages = messages.filter((message) => !message.isCreatedByUser);
|
||||
let lastModel = modelMessages[modelMessages.length - 1].sender;
|
||||
if (lastModel !== sender) {
|
||||
console.log(
|
||||
'Model change! Reseting convo. Original messages: ',
|
||||
messages,
|
||||
'filtered messages: ',
|
||||
modelMessages,
|
||||
'last model: ',
|
||||
lastModel,
|
||||
'sender: ',
|
||||
sender
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
293
package-lock.json
generated
Normal file
293
package-lock.json
generated
Normal file
@@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"sanitize-html": "^2.10.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user