mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-09-14 20:21:02 +00:00
⚡ perf: Optimize First Load of Large Conversations (#14901)
* ⚡ perf: Index the Conversation Fetch and Trim the Client Message Projection * ⚡ perf: Memoize the Message Tree per Cache Write * ⚡ perf: Serve Message Reads via the Trimmed Projection and an Ownership Probe * ⚡ perf: Defer Collapsed Disclosure Bodies Until First Expansion * ⚡ perf: Progressively Mount Long Threads from the Scroll Anchor * 🩹 fix: Address Codex Findings on Retention, Anchoring, and Cache Bounds * 🩹 fix: Poll the Oversized Export Precondition Through the Progressive Mount * 🩹 fix: Keep Video Results in the Client Message Projection
This commit is contained in:
parent
df5abbb377
commit
1b7e2a4e6a
38 changed files with 1192 additions and 138 deletions
|
|
@ -1,5 +1,5 @@
|
|||
jest.mock('~/models', () => ({
|
||||
getConvo: jest.fn(),
|
||||
getConvoOwnership: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/api', () => ({
|
||||
|
|
@ -18,7 +18,7 @@ jest.mock('@librechat/data-schemas', () => ({
|
|||
}));
|
||||
|
||||
const validateMessageReq = require('../validateMessageReq');
|
||||
const { getConvo } = require('~/models');
|
||||
const { getConvoOwnership } = require('~/models');
|
||||
const { GenerationJobManager } = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ describe('validateMessageReq', () => {
|
|||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({ error: 'Conversation ID mismatch' });
|
||||
expect(getConvo).not.toHaveBeenCalled();
|
||||
expect(getConvoOwnership).not.toHaveBeenCalled();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ describe('validateMessageReq', () => {
|
|||
|
||||
expect(res.status).toHaveBeenCalledWith(400);
|
||||
expect(res.json).toHaveBeenCalledWith({ error: 'Conversation ID mismatch' });
|
||||
expect(getConvo).not.toHaveBeenCalled();
|
||||
expect(getConvoOwnership).not.toHaveBeenCalled();
|
||||
expect(next).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -81,11 +81,11 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue({ conversationId: 'convo-owned', user: userId });
|
||||
getConvoOwnership.mockResolvedValue({ conversationId: 'convo-owned', user: userId });
|
||||
|
||||
await validateMessageReq(req, res, next);
|
||||
|
||||
expect(getConvo).toHaveBeenCalledWith(userId, 'convo-owned');
|
||||
expect(getConvoOwnership).toHaveBeenCalledWith(userId, 'convo-owned');
|
||||
expect(next).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
GenerationJobManager.getJob.mockResolvedValue({
|
||||
status: 'running',
|
||||
metadata: { userId, tenantId: 'tenant-a' },
|
||||
|
|
@ -120,7 +120,7 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
GenerationJobManager.getJob.mockResolvedValue({
|
||||
status: 'running',
|
||||
metadata: { userId },
|
||||
|
|
@ -141,7 +141,7 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
GenerationJobManager.getJob.mockResolvedValue({
|
||||
status: 'running',
|
||||
metadata: { userId: 'another-user' },
|
||||
|
|
@ -163,7 +163,7 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
GenerationJobManager.getJob.mockResolvedValue({
|
||||
status: 'running',
|
||||
metadata: { userId, tenantId: 'tenant-b' },
|
||||
|
|
@ -185,7 +185,7 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
|
||||
await validateMessageReq(req, res, next);
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ describe('validateMessageReq', () => {
|
|||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
const error = new Error('job store unavailable');
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
GenerationJobManager.getJob.mockRejectedValue(error);
|
||||
|
||||
await validateMessageReq(req, res, next);
|
||||
|
|
@ -229,7 +229,7 @@ describe('validateMessageReq', () => {
|
|||
};
|
||||
const res = createResponse();
|
||||
const next = jest.fn();
|
||||
getConvo.mockResolvedValue(null);
|
||||
getConvoOwnership.mockResolvedValue(null);
|
||||
|
||||
await validateMessageReq(req, res, next);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ const {
|
|||
isPendingActionStale,
|
||||
} = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { getConvo } = require('~/models');
|
||||
const { getConvoOwnership } = require('~/models');
|
||||
|
||||
module.exports = createMessageRequestMiddleware({
|
||||
getConvo,
|
||||
getConvo: getConvoOwnership,
|
||||
getJob: (conversationId) => GenerationJobManager.getJob(conversationId),
|
||||
isPendingActionStale,
|
||||
logger,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const { CLIENT_MESSAGE_SELECT } = require('@librechat/data-schemas');
|
||||
const express = require('express');
|
||||
const request = require('supertest');
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ jest.mock('librechat-data-provider', () => ({
|
|||
|
||||
jest.mock('~/models', () => ({
|
||||
saveConvo: jest.fn(),
|
||||
getConvo: jest.fn(),
|
||||
getConvoOwnership: jest.fn(),
|
||||
getMessage: jest.fn(),
|
||||
saveMessage: jest.fn(),
|
||||
getMessages: jest.fn(),
|
||||
|
|
@ -90,7 +91,7 @@ jest.mock('~/db/models', () => ({
|
|||
|
||||
describe('GET /api/messages/:conversationId with real validation middleware', () => {
|
||||
let app;
|
||||
const { getConvo, getMessages } = require('~/models');
|
||||
const { getConvoOwnership, getMessages } = require('~/models');
|
||||
const authenticatedUserId = 'user-owner-123';
|
||||
|
||||
beforeAll(() => {
|
||||
|
|
@ -114,7 +115,7 @@ describe('GET /api/messages/:conversationId with real validation middleware', ()
|
|||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual([]);
|
||||
expect(getConvo).not.toHaveBeenCalled();
|
||||
expect(getConvoOwnership).not.toHaveBeenCalled();
|
||||
expect(getMessages).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
|
@ -125,7 +126,7 @@ describe('GET /api/messages/:conversationId with real validation middleware', ()
|
|||
resolveConvo = resolve;
|
||||
});
|
||||
|
||||
getConvo.mockImplementation(() => {
|
||||
getConvoOwnership.mockImplementation(() => {
|
||||
events.push('convo-started');
|
||||
return convoPromise;
|
||||
});
|
||||
|
|
@ -156,10 +157,10 @@ describe('GET /api/messages/:conversationId with real validation middleware', ()
|
|||
const response = await responsePromise;
|
||||
|
||||
expect(eventsBeforeValidation).toEqual(['convo-started', 'messages-started']);
|
||||
expect(getConvo).toHaveBeenCalledWith(authenticatedUserId, 'convo-1');
|
||||
expect(getConvoOwnership).toHaveBeenCalledWith(authenticatedUserId, 'convo-1');
|
||||
expect(getMessages).toHaveBeenCalledWith(
|
||||
{ conversationId: 'convo-1', user: authenticatedUserId },
|
||||
'-_id -__v -user',
|
||||
CLIENT_MESSAGE_SELECT,
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual([{ messageId: 'message-1', conversationId: 'convo-1' }]);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
const { CLIENT_MESSAGE_SELECT } = require('@librechat/data-schemas');
|
||||
const express = require('express');
|
||||
const request = require('supertest');
|
||||
|
||||
|
|
@ -171,7 +172,7 @@ describe('message route conversation ownership filters', () => {
|
|||
expect(response.status).toBe(200);
|
||||
expect(getMessages).toHaveBeenCalledWith(
|
||||
{ conversationId: 'convo-1', user: authenticatedUserId },
|
||||
'-_id -__v -user',
|
||||
CLIENT_MESSAGE_SELECT,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -216,7 +217,7 @@ describe('message route conversation ownership filters', () => {
|
|||
expect(eventsBeforeValidation).toEqual(['messages-started']);
|
||||
expect(getMessages).toHaveBeenCalledWith(
|
||||
{ conversationId: 'convo-1', user: authenticatedUserId },
|
||||
'-_id -__v -user',
|
||||
CLIENT_MESSAGE_SELECT,
|
||||
);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
|
@ -242,7 +243,7 @@ describe('message route conversation ownership filters', () => {
|
|||
|
||||
expect(getMessages).toHaveBeenCalledWith(
|
||||
{ conversationId: 'convo-1', user: authenticatedUserId },
|
||||
'-_id -__v -user',
|
||||
CLIENT_MESSAGE_SELECT,
|
||||
);
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.body).toEqual({ error: 'Conversation not found' });
|
||||
|
|
@ -256,7 +257,7 @@ describe('message route conversation ownership filters', () => {
|
|||
expect(response.status).toBe(200);
|
||||
expect(getMessages).toHaveBeenCalledWith(
|
||||
{ conversationId: 'convo-1', messageId: 'message-1', user: authenticatedUserId },
|
||||
'-_id -__v -user',
|
||||
CLIENT_MESSAGE_SELECT,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { logger, CLIENT_MESSAGE_SELECT } = require('@librechat/data-schemas');
|
||||
const {
|
||||
ContentTypes,
|
||||
feedbackSchema,
|
||||
|
|
@ -289,7 +289,7 @@ router.get('/:conversationId', prepareMessageRequestValidation, async (req, res)
|
|||
// This intentionally starts a user-scoped read before validation resolves;
|
||||
// the response remains gated on validation success below.
|
||||
const messagesPromise = validation.shouldFetchMessages
|
||||
? db.getMessages({ conversationId, user: req.user.id }, '-_id -__v -user').then(
|
||||
? db.getMessages({ conversationId, user: req.user.id }, CLIENT_MESSAGE_SELECT).then(
|
||||
(messages) => ({ messages }),
|
||||
(error) => ({ error }),
|
||||
)
|
||||
|
|
@ -350,7 +350,7 @@ router.get('/:conversationId/:messageId', validateMessageReq, async (req, res) =
|
|||
const { conversationId, messageId } = req.params;
|
||||
const message = await db.getMessages(
|
||||
{ conversationId, messageId, user: req.user.id },
|
||||
'-_id -__v -user',
|
||||
CLIENT_MESSAGE_SELECT,
|
||||
);
|
||||
if (!message) {
|
||||
return res.status(404).json({ error: 'Message not found' });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue