mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-08-19 03:40:09 +02:00
* Implement retrieval and use of fine-tune models Cleanup LLM selection code resolves #311 * Cleanup from PR bot
652 lines
18 KiB
JavaScript
652 lines
18 KiB
JavaScript
process.env.NODE_ENV === "development"
|
|
? require("dotenv").config({ path: `.env.${process.env.NODE_ENV}` })
|
|
: require("dotenv").config();
|
|
const { viewLocalFiles } = require("../utils/files");
|
|
const { exportData, unpackAndOverwriteImport } = require("../utils/files/data");
|
|
const {
|
|
checkPythonAppAlive,
|
|
acceptedFileTypes,
|
|
} = require("../utils/files/documentProcessor");
|
|
const { purgeDocument } = require("../utils/files/purgeDocument");
|
|
const { getVectorDbClass, getLLMProvider } = require("../utils/helpers");
|
|
const { updateENV, dumpENV } = require("../utils/helpers/updateENV");
|
|
const {
|
|
reqBody,
|
|
makeJWT,
|
|
userFromSession,
|
|
multiUserMode,
|
|
} = require("../utils/http");
|
|
const { setupDataImports, setupLogoUploads } = require("../utils/files/multer");
|
|
const { v4 } = require("uuid");
|
|
const { SystemSettings } = require("../models/systemSettings");
|
|
const { User } = require("../models/user");
|
|
const { validatedRequest } = require("../utils/middleware/validatedRequest");
|
|
const { handleImports } = setupDataImports();
|
|
const { handleLogoUploads } = setupLogoUploads();
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const {
|
|
getDefaultFilename,
|
|
determineLogoFilepath,
|
|
fetchLogo,
|
|
validFilename,
|
|
renameLogoFile,
|
|
removeCustomLogo,
|
|
LOGO_FILENAME,
|
|
} = require("../utils/files/logo");
|
|
const { Telemetry } = require("../models/telemetry");
|
|
const { WelcomeMessages } = require("../models/welcomeMessages");
|
|
const { ApiKey } = require("../models/apiKeys");
|
|
const { getCustomModels } = require("../utils/helpers/customModels");
|
|
|
|
function systemEndpoints(app) {
|
|
if (!app) return;
|
|
|
|
app.get("/ping", (_, response) => {
|
|
response.status(200).json({ online: true });
|
|
});
|
|
|
|
app.get("/migrate", async (_, response) => {
|
|
const execSync = require("child_process").execSync;
|
|
execSync("npx prisma migrate deploy --schema=./prisma/schema.prisma", {
|
|
stdio: "inherit",
|
|
});
|
|
response.sendStatus(200);
|
|
});
|
|
|
|
app.get("/env-dump", async (_, response) => {
|
|
if (process.env.NODE_ENV !== "production")
|
|
return response.sendStatus(200).end();
|
|
await dumpENV();
|
|
response.sendStatus(200).end();
|
|
});
|
|
|
|
app.get("/setup-complete", async (_, response) => {
|
|
try {
|
|
const results = await SystemSettings.currentSettings();
|
|
response.status(200).json({ results });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/check-token",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (multiUserMode(response)) {
|
|
const user = await userFromSession(request, response);
|
|
if (!user || user.suspended) {
|
|
response.sendStatus(403).end();
|
|
return;
|
|
}
|
|
|
|
response.sendStatus(200).end();
|
|
return;
|
|
}
|
|
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post("/request-token", async (request, response) => {
|
|
try {
|
|
if (await SystemSettings.isMultiUserMode()) {
|
|
const { username, password } = reqBody(request);
|
|
const existingUser = await User.get({ username });
|
|
|
|
if (!existingUser) {
|
|
response.status(200).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message: "[001] Invalid login credentials.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const bcrypt = require("bcrypt");
|
|
if (!bcrypt.compareSync(password, existingUser.password)) {
|
|
response.status(200).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message: "[002] Invalid login credentials.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (existingUser.suspended) {
|
|
response.status(200).json({
|
|
user: null,
|
|
valid: false,
|
|
token: null,
|
|
message: "[004] Account suspended by admin.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
response.status(200).json({
|
|
valid: true,
|
|
user: existingUser,
|
|
token: makeJWT(
|
|
{ id: existingUser.id, username: existingUser.username },
|
|
"30d"
|
|
),
|
|
message: null,
|
|
});
|
|
return;
|
|
} else {
|
|
const { password } = reqBody(request);
|
|
if (password !== process.env.AUTH_TOKEN) {
|
|
response.status(401).json({
|
|
valid: false,
|
|
token: null,
|
|
message: "[003] Invalid password provided",
|
|
});
|
|
return;
|
|
}
|
|
|
|
response.status(200).json({
|
|
valid: true,
|
|
token: makeJWT({ p: password }, "30d"),
|
|
message: null,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get("/system/system-vectors", [validatedRequest], async (_, response) => {
|
|
try {
|
|
const VectorDb = getVectorDbClass();
|
|
const vectorCount = await VectorDb.totalVectors();
|
|
response.status(200).json({ vectorCount });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.delete(
|
|
"/system/remove-document",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
const { name, meta } = reqBody(request);
|
|
await purgeDocument(name, meta);
|
|
response.sendStatus(200).end();
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/local-files", [validatedRequest], async (_, response) => {
|
|
try {
|
|
const localFiles = await viewLocalFiles();
|
|
response.status(200).json({ localFiles });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/document-processing-status",
|
|
[validatedRequest],
|
|
async (_, response) => {
|
|
try {
|
|
const online = await checkPythonAppAlive();
|
|
response.sendStatus(online ? 200 : 503);
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/accepted-document-types",
|
|
[validatedRequest],
|
|
async (_, response) => {
|
|
try {
|
|
const types = await acceptedFileTypes();
|
|
if (!types) {
|
|
response.sendStatus(404).end();
|
|
return;
|
|
}
|
|
|
|
response.status(200).json({ types });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/update-env",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
const body = reqBody(request);
|
|
|
|
// Only admins can update the ENV settings.
|
|
if (multiUserMode(response)) {
|
|
const user = await userFromSession(request, response);
|
|
if (!user || user?.role !== "admin") {
|
|
response.sendStatus(401).end();
|
|
return;
|
|
}
|
|
}
|
|
|
|
const { newValues, error } = updateENV(body);
|
|
if (process.env.NODE_ENV === "production") await dumpENV();
|
|
response.status(200).json({ newValues, error });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/update-password",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
// Cannot update password in multi - user mode.
|
|
if (multiUserMode(response)) {
|
|
response.sendStatus(401).end();
|
|
return;
|
|
}
|
|
|
|
const { usePassword, newPassword } = reqBody(request);
|
|
const { error } = updateENV(
|
|
{
|
|
AuthToken: usePassword ? newPassword : "",
|
|
JWTSecret: usePassword ? v4() : "",
|
|
},
|
|
true
|
|
);
|
|
if (process.env.NODE_ENV === "production") await dumpENV();
|
|
response.status(200).json({ success: !error, error });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.post(
|
|
"/system/enable-multi-user",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
const { username, password } = reqBody(request);
|
|
const multiUserModeEnabled = await SystemSettings.isMultiUserMode();
|
|
if (multiUserModeEnabled) {
|
|
response.status(200).json({
|
|
success: false,
|
|
error: "Multi-user mode is already enabled.",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const { user, error } = await User.create({
|
|
username,
|
|
password,
|
|
role: "admin",
|
|
});
|
|
await SystemSettings.updateSettings({
|
|
multi_user_mode: true,
|
|
users_can_delete_workspaces: false,
|
|
limit_user_messages: false,
|
|
message_limit: 25,
|
|
});
|
|
|
|
updateENV(
|
|
{
|
|
AuthToken: "",
|
|
JWTSecret: process.env.JWT_SECRET || v4(),
|
|
},
|
|
true
|
|
);
|
|
if (process.env.NODE_ENV === "production") await dumpENV();
|
|
await Telemetry.sendTelemetry("enabled_multi_user_mode");
|
|
response.status(200).json({ success: !!user, error });
|
|
} catch (e) {
|
|
await User.delete({});
|
|
await SystemSettings.updateSettings({
|
|
multi_user_mode: false,
|
|
});
|
|
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/multi-user-mode", async (request, response) => {
|
|
try {
|
|
const multiUserMode = await SystemSettings.isMultiUserMode();
|
|
response.status(200).json({ multiUserMode });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get("/system/data-export", [validatedRequest], async (_, response) => {
|
|
try {
|
|
const { filename, error } = await exportData();
|
|
response.status(200).json({ filename, error });
|
|
} catch (e) {
|
|
console.log(e.message, e);
|
|
response.sendStatus(500).end();
|
|
}
|
|
});
|
|
|
|
app.get("/system/data-exports/:filename", (request, response) => {
|
|
const exportLocation = __dirname + "/../storage/exports/";
|
|
const sanitized = path
|
|
.normalize(request.params.filename)
|
|
.replace(/^(\.\.(\/|\\|$))+/, "");
|
|
const finalDestination = path.join(exportLocation, sanitized);
|
|
|
|
if (!fs.existsSync(finalDestination)) {
|
|
response.status(404).json({
|
|
error: 404,
|
|
msg: `File ${request.params.filename} does not exist in exports.`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
response.download(finalDestination, request.params.filename, (err) => {
|
|
if (err) {
|
|
response.send({
|
|
error: err,
|
|
msg: "Problem downloading the file",
|
|
});
|
|
}
|
|
// delete on download because endpoint is not authenticated.
|
|
fs.rmSync(finalDestination);
|
|
});
|
|
});
|
|
|
|
app.post(
|
|
"/system/data-import",
|
|
handleImports.single("file"),
|
|
async function (request, response) {
|
|
const { originalname } = request.file;
|
|
const { success, error } = await unpackAndOverwriteImport(originalname);
|
|
response.status(200).json({ success, error });
|
|
}
|
|
);
|
|
|
|
app.get("/system/logo", async function (request, response) {
|
|
try {
|
|
const defaultFilename = getDefaultFilename();
|
|
const logoPath = await determineLogoFilepath(defaultFilename);
|
|
const { buffer, size, mime } = fetchLogo(logoPath);
|
|
response.writeHead(200, {
|
|
"Content-Type": mime || "image/png",
|
|
"Content-Disposition": `attachment; filename=${path.basename(
|
|
logoPath
|
|
)}`,
|
|
"Content-Length": size,
|
|
});
|
|
response.end(Buffer.from(buffer, "base64"));
|
|
return;
|
|
} catch (error) {
|
|
console.error("Error processing the logo request:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.post(
|
|
"/system/upload-logo",
|
|
[validatedRequest],
|
|
handleLogoUploads.single("logo"),
|
|
async (request, response) => {
|
|
if (!request.file || !request.file.originalname) {
|
|
return response.status(400).json({ message: "No logo file provided." });
|
|
}
|
|
|
|
if (!validFilename(request.file.originalname)) {
|
|
return response.status(400).json({
|
|
message: "Invalid file name. Please choose a different file.",
|
|
});
|
|
}
|
|
|
|
try {
|
|
if (
|
|
response.locals.multiUserMode &&
|
|
response.locals.user?.role !== "admin"
|
|
) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const newFilename = await renameLogoFile(request.file.originalname);
|
|
const existingLogoFilename = await SystemSettings.currentLogoFilename();
|
|
await removeCustomLogo(existingLogoFilename);
|
|
|
|
const { success, error } = await SystemSettings.updateSettings({
|
|
logo_filename: newFilename,
|
|
});
|
|
|
|
return response.status(success ? 200 : 500).json({
|
|
message: success
|
|
? "Logo uploaded successfully."
|
|
: error || "Failed to update with new logo.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the logo upload:", error);
|
|
response.status(500).json({ message: "Error uploading the logo." });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/is-default-logo", async (request, response) => {
|
|
try {
|
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
|
const isDefaultLogo = currentLogoFilename === LOGO_FILENAME;
|
|
response.status(200).json({ isDefaultLogo });
|
|
} catch (error) {
|
|
console.error("Error processing the logo request:", error);
|
|
response.status(500).json({ message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.get(
|
|
"/system/remove-logo",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (
|
|
response.locals.multiUserMode &&
|
|
response.locals.user?.role !== "admin"
|
|
) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const currentLogoFilename = await SystemSettings.currentLogoFilename();
|
|
await removeCustomLogo(currentLogoFilename);
|
|
const { success, error } = await SystemSettings.updateSettings({
|
|
logo_filename: LOGO_FILENAME,
|
|
});
|
|
|
|
return response.status(success ? 200 : 500).json({
|
|
message: success
|
|
? "Logo removed successfully."
|
|
: error || "Failed to update with new logo.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the logo removal:", error);
|
|
response.status(500).json({ message: "Error removing the logo." });
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get(
|
|
"/system/can-delete-workspaces",
|
|
[validatedRequest],
|
|
async function (request, response) {
|
|
try {
|
|
if (!response.locals.multiUserMode) {
|
|
return response.status(200).json({ canDelete: true });
|
|
}
|
|
|
|
if (response.locals.user?.role === "admin") {
|
|
return response.status(200).json({ canDelete: true });
|
|
}
|
|
|
|
const canDelete = await SystemSettings.canDeleteWorkspaces();
|
|
response.status(200).json({ canDelete });
|
|
} catch (error) {
|
|
console.error("Error fetching can delete workspaces:", error);
|
|
response.status(500).json({
|
|
success: false,
|
|
message: "Internal server error",
|
|
canDelete: false,
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/welcome-messages", async function (request, response) {
|
|
try {
|
|
const welcomeMessages = await WelcomeMessages.getMessages();
|
|
response.status(200).json({ success: true, welcomeMessages });
|
|
} catch (error) {
|
|
console.error("Error fetching welcome messages:", error);
|
|
response
|
|
.status(500)
|
|
.json({ success: false, message: "Internal server error" });
|
|
}
|
|
});
|
|
|
|
app.post(
|
|
"/system/set-welcome-messages",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
if (
|
|
response.locals.multiUserMode &&
|
|
response.locals.user?.role !== "admin"
|
|
) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const { messages = [] } = reqBody(request);
|
|
if (!Array.isArray(messages)) {
|
|
return response.status(400).json({
|
|
success: false,
|
|
message: "Invalid message format. Expected an array of messages.",
|
|
});
|
|
}
|
|
|
|
await WelcomeMessages.saveAll(messages);
|
|
return response.status(200).json({
|
|
success: true,
|
|
message: "Welcome messages saved successfully.",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error processing the welcome messages:", error);
|
|
response.status(500).json({
|
|
success: true,
|
|
message: "Error saving the welcome messages.",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.get("/system/api-keys", [validatedRequest], async (_, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const apiKeys = await ApiKey.where({});
|
|
return response.status(200).json({
|
|
apiKeys,
|
|
error: null,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).json({
|
|
apiKey: null,
|
|
error: "Could not find an API Key.",
|
|
});
|
|
}
|
|
});
|
|
|
|
app.post(
|
|
"/system/generate-api-key",
|
|
[validatedRequest],
|
|
async (_, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
const { apiKey, error } = await ApiKey.create();
|
|
return response.status(200).json({
|
|
apiKey,
|
|
error,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).json({
|
|
apiKey: null,
|
|
error: "Error generating api key.",
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
app.delete("/system/api-key", [validatedRequest], async (_, response) => {
|
|
try {
|
|
if (response.locals.multiUserMode) {
|
|
return response.sendStatus(401).end();
|
|
}
|
|
|
|
await ApiKey.delete();
|
|
return response.status(200).end();
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).end();
|
|
}
|
|
});
|
|
|
|
app.post(
|
|
"/system/custom-models",
|
|
[validatedRequest],
|
|
async (request, response) => {
|
|
try {
|
|
const { provider, apiKey } = reqBody(request);
|
|
const { models, error } = await getCustomModels(provider, apiKey);
|
|
return response.status(200).json({
|
|
models,
|
|
error,
|
|
});
|
|
} catch (error) {
|
|
console.error(error);
|
|
response.status(500).end();
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
module.exports = { systemEndpoints };
|