Skip to content

✏️ feat: LaTeX parsing for Messages #1585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions client/src/components/Chat/Messages/Content/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useRecoilValue } from 'recoil';
import React, { useState, useEffect } from 'react';
import type { TMessage } from 'librechat-data-provider';
import rehypeHighlight from 'rehype-highlight';
Expand All @@ -8,9 +9,10 @@ import rehypeKatex from 'rehype-katex';
import remarkMath from 'remark-math';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import { useChatContext } from '~/Providers';
import { langSubset, validateIframe } from '~/utils';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
import { langSubset, validateIframe, processLaTeX } from '~/utils';
import { useChatContext } from '~/Providers';
import store from '~/store';

type TCodeProps = {
inline: boolean;
Expand Down Expand Up @@ -42,11 +44,15 @@ const p = React.memo(({ children }: { children: React.ReactNode }) => {
const Markdown = React.memo(({ content, message, showCursor }: TContentProps) => {
const [cursor, setCursor] = useState('█');
const { isSubmitting, latestMessage } = useChatContext();
const LaTeXParsing = useRecoilValue<boolean>(store.LaTeXParsing);

const isInitializing = content === '<span className="result-streaming">█</span>';

const { isEdited, messageId } = message ?? {};
const isLatestMessage = messageId === latestMessage?.messageId;
const currentContent = content?.replace('z-index: 1;', '') ?? '';

const _content = content?.replace('z-index: 1;', '') ?? '';
const currentContent = LaTeXParsing ? processLaTeX(_content) : _content;

useEffect(() => {
let timer1: NodeJS.Timeout, timer2: NodeJS.Timeout;
Expand Down
26 changes: 20 additions & 6 deletions client/src/components/Nav/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import type { TDialogProps } from '~/common';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '~/components/ui';
import { GearIcon, DataIcon, UserIcon } from '~/components/svg';
import { General, Data, Account } from './SettingsTabs';
import { GearIcon, DataIcon, UserIcon, ExperimentIcon } from '~/components/svg';
import { General, Beta, Data, Account } from './SettingsTabs';
import { useMediaQuery, useLocalize } from '~/hooks';
import { cn } from '~/utils';

Expand All @@ -23,7 +24,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
</DialogHeader>
<div className="px-6">
<Tabs.Root
defaultValue="general"
defaultValue={SettingsTabValues.GENERAL}
className="flex flex-col gap-10 md:flex-row"
orientation="vertical"
>
Expand All @@ -44,7 +45,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
? 'flex-1 items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: '',
)}
value="general"
value={SettingsTabValues.GENERAL}
>
<GearIcon />
{localize('com_nav_setting_general')}
Expand All @@ -56,7 +57,19 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
? 'flex-1 items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: '',
)}
value="data"
value={SettingsTabValues.BETA}
>
<ExperimentIcon />
{localize('com_nav_setting_beta')}
</Tabs.Trigger>
<Tabs.Trigger
className={cn(
'group my-1 flex items-center justify-start gap-2 rounded-md px-2 py-1.5 text-sm text-black radix-state-active:bg-gray-100 radix-state-active:text-black dark:text-white dark:radix-state-active:bg-gray-800',
isSmallScreen
? 'flex-1 items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: '',
)}
value={SettingsTabValues.DATA}
>
<DataIcon />
{localize('com_nav_setting_data')}
Expand All @@ -68,13 +81,14 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
? 'flex-1 items-center justify-center text-sm dark:text-gray-500 dark:radix-state-active:text-white'
: '',
)}
value="account"
value={SettingsTabValues.ACCOUNT}
>
<UserIcon />
{localize('com_nav_setting_account')}
</Tabs.Trigger>
</Tabs.List>
<General />
<Beta />
<Data />
<Account />
</Tabs.Root>
Expand Down
9 changes: 7 additions & 2 deletions client/src/components/Nav/SettingsTabs/Account/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React from 'react';
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import Avatar from './Avatar';
import React from 'react';

function Account() {
return (
<Tabs.Content value="account" role="tabpanel" className="w-full md:min-h-[300px]">
<Tabs.Content
value={SettingsTabValues.ACCOUNT}
role="tabpanel"
className="w-full md:min-h-[300px]"
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<Avatar />
Expand Down
26 changes: 26 additions & 0 deletions client/src/components/Nav/SettingsTabs/Beta/Beta.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { memo } from 'react';
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import LaTeXParsing from './LaTeXParsing';
import ModularChat from './ModularChat';

function Beta() {
return (
<Tabs.Content
value={SettingsTabValues.BETA}
role="tabpanel"
className="w-full md:min-h-[300px]"
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<ModularChat />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<LaTeXParsing />
</div>
</div>
</Tabs.Content>
);
}

export default memo(Beta);
33 changes: 33 additions & 0 deletions client/src/components/Nav/SettingsTabs/Beta/LaTeXParsing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useRecoilState } from 'recoil';
import { Switch } from '~/components/ui';
import { useLocalize } from '~/hooks';
import store from '~/store';

export default function LaTeXParsingSwitch({
onCheckedChange,
}: {
onCheckedChange?: (value: boolean) => void;
}) {
const [LaTeXParsing, setLaTeXParsing] = useRecoilState<boolean>(store.LaTeXParsing);
const localize = useLocalize();

const handleCheckedChange = (value: boolean) => {
setLaTeXParsing(value);
if (onCheckedChange) {
onCheckedChange(value);
}
};

return (
<div className="flex items-center justify-between">
<div>{localize('com_nav_latex_parsing')} </div>
<Switch
id="LaTeXParsing"
checked={LaTeXParsing}
onCheckedChange={handleCheckedChange}
className="ml-4 mt-2"
data-testid="LaTeXParsing"
/>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ export default function ModularChatSwitch({

return (
<div className="flex items-center justify-between">
<div>
{`[${localize('com_ui_experimental')}]`} {localize('com_nav_modular_chat')}{' '}
</div>
<div>{localize('com_nav_modular_chat')} </div>
<Switch
id="modularChat"
checked={modularChat}
Expand Down
7 changes: 6 additions & 1 deletion client/src/components/Nav/SettingsTabs/Data/Data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
useRevokeAllUserKeysMutation,
useRevokeUserKeyMutation,
} from 'librechat-data-provider/react-query';
import { SettingsTabValues } from 'librechat-data-provider';
import React, { useState, useCallback, useRef } from 'react';
import { useOnClickOutside } from '~/hooks';
import DangerButton from '../DangerButton';
Expand Down Expand Up @@ -66,7 +67,11 @@ export const RevokeKeysButton = ({

function Data() {
return (
<Tabs.Content value="data" role="tabpanel" className="w-full md:min-h-[300px]">
<Tabs.Content
value={SettingsTabValues.DATA}
role="tabpanel"
className="w-full md:min-h-[300px]"
>
<div className="flex flex-col gap-3 text-sm text-gray-600 dark:text-gray-300">
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<RevokeKeysButton all={true} />
Expand Down
7 changes: 2 additions & 5 deletions client/src/components/Nav/SettingsTabs/General/General.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useRecoilState } from 'recoil';
import * as Tabs from '@radix-ui/react-tabs';
import { SettingsTabValues } from 'librechat-data-provider';
import React, { useState, useContext, useCallback, useRef } from 'react';
import { useClearConversationsMutation } from 'librechat-data-provider/react-query';
import {
Expand All @@ -14,7 +15,6 @@ import type { TDangerButtonProps } from '~/common';
import AutoScrollSwitch from './AutoScrollSwitch';
import { Dropdown } from '~/components/ui';
import DangerButton from '../DangerButton';
import ModularChat from './ModularChat';
import store from '~/store';

export const ThemeSelector = ({
Expand Down Expand Up @@ -167,7 +167,7 @@ function General() {

return (
<Tabs.Content
value="general"
value={SettingsTabValues.GENERAL}
role="tabpanel"
className="w-full md:min-h-[300px]"
ref={contentRef}
Expand All @@ -190,9 +190,6 @@ function General() {
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<AutoScrollSwitch />
</div>
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-700">
<ModularChat />
</div>
</div>
</Tabs.Content>
);
Expand Down
1 change: 1 addition & 0 deletions client/src/components/Nav/SettingsTabs/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { default as General } from './General/General';
export { ClearChatsButton } from './General/General';
export { default as Data } from './Data/Data';
export { default as Beta } from './Beta/Beta';
export { RevokeKeysButton } from './Data/Data';
export { default as Account } from './Account/Account';
27 changes: 27 additions & 0 deletions client/src/components/svg/ExperimentIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default function ExperimentIcon() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="icon-sm"
>
<path
d="M9 3H15M9 3V9.2759C9 9.74377 8.83597 10.1968 8.53644 10.5563L4.85085 14.979C4.30108 15.6387 4 16.4703 4 17.3291V17.3291C4 19.3565 5.64353 21 7.67094 21H16.3291C18.3565 21 20 19.3565 20 17.3291V17.3291C20 16.4703 19.6989 15.6387 19.1492 14.979L15.4636 10.5563C15.164 10.1968 15 9.74377 15 9.2759V3M9 3H8M15 3H16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
></path>
<path
d="M5 14.774C11.5 12.839 12.15 16.7089 18 14"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
></path>
</svg>
);
}
1 change: 1 addition & 0 deletions client/src/components/svg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ export { default as GoogleMinimalIcon } from './GoogleMinimalIcon';
export { default as AnthropicMinimalIcon } from './AnthropicMinimalIcon';
export { default as SendMessageIcon } from './SendMessageIcon';
export { default as UserIcon } from './UserIcon';
export { default as ExperimentIcon } from './ExperimentIcon';
5 changes: 4 additions & 1 deletion client/src/localization/languages/Eng.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default {
com_ui_limitation_harmful_biased:
'May occasionally produce harmful instructions or biased content',
com_ui_limitation_limited_2021: 'Limited knowledge of world and events after 2021',
com_ui_experimental: 'Experimental',
com_ui_experimental: 'Experimental Features',
com_ui_input: 'Input',
com_ui_close: 'Close',
com_ui_model: 'Model',
Expand Down Expand Up @@ -265,6 +265,8 @@ export default {
com_nav_welcome_message: 'How can I help you today?',
com_nav_auto_scroll: 'Auto-scroll to Newest on Open',
com_nav_modular_chat: 'Enable switching Endpoints mid-conversation',
com_nav_latex_parsing:
'Toggle parsing LaTeX in messages. Enabled by default but may affect performance on mobile or longer conversations.',
com_nav_profile_picture: 'Profile Picture',
com_nav_change_picture: 'Change picture',
com_nav_plugin_store: 'Plugin store',
Expand Down Expand Up @@ -303,6 +305,7 @@ export default {
com_nav_settings: 'Settings',
com_nav_search_placeholder: 'Search messages',
com_nav_setting_general: 'General',
com_nav_setting_beta: 'Beta features',
com_nav_setting_data: 'Data controls',
com_nav_setting_account: 'Account',
com_nav_language: 'Language',
Expand Down
20 changes: 20 additions & 0 deletions client/src/store/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ const modularChat = atom<boolean>({
] as const,
});

const LaTeXParsing = atom<boolean>({
key: 'LaTeXParsing',
default: true,
effects: [
({ setSelf, onSet }) => {
const savedValue = localStorage.getItem('LaTeXParsing');
if (savedValue != null) {
setSelf(savedValue === 'true');
}

onSet((newValue: unknown) => {
if (typeof newValue === 'boolean') {
localStorage.setItem('LaTeXParsing', newValue.toString());
}
});
},
] as const,
});

export default {
abortScroll,
optionSettings,
Expand All @@ -78,4 +97,5 @@ export default {
showPopover,
autoScroll,
modularChat,
LaTeXParsing,
};
1 change: 1 addition & 0 deletions client/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './json';
export * from './files';
export * from './latex';
export * from './presets';
export * from './languages';
export * from './endpoints';
Expand Down
Loading