🧠 feat: Prompt caching switch, prompt query params; refactor: static cache, prompt/markdown styling, trim copied code, switch new chat to convo URL (#3784)

* refactor: Update staticCache to use oneDayInSeconds for sMaxAge and maxAge

* refactor: role updates

* style: first pass cursor

* style: Update nested list styles in style.css

* feat: setIsSubmitting to true in message handler to prevent edge case where submitting turns false during message stream

* feat: Add logic to redirect to conversation page after creating a new conversation

* refactor: Trim code string before copying in CodeBlock component

* feat: configSchema bookmarks and presets defaults

* feat: Update loadDefaultInterface to handle undefined config

* refactor: use  for compression check

* feat: first pass, query params

* fix: styling issues for prompt cards

* feat: anthropic prompt caching UI switch

* chore: Update static file cache control defaults/comments in .env.example

* ci: fix tests

* ci: fix tests

* chore:  use "submitting" class server error connection suspense fallback
This commit is contained in:
Danny Avila 2024-08-26 15:34:46 -04:00 committed by GitHub
parent bd701c197e
commit 5694ad4e55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 519 additions and 112 deletions

View file

@ -16,9 +16,9 @@ const validateImageRequest = require('./middleware/validateImageRequest');
const errorController = require('./controllers/ErrorController');
const configureSocialLogins = require('./socialLogins');
const AppService = require('./services/AppService');
const staticCache = require('./utils/staticCache');
const noIndex = require('./middleware/noIndex');
const routes = require('./routes');
const staticCache = require('./utils/staticCache');
const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION } = process.env ?? {};
@ -51,7 +51,7 @@ const startServer = async () => {
app.set('trust proxy', 1); /* trust first proxy */
app.use(cors());
if (DISABLE_COMPRESSION !== 'true') {
if (!isEnabled(DISABLE_COMPRESSION)) {
app.use(compression());
}

View file

@ -1,6 +1,6 @@
jest.mock('~/models/Role', () => ({
initializeRoles: jest.fn(),
updatePromptsAccess: jest.fn(),
updateAccessPermissions: jest.fn(),
getRoleByName: jest.fn(),
updateRoleByName: jest.fn(),
}));
@ -30,7 +30,7 @@ jest.mock('./start/checks', () => ({
const AppService = require('./AppService');
const { loadDefaultInterface } = require('./start/interface');
describe('AppService interface.prompts configuration', () => {
describe('AppService interface configuration', () => {
let app;
let mockLoadCustomConfig;
@ -41,33 +41,47 @@ describe('AppService interface.prompts configuration', () => {
mockLoadCustomConfig = require('./Config/loadCustomConfig');
});
it('should set prompts to true when loadDefaultInterface returns true', async () => {
it('should set prompts and bookmarks to true when loadDefaultInterface returns true for both', async () => {
mockLoadCustomConfig.mockResolvedValue({});
loadDefaultInterface.mockResolvedValue({ prompts: true });
loadDefaultInterface.mockResolvedValue({ prompts: true, bookmarks: true });
await AppService(app);
expect(app.locals.interfaceConfig.prompts).toBe(true);
expect(app.locals.interfaceConfig.bookmarks).toBe(true);
expect(loadDefaultInterface).toHaveBeenCalled();
});
it('should set prompts to false when loadDefaultInterface returns false', async () => {
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: false } });
loadDefaultInterface.mockResolvedValue({ prompts: false });
it('should set prompts and bookmarks to false when loadDefaultInterface returns false for both', async () => {
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: false, bookmarks: false } });
loadDefaultInterface.mockResolvedValue({ prompts: false, bookmarks: false });
await AppService(app);
expect(app.locals.interfaceConfig.prompts).toBe(false);
expect(app.locals.interfaceConfig.bookmarks).toBe(false);
expect(loadDefaultInterface).toHaveBeenCalled();
});
it('should not set prompts when loadDefaultInterface returns undefined', async () => {
it('should not set prompts and bookmarks when loadDefaultInterface returns undefined for both', async () => {
mockLoadCustomConfig.mockResolvedValue({});
loadDefaultInterface.mockResolvedValue({});
await AppService(app);
expect(app.locals.interfaceConfig.prompts).toBeUndefined();
expect(app.locals.interfaceConfig.bookmarks).toBeUndefined();
expect(loadDefaultInterface).toHaveBeenCalled();
});
it('should set prompts and bookmarks to different values when loadDefaultInterface returns different values', async () => {
mockLoadCustomConfig.mockResolvedValue({ interface: { prompts: true, bookmarks: false } });
loadDefaultInterface.mockResolvedValue({ prompts: true, bookmarks: false });
await AppService(app);
expect(app.locals.interfaceConfig.prompts).toBe(true);
expect(app.locals.interfaceConfig.bookmarks).toBe(false);
expect(loadDefaultInterface).toHaveBeenCalled();
});
});

View file

@ -23,8 +23,7 @@ jest.mock('./Files/Firebase/initialize', () => ({
}));
jest.mock('~/models/Role', () => ({
initializeRoles: jest.fn(),
updatePromptsAccess: jest.fn(),
updateBookmarksAccess: jest.fn(),
updateAccessPermissions: jest.fn(),
}));
jest.mock('./ToolService', () => ({
loadAndFormatTools: jest.fn().mockReturnValue({

View file

@ -6,6 +6,7 @@ const buildOptions = (endpoint, parsedBody) => {
promptPrefix,
maxContextTokens,
resendFiles = true,
promptCache = true,
iconURL,
greeting,
spec,
@ -17,6 +18,7 @@ const buildOptions = (endpoint, parsedBody) => {
modelLabel,
promptPrefix,
resendFiles,
promptCache,
iconURL,
greeting,
spec,

View file

@ -1,5 +1,10 @@
const { SystemRoles, Permissions, removeNullishValues } = require('librechat-data-provider');
const { updatePromptsAccess, updateBookmarksAccess } = require('~/models/Role');
const {
SystemRoles,
Permissions,
PermissionTypes,
removeNullishValues,
} = require('librechat-data-provider');
const { updateAccessPermissions } = require('~/models/Role');
const { logger } = require('~/config');
/**
@ -28,8 +33,10 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
prompts: interfaceConfig?.prompts ?? defaults.prompts,
});
await updatePromptsAccess(roleName, { [Permissions.USE]: loadedInterface.prompts });
await updateBookmarksAccess(roleName, { [Permissions.USE]: loadedInterface.bookmarks });
await updateAccessPermissions(roleName, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: loadedInterface.prompts },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: loadedInterface.bookmarks },
});
let i = 0;
const logSettings = () => {

View file

@ -1,52 +1,81 @@
const { SystemRoles, Permissions } = require('librechat-data-provider');
const { updatePromptsAccess } = require('~/models/Role');
const { SystemRoles, Permissions, PermissionTypes } = require('librechat-data-provider');
const { updateAccessPermissions } = require('~/models/Role');
const { loadDefaultInterface } = require('./interface');
jest.mock('~/models/Role', () => ({
updatePromptsAccess: jest.fn(),
updateBookmarksAccess: jest.fn(),
updateAccessPermissions: jest.fn(),
}));
describe('loadDefaultInterface', () => {
it('should call updatePromptsAccess with the correct parameters when prompts is true', async () => {
const config = { interface: { prompts: true } };
it('should call updateAccessPermissions with the correct parameters when prompts and bookmarks are true', async () => {
const config = { interface: { prompts: true, bookmarks: true } };
const configDefaults = { interface: {} };
await loadDefaultInterface(config, configDefaults);
expect(updatePromptsAccess).toHaveBeenCalledWith(SystemRoles.USER, { [Permissions.USE]: true });
});
it('should call updatePromptsAccess with false when prompts is false', async () => {
const config = { interface: { prompts: false } };
const configDefaults = { interface: {} };
await loadDefaultInterface(config, configDefaults);
expect(updatePromptsAccess).toHaveBeenCalledWith(SystemRoles.USER, {
[Permissions.USE]: false,
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
});
});
it('should call updatePromptsAccess with undefined when prompts is not specified in config', async () => {
it('should call updateAccessPermissions with false when prompts and bookmarks are false', async () => {
const config = { interface: { prompts: false, bookmarks: false } };
const configDefaults = { interface: {} };
await loadDefaultInterface(config, configDefaults);
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: false },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
});
});
it('should call updateAccessPermissions with undefined when prompts and bookmarks are not specified in config', async () => {
const config = {};
const configDefaults = { interface: {} };
await loadDefaultInterface(config, configDefaults);
expect(updatePromptsAccess).toHaveBeenCalledWith(SystemRoles.USER, {
[Permissions.USE]: undefined,
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
});
});
it('should call updatePromptsAccess with undefined when prompts is explicitly undefined', async () => {
const config = { interface: { prompts: undefined } };
it('should call updateAccessPermissions with undefined when prompts and bookmarks are explicitly undefined', async () => {
const config = { interface: { prompts: undefined, bookmarks: undefined } };
const configDefaults = { interface: {} };
await loadDefaultInterface(config, configDefaults);
expect(updatePromptsAccess).toHaveBeenCalledWith(SystemRoles.USER, {
[Permissions.USE]: undefined,
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: undefined },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: undefined },
});
});
it('should call updateAccessPermissions with mixed values for prompts and bookmarks', async () => {
const config = { interface: { prompts: true, bookmarks: false } };
const configDefaults = { interface: {} };
await loadDefaultInterface(config, configDefaults);
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: false },
});
});
it('should call updateAccessPermissions with true when config is undefined', async () => {
const config = undefined;
const configDefaults = { interface: { prompts: true, bookmarks: true } };
await loadDefaultInterface(config, configDefaults);
expect(updateAccessPermissions).toHaveBeenCalledWith(SystemRoles.USER, {
[PermissionTypes.PROMPTS]: { [Permissions.USE]: true },
[PermissionTypes.BOOKMARKS]: { [Permissions.USE]: true },
});
});
});

View file

@ -1,9 +1,9 @@
const express = require('express');
const oneWeekInSeconds = 24 * 60 * 60 * 7;
const oneDayInSeconds = 24 * 60 * 60;
const sMaxAge = process.env.STATIC_CACHE_S_MAX_AGE || oneWeekInSeconds;
const maxAge = process.env.STATIC_CACHE_MAX_AGE || oneWeekInSeconds * 4;
const sMaxAge = process.env.STATIC_CACHE_S_MAX_AGE || oneDayInSeconds;
const maxAge = process.env.STATIC_CACHE_MAX_AGE || oneDayInSeconds * 2;
const staticCache = (staticPath) =>
express.static(staticPath, {