mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
🔉 feat: TTS/STT rate limiters (#2925)
* fix: remove double initialization of speech routes * refactor(useMessageHelpers): more consistent latestMessage updates based on unique textKey and early returns when setting * feat: TTS/STT rate limiters * chore: remove console log * fix: make modular chat true by default
This commit is contained in:
parent
08d6bea359
commit
8318f26d66
12 changed files with 265 additions and 35 deletions
2
api/cache/getLogStores.js
vendored
2
api/cache/getLogStores.js
vendored
|
|
@ -60,6 +60,8 @@ const namespaces = {
|
||||||
message_limit: createViolationInstance('message_limit'),
|
message_limit: createViolationInstance('message_limit'),
|
||||||
token_balance: createViolationInstance(ViolationTypes.TOKEN_BALANCE),
|
token_balance: createViolationInstance(ViolationTypes.TOKEN_BALANCE),
|
||||||
registrations: createViolationInstance('registrations'),
|
registrations: createViolationInstance('registrations'),
|
||||||
|
[ViolationTypes.TTS_LIMIT]: createViolationInstance(ViolationTypes.TTS_LIMIT),
|
||||||
|
[ViolationTypes.STT_LIMIT]: createViolationInstance(ViolationTypes.STT_LIMIT),
|
||||||
[ViolationTypes.FILE_UPLOAD_LIMIT]: createViolationInstance(ViolationTypes.FILE_UPLOAD_LIMIT),
|
[ViolationTypes.FILE_UPLOAD_LIMIT]: createViolationInstance(ViolationTypes.FILE_UPLOAD_LIMIT),
|
||||||
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: createViolationInstance(
|
[ViolationTypes.ILLEGAL_MODEL_REQUEST]: createViolationInstance(
|
||||||
ViolationTypes.ILLEGAL_MODEL_REQUEST,
|
ViolationTypes.ILLEGAL_MODEL_REQUEST,
|
||||||
|
|
|
||||||
7
api/server/middleware/speech/index.js
Normal file
7
api/server/middleware/speech/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
const createTTSLimiters = require('./ttsLimiters');
|
||||||
|
const createSTTLimiters = require('./sttLimiters');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createTTSLimiters,
|
||||||
|
createSTTLimiters,
|
||||||
|
};
|
||||||
68
api/server/middleware/speech/sttLimiters.js
Normal file
68
api/server/middleware/speech/sttLimiters.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
|
const { ViolationTypes } = require('librechat-data-provider');
|
||||||
|
const logViolation = require('~/cache/logViolation');
|
||||||
|
|
||||||
|
const getEnvironmentVariables = () => {
|
||||||
|
const STT_IP_MAX = parseInt(process.env.STT_IP_MAX) || 100;
|
||||||
|
const STT_IP_WINDOW = parseInt(process.env.STT_IP_WINDOW) || 1;
|
||||||
|
const STT_USER_MAX = parseInt(process.env.STT_USER_MAX) || 50;
|
||||||
|
const STT_USER_WINDOW = parseInt(process.env.STT_USER_WINDOW) || 1;
|
||||||
|
|
||||||
|
const sttIpWindowMs = STT_IP_WINDOW * 60 * 1000;
|
||||||
|
const sttIpMax = STT_IP_MAX;
|
||||||
|
const sttIpWindowInMinutes = sttIpWindowMs / 60000;
|
||||||
|
|
||||||
|
const sttUserWindowMs = STT_USER_WINDOW * 60 * 1000;
|
||||||
|
const sttUserMax = STT_USER_MAX;
|
||||||
|
const sttUserWindowInMinutes = sttUserWindowMs / 60000;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sttIpWindowMs,
|
||||||
|
sttIpMax,
|
||||||
|
sttIpWindowInMinutes,
|
||||||
|
sttUserWindowMs,
|
||||||
|
sttUserMax,
|
||||||
|
sttUserWindowInMinutes,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSTTHandler = (ip = true) => {
|
||||||
|
const { sttIpMax, sttIpWindowInMinutes, sttUserMax, sttUserWindowInMinutes } =
|
||||||
|
getEnvironmentVariables();
|
||||||
|
|
||||||
|
return async (req, res) => {
|
||||||
|
const type = ViolationTypes.STT_LIMIT;
|
||||||
|
const errorMessage = {
|
||||||
|
type,
|
||||||
|
max: ip ? sttIpMax : sttUserMax,
|
||||||
|
limiter: ip ? 'ip' : 'user',
|
||||||
|
windowInMinutes: ip ? sttIpWindowInMinutes : sttUserWindowInMinutes,
|
||||||
|
};
|
||||||
|
|
||||||
|
await logViolation(req, res, type, errorMessage);
|
||||||
|
res.status(429).json({ message: 'Too many STT requests. Try again later' });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSTTLimiters = () => {
|
||||||
|
const { sttIpWindowMs, sttIpMax, sttUserWindowMs, sttUserMax } = getEnvironmentVariables();
|
||||||
|
|
||||||
|
const sttIpLimiter = rateLimit({
|
||||||
|
windowMs: sttIpWindowMs,
|
||||||
|
max: sttIpMax,
|
||||||
|
handler: createSTTHandler(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const sttUserLimiter = rateLimit({
|
||||||
|
windowMs: sttUserWindowMs,
|
||||||
|
max: sttUserMax,
|
||||||
|
handler: createSTTHandler(false),
|
||||||
|
keyGenerator: function (req) {
|
||||||
|
return req.user?.id; // Use the user ID or NULL if not available
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { sttIpLimiter, sttUserLimiter };
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = createSTTLimiters;
|
||||||
68
api/server/middleware/speech/ttsLimiters.js
Normal file
68
api/server/middleware/speech/ttsLimiters.js
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
const rateLimit = require('express-rate-limit');
|
||||||
|
const { ViolationTypes } = require('librechat-data-provider');
|
||||||
|
const logViolation = require('~/cache/logViolation');
|
||||||
|
|
||||||
|
const getEnvironmentVariables = () => {
|
||||||
|
const TTS_IP_MAX = parseInt(process.env.TTS_IP_MAX) || 100;
|
||||||
|
const TTS_IP_WINDOW = parseInt(process.env.TTS_IP_WINDOW) || 1;
|
||||||
|
const TTS_USER_MAX = parseInt(process.env.TTS_USER_MAX) || 50;
|
||||||
|
const TTS_USER_WINDOW = parseInt(process.env.TTS_USER_WINDOW) || 1;
|
||||||
|
|
||||||
|
const ttsIpWindowMs = TTS_IP_WINDOW * 60 * 1000;
|
||||||
|
const ttsIpMax = TTS_IP_MAX;
|
||||||
|
const ttsIpWindowInMinutes = ttsIpWindowMs / 60000;
|
||||||
|
|
||||||
|
const ttsUserWindowMs = TTS_USER_WINDOW * 60 * 1000;
|
||||||
|
const ttsUserMax = TTS_USER_MAX;
|
||||||
|
const ttsUserWindowInMinutes = ttsUserWindowMs / 60000;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ttsIpWindowMs,
|
||||||
|
ttsIpMax,
|
||||||
|
ttsIpWindowInMinutes,
|
||||||
|
ttsUserWindowMs,
|
||||||
|
ttsUserMax,
|
||||||
|
ttsUserWindowInMinutes,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTTSHandler = (ip = true) => {
|
||||||
|
const { ttsIpMax, ttsIpWindowInMinutes, ttsUserMax, ttsUserWindowInMinutes } =
|
||||||
|
getEnvironmentVariables();
|
||||||
|
|
||||||
|
return async (req, res) => {
|
||||||
|
const type = ViolationTypes.TTS_LIMIT;
|
||||||
|
const errorMessage = {
|
||||||
|
type,
|
||||||
|
max: ip ? ttsIpMax : ttsUserMax,
|
||||||
|
limiter: ip ? 'ip' : 'user',
|
||||||
|
windowInMinutes: ip ? ttsIpWindowInMinutes : ttsUserWindowInMinutes,
|
||||||
|
};
|
||||||
|
|
||||||
|
await logViolation(req, res, type, errorMessage);
|
||||||
|
res.status(429).json({ message: 'Too many TTS requests. Try again later' });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createTTSLimiters = () => {
|
||||||
|
const { ttsIpWindowMs, ttsIpMax, ttsUserWindowMs, ttsUserMax } = getEnvironmentVariables();
|
||||||
|
|
||||||
|
const ttsIpLimiter = rateLimit({
|
||||||
|
windowMs: ttsIpWindowMs,
|
||||||
|
max: ttsIpMax,
|
||||||
|
handler: createTTSHandler(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const ttsUserLimiter = rateLimit({
|
||||||
|
windowMs: ttsUserWindowMs,
|
||||||
|
max: ttsUserMax,
|
||||||
|
handler: createTTSHandler(false),
|
||||||
|
keyGenerator: function (req) {
|
||||||
|
return req.user?.id; // Use the user ID or NULL if not available
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ttsIpLimiter, ttsUserLimiter };
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = createTTSLimiters;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { uaParser, checkBan, requireJwtAuth, createFileLimiters } = require('~/server/middleware');
|
const { uaParser, checkBan, requireJwtAuth, createFileLimiters } = require('~/server/middleware');
|
||||||
|
const { createTTSLimiters, createSTTLimiters } = require('~/server/middleware/speech');
|
||||||
const { createMulterInstance } = require('./multer');
|
const { createMulterInstance } = require('./multer');
|
||||||
|
|
||||||
const files = require('./files');
|
const files = require('./files');
|
||||||
|
|
@ -15,8 +16,10 @@ const initialize = async () => {
|
||||||
router.use(uaParser);
|
router.use(uaParser);
|
||||||
|
|
||||||
/* Important: stt/tts routes must be added before the upload limiters */
|
/* Important: stt/tts routes must be added before the upload limiters */
|
||||||
router.use('/stt', stt);
|
const { sttIpLimiter, sttUserLimiter } = createSTTLimiters();
|
||||||
router.use('/tts', tts);
|
const { ttsIpLimiter, ttsUserLimiter } = createTTSLimiters();
|
||||||
|
router.use('/stt', sttIpLimiter, sttUserLimiter, stt);
|
||||||
|
router.use('/tts', ttsIpLimiter, ttsUserLimiter, tts);
|
||||||
|
|
||||||
const upload = await createMulterInstance();
|
const upload = await createMulterInstance();
|
||||||
const { fileUploadIpLimiter, fileUploadUserLimiter } = createFileLimiters();
|
const { fileUploadIpLimiter, fileUploadUserLimiter } = createFileLimiters();
|
||||||
|
|
@ -24,9 +27,6 @@ const initialize = async () => {
|
||||||
router.post('/', upload.single('file'));
|
router.post('/', upload.single('file'));
|
||||||
router.post('/images', upload.single('file'));
|
router.post('/images', upload.single('file'));
|
||||||
|
|
||||||
router.use('/stt', stt);
|
|
||||||
router.use('/tts', tts);
|
|
||||||
|
|
||||||
router.use('/', files);
|
router.use('/', files);
|
||||||
router.use('/images', images);
|
router.use('/images', images);
|
||||||
router.use('/images/avatar', avatar);
|
router.use('/images/avatar', avatar);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const { RateLimitPrefix } = require('librechat-data-provider');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {TCustomConfig['rateLimits'] | undefined} rateLimits
|
* @param {TCustomConfig['rateLimits'] | undefined} rateLimits
|
||||||
|
|
@ -6,24 +8,41 @@ const handleRateLimits = (rateLimits) => {
|
||||||
if (!rateLimits) {
|
if (!rateLimits) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { fileUploads, conversationsImport } = rateLimits;
|
|
||||||
if (fileUploads) {
|
|
||||||
process.env.FILE_UPLOAD_IP_MAX = fileUploads.ipMax ?? process.env.FILE_UPLOAD_IP_MAX;
|
|
||||||
process.env.FILE_UPLOAD_IP_WINDOW =
|
|
||||||
fileUploads.ipWindowInMinutes ?? process.env.FILE_UPLOAD_IP_WINDOW;
|
|
||||||
process.env.FILE_UPLOAD_USER_MAX = fileUploads.userMax ?? process.env.FILE_UPLOAD_USER_MAX;
|
|
||||||
process.env.FILE_UPLOAD_USER_WINDOW =
|
|
||||||
fileUploads.userWindowInMinutes ?? process.env.FILE_UPLOAD_USER_WINDOW;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (conversationsImport) {
|
const rateLimitKeys = {
|
||||||
process.env.IMPORT_IP_MAX = conversationsImport.ipMax ?? process.env.IMPORT_IP_MAX;
|
fileUploads: RateLimitPrefix.FILE_UPLOAD,
|
||||||
process.env.IMPORT_IP_WINDOW =
|
conversationsImport: RateLimitPrefix.IMPORT,
|
||||||
conversationsImport.ipWindowInMinutes ?? process.env.IMPORT_IP_WINDOW;
|
tts: RateLimitPrefix.TTS,
|
||||||
process.env.IMPORT_USER_MAX = conversationsImport.userMax ?? process.env.IMPORT_USER_MAX;
|
stt: RateLimitPrefix.STT,
|
||||||
process.env.IMPORT_USER_WINDOW =
|
};
|
||||||
conversationsImport.userWindowInMinutes ?? process.env.IMPORT_USER_WINDOW;
|
|
||||||
}
|
Object.entries(rateLimitKeys).forEach(([key, prefix]) => {
|
||||||
|
const rateLimit = rateLimits[key];
|
||||||
|
if (rateLimit) {
|
||||||
|
setRateLimitEnvVars(prefix, rateLimit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set environment variables for rate limit configurations
|
||||||
|
*
|
||||||
|
* @param {string} prefix - Prefix for environment variable names
|
||||||
|
* @param {object} rateLimit - Rate limit configuration object
|
||||||
|
*/
|
||||||
|
const setRateLimitEnvVars = (prefix, rateLimit) => {
|
||||||
|
const envVarsMapping = {
|
||||||
|
ipMax: `${prefix}_IP_MAX`,
|
||||||
|
ipWindowInMinutes: `${prefix}_IP_WINDOW`,
|
||||||
|
userMax: `${prefix}_USER_MAX`,
|
||||||
|
userWindowInMinutes: `${prefix}_USER_WINDOW`,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.entries(envVarsMapping).forEach(([key, envVar]) => {
|
||||||
|
if (rateLimit[key] !== undefined) {
|
||||||
|
process.env[envVar] = rateLimit[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = handleRateLimits;
|
module.exports = handleRateLimits;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import type { TMessage } from 'librechat-data-provider';
|
||||||
import { useCustomAudioRef, MediaSourceAppender, usePauseGlobalAudio } from '~/hooks/Audio';
|
import { useCustomAudioRef, MediaSourceAppender, usePauseGlobalAudio } from '~/hooks/Audio';
|
||||||
import { useAuthContext } from '~/hooks';
|
import { useAuthContext } from '~/hooks';
|
||||||
import { globalAudioId } from '~/common';
|
import { globalAudioId } from '~/common';
|
||||||
|
import { getLatestText } from '~/utils';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
function timeoutPromise(ms: number, message?: string) {
|
function timeoutPromise(ms: number, message?: string) {
|
||||||
|
|
@ -47,13 +48,14 @@ export default function StreamAudio({ index = 0 }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const latestText = getLatestText(latestMessage);
|
||||||
const shouldFetch =
|
const shouldFetch =
|
||||||
token &&
|
token &&
|
||||||
automaticPlayback &&
|
automaticPlayback &&
|
||||||
isSubmitting &&
|
isSubmitting &&
|
||||||
latestMessage &&
|
latestMessage &&
|
||||||
!latestMessage.isCreatedByUser &&
|
!latestMessage.isCreatedByUser &&
|
||||||
(latestMessage.text || latestMessage.content) &&
|
latestText &&
|
||||||
latestMessage.messageId &&
|
latestMessage.messageId &&
|
||||||
!latestMessage.messageId.includes('_') &&
|
!latestMessage.messageId.includes('_') &&
|
||||||
!isFetching &&
|
!isFetching &&
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useEffect, useRef, useCallback } from 'react';
|
||||||
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps } from '~/common';
|
||||||
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
import { useChatContext, useAssistantsMapContext } from '~/Providers';
|
||||||
|
import { getLatestText, getLengthAndFirstFiveChars } from '~/utils';
|
||||||
import useCopyToClipboard from './useCopyToClipboard';
|
import useCopyToClipboard from './useCopyToClipboard';
|
||||||
|
|
||||||
export default function useMessageHelpers(props: TMessageProps) {
|
export default function useMessageHelpers(props: TMessageProps) {
|
||||||
|
|
@ -26,20 +27,25 @@ export default function useMessageHelpers(props: TMessageProps) {
|
||||||
const isLast = !children?.length;
|
const isLast = !children?.length;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let contentChanged = message?.content
|
if (conversation?.conversationId === 'new') {
|
||||||
? message?.content?.length !== latestText.current
|
return;
|
||||||
: message?.text !== latestText.current;
|
|
||||||
|
|
||||||
if (!isLast) {
|
|
||||||
contentChanged = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return;
|
return;
|
||||||
} else if (isLast && conversation?.conversationId !== 'new' && contentChanged) {
|
|
||||||
setLatestMessage({ ...message });
|
|
||||||
latestText.current = message?.content ? message.content.length : message.text;
|
|
||||||
}
|
}
|
||||||
|
if (!isLast) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = getLatestText(message);
|
||||||
|
const textKey = `${message?.messageId ?? ''}${getLengthAndFirstFiveChars(text)}`;
|
||||||
|
|
||||||
|
if (textKey === latestText.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
latestText.current = textKey;
|
||||||
|
setLatestMessage({ ...message });
|
||||||
}, [isLast, message, setLatestMessage, conversation?.conversationId]);
|
}, [isLast, message, setLatestMessage, conversation?.conversationId]);
|
||||||
|
|
||||||
const enterEdit = useCallback(
|
const enterEdit = useCallback(
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ const localStorageAtoms = {
|
||||||
autoScroll: atomWithLocalStorage('autoScroll', false),
|
autoScroll: atomWithLocalStorage('autoScroll', false),
|
||||||
showCode: atomWithLocalStorage('showCode', false),
|
showCode: atomWithLocalStorage('showCode', false),
|
||||||
hideSidePanel: atomWithLocalStorage('hideSidePanel', false),
|
hideSidePanel: atomWithLocalStorage('hideSidePanel', false),
|
||||||
modularChat: atomWithLocalStorage('modularChat', false),
|
modularChat: atomWithLocalStorage('modularChat', true),
|
||||||
LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true),
|
LaTeXParsing: atomWithLocalStorage('LaTeXParsing', true),
|
||||||
UsernameDisplay: atomWithLocalStorage('UsernameDisplay', true),
|
UsernameDisplay: atomWithLocalStorage('UsernameDisplay', true),
|
||||||
TextToSpeech: atomWithLocalStorage('textToSpeech', true),
|
TextToSpeech: atomWithLocalStorage('textToSpeech', true),
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ export * from './latex';
|
||||||
export * from './convos';
|
export * from './convos';
|
||||||
export * from './presets';
|
export * from './presets';
|
||||||
export * from './textarea';
|
export * from './textarea';
|
||||||
|
export * from './messages';
|
||||||
export * from './languages';
|
export * from './languages';
|
||||||
export * from './endpoints';
|
export * from './endpoints';
|
||||||
export * from './sharedLink';
|
export * from './sharedLink';
|
||||||
|
|
|
||||||
26
client/src/utils/messages.ts
Normal file
26
client/src/utils/messages.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { ContentTypes } from 'librechat-data-provider';
|
||||||
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
|
|
||||||
|
export const getLengthAndFirstFiveChars = (str?: string) => {
|
||||||
|
const length = str ? str.length : 0;
|
||||||
|
const firstFiveChars = str ? str.substring(0, 5) : '';
|
||||||
|
return `${length}${firstFiveChars}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLatestText = (message?: TMessage | null) => {
|
||||||
|
if (!message) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (message.text) {
|
||||||
|
return message.text;
|
||||||
|
}
|
||||||
|
if (message.content?.length) {
|
||||||
|
for (let i = message.content.length - 1; i >= 0; i--) {
|
||||||
|
const part = message.content[i];
|
||||||
|
if (part.type === ContentTypes.TEXT && part[ContentTypes.TEXT]?.value?.length > 0) {
|
||||||
|
return part[ContentTypes.TEXT].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
@ -273,6 +273,13 @@ const sttSchema = z.object({
|
||||||
.optional(),
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export enum RateLimitPrefix {
|
||||||
|
FILE_UPLOAD = 'FILE_UPLOAD',
|
||||||
|
IMPORT = 'IMPORT',
|
||||||
|
TTS = 'TTS',
|
||||||
|
STT = 'STT',
|
||||||
|
}
|
||||||
|
|
||||||
export const rateLimitSchema = z.object({
|
export const rateLimitSchema = z.object({
|
||||||
fileUploads: z
|
fileUploads: z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -290,6 +297,22 @@ export const rateLimitSchema = z.object({
|
||||||
userWindowInMinutes: z.number().optional(),
|
userWindowInMinutes: z.number().optional(),
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
tts: z
|
||||||
|
.object({
|
||||||
|
ipMax: z.number().optional(),
|
||||||
|
ipWindowInMinutes: z.number().optional(),
|
||||||
|
userMax: z.number().optional(),
|
||||||
|
userWindowInMinutes: z.number().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
stt: z
|
||||||
|
.object({
|
||||||
|
ipMax: z.number().optional(),
|
||||||
|
ipWindowInMinutes: z.number().optional(),
|
||||||
|
userMax: z.number().optional(),
|
||||||
|
userWindowInMinutes: z.number().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export enum EImageOutputType {
|
export enum EImageOutputType {
|
||||||
|
|
@ -646,6 +669,14 @@ export enum ViolationTypes {
|
||||||
* An issued ban.
|
* An issued ban.
|
||||||
*/
|
*/
|
||||||
BAN = 'ban',
|
BAN = 'ban',
|
||||||
|
/**
|
||||||
|
* TTS Request Limit Violation.
|
||||||
|
*/
|
||||||
|
TTS_LIMIT = 'tts_limit',
|
||||||
|
/**
|
||||||
|
* STT Request Limit Violation.
|
||||||
|
*/
|
||||||
|
STT_LIMIT = 'stt_limit',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -749,7 +780,7 @@ export enum Constants {
|
||||||
/** Key for the app's version. */
|
/** Key for the app's version. */
|
||||||
VERSION = 'v0.7.2',
|
VERSION = 'v0.7.2',
|
||||||
/** Key for the Custom Config's version (librechat.yaml). */
|
/** Key for the Custom Config's version (librechat.yaml). */
|
||||||
CONFIG_VERSION = '1.1.3',
|
CONFIG_VERSION = '1.1.4',
|
||||||
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
|
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
|
||||||
NO_PARENT = '00000000-0000-0000-0000-000000000000',
|
NO_PARENT = '00000000-0000-0000-0000-000000000000',
|
||||||
/** Fixed, encoded domain length for Azure OpenAI Assistants Function name parsing. */
|
/** Fixed, encoded domain length for Azure OpenAI Assistants Function name parsing. */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue