anything-llm/server/models/embedConfig.js
Sean Hatfield 1846a99b93
[FEAT] Embedded AnythingLLM (#656)
* WIP embedded app

* WIP got response from backend in embedded app

* WIP streaming prints to embedded app

* implemented streaming and tailwind min for styling into embedded app

* WIP embedded app history functional

* load params from script tag into embedded app

* rough in modularization of embed chat
cleanup dev process for easier dev support
move all chat to components
todo: build process
todo: backend support

* remove eslint config

* Implement models and cleanup embed chat endpoints
Improve build process for embed
prod minification and bundle size awareness
WIP

* forgot files

* rename to embed folder

* introduce chat modal styles

* add middleware validations on embed chat

* auto open param and default greeting

* reset chat history

* Admin embed config page

* Admin Embed Chats mgmt page

* update embed

* nonpriv

* more style support
reopen if chat was last opened

* update comments

* remove unused imports

* allow change of workspace for embedconfig

* update failure to lookup message

* update reset script

* update instructions

* Add more styling options
Add sponsor text at bottom
Support dynamic container height
Loading animations

* publish new embed script

* Add back syntax highlighting and keep bundle small via dynamic script build

* add hint

* update readme

* update copy model for snippet with link to styles

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
2024-02-05 14:21:34 -08:00

240 lines
6.1 KiB
JavaScript

const { v4 } = require("uuid");
const prisma = require("../utils/prisma");
const { VALID_CHAT_MODE } = require("../utils/chats/stream");
const EmbedConfig = {
writable: [
// Used for generic updates so we can validate keys in request body
"enabled",
"allowlist_domains",
"allow_model_override",
"allow_temperature_override",
"allow_prompt_override",
"max_chats_per_day",
"max_chats_per_session",
"chat_mode",
"workspace_id",
],
new: async function (data, creatorId = null) {
try {
const embed = await prisma.embed_configs.create({
data: {
uuid: v4(),
enabled: true,
chat_mode: validatedCreationData(data?.chat_mode, "chat_mode"),
allowlist_domains: validatedCreationData(
data?.allowlist_domains,
"allowlist_domains"
),
allow_model_override: validatedCreationData(
data?.allow_model_override,
"allow_model_override"
),
allow_temperature_override: validatedCreationData(
data?.allow_temperature_override,
"allow_temperature_override"
),
allow_prompt_override: validatedCreationData(
data?.allow_prompt_override,
"allow_prompt_override"
),
max_chats_per_day: validatedCreationData(
data?.max_chats_per_day,
"max_chats_per_day"
),
max_chats_per_session: validatedCreationData(
data?.max_chats_per_session,
"max_chats_per_session"
),
createdBy: Number(creatorId) ?? null,
workspace: {
connect: { id: Number(data.workspace_id) },
},
},
});
return { embed, message: null };
} catch (error) {
console.error(error.message);
return { embed: null, message: error.message };
}
},
update: async function (embedId = null, data = {}) {
if (!embedId) throw new Error("No embed id provided for update");
const validKeys = Object.keys(data).filter((key) =>
this.writable.includes(key)
);
if (validKeys.length === 0)
return { embed: { id }, message: "No valid fields to update!" };
const updates = {};
validKeys.map((key) => {
updates[key] = validatedCreationData(data[key], key);
});
try {
await prisma.embed_configs.update({
where: { id: Number(embedId) },
data: updates,
});
return { success: true, error: null };
} catch (error) {
console.error(error.message);
return { success: false, error: error.message };
}
},
get: async function (clause = {}) {
try {
const embedConfig = await prisma.embed_configs.findFirst({
where: clause,
});
return embedConfig || null;
} catch (error) {
console.error(error.message);
return null;
}
},
getWithWorkspace: async function (clause = {}) {
try {
const embedConfig = await prisma.embed_configs.findFirst({
where: clause,
include: {
workspace: true,
},
});
return embedConfig || null;
} catch (error) {
console.error(error.message);
return null;
}
},
delete: async function (clause = {}) {
try {
await prisma.embed_configs.delete({
where: clause,
});
return true;
} catch (error) {
console.error(error.message);
return false;
}
},
where: async function (clause = {}, limit = null, orderBy = null) {
try {
const results = await prisma.embed_configs.findMany({
where: clause,
...(limit !== null ? { take: limit } : {}),
...(orderBy !== null ? { orderBy } : {}),
});
return results;
} catch (error) {
console.error(error.message);
return [];
}
},
whereWithWorkspace: async function (
clause = {},
limit = null,
orderBy = null
) {
try {
const results = await prisma.embed_configs.findMany({
where: clause,
include: {
workspace: true,
_count: {
select: { embed_chats: true },
},
},
...(limit !== null ? { take: limit } : {}),
...(orderBy !== null ? { orderBy } : {}),
});
return results;
} catch (error) {
console.error(error.message);
return [];
}
},
// Will return null if process should be skipped
// an empty array means the system will check. This
// prevents a bad parse from allowing all requests
parseAllowedHosts: function (embed) {
if (!embed.allowlist_domains) return null;
try {
return JSON.parse(embed.allowlist_domains);
} catch {
console.error(`Failed to parse allowlist_domains for Embed ${embed.id}!`);
return [];
}
},
};
const BOOLEAN_KEYS = [
"allow_model_override",
"allow_temperature_override",
"allow_prompt_override",
"enabled",
];
const NUMBER_KEYS = [
"max_chats_per_day",
"max_chats_per_session",
"workspace_id",
];
// Helper to validate a data object strictly into the proper format
function validatedCreationData(value, field) {
if (field === "chat_mode") {
if (!value || !VALID_CHAT_MODE.includes(value)) return "query";
return value;
}
if (field === "allowlist_domains") {
try {
if (!value) return null;
return JSON.stringify(
// Iterate and force all domains to URL object
// and stringify the result.
value
.split(",")
.map((input) => {
let url = input;
if (!url.includes("http://") && !url.includes("https://"))
url = `https://${url}`;
try {
new URL(url);
return url;
} catch {
return null;
}
})
.filter((u) => !!u)
);
} catch {
return null;
}
}
if (BOOLEAN_KEYS.includes(field)) {
return value === true || value === false ? value : false;
}
if (NUMBER_KEYS.includes(field)) {
return isNaN(value) || Number(value) <= 0 ? null : Number(value);
}
return null;
}
module.exports = { EmbedConfig };