Skip to content

Commit e42709b

Browse files
authored
🔍 feat: Show Messages from Search Result (#2699)
* refactor(Nav): delegate Search-specific variables/hooks to SearchContext * fix: safely determine firstTodayConvoId if convo is undefined * chore: remove empty line * feat: initial render of search messages * feat: SearchButtons * update Ko.ts * update localizations with new key phrases * chore: localization comparisons * fix: clear conversation state on searchQuery navigation * style: search messages view styling * refactor(Convo): consolidate logic to navigateWithLastTools from useNavigateToConvo * fix(SearchButtons): styling and correct navigation logic * fix(SearchBar): invalidate all message queries and invoke `clearText` if onChange value is empty * refactor(NewChat): consolidate new chat button logic to NewChatButtonIcon * chore: localizations for Nav date groups * chore: update comparisons * fix: early return from sendRequest to avoid quick searchQuery reset * style: Link Icon * chore: bump tiktoken, use o200k_base for gpt-4o
1 parent 638ac5b commit e42709b

36 files changed

+2742
-234
lines changed

api/app/clients/OpenAIClient.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ class OpenAIClient extends BaseClient {
308308
let tokenizer;
309309
this.encoding = 'text-davinci-003';
310310
if (this.isChatCompletion) {
311-
this.encoding = 'cl100k_base';
311+
this.encoding = this.modelOptions.model.includes('gpt-4o') ? 'o200k_base' : 'cl100k_base';
312312
tokenizer = this.constructor.getTokenizer(this.encoding);
313313
} else if (this.isUnofficialChatGptModel) {
314314
const extendSpecialTokens = {

api/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"passport-local": "^1.0.0",
9090
"pino": "^8.12.1",
9191
"sharp": "^0.32.6",
92-
"tiktoken": "^1.0.10",
92+
"tiktoken": "^1.0.15",
9393
"traverse": "^0.6.7",
9494
"ua-parser-js": "^1.0.36",
9595
"winston": "^3.11.0",
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createContext, useContext } from 'react';
2+
import useSearch from '~/hooks/Conversations/useSearch';
3+
type SearchContextType = ReturnType<typeof useSearch>;
4+
5+
export const SearchContext = createContext<SearchContextType>({} as SearchContextType);
6+
export const useSearchContext = () => useContext(SearchContext);

client/src/Providers/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { default as ToastProvider } from './ToastContext';
22
export { default as AssistantsProvider } from './AssistantsContext';
33
export * from './ChatContext';
44
export * from './ToastContext';
5+
export * from './SearchContext';
56
export * from './FileMapContext';
67
export * from './AssistantsContext';
78
export * from './AssistantsMapContext';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Suspense } from 'react';
2+
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
3+
import { UnfinishedMessage } from './MessageContent';
4+
import { DelayedRender } from '~/components/ui';
5+
import MarkdownLite from './MarkdownLite';
6+
import { cn } from '~/utils';
7+
import Part from './Part';
8+
9+
const SearchContent = ({ message }: { message: TMessage }) => {
10+
const { messageId } = message;
11+
if (Array.isArray(message.content) && message.content.length > 0) {
12+
return (
13+
<>
14+
{message.content
15+
.filter((part: TMessageContentParts | undefined) => part)
16+
.map((part: TMessageContentParts | undefined, idx: number) => {
17+
if (!part) {
18+
return null;
19+
}
20+
return (
21+
<Part
22+
key={`display-${messageId}-${idx}`}
23+
showCursor={false}
24+
isSubmitting={false}
25+
part={part}
26+
message={message}
27+
/>
28+
);
29+
})}
30+
{message.unfinished && (
31+
<Suspense>
32+
<DelayedRender delay={250}>
33+
<UnfinishedMessage message={message} key={`unfinished-${messageId}`} />
34+
</DelayedRender>
35+
</Suspense>
36+
)}
37+
</>
38+
);
39+
}
40+
41+
return (
42+
<div
43+
className={cn(
44+
'markdown prose dark:prose-invert light w-full break-words',
45+
message.isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
46+
)}
47+
>
48+
<MarkdownLite content={message.text ?? ''} />
49+
</div>
50+
);
51+
};
52+
53+
export default SearchContent;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import { cn } from '~/utils';
3+
4+
const MinimalMessages = React.forwardRef(
5+
(
6+
props: { children: React.ReactNode; className?: string },
7+
ref: React.ForwardedRef<HTMLDivElement>,
8+
) => {
9+
return (
10+
<div
11+
className={cn(
12+
'relative flex w-full grow overflow-hidden bg-white dark:bg-gray-800',
13+
props.className,
14+
)}
15+
>
16+
<div className="transition-width relative h-full w-full flex-1 overflow-auto bg-white dark:bg-gray-800">
17+
<div className="flex h-full flex-col" role="presentation" tabIndex={0}>
18+
<div className="flex-1 overflow-hidden overflow-y-auto">
19+
<div className="dark:gpt-dark-gray relative h-full">
20+
<div
21+
ref={ref}
22+
style={{
23+
height: '100%',
24+
overflowY: 'auto',
25+
width: '100%',
26+
}}
27+
>
28+
<div className="flex flex-col pb-9 text-sm dark:bg-transparent">
29+
{props.children}
30+
<div className="dark:gpt-dark-gray group h-0 w-full flex-shrink-0 dark:border-gray-800/50" />
31+
</div>
32+
</div>
33+
</div>
34+
</div>
35+
</div>
36+
</div>
37+
</div>
38+
);
39+
},
40+
);
41+
42+
export default MinimalMessages;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Link } from 'lucide-react';
2+
import type { TMessage } from 'librechat-data-provider';
3+
import { useLocalize, useNavigateToConvo } from '~/hooks';
4+
import { useSearchContext } from '~/Providers';
5+
import { getConversationById } from '~/utils';
6+
7+
export default function SearchButtons({ message }: { message: TMessage }) {
8+
const localize = useLocalize();
9+
const { searchQueryRes } = useSearchContext();
10+
const { navigateWithLastTools } = useNavigateToConvo();
11+
12+
if (!message.conversationId) {
13+
return null;
14+
}
15+
16+
const clickHandler = (event: React.MouseEvent<HTMLAnchorElement>) => {
17+
event.preventDefault();
18+
19+
const conversation = getConversationById(searchQueryRes?.data, message.conversationId);
20+
if (!conversation) {
21+
return;
22+
}
23+
24+
document.title = message.title ?? '';
25+
navigateWithLastTools(conversation);
26+
};
27+
28+
return (
29+
<div className="visible mt-0 flex items-center justify-center gap-1 self-end text-gray-400 lg:justify-start">
30+
<a
31+
className="ml-0 flex cursor-pointer items-center gap-1.5 rounded-md p-1 text-xs hover:text-gray-900 hover:underline dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400"
32+
onClick={clickHandler}
33+
title={localize('com_ui_go_to_conversation')}
34+
>
35+
<Link className="icon-sm" />
36+
{message.title}
37+
</a>
38+
</div>
39+
);
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useRecoilValue } from 'recoil';
2+
import { useAuthContext, useLocalize } from '~/hooks';
3+
import type { TMessageProps } from '~/common';
4+
import Icon from '~/components/Chat/Messages/MessageIcon';
5+
import SearchContent from './Content/SearchContent';
6+
import SearchButtons from './SearchButtons';
7+
import SubRow from './SubRow';
8+
import { cn } from '~/utils';
9+
import store from '~/store';
10+
11+
export default function Message({ message }: Pick<TMessageProps, 'message'>) {
12+
const UsernameDisplay = useRecoilValue<boolean>(store.UsernameDisplay);
13+
const { user } = useAuthContext();
14+
const localize = useLocalize();
15+
16+
if (!message) {
17+
return null;
18+
}
19+
20+
const { isCreatedByUser } = message ?? {};
21+
22+
let messageLabel = '';
23+
if (isCreatedByUser) {
24+
messageLabel = UsernameDisplay ? user?.name || user?.username : localize('com_user_message');
25+
} else {
26+
messageLabel = message.sender;
27+
}
28+
29+
return (
30+
<>
31+
<div className="text-token-text-primary w-full border-0 bg-transparent dark:border-0 dark:bg-transparent">
32+
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
33+
<div className="final-completion group mx-auto flex flex-1 gap-3 text-base md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
34+
<div className="relative flex flex-shrink-0 flex-col items-end">
35+
<div>
36+
<div className="pt-0.5">
37+
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
38+
<Icon message={message} />
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
<div
44+
className={cn('relative flex w-11/12 flex-col', isCreatedByUser ? '' : 'agent-turn')}
45+
>
46+
<div className="select-none font-semibold">{messageLabel}</div>
47+
<div className="flex-col gap-1 md:gap-3">
48+
<div className="flex max-w-full flex-grow flex-col gap-0">
49+
<SearchContent message={message} />
50+
</div>
51+
</div>
52+
<SubRow classes="text-xs">
53+
<SearchButtons message={message} />
54+
</SubRow>
55+
</div>
56+
</div>
57+
</div>
58+
</div>
59+
</>
60+
);
61+
}

client/src/components/Chat/SearchView.tsx

-22
This file was deleted.

client/src/components/Conversations/Conversations.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useMemo, memo } from 'react';
22
import { parseISO, isToday } from 'date-fns';
33
import { TConversation } from 'librechat-data-provider';
44
import { groupConversationsByDate } from '~/utils';
5+
import { useLocalize } from '~/hooks';
56
import Convo from './Convo';
67

78
const Conversations = ({
@@ -13,12 +14,14 @@ const Conversations = ({
1314
moveToTop: () => void;
1415
toggleNav: () => void;
1516
}) => {
17+
const localize = useLocalize();
1618
const groupedConversations = useMemo(
1719
() => groupConversationsByDate(conversations),
1820
[conversations],
1921
);
2022
const firstTodayConvoId = useMemo(
21-
() => conversations.find((convo) => isToday(parseISO(convo.updatedAt)))?.conversationId,
23+
() =>
24+
conversations.find((convo) => convo && isToday(parseISO(convo.updatedAt)))?.conversationId,
2225
[conversations],
2326
);
2427

@@ -37,7 +40,7 @@ const Conversations = ({
3740
paddingLeft: '10px',
3841
}}
3942
>
40-
{groupName}
43+
{localize(groupName) || groupName}
4144
</div>
4245
{convos.map((convo, i) => (
4346
<Convo

client/src/components/Conversations/Convo.tsx

+2-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useRecoilValue } from 'recoil';
22
import { useParams } from 'react-router-dom';
33
import { useState, useRef, useMemo } from 'react';
4-
import { EModelEndpoint, LocalStorageKeys } from 'librechat-data-provider';
54
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
65
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
76
import { useUpdateConversationMutation } from '~/data-provider';
@@ -26,8 +25,8 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
2625
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
2726
const activeConvos = useRecoilValue(store.allConversationsSelector);
2827
const { data: endpointsConfig } = useGetEndpointsQuery();
28+
const { navigateWithLastTools } = useNavigateToConvo();
2929
const { refreshConversations } = useConversations();
30-
const { navigateToConvo } = useNavigateToConvo();
3130
const { showToast } = useToastContext();
3231

3332
const { conversationId, title } = conversation;
@@ -51,23 +50,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
5150

5251
// set document title
5352
document.title = title;
54-
55-
// set conversation to the new conversation
56-
if (conversation?.endpoint === EModelEndpoint.gptPlugins) {
57-
let lastSelectedTools = [];
58-
try {
59-
lastSelectedTools =
60-
JSON.parse(localStorage.getItem(LocalStorageKeys.LAST_TOOLS) ?? '') ?? [];
61-
} catch (e) {
62-
// console.error(e);
63-
}
64-
navigateToConvo({
65-
...conversation,
66-
tools: conversation?.tools?.length ? conversation?.tools : lastSelectedTools,
67-
});
68-
} else {
69-
navigateToConvo(conversation);
70-
}
53+
navigateWithLastTools(conversation);
7154
};
7255

7356
const renameHandler = (e: MouseEvent<HTMLButtonElement>) => {

0 commit comments

Comments
 (0)