merlyn/server/utils/chats/index.js
Marcello Fitton 5716ac5ed5
Custom Default System Prompt (#4487)
* Add Default System Prompt Management

- Introduced a new route for fetching and updating the default system prompt in the backend.
- Added a new Admin page for managing the default system prompt, including a form for editing and saving changes.
- Updated the SettingsSidebar to include a link to the new Default System Prompt page.
- Implemented fetching of available system prompt variables for use in the prompt editor.
- Enhanced the ChatSettings and ChatPromptSettings components to support the new default system prompt functionality.

This commit lays the groundwork for improved management of system prompts across workspaces.

* Remove validation for system prompt in ChatSettings component

* Add comment for system prompt in workspaces model

* linting, simplify logic for default assumption

* dev build

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
2025-11-24 13:24:10 -08:00

120 lines
3.8 KiB
JavaScript

const { v4: uuidv4 } = require("uuid");
const { WorkspaceChats } = require("../../models/workspaceChats");
const { resetMemory } = require("./commands/reset");
const { convertToPromptHistory } = require("../helpers/chat/responses");
const { SlashCommandPresets } = require("../../models/slashCommandsPresets");
const { SystemPromptVariables } = require("../../models/systemPromptVariables");
const VALID_COMMANDS = {
"/reset": resetMemory,
};
async function grepCommand(message, user = null) {
const userPresets = await SlashCommandPresets.getUserPresets(user?.id);
const availableCommands = Object.keys(VALID_COMMANDS);
// Check if the message starts with any built-in command
for (let i = 0; i < availableCommands.length; i++) {
const cmd = availableCommands[i];
const re = new RegExp(`^(${cmd})`, "i");
if (re.test(message)) {
return cmd;
}
}
// Replace all preset commands with their corresponding prompts
// Allows multiple commands in one message
let updatedMessage = message;
for (const preset of userPresets) {
const regex = new RegExp(
`(?:\\b\\s|^)(${preset.command})(?:\\b\\s|$)`,
"g"
);
updatedMessage = updatedMessage.replace(regex, preset.prompt);
}
return updatedMessage;
}
/**
* @description This function will do recursive replacement of all slash commands with their corresponding prompts.
* @notice This function is used for API calls and is not user-scoped. THIS FUNCTION DOES NOT SUPPORT PRESET COMMANDS.
* @returns {Promise<string>}
*/
async function grepAllSlashCommands(message) {
const allPresets = await SlashCommandPresets.where({});
// Replace all preset commands with their corresponding prompts
// Allows multiple commands in one message
let updatedMessage = message;
for (const preset of allPresets) {
const regex = new RegExp(
`(?:\\b\\s|^)(${preset.command})(?:\\b\\s|$)`,
"g"
);
updatedMessage = updatedMessage.replace(regex, preset.prompt);
}
return updatedMessage;
}
async function recentChatHistory({
user = null,
workspace,
thread = null,
messageLimit = 20,
apiSessionId = null,
}) {
const rawHistory = (
await WorkspaceChats.where(
{
workspaceId: workspace.id,
user_id: user?.id || null,
thread_id: thread?.id || null,
api_session_id: apiSessionId || null,
include: true,
},
messageLimit,
{ id: "desc" }
)
).reverse();
return { rawHistory, chatHistory: convertToPromptHistory(rawHistory) };
}
/**
* Returns the base prompt for the chat. This method will also do variable
* substitution on the prompt if there are any defined variables in the prompt.
* @param {Object|null} workspace - the workspace object
* @param {Object|null} user - the user object
* @returns {Promise<string>} - the base prompt
*/
async function chatPrompt(workspace, user = null) {
const { SystemSettings } = require("../../models/systemSettings");
const basePrompt =
workspace?.openAiPrompt ?? SystemSettings.saneDefaultSystemPrompt;
return await SystemPromptVariables.expandSystemPromptVariables(
basePrompt,
user?.id,
workspace?.id
);
}
// We use this util function to deduplicate sources from similarity searching
// if the document is already pinned.
// Eg: You pin a csv, if we RAG + full-text that you will get the same data
// points both in the full-text and possibly from RAG - result in bad results
// even if the LLM was not even going to hallucinate.
function sourceIdentifier(sourceDocument) {
if (!sourceDocument?.title || !sourceDocument?.published) return uuidv4();
return `title:${sourceDocument.title}-timestamp:${sourceDocument.published}`;
}
module.exports = {
sourceIdentifier,
recentChatHistory,
chatPrompt,
grepCommand,
grepAllSlashCommands,
VALID_COMMANDS,
};