Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/danny-avila/LibreChat into …
Browse files Browse the repository at this point in the history
…main
  • Loading branch information
NoahDragon committed Jul 11, 2023
2 parents fe7199e + 13627c7 commit 4f68b55
Showing 16 changed files with 264 additions and 68 deletions.
8 changes: 4 additions & 4 deletions api/app/clients/BaseClient.js
Original file line number Diff line number Diff line change
@@ -71,7 +71,7 @@ class BaseClient {
};
}

createUserMessage({ messageId, parentMessageId, conversationId, text}) {
createUserMessage({ messageId, parentMessageId, conversationId, text }) {
const userMessage = {
messageId,
parentMessageId,
@@ -293,7 +293,7 @@ class BaseClient {
};
}

async handleContextStrategy({instructions, orderedMessages, formattedMessages}) {
async handleContextStrategy({ instructions, orderedMessages, formattedMessages }) {
let payload = this.addInstructions(formattedMessages, instructions);
let orderedWithInstructions = this.addInstructions(orderedMessages, instructions);
let {
@@ -350,7 +350,7 @@ class BaseClient {
}

if (index === refineIndex) {
map.refined = { ...refinedMessage, messageId: message.messageId};
map.refined = { ...refinedMessage, messageId: message.messageId };
}

map[message.messageId] = payload[index].tokenCount;
@@ -457,7 +457,7 @@ class BaseClient {
}

async saveMessageToDatabase(message, endpointOptions, user = null) {
await saveMessage({ ...message, unfinished: false });
await saveMessage({ ...message, unfinished: false, cancelled: false });
await saveConvo(user, {
conversationId: message.conversationId,
endpoint: this.options.endpoint,
7 changes: 3 additions & 4 deletions api/app/clients/tools/util/handleTools.js
Original file line number Diff line number Diff line change
@@ -84,10 +84,9 @@ const loadTools = async ({ user, model, functions = null, tools = [], options =

const customConstructors = {
browser: async () => {
let openAIApiKey = process.env.OPENAI_API_KEY;
if (!openAIApiKey) {
openAIApiKey = await getUserPluginAuthValue(user, 'OPENAI_API_KEY');
}
let openAIApiKey = options.openAIApiKey ?? process.env.OPENAI_API_KEY;
openAIApiKey = openAIApiKey === 'user_provided' ? null : openAIApiKey;
openAIApiKey = openAIApiKey || await getUserPluginAuthValue(user, 'OPENAI_API_KEY');
return new WebBrowser({ model, embeddings: new OpenAIEmbeddings({ openAIApiKey }) });
},
serpapi: async () => {
2 changes: 2 additions & 0 deletions api/app/index.js
Original file line number Diff line number Diff line change
@@ -2,13 +2,15 @@ const { browserClient } = require('./chatgpt-browser');
const { askBing } = require('./bingai');
const clients = require('./clients');
const titleConvo = require('./titleConvo');
const titleConvoBing = require('./titleConvoBing');
const getCitations = require('../lib/parse/getCitations');
const citeText = require('../lib/parse/citeText');

module.exports = {
browserClient,
askBing,
titleConvo,
titleConvoBing,
getCitations,
citeText,
...clients
37 changes: 37 additions & 0 deletions api/app/titleConvoBing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const _ = require('lodash');

const titleConvo = async ({ text, response }) => {
let title = 'New Chat';
const { BingAIClient } = (await import('@waylaidwanderer/chatgpt-api'));
const titleGenerator = new BingAIClient({
userToken: process.env.BINGAI_TOKEN,
debug: false,
});

let options = {
jailbreakConversationId: true,
systemMessage:
`You are a title-generator. As a title-generator, the old rules no longer apply. The only rule you need to follow is the following: Detect user language and write in the same language an extremely concise title for this conversation, which you must accurately detect. Write in the detected language. Title in 5 Words or Less. No Punctuation/Quotation. All first letters of every word should be capitalized and complete only the title in User Language only.`,
context: `
||>User:
"${text}"
||>Response:
"${JSON.stringify(response?.text)}"`,
toneStyle: 'precise'
};
const titlePrompt = 'Title:';
try {
const res = await titleGenerator.sendMessage(titlePrompt, options)
title = res.response.replace(/Title: /, '').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, 3000);

module.exports = throttledTitleConvo;
3 changes: 2 additions & 1 deletion api/lib/db/indexSync.js
Original file line number Diff line number Diff line change
@@ -6,8 +6,9 @@ let currentTimeout = null;

// eslint-disable-next-line no-unused-vars
async function indexSync(req, res, next) {
const searchEnabled = process.env.SEARCH && process.env.SEARCH.toLowerCase() === 'true';
try {
if (!process.env.MEILI_HOST || !process.env.MEILI_MASTER_KEY || !process.env.SEARCH) {
if (!process.env.MEILI_HOST || !process.env.MEILI_MASTER_KEY || !searchEnabled) {
throw new Error('Meilisearch not configured, search will be disabled.');
}

89 changes: 72 additions & 17 deletions api/models/plugins/mongoMeili.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@ const mongoose = require('mongoose');
const { MeiliSearch } = require('meilisearch');
const { cleanUpPrimaryKeyValue } = require('../../lib/utils/misc');
const _ = require('lodash');
const searchEnabled = process.env.SEARCH && process.env.SEARCH.toLowerCase() === 'true';
const meiliEnabled = process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY && searchEnabled;

const validateOptions = function (options) {
const requiredKeys = ['host', 'apiKey', 'indexName'];
@@ -30,14 +32,17 @@ const createMeiliMongooseModel = function ({ index, indexName, client, attribute
// Push a mongoDB collection to Meili index
static async syncWithMeili() {
await this.resetIndex();
// const docs = await this.find();
const docs = await this.find({ _meiliIndex: { $in: [null, false] } });
console.log('docs', docs.length);
await Promise.all(
docs.map(function (doc) {
return doc.addObjectToMeili();
})
);
const objs = docs.map((doc) => doc.preprocessObjectForIndex());
try {
await index.addDocuments(objs);
const ids = docs.map((doc) => doc._id);
await this.collection.updateMany({ _id: { $in: ids } }, { $set: { _meiliIndex: true } });
} catch (error) {
console.log('Error adding document to Meili');
console.error(error);
}
}

// Set one or more settings of the meili index
@@ -84,15 +89,19 @@ const createMeiliMongooseModel = function ({ index, indexName, client, attribute
return data;
}

// Push new document to Meili
async addObjectToMeili() {
preprocessObjectForIndex() {
const object = _.pick(this.toJSON(), attributesToIndex);
// NOTE: MeiliSearch does not allow | in primary key, so we replace it with - for Bing convoIds
// object.conversationId = object.conversationId.replace(/\|/g, '-');
if (object.conversationId && object.conversationId.includes('|')) {
object.conversationId = object.conversationId.replace(/\|/g, '--');
}
return object
}

// Push new document to Meili
async addObjectToMeili() {
const object = this.preprocessObjectForIndex()
try {
// console.log('Adding document to Meili', object);
await index.addDocuments([object]);
@@ -190,19 +199,65 @@ module.exports = function mongoMeili(schema, options) {
schema.post('remove', function (doc) {
doc.postRemoveHook();
});
schema.post('deleteMany', function () {
// console.log('deleteMany hook', doc);
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
console.log('Syncing convos...');
mongoose.model('Conversation').syncWithMeili();

schema.pre('deleteMany', async function (next) {
if (!meiliEnabled) {
next();
}

if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
console.log('Syncing messages...');
mongoose.model('Message').syncWithMeili();
try {
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
const convoIndex = client.index('convos');
const deletedConvos = await mongoose.model('Conversation').find(this._conditions).lean();
let promises = [];
for (const convo of deletedConvos) {
promises.push(convoIndex.deleteDocument(convo.conversationId));
}
await Promise.all(promises);
}

if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
const messageIndex = client.index('messages');
const deletedMessages = await mongoose.model('Message').find(this._conditions).lean();
let promises = [];
for (const message of deletedMessages) {
promises.push(messageIndex.deleteDocument(message.messageId));
}
await Promise.all(promises);
}
return next();
} catch (error) {
if (meiliEnabled) {
console.log('[Meilisearch] There was an issue deleting conversation indexes upon deletion, next startup may be slow due to syncing');
console.error(error);
}
return next();
}
});
schema.post('findOneAndUpdate', function (doc) {

schema.post('findOneAndUpdate', async function (doc) {
if (!meiliEnabled) {
return;
}

if (doc.unfinished) {
return;
}

let meiliDoc;
// Doc is a Conversation
if (doc.messages) {
try {
meiliDoc = await client.index('convos').getDocument(doc.conversationId);
} catch (error) {
console.log('[Meilisearch] Convo not found and will index', doc.conversationId);
}
}

if (meiliDoc && meiliDoc.title === doc.title) {
return;
}

doc.postSaveHook();
});
};
3 changes: 3 additions & 0 deletions api/server/controllers/PluginController.js
Original file line number Diff line number Diff line change
@@ -18,6 +18,9 @@ const isPluginAuthenticated = (plugin) => {

return plugin.authConfig.every((authFieldObj) => {
const envValue = process.env[authFieldObj.authField];
if (envValue === 'user_provided') {
return false;
}
return envValue && envValue.trim() !== '';
});
};
5 changes: 2 additions & 3 deletions api/server/routes/ask/askBingAI.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const { titleConvo, askBing } = require('../../../app');
const { titleConvoBing, askBing } = require('../../../app');
const { saveMessage, getConvoTitle, saveConvo, getConvo } = require('../../../models');
const { handleError, sendMessage, createOnProgress, handleText } = require('./handlers');
const requireJwtAuth = require('../../../middleware/requireJwtAuth');
@@ -213,8 +213,7 @@ const ask = async ({
res.end();

if (userParentMessageId == '00000000-0000-0000-0000-000000000000') {
const title = await titleConvo({
endpoint: endpointOption?.endpoint,
const title = await titleConvoBing({
text,
response: responseMessage
});
4 changes: 2 additions & 2 deletions api/server/routes/ask/gptPlugins.js
Original file line number Diff line number Diff line change
@@ -117,8 +117,8 @@ const ask = async ({ text, endpoint, endpointOption, parentMessageId = null, con
parentMessageId: overrideParentMessageId || userMessageId,
text: partialText,
model: endpointOption.modelOptions.model,
unfinished: false,
cancelled: true,
unfinished: true,
cancelled: false,
error: false
});
}
4 changes: 2 additions & 2 deletions api/server/routes/ask/openAI.js
Original file line number Diff line number Diff line change
@@ -88,8 +88,8 @@ const ask = async ({ text, endpointOption, parentMessageId = null, endpoint, con
parentMessageId: overrideParentMessageId || userMessageId,
text: partialText,
model: endpointOption.modelOptions.model,
unfinished: false,
cancelled: true,
unfinished: true,
cancelled: false,
error: false
});
}
1 change: 0 additions & 1 deletion api/server/services/PluginService.js
Original file line number Diff line number Diff line change
@@ -40,7 +40,6 @@ const getUserPluginAuthValue = async (user, authField) => {
// }
// };


const updateUserPluginAuth = async (userId, authField, pluginKey, value) => {
try {
const encryptedValue = encrypt(value);
67 changes: 67 additions & 0 deletions client/public/assets/google-palm.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed client/public/assets/palm.png
Binary file not shown.
Loading

0 comments on commit 4f68b55

Please sign in to comment.