mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🧭 fix: Add Base Path Support for Login/Register and Image Paths (#10116)
* fix: add basePath pattern to support login/register and image paths * Fix linter errors * refactor: Update import statements for getBasePath and isEnabled, and add path utility functions with tests - Refactored imports in addImages.js and StableDiffusion.js to use getBasePath from '@librechat/api'. - Consolidated isEnabled and getBasePath imports in validateImageRequest.js. - Introduced new path utility functions in path.ts and corresponding unit tests in path.spec.ts to validate base path extraction logic. * fix: Update domain server base URL in MarkdownComponents and refactor authentication redirection logic - Changed the domain server base URL in MarkdownComponents.tsx to use the API base URL. - Refactored the useAuthRedirect hook to utilize React Router's navigate for redirection instead of window.location, ensuring a smoother SPA experience. - Added unit tests for the useAuthRedirect hook to verify authentication redirection behavior. * test: Mock isEnabled in validateImages.spec.js for improved test isolation - Updated validateImages.spec.js to mock the isEnabled function from @librechat/api, ensuring that tests can run independently of the actual implementation. - Cleared the DOMAIN_CLIENT environment variable before tests to avoid interference with basePath resolution. --------- Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
parent
ef3bf0a932
commit
7aa8d49f3a
21 changed files with 717 additions and 30 deletions
|
|
@ -1,3 +1,4 @@
|
|||
const { getBasePath } = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
|
||||
/**
|
||||
|
|
@ -32,6 +33,8 @@ function addImages(intermediateSteps, responseMessage) {
|
|||
return;
|
||||
}
|
||||
|
||||
const basePath = getBasePath();
|
||||
|
||||
// Correct any erroneous URLs in the responseMessage.text first
|
||||
intermediateSteps.forEach((step) => {
|
||||
const { observation } = step;
|
||||
|
|
@ -44,12 +47,14 @@ function addImages(intermediateSteps, responseMessage) {
|
|||
return;
|
||||
}
|
||||
const essentialImagePath = match[0];
|
||||
const fullImagePath = `${basePath}${essentialImagePath}`;
|
||||
|
||||
const regex = /!\[.*?\]\((.*?)\)/g;
|
||||
let matchErroneous;
|
||||
while ((matchErroneous = regex.exec(responseMessage.text)) !== null) {
|
||||
if (matchErroneous[1] && !matchErroneous[1].startsWith('/images/')) {
|
||||
responseMessage.text = responseMessage.text.replace(matchErroneous[1], essentialImagePath);
|
||||
if (matchErroneous[1] && !matchErroneous[1].startsWith(`${basePath}/images/`)) {
|
||||
// Replace with the full path including base path
|
||||
responseMessage.text = responseMessage.text.replace(matchErroneous[1], fullImagePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -61,9 +66,23 @@ function addImages(intermediateSteps, responseMessage) {
|
|||
return;
|
||||
}
|
||||
const observedImagePath = observation.match(/!\[[^(]*\]\([^)]*\)/g);
|
||||
if (observedImagePath && !responseMessage.text.includes(observedImagePath[0])) {
|
||||
responseMessage.text += '\n' + observedImagePath[0];
|
||||
logger.debug('[addImages] added image from intermediateSteps:', observedImagePath[0]);
|
||||
if (observedImagePath) {
|
||||
// Fix the image path to include base path if it doesn't already
|
||||
let imageMarkdown = observedImagePath[0];
|
||||
const urlMatch = imageMarkdown.match(/\(([^)]+)\)/);
|
||||
if (
|
||||
urlMatch &&
|
||||
urlMatch[1] &&
|
||||
!urlMatch[1].startsWith(`${basePath}/images/`) &&
|
||||
urlMatch[1].startsWith('/images/')
|
||||
) {
|
||||
imageMarkdown = imageMarkdown.replace(urlMatch[1], `${basePath}${urlMatch[1]}`);
|
||||
}
|
||||
|
||||
if (!responseMessage.text.includes(imageMarkdown)) {
|
||||
responseMessage.text += '\n' + imageMarkdown;
|
||||
logger.debug('[addImages] added image from intermediateSteps:', imageMarkdown);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ describe('addImages', () => {
|
|||
|
||||
it('should append correctly from a real scenario', () => {
|
||||
responseMessage.text =
|
||||
'Here is the generated image based on your request. It depicts a surreal landscape filled with floating musical notes. The style is impressionistic, with vibrant sunset hues dominating the scene. At the center, there\'s a silhouette of a grand piano, adding a dreamy emotion to the overall image. This could serve as a unique and creative music album cover. Would you like to make any changes or generate another image?';
|
||||
"Here is the generated image based on your request. It depicts a surreal landscape filled with floating musical notes. The style is impressionistic, with vibrant sunset hues dominating the scene. At the center, there's a silhouette of a grand piano, adding a dreamy emotion to the overall image. This could serve as a unique and creative music album cover. Would you like to make any changes or generate another image?";
|
||||
const originalText = responseMessage.text;
|
||||
const imageMarkdown = '';
|
||||
intermediateSteps.push({ observation: imageMarkdown });
|
||||
|
|
@ -139,4 +139,108 @@ describe('addImages', () => {
|
|||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
describe('basePath functionality', () => {
|
||||
let originalDomainClient;
|
||||
|
||||
beforeEach(() => {
|
||||
originalDomainClient = process.env.DOMAIN_CLIENT;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env.DOMAIN_CLIENT = originalDomainClient;
|
||||
});
|
||||
|
||||
it('should prepend base path to image URLs when DOMAIN_CLIENT is set', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should not prepend base path when image URL already has base path', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should correct erroneous URLs with base path', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
responseMessage.text = '';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('');
|
||||
});
|
||||
|
||||
it('should handle empty base path (root deployment)', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should handle missing DOMAIN_CLIENT', () => {
|
||||
delete process.env.DOMAIN_CLIENT;
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should handle observation without image path match', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should handle nested subdirectories in base path', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/apps/librechat';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should handle multiple observations with mixed base path scenarios', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe(
|
||||
'\n\n',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle complex markdown with base path', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const complexMarkdown = `
|
||||
# Document Title
|
||||

|
||||
Some text between images
|
||||

|
||||
`;
|
||||
intermediateSteps.push({ observation: complexMarkdown });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should handle URLs that are already absolute', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
intermediateSteps.push({ observation: '' });
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe('\n');
|
||||
});
|
||||
|
||||
it('should handle data URLs', () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
intermediateSteps.push({
|
||||
observation:
|
||||
'',
|
||||
});
|
||||
addImages(intermediateSteps, responseMessage);
|
||||
expect(responseMessage.text).toBe(
|
||||
'\n',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const { v4: uuidv4 } = require('uuid');
|
|||
const { Tool } = require('@langchain/core/tools');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { FileContext, ContentTypes } = require('librechat-data-provider');
|
||||
const { getBasePath } = require('@librechat/api');
|
||||
const paths = require('~/config/paths');
|
||||
|
||||
const displayMessage =
|
||||
|
|
@ -36,7 +37,7 @@ class StableDiffusionAPI extends Tool {
|
|||
this.description_for_model = `// Generate images and visuals using text.
|
||||
// Guidelines:
|
||||
// - ALWAYS use {{"prompt": "7+ detailed keywords", "negative_prompt": "7+ detailed keywords"}} structure for queries.
|
||||
// - ALWAYS include the markdown url in your final response to show the user: 
|
||||
// - ALWAYS include the markdown url in your final response to show the user: }/images/id.png)
|
||||
// - Visually describe the moods, details, structures, styles, and/or proportions of the image. Remember, the focus is on visual attributes.
|
||||
// - Craft your input by "showing" and not "telling" the imagery. Think in terms of what you'd want to see in a photograph or a painting.
|
||||
// - Here's an example for generating a realistic portrait photo of a man:
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
const jwt = require('jsonwebtoken');
|
||||
const { isEnabled } = require('@librechat/api');
|
||||
const createValidateImageRequest = require('~/server/middleware/validateImageRequest');
|
||||
|
||||
// Mock only isEnabled, keep getBasePath real so it reads process.env.DOMAIN_CLIENT
|
||||
jest.mock('@librechat/api', () => ({
|
||||
...jest.requireActual('@librechat/api'),
|
||||
isEnabled: jest.fn(),
|
||||
}));
|
||||
|
||||
const { isEnabled } = require('@librechat/api');
|
||||
|
||||
describe('validateImageRequest middleware', () => {
|
||||
let req, res, next, validateImageRequest;
|
||||
const validObjectId = '65cfb246f7ecadb8b1e8036b';
|
||||
|
|
@ -23,6 +26,7 @@ describe('validateImageRequest middleware', () => {
|
|||
next = jest.fn();
|
||||
process.env.JWT_REFRESH_SECRET = 'test-secret';
|
||||
process.env.OPENID_REUSE_TOKENS = 'false';
|
||||
delete process.env.DOMAIN_CLIENT; // Clear for tests without basePath
|
||||
|
||||
// Default: OpenID token reuse disabled
|
||||
isEnabled.mockReturnValue(false);
|
||||
|
|
@ -296,4 +300,175 @@ describe('validateImageRequest middleware', () => {
|
|||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||
});
|
||||
});
|
||||
|
||||
describe('basePath functionality', () => {
|
||||
let originalDomainClient;
|
||||
|
||||
beforeEach(() => {
|
||||
originalDomainClient = process.env.DOMAIN_CLIENT;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env.DOMAIN_CLIENT = originalDomainClient;
|
||||
});
|
||||
|
||||
test('should validate image paths with base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should validate agent avatar paths with base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/agent-avatar.png`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should reject image paths without base path when DOMAIN_CLIENT is set', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||
});
|
||||
|
||||
test('should handle empty base path (root deployment)', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle missing DOMAIN_CLIENT', async () => {
|
||||
delete process.env.DOMAIN_CLIENT;
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle nested subdirectories in base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/apps/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/apps/librechat/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should prevent path traversal with base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/../../../etc/passwd`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(res.status).toHaveBeenCalledWith(403);
|
||||
expect(res.send).toHaveBeenCalledWith('Access Denied');
|
||||
});
|
||||
|
||||
test('should handle URLs with query parameters and base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/test.jpg?version=1`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle URLs with fragments and base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/test.jpg#section`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle HTTPS URLs with base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'https://example.com/librechat';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle invalid DOMAIN_CLIENT gracefully', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'not-a-valid-url';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}`;
|
||||
req.originalUrl = `/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle OpenID flow with base path', async () => {
|
||||
process.env.DOMAIN_CLIENT = 'http://localhost:3080/librechat';
|
||||
process.env.OPENID_REUSE_TOKENS = 'true';
|
||||
const validToken = jwt.sign(
|
||||
{ id: validObjectId, exp: Math.floor(Date.now() / 1000) + 3600 },
|
||||
process.env.JWT_REFRESH_SECRET,
|
||||
);
|
||||
req.headers.cookie = `refreshToken=${validToken}; token_provider=openid; openid_user_id=${validToken}`;
|
||||
req.originalUrl = `/librechat/images/${validObjectId}/test.jpg`;
|
||||
|
||||
await validateImageRequest(req, res, next);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const cookies = require('cookie');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { isEnabled } = require('@librechat/api');
|
||||
const { logger } = require('@librechat/data-schemas');
|
||||
const { isEnabled, getBasePath } = require('@librechat/api');
|
||||
|
||||
const OBJECT_ID_LENGTH = 24;
|
||||
const OBJECT_ID_PATTERN = /^[0-9a-f]{24}$/i;
|
||||
|
|
@ -124,14 +124,21 @@ function createValidateImageRequest(secureImageLinks) {
|
|||
return res.status(403).send('Access Denied');
|
||||
}
|
||||
|
||||
const agentAvatarPattern = /^\/images\/[a-f0-9]{24}\/agent-[^/]*$/;
|
||||
const basePath = getBasePath();
|
||||
const imagesPath = `${basePath}/images`;
|
||||
|
||||
const agentAvatarPattern = new RegExp(
|
||||
`^${imagesPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/[a-f0-9]{24}/agent-[^/]*$`,
|
||||
);
|
||||
if (agentAvatarPattern.test(fullPath)) {
|
||||
logger.debug('[validateImageRequest] Image request validated');
|
||||
return next();
|
||||
}
|
||||
|
||||
const escapedUserId = userIdForPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pathPattern = new RegExp(`^/images/${escapedUserId}/[^/]+$`);
|
||||
const pathPattern = new RegExp(
|
||||
`^${imagesPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/${escapedUserId}/[^/]+$`,
|
||||
);
|
||||
|
||||
if (pathPattern.test(fullPath)) {
|
||||
logger.debug('[validateImageRequest] Image request validated');
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const { filterFilesByAgentAccess } = require('~/server/services/Files/permission
|
|||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||
const { convertImage } = require('~/server/services/Files/images/convert');
|
||||
const { createFile, getFiles, updateFile } = require('~/models/File');
|
||||
const { getBasePath } = require('~/server/utils');
|
||||
|
||||
/**
|
||||
* Process OpenAI image files, convert to target format, save and return file metadata.
|
||||
|
|
@ -41,11 +42,12 @@ const processCodeOutput = async ({
|
|||
const appConfig = req.config;
|
||||
const currentDate = new Date();
|
||||
const baseURL = getCodeBaseURL();
|
||||
const basePath = getBasePath();
|
||||
const fileExt = path.extname(name);
|
||||
if (!fileExt || !imageExtRegex.test(name)) {
|
||||
return {
|
||||
filename: name,
|
||||
filepath: `/api/files/code/download/${session_id}/${id}`,
|
||||
filepath: `${basePath}/api/files/code/download/${session_id}/${id}`,
|
||||
/** Note: expires 24 hours after creation */
|
||||
expiresAt: currentDate.getTime() + 86400000,
|
||||
conversationId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue