mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00

* localization + api-endpoint * docs: added firebase documentation * chore: icons * chore: SettingsTabs * feat: account pannel; fix: gear icons * docs: position update * feat: firebase * feat: plugin support * route * fixed bugs with firebase and moved a lot of files * chore(DALLE3): using UUID v4 * feat: support for social strategies; moved '/images' path * fix: data ignored * gitignore update * docs: update firebase guide * refactor: Firebase - use singleton pattern for firebase initialization, initially on server start - reorganize imports, move firebase specific files to own service under Files - rename modules to remove 'avatar' redundancy - fix imports based on changes * ci(DALLE/DALLE3): fix tests to use logger and new expected outputs, add firebase tests * refactor(loadToolWithAuth): pass userId to tool as field * feat(images/parse): feat: Add URL Image Basename Extraction Implement a new module to extract the basename of an image from a given URL. This addition includes the function, which parses the URL and retrieves the basename using the Node.js 'url' and 'path' modules. The function is documented with JSDoc comments for better maintainability and understanding. This feature enhances the application's ability to handle and process image URLs efficiently. * refactor(addImages): function to use a more specific regular expression for observedImagePath based on the generated image markdown standard across the app * refactor(DALLE/DALLE3): utilize `getImageBasename` and `this.userId`; fix: pass correct image path to firebase url helper * fix(addImages): make more general to match any image markdown descriptor * fix(parse/getImageBasename): test result of this function for an actual image basename * ci(DALLE3): mock getImageBasename * refactor(AuthContext): use Recoil atom state for user * feat: useUploadAvatarMutation, react-query hook for avatar upload * fix(Toast): stack z-order of Toast over all components (1000) * refactor(showToast): add optional status field to avoid importing NotificationSeverity on each use of the function * refactor(routes/avatar): remove unnecessary get route, get userId from req.user.id, require auth on POST request * chore(uploadAvatar): TODO: remove direct use of Model, `User` * fix(client): fix Spinner imports * refactor(Avatar): use react-query hook, Toast, remove unnecessary states, add optimistic UI to upload * fix(avatar/localStrategy): correctly save local profile picture and cache bust for immediate rendering; fix: firebase init info message (only show once) * fix: use `includes` instead of `endsWith` for checking manual query of avatar image path in case more queries are appended (as is done in avatar/localStrategy) --------- Co-authored-by: Danny Avila <messagedaniel@protonmail.com>
253 lines
6.5 KiB
JavaScript
253 lines
6.5 KiB
JavaScript
const { ZapierToolKit } = require('langchain/agents');
|
|
const { Calculator } = require('langchain/tools/calculator');
|
|
const { WebBrowser } = require('langchain/tools/webbrowser');
|
|
const { SerpAPI, ZapierNLAWrapper } = require('langchain/tools');
|
|
const { OpenAIEmbeddings } = require('langchain/embeddings/openai');
|
|
const { getUserPluginAuthValue } = require('~/server/services/PluginService');
|
|
const {
|
|
availableTools,
|
|
GoogleSearchAPI,
|
|
WolframAlphaAPI,
|
|
StructuredWolfram,
|
|
OpenAICreateImage,
|
|
StableDiffusionAPI,
|
|
DALLE3,
|
|
StructuredSD,
|
|
AzureAISearch,
|
|
StructuredACS,
|
|
E2BTools,
|
|
CodeSherpa,
|
|
CodeSherpaTools,
|
|
CodeBrew,
|
|
} = require('../');
|
|
const { loadToolSuite } = require('./loadToolSuite');
|
|
const { loadSpecs } = require('./loadSpecs');
|
|
const { logger } = require('~/config');
|
|
|
|
const getOpenAIKey = async (options, user) => {
|
|
let openAIApiKey = options.openAIApiKey ?? process.env.OPENAI_API_KEY;
|
|
openAIApiKey = openAIApiKey === 'user_provided' ? null : openAIApiKey;
|
|
return openAIApiKey || (await getUserPluginAuthValue(user, 'OPENAI_API_KEY'));
|
|
};
|
|
|
|
const validateTools = async (user, tools = []) => {
|
|
try {
|
|
const validToolsSet = new Set(tools);
|
|
const availableToolsToValidate = availableTools.filter((tool) =>
|
|
validToolsSet.has(tool.pluginKey),
|
|
);
|
|
|
|
const validateCredentials = async (authField, toolName) => {
|
|
const adminAuth = process.env[authField];
|
|
if (adminAuth && adminAuth.length > 0) {
|
|
return;
|
|
}
|
|
|
|
const userAuth = await getUserPluginAuthValue(user, authField);
|
|
if (userAuth && userAuth.length > 0) {
|
|
return;
|
|
}
|
|
validToolsSet.delete(toolName);
|
|
};
|
|
|
|
for (const tool of availableToolsToValidate) {
|
|
if (!tool.authConfig || tool.authConfig.length === 0) {
|
|
continue;
|
|
}
|
|
|
|
for (const auth of tool.authConfig) {
|
|
await validateCredentials(auth.authField, tool.pluginKey);
|
|
}
|
|
}
|
|
|
|
return Array.from(validToolsSet.values());
|
|
} catch (err) {
|
|
logger.error('[validateTools] There was a problem validating tools', err);
|
|
throw new Error(err);
|
|
}
|
|
};
|
|
|
|
const loadToolWithAuth = async (userId, authFields, ToolConstructor, options = {}) => {
|
|
return async function () {
|
|
let authValues = {};
|
|
|
|
for (const authField of authFields) {
|
|
let authValue = process.env[authField];
|
|
if (!authValue) {
|
|
authValue = await getUserPluginAuthValue(userId, authField);
|
|
}
|
|
authValues[authField] = authValue;
|
|
}
|
|
|
|
return new ToolConstructor({ ...options, ...authValues, userId });
|
|
};
|
|
};
|
|
|
|
const loadTools = async ({
|
|
user,
|
|
model,
|
|
functions = null,
|
|
returnMap = false,
|
|
tools = [],
|
|
options = {},
|
|
}) => {
|
|
const toolConstructors = {
|
|
calculator: Calculator,
|
|
google: GoogleSearchAPI,
|
|
wolfram: functions ? StructuredWolfram : WolframAlphaAPI,
|
|
'dall-e': OpenAICreateImage,
|
|
'stable-diffusion': functions ? StructuredSD : StableDiffusionAPI,
|
|
'azure-ai-search': functions ? StructuredACS : AzureAISearch,
|
|
CodeBrew: CodeBrew,
|
|
};
|
|
|
|
const openAIApiKey = await getOpenAIKey(options, user);
|
|
|
|
const customConstructors = {
|
|
e2b_code_interpreter: async () => {
|
|
if (!functions) {
|
|
return null;
|
|
}
|
|
|
|
return await loadToolSuite({
|
|
pluginKey: 'e2b_code_interpreter',
|
|
tools: E2BTools,
|
|
user,
|
|
options: {
|
|
model,
|
|
openAIApiKey,
|
|
...options,
|
|
},
|
|
});
|
|
},
|
|
codesherpa_tools: async () => {
|
|
if (!functions) {
|
|
return null;
|
|
}
|
|
|
|
return await loadToolSuite({
|
|
pluginKey: 'codesherpa_tools',
|
|
tools: CodeSherpaTools,
|
|
user,
|
|
options,
|
|
});
|
|
},
|
|
'web-browser': async () => {
|
|
// let openAIApiKey = options.openAIApiKey ?? process.env.OPENAI_API_KEY;
|
|
// openAIApiKey = openAIApiKey === 'user_provided' ? null : openAIApiKey;
|
|
// openAIApiKey = openAIApiKey || (await getUserPluginAuthValue(user, 'OPENAI_API_KEY'));
|
|
const browser = new WebBrowser({ model, embeddings: new OpenAIEmbeddings({ openAIApiKey }) });
|
|
browser.description_for_model = browser.description;
|
|
return browser;
|
|
},
|
|
serpapi: async () => {
|
|
let apiKey = process.env.SERPAPI_API_KEY;
|
|
if (!apiKey) {
|
|
apiKey = await getUserPluginAuthValue(user, 'SERPAPI_API_KEY');
|
|
}
|
|
return new SerpAPI(apiKey, {
|
|
location: 'Austin,Texas,United States',
|
|
hl: 'en',
|
|
gl: 'us',
|
|
});
|
|
},
|
|
zapier: async () => {
|
|
let apiKey = process.env.ZAPIER_NLA_API_KEY;
|
|
if (!apiKey) {
|
|
apiKey = await getUserPluginAuthValue(user, 'ZAPIER_NLA_API_KEY');
|
|
}
|
|
const zapier = new ZapierNLAWrapper({ apiKey });
|
|
return ZapierToolKit.fromZapierNLAWrapper(zapier);
|
|
},
|
|
};
|
|
|
|
const requestedTools = {};
|
|
|
|
if (functions) {
|
|
toolConstructors.dalle = DALLE3;
|
|
toolConstructors.codesherpa = CodeSherpa;
|
|
}
|
|
|
|
const toolOptions = {
|
|
serpapi: { location: 'Austin,Texas,United States', hl: 'en', gl: 'us' },
|
|
};
|
|
|
|
const toolAuthFields = {};
|
|
|
|
availableTools.forEach((tool) => {
|
|
if (customConstructors[tool.pluginKey]) {
|
|
return;
|
|
}
|
|
|
|
toolAuthFields[tool.pluginKey] = tool.authConfig.map((auth) => auth.authField);
|
|
});
|
|
|
|
const remainingTools = [];
|
|
|
|
for (const tool of tools) {
|
|
if (customConstructors[tool]) {
|
|
requestedTools[tool] = customConstructors[tool];
|
|
continue;
|
|
}
|
|
|
|
if (toolConstructors[tool]) {
|
|
const options = toolOptions[tool] || {};
|
|
const toolInstance = await loadToolWithAuth(
|
|
user,
|
|
toolAuthFields[tool],
|
|
toolConstructors[tool],
|
|
options,
|
|
);
|
|
requestedTools[tool] = toolInstance;
|
|
continue;
|
|
}
|
|
|
|
if (functions) {
|
|
remainingTools.push(tool);
|
|
}
|
|
}
|
|
|
|
let specs = null;
|
|
if (functions && remainingTools.length > 0) {
|
|
specs = await loadSpecs({
|
|
llm: model,
|
|
user,
|
|
message: options.message,
|
|
memory: options.memory,
|
|
signal: options.signal,
|
|
tools: remainingTools,
|
|
map: true,
|
|
verbose: false,
|
|
});
|
|
}
|
|
|
|
for (const tool of remainingTools) {
|
|
if (specs && specs[tool]) {
|
|
requestedTools[tool] = specs[tool];
|
|
}
|
|
}
|
|
|
|
if (returnMap) {
|
|
return requestedTools;
|
|
}
|
|
|
|
// load tools
|
|
let result = [];
|
|
for (const tool of tools) {
|
|
const validTool = requestedTools[tool];
|
|
const plugin = await validTool();
|
|
|
|
if (Array.isArray(plugin)) {
|
|
result = [...result, ...plugin];
|
|
} else if (plugin) {
|
|
result.push(plugin);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
module.exports = {
|
|
validateTools,
|
|
loadTools,
|
|
};
|