mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🅰️ feat: Dynamic Font Size (#3568)
* wip: general setup * added: translations for font-size * fix: prompts related linter errors and add theming * wip: font size selector * refactor: Update FontSizeSelector options display property * refactor: adjust Intersection Observer threshold and debounce rate * feat: Update selectedPrompt type in PromptForm to be optional * feat: dynamic font size * refactor: Update message font size in navigation bar * refactor: Update code analyze block styling * refactor: ProgressText dynamic font size * refactor: move FontSizeSelector component to Chat from General settings * fix: HoverButtons styling for better visibility * refactor: Update HoverButtons styling for better visibility --------- Co-authored-by: kraken <solodarken@gmail.com>
This commit is contained in:
parent
b390ba781f
commit
2bb0842650
44 changed files with 340 additions and 132 deletions
|
|
@ -26,6 +26,11 @@ import type {
|
|||
import type { UseMutationResult } from '@tanstack/react-query';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
export enum PromptsEditorMode {
|
||||
SIMPLE = 'simple',
|
||||
ADVANCED = 'advanced',
|
||||
}
|
||||
|
||||
export type AudioChunk = {
|
||||
audio: string;
|
||||
isFinal: boolean;
|
||||
|
|
|
|||
|
|
@ -54,11 +54,11 @@ export default function CodeAnalyze({
|
|||
onClick={() => setShowCode((prev) => !prev)}
|
||||
inProgressText="Analyzing"
|
||||
finishedText="Finished analyzing"
|
||||
hasInput={!!code?.length}
|
||||
hasInput={!!code.length}
|
||||
/>
|
||||
</div>
|
||||
{showCode && (
|
||||
<div className="mb-3 mt-0.5 overflow-hidden rounded-xl bg-black">
|
||||
<div className="code-analyze-block mb-3 mt-0.5 overflow-hidden rounded-xl bg-black">
|
||||
<MarkdownLite content={code ? `\`\`\`python\n${code}\n\`\`\`` : ''} />
|
||||
{logs && (
|
||||
<div className="bg-gray-700 p-4 text-xs">
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ const DisplayMessage = ({ text, isCreatedByUser, message, showCursor }: TDisplay
|
|||
<Container message={message}>
|
||||
<div
|
||||
className={cn(
|
||||
showCursor && !!text?.length ? 'result-streaming' : '',
|
||||
'markdown prose dark:prose-invert light w-full break-words',
|
||||
showCursor && !!text.length ? 'result-streaming' : '',
|
||||
'markdown prose message-content dark:prose-invert light w-full break-words',
|
||||
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-100',
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ const DisplayMessage = ({ text, isCreatedByUser = false, message, showCursor }:
|
|||
return (
|
||||
<div
|
||||
className={cn(
|
||||
showCursor && !!text?.length ? 'result-streaming' : '',
|
||||
'markdown prose dark:prose-invert light w-full break-words',
|
||||
showCursor && !!text.length ? 'result-streaming' : '',
|
||||
'markdown prose message-content dark:prose-invert light w-full break-words',
|
||||
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
|
||||
)}
|
||||
>
|
||||
|
|
@ -58,14 +58,12 @@ export default function Part({
|
|||
// Access the value property
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="markdown prose dark:prose-invert light dark:text-gray-70 my-1 w-full break-words">
|
||||
<DisplayMessage
|
||||
text={part[ContentTypes.TEXT].value}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</div>
|
||||
<DisplayMessage
|
||||
text={part[ContentTypes.TEXT].value}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
} else if (
|
||||
|
|
@ -107,14 +105,12 @@ export default function Part({
|
|||
if (isSubmitting && showCursor) {
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="markdown prose dark:prose-invert light dark:text-gray-70 my-1 w-full break-words">
|
||||
<DisplayMessage
|
||||
text={''}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</div>
|
||||
<DisplayMessage
|
||||
text={''}
|
||||
isCreatedByUser={message.isCreatedByUser}
|
||||
message={message}
|
||||
showCursor={showCursor}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
import * as Popover from '@radix-ui/react-popover';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
const wrapperClass =
|
||||
'progress-text-wrapper text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5';
|
||||
|
||||
const Wrapper = ({ popover, children }: { popover: boolean; children: React.ReactNode }) => {
|
||||
if (popover) {
|
||||
return (
|
||||
<div className="text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5">
|
||||
<div className={wrapperClass}>
|
||||
<Popover.Trigger asChild>
|
||||
<div
|
||||
className="absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
className="progress-text-content absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
data-projection-id="78"
|
||||
>
|
||||
|
|
@ -19,9 +22,9 @@ const Wrapper = ({ popover, children }: { popover: boolean; children: React.Reac
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="text-token-text-secondary relative -mt-[0.75px] h-5 w-full leading-5">
|
||||
<div className={wrapperClass}>
|
||||
<div
|
||||
className="absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
className="progress-text-content absolute left-0 top-0 line-clamp-1 overflow-visible"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
data-projection-id="78"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -72,12 +72,12 @@ export default function HoverButtons({
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-400 lg:justify-start">
|
||||
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-500 lg:justify-start">
|
||||
{TextToSpeech && <MessageAudio index={index} message={message} isLast={isLast} />}
|
||||
{isEditableEndpoint && (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
'hover-button rounded-md p-1 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
isCreatedByUser ? '' : 'active',
|
||||
hideEditButton ? 'opacity-0' : '',
|
||||
isEditing ? 'active text-gray-700 dark:text-gray-200' : '',
|
||||
|
|
@ -93,7 +93,7 @@ export default function HoverButtons({
|
|||
)}
|
||||
<button
|
||||
className={cn(
|
||||
'ml-0 flex items-center gap-1.5 rounded-md p-1 text-xs text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
'ml-0 flex items-center gap-1.5 rounded-md p-1 text-xs hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
isSubmitting && isCreatedByUser ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
)}
|
||||
|
|
@ -108,7 +108,7 @@ export default function HoverButtons({
|
|||
{regenerateEnabled ? (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button active rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
'hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible md:group-[.final-completion]:visible',
|
||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
)}
|
||||
onClick={regenerate}
|
||||
|
|
@ -131,7 +131,7 @@ export default function HoverButtons({
|
|||
{continueSupported ? (
|
||||
<button
|
||||
className={cn(
|
||||
'hover-button active rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible',
|
||||
'hover-button active rounded-md p-1 hover:bg-gray-100 hover:text-gray-500 focus:opacity-100 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible',
|
||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||
)}
|
||||
onClick={handleContinue}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function Message(props: TMessageProps) {
|
|||
<>
|
||||
<MessageContainer handleScroll={handleScroll}>
|
||||
{showSibling ? (
|
||||
<div className="m-auto my-2 flex justify-center p-4 py-2 text-base md:gap-6">
|
||||
<div className="m-auto my-2 flex justify-center p-4 py-2 md:gap-6">
|
||||
<div className="flex w-full flex-row flex-wrap justify-between gap-1 md:max-w-5xl md:flex-nowrap md:gap-2 lg:max-w-5xl xl:max-w-6xl">
|
||||
<MessageRender
|
||||
{...props}
|
||||
|
|
@ -58,7 +58,7 @@ export default function Message(props: TMessageProps) {
|
|||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<MessageRender {...props} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||
import ContentParts from './Content/ContentParts';
|
||||
|
|
@ -8,6 +9,7 @@ import MultiMessage from './MultiMessage';
|
|||
import HoverButtons from './HoverButtons';
|
||||
import SubRow from './SubRow';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function Message(props: TMessageProps) {
|
||||
const { message, siblingIdx, siblingCount, setSiblingIdx, currentEditId, setCurrentEditId } =
|
||||
|
|
@ -28,6 +30,7 @@ export default function Message(props: TMessageProps) {
|
|||
copyToClipboard,
|
||||
regenerateMessage,
|
||||
} = useMessageHelpers(props);
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
|
||||
const { content, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
|
||||
|
||||
|
|
@ -42,8 +45,8 @@ export default function Message(props: TMessageProps) {
|
|||
onWheel={handleScroll}
|
||||
onTouchMove={handleScroll}
|
||||
>
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<div className="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">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<div className="group mx-auto flex flex-1 gap-3 md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||
<div>
|
||||
<div className="pt-0.5">
|
||||
|
|
@ -54,10 +57,14 @@ export default function Message(props: TMessageProps) {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn('relative flex w-full flex-col', isCreatedByUser ? '' : 'agent-turn')}
|
||||
className={cn(
|
||||
'relative flex w-full flex-col',
|
||||
isCreatedByUser != null ? '' : 'agent-turn',
|
||||
)}
|
||||
>
|
||||
<div className="select-none font-semibold">
|
||||
{isCreatedByUser ? 'You' : (assistant && assistant?.name) ?? 'Assistant'}
|
||||
<div className={cn('select-none font-semibold', fontSize)}>
|
||||
{/* TODO: LOCALIZE */}
|
||||
{isCreatedByUser != null ? 'You' : (assistant && assistant.name) ?? 'Assistant'}
|
||||
</div>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import ScrollToBottom from '~/components/Messages/ScrollToBottom';
|
||||
import { useScreenshot, useMessageScrolling } from '~/hooks';
|
||||
import { CSSTransition } from 'react-transition-group';
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
export default function MessagesView({
|
||||
messagesTree: _messagesTree,
|
||||
|
|
@ -13,6 +16,7 @@ export default function MessagesView({
|
|||
messagesTree?: TMessage[] | null;
|
||||
Header?: ReactNode;
|
||||
}) {
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const { screenshotTargetRef } = useScreenshot();
|
||||
const [currentEditId, setCurrentEditId] = useState<number | string | null>(-1);
|
||||
|
||||
|
|
@ -39,9 +43,14 @@ export default function MessagesView({
|
|||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col pb-9 text-sm dark:bg-transparent">
|
||||
<div className="flex flex-col pb-9 dark:bg-transparent">
|
||||
{(_messagesTree && _messagesTree.length == 0) || _messagesTree === null ? (
|
||||
<div className="flex w-full items-center justify-center gap-1 bg-gray-50 p-3 text-sm text-gray-500 dark:border-gray-800/50 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div
|
||||
className={cn(
|
||||
'flex w-full items-center justify-center gap-1 bg-gray-50 p-3 text-gray-500 dark:border-gray-800/50 dark:bg-gray-800 dark:text-gray-300',
|
||||
fontSize,
|
||||
)}
|
||||
>
|
||||
Nothing found
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import store from '~/store';
|
|||
|
||||
export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
||||
const UsernameDisplay = useRecoilValue<boolean>(store.UsernameDisplay);
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const { user } = useAuthContext();
|
||||
const localize = useLocalize();
|
||||
|
||||
|
|
@ -18,7 +19,7 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { isCreatedByUser } = message ?? {};
|
||||
const { isCreatedByUser } = message;
|
||||
|
||||
let messageLabel = '';
|
||||
if (isCreatedByUser) {
|
||||
|
|
@ -30,8 +31,8 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
|||
return (
|
||||
<>
|
||||
<div className="text-token-text-primary w-full border-0 bg-transparent dark:border-0 dark:bg-transparent">
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<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">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<div className="final-completion group mx-auto flex flex-1 gap-3 md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||
<div>
|
||||
<div className="pt-0.5">
|
||||
|
|
@ -44,7 +45,7 @@ export default function Message({ message }: Pick<TMessageProps, 'message'>) {
|
|||
<div
|
||||
className={cn('relative flex w-11/12 flex-col', isCreatedByUser ? '' : 'agent-turn')}
|
||||
>
|
||||
<div className="select-none font-semibold">{messageLabel}</div>
|
||||
<div className={cn('select-none font-semibold', fontSize)}>{messageLabel}</div>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
<SearchContent message={message} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useMessageActions } from '~/hooks';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useCallback, useMemo, memo } from 'react';
|
||||
import type { TMessage } from 'librechat-data-provider';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
||||
|
|
@ -9,7 +9,9 @@ import HoverButtons from '~/components/Chat/Messages/HoverButtons';
|
|||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||
import { Plugin } from '~/components/Messages/Content';
|
||||
import SubRow from '~/components/Chat/Messages/SubRow';
|
||||
import { useMessageActions } from '~/hooks';
|
||||
import { cn, logger } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type MessageRenderProps = {
|
||||
message?: TMessage;
|
||||
|
|
@ -21,7 +23,7 @@ type MessageRenderProps = {
|
|||
'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
|
||||
>;
|
||||
|
||||
const MessageRender = React.memo(
|
||||
const MessageRender = memo(
|
||||
({
|
||||
isCard,
|
||||
siblingIdx,
|
||||
|
|
@ -54,6 +56,7 @@ const MessageRender = React.memo(
|
|||
setCurrentEditId,
|
||||
});
|
||||
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
|
||||
const { isCreatedByUser, error, unfinished } = msg ?? {};
|
||||
const isLast = useMemo(
|
||||
|
|
@ -81,7 +84,7 @@ const MessageRender = React.memo(
|
|||
<div
|
||||
aria-label={`message-${msg.depth}-${msg.messageId}`}
|
||||
className={cn(
|
||||
'final-completion group mx-auto flex flex-1 gap-3 text-base',
|
||||
'final-completion group mx-auto flex flex-1 gap-3',
|
||||
isCard === true
|
||||
? 'relative w-full gap-1 rounded-lg border border-border-medium bg-surface-primary-alt p-2 md:w-1/2 md:gap-3 md:p-4'
|
||||
: 'md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5',
|
||||
|
|
@ -113,7 +116,7 @@ const MessageRender = React.memo(
|
|||
<div
|
||||
className={cn('relative flex w-11/12 flex-col', msg.isCreatedByUser ? '' : 'agent-turn')}
|
||||
>
|
||||
<h2 className="select-none font-semibold">{messageLabel}</h2>
|
||||
<h2 className={cn('select-none font-semibold', fontSize)}>{messageLabel}</h2>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
{msg.plugin && <Plugin plugin={msg.plugin} />}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { memo } from 'react';
|
||||
import * as Tabs from '@radix-ui/react-tabs';
|
||||
import { SettingsTabValues } from 'librechat-data-provider';
|
||||
import FontSizeSelector from './FontSizeSelector';
|
||||
import SendMessageKeyEnter from './EnterToSend';
|
||||
import ShowCodeSwitch from './ShowCodeSwitch';
|
||||
import { ForkSettings } from './ForkSettings';
|
||||
|
|
@ -12,11 +13,14 @@ function Chat() {
|
|||
<Tabs.Content value={SettingsTabValues.CHAT} role="tabpanel" className="md: w-full">
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<SendMessageKeyEnter />
|
||||
<FontSizeSelector />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<ChatDirection />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<SendMessageKeyEnter />
|
||||
</div>
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<ShowCodeSwitch />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { useRecoilState } from 'recoil';
|
||||
import { Dropdown } from '~/components/ui';
|
||||
import { applyFontSize } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
export default function FontSizeSelector() {
|
||||
const [fontSize, setFontSize] = useRecoilState(store.fontSize);
|
||||
const localize = useLocalize();
|
||||
|
||||
const handleChange = (val: string) => {
|
||||
setFontSize(val);
|
||||
applyFontSize(val);
|
||||
};
|
||||
|
||||
const options = [
|
||||
{ value: 'text-xs', display: localize('com_nav_font_size_xs') },
|
||||
{ value: 'text-sm', display: localize('com_nav_font_size_sm') },
|
||||
{ value: 'text-base', display: localize('com_nav_font_size_base') },
|
||||
{ value: 'text-lg', display: localize('com_nav_font_size_lg') },
|
||||
{ value: 'text-xl', display: localize('com_nav_font_size_xl') },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div>{localize('com_nav_font_size')}</div>
|
||||
<Dropdown
|
||||
value={fontSize}
|
||||
options={options}
|
||||
onChange={handleChange}
|
||||
testId="font-size-selector"
|
||||
sizeClasses="w-[150px]"
|
||||
anchor="bottom start"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,10 +6,10 @@ import type { TDangerButtonProps } from '~/common';
|
|||
import { ThemeContext, useLocalize, useLocalStorage } from '~/hooks';
|
||||
import HideSidePanelSwitch from './HideSidePanelSwitch';
|
||||
import AutoScrollSwitch from './AutoScrollSwitch';
|
||||
import ArchivedChats from './ArchivedChats';
|
||||
import { Dropdown } from '~/components/ui';
|
||||
import DangerButton from '../DangerButton';
|
||||
import store from '~/store';
|
||||
import ArchivedChats from './ArchivedChats';
|
||||
|
||||
export const ThemeSelector = ({
|
||||
theme,
|
||||
|
|
@ -156,7 +156,7 @@ function General() {
|
|||
className="w-full md:min-h-[271px]"
|
||||
ref={contentRef}
|
||||
>
|
||||
<div className="flex flex-col gap-3 text-sm text-black dark:text-gray-50">
|
||||
<div className="flex flex-col gap-3 text-sm text-text-primary">
|
||||
<div className="border-b pb-3 last-of-type:border-b-0 dark:border-gray-600">
|
||||
<ThemeSelector theme={theme} onChange={changeTheme} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import { PromptsEditorMode } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const { PromptsEditorMode, promptsEditorMode, alwaysMakeProd } = store;
|
||||
const { promptsEditorMode, alwaysMakeProd } = store;
|
||||
|
||||
const AdvancedSwitch = () => {
|
||||
const localize = useLocalize();
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { Controller, useFormContext, useFormState } from 'react-hook-form';
|
|||
import AlwaysMakeProd from '~/components/Prompts/Groups/AlwaysMakeProd';
|
||||
import { SaveIcon, CrossIcon } from '~/components/svg';
|
||||
import { TextareaAutosize } from '~/components/ui';
|
||||
import { PromptsEditorMode } from '~/common';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
const { PromptsEditorMode, promptsEditorMode } = store;
|
||||
const { promptsEditorMode } = store;
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
|
|
@ -22,17 +23,18 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
|
|||
const { control } = useFormContext();
|
||||
const editorMode = useRecoilValue(promptsEditorMode);
|
||||
const { dirtyFields } = useFormState({ control: control });
|
||||
const { prompt } = dirtyFields as { prompt?: string };
|
||||
|
||||
const EditorIcon = useMemo(() => {
|
||||
if (isEditing && !dirtyFields.prompt) {
|
||||
if (isEditing && prompt?.length == null) {
|
||||
return CrossIcon;
|
||||
}
|
||||
return isEditing ? SaveIcon : EditIcon;
|
||||
}, [isEditing, dirtyFields.prompt]);
|
||||
}, [isEditing, prompt]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="flex items-center justify-between rounded-t-lg border border-gray-300 py-2 pl-4 text-base font-semibold dark:border-gray-600 dark:text-gray-200">
|
||||
<h2 className="flex items-center justify-between rounded-t-lg border border-border-medium py-2 pl-4 text-base font-semibold text-text-primary">
|
||||
{localize('com_ui_prompt_text')}
|
||||
<div className="flex flex-row gap-6">
|
||||
{editorMode === PromptsEditorMode.ADVANCED && (
|
||||
|
|
@ -49,14 +51,21 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
|
|||
</div>
|
||||
</h2>
|
||||
<div
|
||||
role="button"
|
||||
className={cn(
|
||||
'group relative min-h-32 rounded-b-lg border border-gray-300 p-4 transition-all duration-150 hover:opacity-90 dark:border-gray-600',
|
||||
'min-h-[8rem] w-full rounded-b-lg border border-border-medium p-4 transition-all duration-150',
|
||||
{ 'cursor-pointer hover:bg-gray-100/50 dark:hover:bg-gray-100/10': !isEditing },
|
||||
)}
|
||||
onClick={() => !isEditing && setIsEditing(true)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
!isEditing && setIsEditing(true);
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
>
|
||||
{!isEditing && (
|
||||
<EditIcon className="icon-xl absolute inset-0 m-auto hidden opacity-25 group-hover:block dark:text-gray-200" />
|
||||
<EditIcon className="icon-xl absolute inset-0 m-auto hidden text-text-primary opacity-25 group-hover:block" />
|
||||
)}
|
||||
<Controller
|
||||
name={name}
|
||||
|
|
@ -65,12 +74,20 @@ const PromptEditor: React.FC<Props> = ({ name, isEditing, setIsEditing }) => {
|
|||
isEditing ? (
|
||||
<TextareaAutosize
|
||||
{...field}
|
||||
className="w-full rounded border border-gray-300 bg-transparent px-2 py-1 focus:outline-none dark:border-gray-600 dark:text-gray-200"
|
||||
className="w-full rounded border border-border-medium bg-transparent px-2 py-1 text-text-primary focus:outline-none"
|
||||
minRows={3}
|
||||
onBlur={() => setIsEditing(false)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
setIsEditing(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<pre className="block break-words px-2 py-1 dark:text-gray-200">{field.value}</pre>
|
||||
<pre className="block h-full w-full whitespace-pre-wrap break-words px-2 py-1 text-left text-text-primary">
|
||||
{field.value}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { useForm, FormProvider } from 'react-hook-form';
|
|||
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
|
||||
import { useNavigate, useParams, useOutletContext } from 'react-router-dom';
|
||||
import { PermissionTypes, Permissions, SystemRoles } from 'librechat-data-provider';
|
||||
import type { TCreatePrompt } from 'librechat-data-provider';
|
||||
import type { TCreatePrompt, TPrompt } from 'librechat-data-provider';
|
||||
import {
|
||||
useGetPrompts,
|
||||
useCreatePrompt,
|
||||
|
|
@ -22,6 +22,7 @@ import { Button, Skeleton } from '~/components/ui';
|
|||
import PromptVariables from './PromptVariables';
|
||||
import { useToastContext } from '~/Providers';
|
||||
import PromptVersions from './PromptVersions';
|
||||
import { PromptsEditorMode } from '~/common';
|
||||
import DeleteConfirm from './DeleteVersion';
|
||||
import PromptDetails from './PromptDetails';
|
||||
import { findPromptGroup } from '~/utils';
|
||||
|
|
@ -33,7 +34,7 @@ import PromptName from './PromptName';
|
|||
import Command from './Command';
|
||||
import store from '~/store';
|
||||
|
||||
const { PromptsEditorMode, promptsEditorMode } = store;
|
||||
const { promptsEditorMode } = store;
|
||||
|
||||
const PromptForm = () => {
|
||||
const params = useParams();
|
||||
|
|
@ -55,7 +56,10 @@ const PromptForm = () => {
|
|||
const [initialLoad, setInitialLoad] = useState(true);
|
||||
const [selectionIndex, setSelectionIndex] = useState<number>(0);
|
||||
const isOwner = useMemo(() => user?.id === group?.author, [user, group]);
|
||||
const selectedPrompt = useMemo(() => prompts[selectionIndex], [prompts, selectionIndex]);
|
||||
const selectedPrompt = useMemo(
|
||||
() => prompts[selectionIndex] as TPrompt | undefined,
|
||||
[prompts, selectionIndex],
|
||||
);
|
||||
|
||||
const hasShareAccess = useHasAccess({
|
||||
permissionType: PermissionTypes.PROMPTS,
|
||||
|
|
@ -229,7 +233,7 @@ const PromptForm = () => {
|
|||
<Skeleton className="mb-1 flex h-10 w-32 flex-row items-center font-bold sm:text-xl md:mb-0 md:h-12 md:text-2xl" />
|
||||
) : (
|
||||
<PromptName
|
||||
name={group?.name}
|
||||
name={group.name}
|
||||
onSave={(value) => {
|
||||
if (!group) {
|
||||
return console.warn('Group not found');
|
||||
|
|
@ -241,11 +245,11 @@ const PromptForm = () => {
|
|||
<div className="flex h-10 flex-row gap-x-2">
|
||||
<CategorySelector
|
||||
className="w-48 md:w-56"
|
||||
currentCategory={group?.category}
|
||||
currentCategory={group.category}
|
||||
onValueChange={(value) =>
|
||||
updateGroupMutation.mutate({
|
||||
id: group?._id || '',
|
||||
payload: { name: group?.name || '', category: value },
|
||||
id: group._id || '',
|
||||
payload: { name: group.name || '', category: value },
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
|
@ -256,11 +260,11 @@ const PromptForm = () => {
|
|||
className="h-10 border border-transparent bg-green-500 transition-all hover:bg-green-600 dark:bg-green-500 dark:hover:bg-green-600"
|
||||
variant={'default'}
|
||||
onClick={() => {
|
||||
const { _id: promptVersionId = '', prompt } = selectedPrompt;
|
||||
const { _id: promptVersionId = '', prompt } = selectedPrompt ?? ({} as TPrompt);
|
||||
makeProductionMutation.mutate(
|
||||
{
|
||||
id: promptVersionId || '',
|
||||
groupId: group?._id || '',
|
||||
groupId: group._id || '',
|
||||
productionPrompt: { prompt },
|
||||
},
|
||||
{
|
||||
|
|
@ -275,7 +279,7 @@ const PromptForm = () => {
|
|||
}}
|
||||
disabled={
|
||||
isLoadingGroup ||
|
||||
selectedPrompt?._id === group?.productionId ||
|
||||
selectedPrompt?._id === group.productionId ||
|
||||
makeProductionMutation.isLoading
|
||||
}
|
||||
>
|
||||
|
|
@ -288,7 +292,7 @@ const PromptForm = () => {
|
|||
selectHandler={() => {
|
||||
deletePromptMutation.mutate({
|
||||
_id: selectedPrompt?._id || '',
|
||||
groupId: group?._id || '',
|
||||
groupId: group._id || '',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
@ -309,11 +313,11 @@ const PromptForm = () => {
|
|||
<PromptEditor name="prompt" isEditing={isEditing} setIsEditing={setIsEditing} />
|
||||
<PromptVariables promptText={promptText} />
|
||||
<Description
|
||||
initialValue={group?.oneliner ?? ''}
|
||||
initialValue={group.oneliner ?? ''}
|
||||
onValueChange={debouncedUpdateOneliner}
|
||||
/>
|
||||
<Command
|
||||
initialValue={group?.command ?? ''}
|
||||
initialValue={group.command ?? ''}
|
||||
onValueChange={debouncedUpdateCommand}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { useRecoilValue } from 'recoil';
|
||||
import type { TMessageProps } from '~/common';
|
||||
import MinimalHoverButtons from '~/components/Chat/Messages/MinimalHoverButtons';
|
||||
import MessageContent from '~/components/Chat/Messages/Content/MessageContent';
|
||||
|
|
@ -8,9 +9,11 @@ import SubRow from '~/components/Chat/Messages/SubRow';
|
|||
// eslint-disable-next-line import/no-cycle
|
||||
import MultiMessage from './MultiMessage';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
import Icon from './MessageIcon';
|
||||
export default function Message(props: TMessageProps) {
|
||||
const fontSize = useRecoilValue(store.fontSize);
|
||||
const {
|
||||
message,
|
||||
siblingIdx,
|
||||
|
|
@ -37,8 +40,8 @@ export default function Message(props: TMessageProps) {
|
|||
return (
|
||||
<>
|
||||
<div className="text-token-text-primary w-full border-0 bg-transparent dark:border-0 dark:bg-transparent">
|
||||
<div className="m-auto justify-center p-4 py-2 text-base md:gap-6 ">
|
||||
<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">
|
||||
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||
<div className="final-completion group mx-auto flex flex-1 gap-3 md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5">
|
||||
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||
<div>
|
||||
<div className="pt-0.5">
|
||||
|
|
@ -51,12 +54,12 @@ export default function Message(props: TMessageProps) {
|
|||
<div
|
||||
className={cn('relative flex w-11/12 flex-col', isCreatedByUser ? '' : 'agent-turn')}
|
||||
>
|
||||
<div className="select-none font-semibold">{messageLabel}</div>
|
||||
<div className={cn('select-none font-semibold', fontSize)}>{messageLabel}</div>
|
||||
<div className="flex-col gap-1 md:gap-3">
|
||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||
{/* Legacy Plugins */}
|
||||
{message?.plugin && <Plugin plugin={message?.plugin} />}
|
||||
{message?.content ? (
|
||||
{message.plugin && <Plugin plugin={message.plugin} />}
|
||||
{message.content ? (
|
||||
<SearchContent message={message} />
|
||||
) : (
|
||||
<MessageContent
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import useScrollToRef from '~/hooks/useScrollToRef';
|
|||
import { useChatContext } from '~/Providers';
|
||||
import store from '~/store';
|
||||
|
||||
const threshold = 0.14;
|
||||
const debounceRate = 250;
|
||||
|
||||
export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
||||
const autoScroll = useRecoilValue(store.autoScroll);
|
||||
|
||||
|
|
@ -21,7 +24,7 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
|||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = setTimeout(() => {
|
||||
setShowScrollButton(value);
|
||||
}, 150);
|
||||
}, debounceRate);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -33,7 +36,7 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
|||
([entry]) => {
|
||||
debouncedSetShowScrollButton(!entry.isIntersecting);
|
||||
},
|
||||
{ root: scrollableRef.current, threshold: 0.1 },
|
||||
{ root: scrollableRef.current, threshold },
|
||||
);
|
||||
|
||||
observer.observe(messagesEndRef.current);
|
||||
|
|
@ -50,7 +53,7 @@ export default function useMessageScrolling(messagesTree?: TMessage[] | null) {
|
|||
([entry]) => {
|
||||
debouncedSetShowScrollButton(!entry.isIntersecting);
|
||||
},
|
||||
{ root: scrollableRef.current, threshold: 0.1 },
|
||||
{ root: scrollableRef.current, threshold },
|
||||
);
|
||||
observer.observe(messagesEndRef.current);
|
||||
return () => observer.disconnect();
|
||||
|
|
|
|||
|
|
@ -2,22 +2,7 @@
|
|||
// source: https://plainenglish.io/blog/light-and-dark-mode-in-react-web-application-with-tailwind-css-89674496b942
|
||||
|
||||
import React, { createContext, useState, useEffect } from 'react';
|
||||
|
||||
const getInitialTheme = () => {
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
const storedPrefs = window.localStorage.getItem('color-theme');
|
||||
if (typeof storedPrefs === 'string') {
|
||||
return storedPrefs;
|
||||
}
|
||||
|
||||
const userMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (userMedia.matches) {
|
||||
return 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
return 'light'; // light theme as the default;
|
||||
};
|
||||
import { getInitialTheme, applyFontSize } from '~/utils';
|
||||
|
||||
type ProviderValue = {
|
||||
theme: string;
|
||||
|
|
@ -66,6 +51,14 @@ export const ThemeProvider = ({ initialTheme, children }) => {
|
|||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fontSize = localStorage.getItem('fontSize');
|
||||
if (fontSize == null) {
|
||||
return;
|
||||
}
|
||||
applyFontSize(JSON.parse(fontSize));
|
||||
}, []);
|
||||
|
||||
if (initialTheme) {
|
||||
rawSetTheme(initialTheme);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,6 +292,7 @@ export default {
|
|||
com_nav_theme_system: 'النظام',
|
||||
com_nav_theme_dark: 'داكن',
|
||||
com_nav_theme_light: 'فاتح',
|
||||
com_nav_font_size: 'حجم الخط',
|
||||
com_nav_clear_all_chats: 'مسح كل الدردشات',
|
||||
com_nav_confirm_clear: 'تأكيد المسح',
|
||||
com_nav_close_sidebar: 'إغلاق القائمة الجانبية',
|
||||
|
|
@ -914,20 +915,20 @@ export const comparisons = {
|
|||
translated: 'تم حذف الإشارة المرجعية بنجاح',
|
||||
},
|
||||
com_ui_bookmarks_create_error: {
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'حدث خطأ أثناء إنشاء الإشارة المرجعية',
|
||||
english: 'There was an error creating the bookmark',
|
||||
translated: 'حدث خطأ أثناء إنشاء الإشارة المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_update_error: {
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'حدث خطأ أثناء تحديث الإشارة المرجعية',
|
||||
english: 'There was an error updating the bookmark',
|
||||
translated: 'حدث خطأ أثناء تحديث الإشارة المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_delete_error: {
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'حدث خطأ أثناء حذف الإشارة المرجعية',
|
||||
english: 'There was an error deleting the bookmark',
|
||||
translated: 'حدث خطأ أثناء حذف الإشارة المرجعية',
|
||||
},
|
||||
com_ui_bookmarks_add_to_conversation: {
|
||||
english: 'Add to current conversation',
|
||||
translated: 'أضف إلى المحادثة الحالية',
|
||||
english: 'Add to current conversation',
|
||||
translated: 'أضف إلى المحادثة الحالية',
|
||||
},
|
||||
com_auth_error_login: {
|
||||
english:
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ export default {
|
|||
com_nav_settings: 'Configurações',
|
||||
com_nav_search_placeholder: 'Pesquisar mensagens',
|
||||
com_nav_info_bookmarks_rebuild:
|
||||
'Se a contagem de favoritos estiver incorreta, por favor, reconstrua as informações de favoritos. A contagem de favoritos será recalculada e os dados serão restaurados ao estado correto.',
|
||||
'Se a contagem de favoritos estiver incorreta, por favor, reconstrua as informações de favoritos. A contagem de favoritos será recalculada e os dados serão restaurados ao estado correto.',
|
||||
com_nav_setting_general: 'Geral',
|
||||
com_nav_setting_beta: 'Recursos beta',
|
||||
com_nav_setting_data: 'Controles de dados',
|
||||
|
|
@ -2149,6 +2149,10 @@ export const comparisons = {
|
|||
english: 'Light',
|
||||
translated: 'Claro',
|
||||
},
|
||||
com_nav_font_size: {
|
||||
english: 'Font Size',
|
||||
translated: 'Tamanho da fonte',
|
||||
},
|
||||
com_nav_user_name_display: {
|
||||
english: 'Display username in messages',
|
||||
translated: 'Exibir nome de usuário nas mensagens',
|
||||
|
|
|
|||
|
|
@ -640,6 +640,7 @@ export default {
|
|||
com_nav_theme_system: 'System',
|
||||
com_nav_theme_dark: 'Dunkel',
|
||||
com_nav_theme_light: 'Hell',
|
||||
com_nav_font_size: 'Schriftgröße',
|
||||
com_nav_enter_to_send: 'Enter drücken, um Nachrichten zu senden',
|
||||
com_nav_user_name_display: 'Benutzernamen in Nachrichten anzeigen',
|
||||
com_nav_save_drafts: 'Entwürfe lokal speichern',
|
||||
|
|
|
|||
|
|
@ -553,6 +553,12 @@ export default {
|
|||
com_endpoint_config_key_google_service_account: 'Create a Service Account',
|
||||
com_endpoint_config_key_google_vertex_api_role:
|
||||
'Make sure to click \'Create and Continue\' to give at least the \'Vertex AI User\' role. Lastly, create a JSON key to import here.',
|
||||
com_nav_font_size: 'Message Font Size',
|
||||
com_nav_font_size_xs: 'Extra Small',
|
||||
com_nav_font_size_sm: 'Small',
|
||||
com_nav_font_size_base: 'Medium',
|
||||
com_nav_font_size_lg: 'Large',
|
||||
com_nav_font_size_xl: 'Extra Large',
|
||||
com_nav_welcome_assistant: 'Please Select an Assistant',
|
||||
com_nav_welcome_message: 'How can I help you today?',
|
||||
com_nav_auto_scroll: 'Auto-Scroll to latest message on chat open',
|
||||
|
|
|
|||
|
|
@ -465,6 +465,7 @@ export default {
|
|||
com_nav_theme_system: 'Sistema',
|
||||
com_nav_theme_dark: 'Oscuro',
|
||||
com_nav_theme_light: 'Claro',
|
||||
com_nav_font_size: 'Tamaño de fuente',
|
||||
com_nav_user_name_display: 'Mostrar nombre de usuario en los mensajes',
|
||||
com_nav_save_drafts: 'Guardar borradores localmente',
|
||||
com_nav_show_code: 'Mostrar siempre el código cuando se use el intérprete de código',
|
||||
|
|
|
|||
|
|
@ -356,6 +356,7 @@ export default {
|
|||
com_nav_theme_system: 'Système',
|
||||
com_nav_theme_dark: 'Sombre',
|
||||
com_nav_theme_light: 'Clair',
|
||||
com_nav_font_size: 'Taille de police',
|
||||
com_nav_user_name_display: 'Afficher le nom d\'utilisateur dans les messages',
|
||||
com_nav_save_drafts: 'Enregistrer les brouillons localement',
|
||||
com_nav_clear_all_chats: 'Effacer toutes les conversations',
|
||||
|
|
|
|||
|
|
@ -385,6 +385,7 @@ export default {
|
|||
com_nav_theme_system: 'מערכת',
|
||||
com_nav_theme_dark: 'כהה',
|
||||
com_nav_theme_light: 'אור',
|
||||
com_nav_font_size: 'גודל גופן',
|
||||
com_nav_user_name_display: 'הצג שם משתמש בהודעות',
|
||||
com_nav_save_drafts: 'שמיר את האפצה באותו מחשב',
|
||||
com_nav_clear_all_chats: 'נקה את כל השיחות',
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ export default {
|
|||
com_nav_theme_system: 'Sistem',
|
||||
com_nav_theme_dark: 'Gelap',
|
||||
com_nav_theme_light: 'Terang',
|
||||
com_nav_font_size: 'Ukuran huruf',
|
||||
com_nav_user_name_display: 'Tampilkan nama pengguna dalam pesan',
|
||||
com_nav_save_drafts: 'Simpan draft',
|
||||
com_nav_clear_all_chats: 'Hapus semua obrolan',
|
||||
|
|
|
|||
|
|
@ -2455,6 +2455,10 @@ export const comparisons = {
|
|||
english: 'Light',
|
||||
translated: 'Chiaro',
|
||||
},
|
||||
com_nav_font_size: {
|
||||
english: 'Font Size',
|
||||
translate: 'Dimensione del font',
|
||||
},
|
||||
com_nav_enter_to_send: {
|
||||
english: 'Press Enter to send messages',
|
||||
translated: 'Premi Invio per inviare messaggi',
|
||||
|
|
|
|||
|
|
@ -464,6 +464,7 @@ export default {
|
|||
com_nav_theme_system: 'システム',
|
||||
com_nav_theme_dark: 'ダーク',
|
||||
com_nav_theme_light: 'ライト',
|
||||
com_nav_font_size: 'フォントサイズ',
|
||||
com_nav_enter_to_send: 'Enterキーでメッセージを送信する',
|
||||
com_nav_user_name_display: 'メッセージにユーザー名を表示する',
|
||||
com_nav_save_drafts: 'ローカルにドラフトを保存する',
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ export default {
|
|||
com_nav_theme_system: '시스템',
|
||||
com_nav_theme_dark: '다크',
|
||||
com_nav_theme_light: '라이트',
|
||||
com_nav_font_size: '글꼴 크기',
|
||||
com_nav_clear_all_chats: '모든 채팅 지우기',
|
||||
com_nav_confirm_clear: '지우기 확인',
|
||||
com_nav_close_sidebar: '사이드바 닫기',
|
||||
|
|
|
|||
|
|
@ -83,20 +83,16 @@ export default {
|
|||
com_ui_bookmarks: 'Bladwijzers',
|
||||
com_ui_bookmarks_rebuild: 'Herbouwen',
|
||||
com_ui_bookmarks_new: 'Nieuwe bladwijzer',
|
||||
com_ui_bookmark_delete_confirm:
|
||||
'Weet je zeker dat je deze bladwijzer wilt verwijderen?',
|
||||
com_ui_bookmark_delete_confirm: 'Weet je zeker dat je deze bladwijzer wilt verwijderen?',
|
||||
com_ui_bookmarks_title: 'Titel',
|
||||
com_ui_bookmarks_count: 'Aantal',
|
||||
com_ui_bookmarks_description: 'Beschrijving',
|
||||
com_ui_bookmarks_create_success: 'Bladwijzer succesvol aangemaakt',
|
||||
com_ui_bookmarks_update_success: 'Bladwijzer succesvol bijgewerkt',
|
||||
com_ui_bookmarks_delete_success: 'Bladwijzer succesvol verwijderd',
|
||||
com_ui_bookmarks_create_error:
|
||||
'Er is een fout opgetreden bij het maken van de bladwijzer',
|
||||
com_ui_bookmarks_update_error:
|
||||
'Er is een fout opgetreden bij het bijwerken van de bladwijzer',
|
||||
com_ui_bookmarks_delete_error:
|
||||
'Er is een fout opgetreden bij het verwijderen van de bladwijzer',
|
||||
com_ui_bookmarks_create_error: 'Er is een fout opgetreden bij het maken van de bladwijzer',
|
||||
com_ui_bookmarks_update_error: 'Er is een fout opgetreden bij het bijwerken van de bladwijzer',
|
||||
com_ui_bookmarks_delete_error: 'Er is een fout opgetreden bij het verwijderen van de bladwijzer',
|
||||
com_ui_bookmarks_add_to_conversation: 'Toevoegen aan huidig gesprek',
|
||||
com_auth_error_login:
|
||||
'Kan niet inloggen met de verstrekte informatie. Controleer uw referenties en probeer het opnieuw.',
|
||||
|
|
@ -306,6 +302,7 @@ export default {
|
|||
com_nav_theme_system: 'Systeem',
|
||||
com_nav_theme_dark: 'Donker',
|
||||
com_nav_theme_light: 'Licht',
|
||||
com_nav_font_size: 'Lettertypegrootte',
|
||||
com_nav_clear_all_chats: 'Alle chats wissen',
|
||||
com_nav_confirm_clear: 'Wissen bevestigen',
|
||||
com_nav_close_sidebar: 'Zijbalk sluiten',
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ export default {
|
|||
com_nav_theme_system: 'Domyślny',
|
||||
com_nav_theme_dark: 'Ciemny',
|
||||
com_nav_theme_light: 'Jasny',
|
||||
com_nav_font_size: 'Rozmiar czcionki',
|
||||
com_nav_clear: 'Wyczyść',
|
||||
com_nav_clear_all_chats: 'Usuń wszystkie konwersacje',
|
||||
com_nav_confirm_clear: 'Potwierdź usunięcie',
|
||||
|
|
|
|||
|
|
@ -343,6 +343,7 @@ export default {
|
|||
com_nav_theme_system: 'Системная',
|
||||
com_nav_theme_dark: 'Темная',
|
||||
com_nav_theme_light: 'Светлая',
|
||||
com_nav_font_size: 'Размер шрифта',
|
||||
com_nav_user_name_display: 'Отображать имя пользователя в сообщениях',
|
||||
com_nav_save_drafts: 'Сохранить черновики локально',
|
||||
com_nav_language: 'Локализация',
|
||||
|
|
|
|||
|
|
@ -289,6 +289,7 @@ export default {
|
|||
com_nav_theme_system: 'System',
|
||||
com_nav_theme_dark: 'Mörkt',
|
||||
com_nav_theme_light: 'Ljust',
|
||||
com_nav_font_size: 'Textstorlek',
|
||||
com_nav_clear_all_chats: 'Rensa alla chattar',
|
||||
com_nav_confirm_clear: 'Bekräfta rensning',
|
||||
com_nav_close_sidebar: 'Stäng sidofält',
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ export default {
|
|||
com_nav_user_name_display: 'Mesajlarda kullanıcı adını görüntüle',
|
||||
com_nav_save_drafts: 'Taslakları yerel olarak kaydet',
|
||||
com_nav_show_code: 'Kod yorumlayıcı kullanırken her zaman kodu göster',
|
||||
com_nav_font_size: 'Yazı Boyutu',
|
||||
com_nav_clear_all_chats: 'Tüm sohbetleri temizle',
|
||||
com_nav_confirm_clear: 'Temizlemeyi Onayla',
|
||||
com_nav_close_sidebar: 'Yan paneli kapat',
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ export default {
|
|||
com_nav_theme_system: 'Hệ thống',
|
||||
com_nav_theme_dark: 'Tối',
|
||||
com_nav_theme_light: 'Sáng',
|
||||
com_nav_font_size: 'Cỡ chữ',
|
||||
com_nav_clear_all_chats: 'Xóa tất cả cuộc trò chuyện',
|
||||
com_nav_confirm_clear: 'Xác nhận xóa',
|
||||
com_nav_close_sidebar: 'Đóng thanh bên',
|
||||
|
|
|
|||
|
|
@ -424,6 +424,7 @@ export default {
|
|||
com_nav_theme_system: '跟随系统设置',
|
||||
com_nav_theme_dark: '暗色主题',
|
||||
com_nav_theme_light: '亮色主题',
|
||||
com_nav_font_size: '字体大小:',
|
||||
com_nav_user_name_display: '在消息中显示用户名',
|
||||
com_nav_save_drafts: '保存草稿本地',
|
||||
com_nav_show_code: '使用代码解释器时始终显示代码',
|
||||
|
|
|
|||
|
|
@ -276,6 +276,7 @@ export default {
|
|||
com_nav_theme_system: '跟隨系統設定',
|
||||
com_nav_theme_dark: '深色',
|
||||
com_nav_theme_light: '淺色',
|
||||
com_nav_font_size: '字體大小',
|
||||
com_nav_clear_all_chats: '清除所有對話',
|
||||
com_nav_confirm_clear: '確認清除',
|
||||
com_nav_close_sidebar: '關閉側邊選單',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import { atom } from 'recoil';
|
||||
import { atomWithLocalStorage } from '~/store/utils';
|
||||
|
||||
enum PromptsEditorMode {
|
||||
SIMPLE = 'simple',
|
||||
ADVANCED = 'advanced',
|
||||
}
|
||||
import { PromptsEditorMode } from '~/common';
|
||||
|
||||
// Static atoms without localStorage
|
||||
const staticAtoms = {
|
||||
|
|
@ -23,7 +19,10 @@ const localStorageAtoms = {
|
|||
autoSendPrompts: atomWithLocalStorage('autoSendPrompts', true),
|
||||
alwaysMakeProd: atomWithLocalStorage('alwaysMakeProd', true),
|
||||
// Editor mode
|
||||
promptsEditorMode: atomWithLocalStorage<string>('promptsEditorMode', PromptsEditorMode.SIMPLE),
|
||||
promptsEditorMode: atomWithLocalStorage<PromptsEditorMode>(
|
||||
'promptsEditorMode',
|
||||
PromptsEditorMode.SIMPLE,
|
||||
),
|
||||
};
|
||||
|
||||
export default { ...staticAtoms, ...localStorageAtoms, PromptsEditorMode };
|
||||
export default { ...staticAtoms, ...localStorageAtoms };
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const localStorageAtoms = {
|
|||
// General settings
|
||||
autoScroll: atomWithLocalStorage('autoScroll', false),
|
||||
hideSidePanel: atomWithLocalStorage('hideSidePanel', false),
|
||||
fontSize: atomWithLocalStorage('fontSize', 'text-base'),
|
||||
|
||||
// Messages settings
|
||||
enterToSend: atomWithLocalStorage('enterToSend', true),
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@
|
|||
--gray-900: #0d0d0d;
|
||||
--gizmo-gray-500:#999;
|
||||
--gizmo-gray-600:#666;
|
||||
--gizmo-gray-950:#0f0f0f
|
||||
--gizmo-gray-950:#0f0f0f;
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
}
|
||||
html {
|
||||
--text-primary:var(--gray-800);
|
||||
|
|
@ -570,9 +575,12 @@ blockquote, dd, dl, fieldset, figure, h1, h2, h3, h4, h5, h6, hr, p, pre {
|
|||
}
|
||||
|
||||
.prose {
|
||||
color:var(--tw-prose-body);
|
||||
max-width:65ch
|
||||
}
|
||||
color: var(--tw-prose-body);
|
||||
max-width: 65ch;
|
||||
font-size: var(--markdown-font-size, var(--font-size-base));
|
||||
line-height: calc(28px * var(--markdown-font-size, var(--font-size-base)) / var(--font-size-base));
|
||||
}
|
||||
|
||||
.prose :where([class~=lead]):not(:where([class~=not-prose] *)) {
|
||||
color:var(--tw-prose-lead);
|
||||
font-size:1.25em;
|
||||
|
|
@ -1234,11 +1242,15 @@ html {
|
|||
|
||||
.markdown {
|
||||
max-width: none;
|
||||
font-size: var(--markdown-font-size, var(--font-size-base));
|
||||
line-height: calc(28px * var(--markdown-font-size, var(--font-size-base)) / var(--font-size-base));
|
||||
}
|
||||
|
||||
.markdown h1,
|
||||
.markdown h1 {
|
||||
font-size: calc(var(--markdown-font-size) * 1.75);
|
||||
}
|
||||
.markdown h2 {
|
||||
font-weight: 600;
|
||||
font-size: calc(var(--markdown-font-size) * 1.5);
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
|
|
@ -1248,6 +1260,7 @@ html {
|
|||
|
||||
.markdown h3 {
|
||||
font-weight: 600;
|
||||
font-size: calc(var(--markdown-font-size) * 1.25);
|
||||
}
|
||||
|
||||
.markdown h3,
|
||||
|
|
@ -1258,10 +1271,12 @@ html {
|
|||
|
||||
.markdown h4 {
|
||||
font-weight: 400;
|
||||
font-size: calc(var(--markdown-font-size) * 1.1);
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-weight: 600;
|
||||
font-size: var(--markdown-font-size);
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
|
|
@ -1495,8 +1510,8 @@ html {
|
|||
}
|
||||
.markdown ul li:before {
|
||||
content:"•";
|
||||
font-size:.875rem;
|
||||
line-height:1.25rem;
|
||||
font-size: calc(var(--markdown-font-size) * 0.875);
|
||||
line-height: calc(var(--markdown-font-size) * 1.25);
|
||||
margin-left:-1rem;
|
||||
position:absolute
|
||||
}
|
||||
|
|
@ -1955,6 +1970,11 @@ ol ol):not(:where([class~=not-prose] *)) {
|
|||
.markdown .prose ul:last-child {
|
||||
margin-bottom:0
|
||||
}
|
||||
.markdown .prose p {
|
||||
margin-bottom: 1rem;
|
||||
margin-top: 1rem;
|
||||
line-height: calc(28px * var(--markdown-font-size, var(--font-size-base)) / var(--font-size-base));
|
||||
}
|
||||
.markdown .prose ol,
|
||||
.markdown .prose ul {
|
||||
display:contents;
|
||||
|
|
@ -2184,3 +2204,41 @@ ol ol):not(:where([class~=not-prose] *)) {
|
|||
animation: moveUp 4s ease-in-out infinite;
|
||||
|
||||
}
|
||||
|
||||
.message-content {
|
||||
font-size: var(--markdown-font-size, 1rem);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.message-content pre code {
|
||||
font-size: calc(0.85 * var(--markdown-font-size, 1rem));
|
||||
}
|
||||
|
||||
.message-content pre {
|
||||
font-size: var(--markdown-font-size, 1rem);
|
||||
}
|
||||
|
||||
.code-analyze-block pre code,
|
||||
.code-analyze-block .overflow-y-auto code {
|
||||
font-size: calc(0.85 * var(--markdown-font-size, 1rem));
|
||||
}
|
||||
|
||||
.code-analyze-block pre,
|
||||
.code-analyze-block .overflow-y-auto {
|
||||
font-size: var(--markdown-font-size, 1rem);
|
||||
}
|
||||
|
||||
.progress-text-wrapper {
|
||||
font-size: var(--markdown-font-size, 1rem);
|
||||
line-height: calc(1.25 * var(--markdown-font-size, 1rem));
|
||||
}
|
||||
|
||||
.progress-text-content {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.progress-text-wrapper button {
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export * from './map';
|
|||
export * from './json';
|
||||
export * from './files';
|
||||
export * from './latex';
|
||||
export * from './theme';
|
||||
export * from './convos';
|
||||
export * from './presets';
|
||||
export * from './prompts';
|
||||
|
|
|
|||
38
client/src/utils/theme.ts
Normal file
38
client/src/utils/theme.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
export const applyFontSize = (val: string) => {
|
||||
const root = document.documentElement;
|
||||
const size = val.split('-')[1]; // This will be 'xs', 'sm', 'base', 'lg', or 'xl'
|
||||
|
||||
switch (size) {
|
||||
case 'xs':
|
||||
root.style.setProperty('--markdown-font-size', '0.75rem'); // 12px
|
||||
break;
|
||||
case 'sm':
|
||||
root.style.setProperty('--markdown-font-size', '0.875rem'); // 14px
|
||||
break;
|
||||
case 'base':
|
||||
root.style.setProperty('--markdown-font-size', '1rem'); // 16px
|
||||
break;
|
||||
case 'lg':
|
||||
root.style.setProperty('--markdown-font-size', '1.125rem'); // 18px
|
||||
break;
|
||||
case 'xl':
|
||||
root.style.setProperty('--markdown-font-size', '1.25rem'); // 20px
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export const getInitialTheme = () => {
|
||||
if (typeof window !== 'undefined' && window.localStorage) {
|
||||
const storedPrefs = window.localStorage.getItem('color-theme');
|
||||
if (typeof storedPrefs === 'string') {
|
||||
return storedPrefs;
|
||||
}
|
||||
|
||||
const userMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (userMedia.matches) {
|
||||
return 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
return 'light'; // light theme as the default;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue