Skip to content

Commit 1ff4841

Browse files
authored
🧹 chore: pre-release cleanup 2 (#3600)
* refactor: scrollToEnd * fix(validateConvoAccess): search conversation by ID for proper validation * feat: Add unique index for conversationId and user in convoSchema * refactor: Update font sizes 1 rem -> font-size-base in style.css * fix: Assistants map type issues * refactor: Remove obsolete scripts * fix: Update DropdownNoState component to handle both string and OptionType values * refactor: Remove config/loader.js file * fix: remove crypto.randomBytes(); refactor: Create reusable function for generating token and hash
1 parent 6fead10 commit 1ff4841

File tree

20 files changed

+172
-637
lines changed

20 files changed

+172
-637
lines changed

api/models/Conversation.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@ const Conversation = require('./schema/convoSchema');
22
const { getMessages, deleteMessages } = require('./Message');
33
const logger = require('~/config/winston');
44

5+
/**
6+
* Searches for a conversation by conversationId and returns a lean document with only conversationId and user.
7+
* @param {string} conversationId - The conversation's ID.
8+
* @returns {Promise<{conversationId: string, user: string} | null>} The conversation object with selected fields or null if not found.
9+
*/
10+
const searchConversation = async (conversationId) => {
11+
try {
12+
return await Conversation.findOne({ conversationId }, 'conversationId user').lean();
13+
} catch (error) {
14+
logger.error('[searchConversation] Error searching conversation', error);
15+
throw new Error('Error searching conversation');
16+
}
17+
};
18+
519
/**
620
* Retrieves a single conversation for a given user and conversation ID.
721
* @param {string} user - The user's ID.
@@ -19,6 +33,7 @@ const getConvo = async (user, conversationId) => {
1933

2034
module.exports = {
2135
Conversation,
36+
searchConversation,
2237
/**
2338
* Saves a conversation to the database.
2439
* @param {Object} req - The request object.

api/models/schema/convoSchema.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
6161
}
6262

6363
convoSchema.index({ createdAt: 1, updatedAt: 1 });
64+
convoSchema.index({ conversationId: 1, user: 1 }, { unique: true });
6465

6566
const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
6667

api/server/middleware/validate/convoAccess.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const { Constants, ViolationTypes, Time } = require('librechat-data-provider');
2+
const { searchConversation } = require('~/models/Conversation');
23
const denyRequest = require('~/server/middleware/denyRequest');
34
const { logViolation, getLogStores } = require('~/cache');
45
const { isEnabled } = require('~/server/utils');
5-
const { getConvo } = require('~/models');
66

77
const { USE_REDIS, CONVO_ACCESS_VIOLATION_SCORE: score = 0 } = process.env ?? {};
88

@@ -42,7 +42,7 @@ const validateConvoAccess = async (req, res, next) => {
4242
}
4343
}
4444

45-
const conversation = await getConvo(userId, conversationId);
45+
const conversation = await searchConversation(conversationId);
4646

4747
if (!conversation) {
4848
return next();

api/server/services/AuthService.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const bcrypt = require('bcryptjs');
2+
const { webcrypto } = require('node:crypto');
23
const { SystemRoles, errorsToString } = require('librechat-data-provider');
34
const {
45
findUser,
@@ -53,14 +54,23 @@ const logoutUser = async (userId, refreshToken) => {
5354
}
5455
};
5556

57+
/**
58+
* Creates Token and corresponding Hash for verification
59+
* @returns {[string, string]}
60+
*/
61+
const createTokenHash = () => {
62+
const token = Buffer.from(webcrypto.getRandomValues(new Uint8Array(32))).toString('hex');
63+
const hash = bcrypt.hashSync(token, 10);
64+
return [token, hash];
65+
};
66+
5667
/**
5768
* Send Verification Email
5869
* @param {Partial<MongoUser> & { _id: ObjectId, email: string, name: string}} user
5970
* @returns {Promise<void>}
6071
*/
6172
const sendVerificationEmail = async (user) => {
62-
let verifyToken = crypto.randomBytes(32).toString('hex');
63-
const hash = bcrypt.hashSync(verifyToken, 10);
73+
const [verifyToken, hash] = createTokenHash();
6474

6575
const verificationLink = `${
6676
domains.client
@@ -226,8 +236,7 @@ const requestPasswordReset = async (req) => {
226236
await token.deleteOne();
227237
}
228238

229-
let resetToken = crypto.randomBytes(32).toString('hex');
230-
const hash = bcrypt.hashSync(resetToken, 10);
239+
const [resetToken, hash] = createTokenHash();
231240

232241
await new Token({
233242
userId: user._id,
@@ -365,8 +374,7 @@ const resendVerificationEmail = async (req) => {
365374
return { status: 200, message: genericVerificationMessage };
366375
}
367376

368-
let verifyToken = crypto.randomBytes(32).toString('hex');
369-
const hash = bcrypt.hashSync(verifyToken, 10);
377+
const [verifyToken, hash] = createTokenHash();
370378

371379
const verificationLink = `${
372380
domains.client

client/src/components/Chat/Landing.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ export default function Landing({ Header }: { Header?: ReactNode }) {
3131
endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
3232

3333
const isAssistant = isAssistantsEndpoint(endpoint);
34-
const assistant = isAssistant && assistantMap[endpoint][assistant_id ?? ''];
35-
const assistantName = (assistant && assistant.name) || '';
36-
const assistantDesc = (assistant && assistant.description) || '';
37-
const avatar = (assistant && (assistant.metadata?.avatar as string)) || '';
34+
const assistant = isAssistant ? assistantMap?.[endpoint][assistant_id ?? ''] : undefined;
35+
const assistantName = assistant && assistant.name;
36+
const assistantDesc = assistant && assistant.description;
37+
const avatar = assistant && (assistant.metadata?.avatar as string);
3838

3939
const containerClassName =
4040
'shadow-stroke relative flex h-full items-center justify-center rounded-full bg-white text-black';

client/src/components/Chat/Messages/MessageParts.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useRecoilValue } from 'recoil';
22
import type { TMessageProps } from '~/common';
33
import Icon from '~/components/Chat/Messages/MessageIcon';
4+
import { useMessageHelpers, useLocalize } from '~/hooks';
45
import ContentParts from './Content/ContentParts';
56
import SiblingSwitch from './SiblingSwitch';
6-
import { useMessageHelpers } from '~/hooks';
77
// eslint-disable-next-line import/no-cycle
88
import MultiMessage from './MultiMessage';
99
import HoverButtons from './HoverButtons';
@@ -12,6 +12,7 @@ import { cn } from '~/utils';
1212
import store from '~/store';
1313

1414
export default function Message(props: TMessageProps) {
15+
const localize = useLocalize();
1516
const { message, siblingIdx, siblingCount, setSiblingIdx, currentEditId, setCurrentEditId } =
1617
props;
1718

@@ -31,7 +32,6 @@ export default function Message(props: TMessageProps) {
3132
regenerateMessage,
3233
} = useMessageHelpers(props);
3334
const fontSize = useRecoilValue(store.fontSize);
34-
3535
const { content, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
3636

3737
if (!message) {
@@ -59,12 +59,13 @@ export default function Message(props: TMessageProps) {
5959
<div
6060
className={cn(
6161
'relative flex w-full flex-col',
62-
isCreatedByUser != null ? '' : 'agent-turn',
62+
isCreatedByUser === true ? '' : 'agent-turn',
6363
)}
6464
>
6565
<div className={cn('select-none font-semibold', fontSize)}>
66-
{/* TODO: LOCALIZE */}
67-
{isCreatedByUser != null ? 'You' : (assistant && assistant.name) ?? 'Assistant'}
66+
{isCreatedByUser === true
67+
? localize('com_user_message')
68+
: (assistant && assistant.name) ?? localize('com_ui_assistant')}
6869
</div>
6970
<div className="flex-col gap-1 md:gap-3">
7071
<div className="flex max-w-full flex-grow flex-col gap-0">
@@ -76,7 +77,7 @@ export default function Message(props: TMessageProps) {
7677
message={message}
7778
messageId={messageId}
7879
enterEdit={enterEdit}
79-
error={!!error}
80+
error={!!(error ?? false)}
8081
isSubmitting={isSubmitting}
8182
unfinished={unfinished ?? false}
8283
isCreatedByUser={isCreatedByUser ?? true}

client/src/components/Endpoints/ConvoIcon.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import React, { useMemo } from 'react';
12
import { isAssistantsEndpoint } from 'librechat-data-provider';
23
import type {
34
TAssistantsMap,
@@ -20,7 +21,7 @@ export default function ConvoIcon({
2021
}: {
2122
conversation: TConversation | TPreset | null;
2223
endpointsConfig: TEndpointsConfig;
23-
assistantMap: TAssistantsMap;
24+
assistantMap: TAssistantsMap | undefined;
2425
containerClassName?: string;
2526
context?: 'message' | 'nav' | 'landing' | 'menu-item';
2627
className?: string;
@@ -29,11 +30,19 @@ export default function ConvoIcon({
2930
const iconURL = conversation?.iconURL;
3031
let endpoint = conversation?.endpoint;
3132
endpoint = getIconEndpoint({ endpointsConfig, iconURL, endpoint });
32-
const assistant =
33-
isAssistantsEndpoint(endpoint) && assistantMap?.[endpoint]?.[conversation?.assistant_id ?? ''];
34-
const assistantName = (assistant && assistant?.name) || '';
33+
const assistant = useMemo(() => {
34+
if (!isAssistantsEndpoint(conversation?.endpoint)) {
35+
return undefined;
36+
}
3537

36-
const avatar = (assistant && (assistant?.metadata?.avatar as string)) || '';
38+
const endpointKey = conversation?.endpoint ?? '';
39+
const assistantId = conversation?.assistant_id ?? '';
40+
41+
return assistantMap?.[endpointKey] ? assistantMap[endpointKey][assistantId] : undefined;
42+
}, [conversation?.endpoint, conversation?.assistant_id, assistantMap]);
43+
const assistantName = assistant && (assistant.name ?? '');
44+
45+
const avatar = (assistant && (assistant.metadata?.avatar as string)) || '';
3746
const endpointIconURL = getEndpointField(endpointsConfig, endpoint, 'iconURL');
3847
const iconKey = getIconKey({ endpoint, endpointsConfig, endpointIconURL });
3948
const Icon = icons[iconKey];

client/src/components/SidePanel/Builder/ActionsInput.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ export default function ActionsInput({
6262
const [functions, setFunctions] = useState<FunctionTool[] | null>(null);
6363

6464
useEffect(() => {
65-
if (!action?.metadata?.raw_spec) {
65+
if (!action?.metadata.raw_spec) {
6666
return;
6767
}
6868
setInputValue(action.metadata.raw_spec);
6969
debouncedValidation(action.metadata.raw_spec, handleResult);
70-
}, [action?.metadata?.raw_spec]);
70+
}, [action?.metadata.raw_spec]);
7171

7272
useEffect(() => {
7373
if (!validationResult || !validationResult.status || !validationResult.spec) {
@@ -100,7 +100,7 @@ export default function ActionsInput({
100100
},
101101
onError(error) {
102102
showToast({
103-
message: (error as Error)?.message ?? localize('com_assistants_update_actions_error'),
103+
message: (error as Error).message ?? localize('com_assistants_update_actions_error'),
104104
status: 'error',
105105
});
106106
},
@@ -180,7 +180,7 @@ export default function ActionsInput({
180180
assistant_id,
181181
endpoint,
182182
version,
183-
model: assistantMap[endpoint][assistant_id].model,
183+
model: assistantMap?.[endpoint][assistant_id].model ?? '',
184184
});
185185
});
186186

@@ -195,16 +195,32 @@ export default function ActionsInput({
195195
debouncedValidation(newValue, handleResult);
196196
};
197197

198+
const submitContext = () => {
199+
if (updateAction.isLoading) {
200+
return <Spinner className="icon-md" />;
201+
} else if (action?.action_id.length ?? 0) {
202+
return localize('com_ui_update');
203+
} else {
204+
return localize('com_ui_create');
205+
}
206+
};
207+
198208
return (
199209
<>
200210
<div className="">
201211
<div className="mb-1 flex flex-wrap items-center justify-between gap-4">
202-
<label className="text-token-text-primary whitespace-nowrap font-medium">Schema</label>
212+
<label
213+
htmlFor="example-schema"
214+
className="text-token-text-primary whitespace-nowrap font-medium"
215+
>
216+
Schema
217+
</label>
203218
<div className="flex items-center gap-2">
204219
{/* <button className="btn btn-neutral border-token-border-light relative h-8 min-w-[100px] rounded-lg font-medium">
205220
<div className="flex w-full items-center justify-center text-xs">Import from URL</div>
206221
</button> */}
207222
<select
223+
id="example-schema"
208224
onChange={(e) => console.log(e.target.value)}
209225
className="border-token-border-medium h-8 min-w-[100px] rounded-lg border bg-transparent px-2 py-0 text-sm"
210226
>
@@ -250,23 +266,15 @@ export default function ActionsInput({
250266
</div>
251267
)}
252268
<div className="mt-4">
253-
<div className="mb-1.5 flex items-center">
254-
<span className="" data-state="closed">
255-
<label className="text-token-text-primary block font-medium">
256-
{localize('com_ui_privacy_policy')}
257-
</label>
258-
</span>
259-
</div>
260269
<div className="rounded-md border border-gray-300 px-3 py-2 shadow-none focus-within:border-gray-800 focus-within:ring-1 focus-within:ring-gray-800 dark:border-gray-700 dark:bg-gray-700 dark:focus-within:border-gray-500 dark:focus-within:ring-gray-500">
261-
<label
262-
htmlFor="privacyPolicyUrl"
263-
className="block text-xs font-medium text-gray-900 dark:text-gray-100"
264-
/>
270+
<label htmlFor="privacyPolicyUrl" className="block text-xs text-text-secondary">
271+
Privacy Policy URL
272+
</label>
265273
<div className="relative">
266274
<input
267275
name="privacyPolicyUrl"
268276
id="privacyPolicyUrl"
269-
className="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 shadow-none outline-none focus-within:shadow-none focus-within:outline-none focus-within:ring-0 focus:border-none focus:ring-0 dark:bg-gray-700 dark:text-gray-100 sm:text-sm"
277+
className="block w-full border-0 bg-transparent p-0 placeholder-text-secondary shadow-none outline-none focus-within:shadow-none focus-within:outline-none focus-within:ring-0 focus:border-none focus:ring-0 sm:text-sm"
270278
placeholder="https://api.example-weather-app.com/privacy"
271279
// value=""
272280
/>
@@ -280,13 +288,7 @@ export default function ActionsInput({
280288
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
281289
type="button"
282290
>
283-
{updateAction.isLoading ? (
284-
<Spinner className="icon-md" />
285-
) : action?.action_id ? (
286-
localize('com_ui_update')
287-
) : (
288-
localize('com_ui_create')
289-
)}
291+
{submitContext()}
290292
</button>
291293
</div>
292294
</>

client/src/components/SidePanel/Builder/AssistantAvatar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ function Avatar({
5151
const { showToast } = useToastContext();
5252

5353
const activeModel = useMemo(() => {
54-
return assistantsMap[endpoint][assistant_id ?? '']?.model ?? '';
54+
return assistantsMap?.[endpoint][assistant_id ?? '']?.model ?? '';
5555
}, [assistantsMap, endpoint, assistant_id]);
5656

5757
const { mutate: uploadAvatar } = useUploadAssistantAvatarMutation({
5858
onMutate: () => {
5959
setProgress(0.4);
6060
},
6161
onSuccess: (data, vars) => {
62-
if (!vars.postCreation) {
62+
if (vars.postCreation !== true) {
6363
showToast({ message: localize('com_ui_upload_success') });
6464
} else if (lastSeenCreatedId.current !== createMutation.data?.id) {
6565
lastSeenCreatedId.current = createMutation.data?.id ?? '';
@@ -136,9 +136,9 @@ function Avatar({
136136
createMutation.isSuccess &&
137137
input &&
138138
previewUrl &&
139-
previewUrl?.includes('base64')
139+
previewUrl.includes('base64')
140140
);
141-
if (sharedUploadCondition && lastSeenCreatedId.current === createMutation.data?.id) {
141+
if (sharedUploadCondition && lastSeenCreatedId.current === createMutation.data.id) {
142142
return;
143143
}
144144

@@ -149,8 +149,8 @@ function Avatar({
149149
formData.append('file', input, input.name);
150150
formData.append('assistant_id', createMutation.data.id);
151151

152-
if (typeof createMutation.data?.metadata === 'object') {
153-
formData.append('metadata', JSON.stringify(createMutation.data?.metadata));
152+
if (typeof createMutation.data.metadata === 'object') {
153+
formData.append('metadata', JSON.stringify(createMutation.data.metadata));
154154
}
155155

156156
uploadAvatar({

0 commit comments

Comments
 (0)