📶 fix: Mobile Stylings (#2639)

* chore: remove unused mobile nav

* fix: mobile nav fix for 'more' and 'archive' buttons div

* refactor(useTextarea): rewrite handleKeyUp for backwards compatibility

refactor(useTextarea): rewrite handleKeyUp for backwards compatibility

* experimental: add processing delay to azure streams for better performance/UX

* experiemental: adjust gpt-3 azureDelay

* fix: perplexity titles
This commit is contained in:
Danny Avila 2024-05-08 16:40:20 -04:00 committed by GitHub
parent b6d6343f54
commit 3c5fa40435
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 49 additions and 105 deletions

View file

@ -26,11 +26,11 @@ const {
createContextHandlers,
} = require('./prompts');
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
const { isEnabled, sleep } = require('~/server/utils');
const { handleOpenAIErrors } = require('./tools/util');
const spendTokens = require('~/models/spendTokens');
const { createLLM, RunManager } = require('./llm');
const ChatGPTClient = require('./ChatGPTClient');
const { isEnabled } = require('~/server/utils');
const { summaryBuffer } = require('./memory');
const { runTitleChain } = require('./chains');
const { tokenSplit } = require('./document');
@ -1079,11 +1079,8 @@ ${convo}
...opts,
});
/* hacky fixes for Mistral AI API:
- Re-orders system message to the top of the messages payload, as not allowed anywhere else
- If there is only one message and it's a system message, change the role to user
*/
if (opts.baseURL.includes('https://api.mistral.ai/v1') && modelOptions.messages) {
/* Re-orders system message to the top of the messages payload, as not allowed anywhere else */
if (opts.baseURL.includes('api.mistral.ai') && modelOptions.messages) {
const { messages } = modelOptions;
const systemMessageIndex = messages.findIndex((msg) => msg.role === 'system');
@ -1094,10 +1091,16 @@ ${convo}
}
modelOptions.messages = messages;
}
if (messages.length === 1 && messages[0].role === 'system') {
modelOptions.messages[0].role = 'user';
}
/* If there is only one message and it's a system message, change the role to user */
if (
(opts.baseURL.includes('api.mistral.ai') || opts.baseURL.includes('api.perplexity.ai')) &&
modelOptions.messages &&
modelOptions.messages.length === 1 &&
modelOptions.messages[0]?.role === 'system'
) {
modelOptions.messages[0].role = 'user';
}
if (this.options.addParams && typeof this.options.addParams === 'object') {
@ -1151,6 +1154,7 @@ ${convo}
}
});
const azureDelay = this.modelOptions.model?.includes('gpt-4') ? 30 : 17;
for await (const chunk of stream) {
const token = chunk.choices[0]?.delta?.content || '';
intermediateReply += token;
@ -1159,6 +1163,10 @@ ${convo}
stream.controller.abort();
break;
}
if (this.azure) {
await sleep(azureDelay);
}
}
if (!UnexpectedRoleError) {

View file

@ -138,6 +138,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
onRename={onRename}
renameHandler={renameHandler}
appendLabel={true}
className="group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
/>
<DeleteButton
conversationId={conversationId}
@ -152,7 +153,7 @@ export default function Conversation({ conversation, retainView, toggleNav, isLa
conversationId={conversationId}
retainView={retainView}
shouldArchive={true}
icon={<ArchiveIcon className="w-full hover:text-gray-400" />}
icon={<ArchiveIcon className="hover:text-gray-400" />}
/>
</HoverToggle>
)}

View file

@ -7,6 +7,7 @@ interface RenameButtonProps {
renameHandler: (e: MouseEvent<HTMLButtonElement>) => void;
onRename: (e: MouseEvent<HTMLButtonElement>) => void;
appendLabel?: boolean;
className?: string;
}
export default function RenameButton({
@ -14,15 +15,13 @@ export default function RenameButton({
renameHandler,
onRename,
appendLabel = false,
className = '',
}: RenameButtonProps): ReactElement {
const localize = useLocalize();
const handler = renaming ? onRename : renameHandler;
return (
<button
className="group m-1.5 flex w-full cursor-pointer items-center gap-2 rounded p-2.5 text-sm hover:bg-gray-200 focus-visible:bg-gray-200 focus-visible:outline-0 radix-disabled:pointer-events-none radix-disabled:opacity-50 dark:hover:bg-gray-600 dark:focus-visible:bg-gray-600"
onClick={handler}
>
<button className={className} onClick={handler}>
{renaming ? (
<CheckMark />
) : appendLabel ? (

View file

@ -1,73 +0,0 @@
import React from 'react';
import { useRecoilValue } from 'recoil';
import type { Dispatch, SetStateAction } from 'react';
import { useLocalize, useNewConvo } from '~/hooks';
import store from '~/store';
export default function MobileNav({
setNavVisible,
}: {
setNavVisible: Dispatch<SetStateAction<boolean>>;
}) {
const localize = useLocalize();
const { newConversation } = useNewConvo(0);
const conversation = useRecoilValue(store.conversationByIndex(0));
const { title = 'New Chat' } = conversation || {};
return (
<div className="text-token-primary border-token-border-medium bg-token-surface-primary sticky top-0 z-10 flex min-h-[40px] items-center border-b bg-white dark:bg-gray-800 dark:text-white md:hidden">
<button
type="button"
data-testid="mobile-header-new-chat-button"
className="inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white active:opacity-50 dark:hover:text-white"
onClick={() =>
setNavVisible((prev) => {
localStorage.setItem('navVisible', JSON.stringify(!prev));
return !prev;
})
}
>
<span className="sr-only">{localize('com_nav_open_sidebar')}</span>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="icon-md"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3 8C3 7.44772 3.44772 7 4 7H20C20.5523 7 21 7.44772 21 8C21 8.55228 20.5523 9 20 9H4C3.44772 9 3 8.55228 3 8ZM3 16C3 15.4477 3.44772 15 4 15H14C14.5523 15 15 15.4477 15 16C15 16.5523 14.5523 17 14 17H4C3.44772 17 3 16.5523 3 16Z"
fill="currentColor"
/>
</svg>
</button>
<h1 className="flex-1 text-center text-base font-normal">
{title || localize('com_ui_new_chat')}
</h1>
<button
type="button"
className="inline-flex h-10 w-10 items-center justify-center rounded-md hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white active:opacity-50 dark:hover:text-white"
onClick={() => newConversation()}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="icon-md"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16.7929 2.79289C18.0118 1.57394 19.9882 1.57394 21.2071 2.79289C22.4261 4.01184 22.4261 5.98815 21.2071 7.20711L12.7071 15.7071C12.5196 15.8946 12.2652 16 12 16H9C8.44772 16 8 15.5523 8 15V12C8 11.7348 8.10536 11.4804 8.29289 11.2929L16.7929 2.79289ZM19.7929 4.20711C19.355 3.7692 18.645 3.7692 18.2071 4.2071L10 12.4142V14H11.5858L19.7929 5.79289C20.2308 5.35499 20.2308 4.64501 19.7929 4.20711ZM6 5C5.44772 5 5 5.44771 5 6V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V14C19 13.4477 19.4477 13 20 13C20.5523 13 21 13.4477 21 14V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6C3 4.34314 4.34315 3 6 3H10C10.5523 3 11 3.44771 11 4C11 4.55228 10.5523 5 10 5H6Z"
fill="currentColor"
/>
</svg>
</button>
</div>
);
}

View file

@ -2,7 +2,6 @@ export * from './ExportConversation';
export * from './SettingsTabs/';
export { default as ClearConvos } from './ClearConvos';
export { default as Logout } from './Logout';
export { default as MobileNav } from './MobileNav';
export { default as Nav } from './Nav';
export { default as NavLink } from './NavLink';
export { default as NavLinks } from './NavLinks';

View file

@ -14,6 +14,11 @@ import store from '~/store';
type KeyEvent = KeyboardEvent<HTMLTextAreaElement>;
const keyMap = {
50: '2',
192: '@',
};
export default function useTextarea({
textAreaRef,
submitButtonRef,
@ -138,24 +143,30 @@ export default function useTextarea({
const handleKeyUp = useCallback(
(e: KeyEvent) => {
let isMention = false;
if (e.key === '@' || e.key === '2') {
const text = textAreaRef.current?.value;
isMention = !!(text && text[text.length - 1] === '@');
let normalizedKey = e.key;
if (!normalizedKey || normalizedKey === 'Unidentified') {
normalizedKey = keyMap[e.keyCode] || normalizedKey;
}
if (isMention) {
const startPos = textAreaRef.current?.selectionStart;
const isAtStart = startPos === 1;
const isPrecededBySpace =
startPos && textAreaRef.current?.value.charAt(startPos - 2) === ' ';
if (isAtStart || isPrecededBySpace) {
setShowMentionPopover(true);
} else {
setShowMentionPopover(false);
}
if (normalizedKey !== '@' && normalizedKey !== '2') {
return;
}
const text = textAreaRef.current?.value;
if (!(text && text[text.length - 1] === '@')) {
return;
}
const startPos = textAreaRef.current?.selectionStart;
if (!startPos) {
return;
}
const isAtStart = startPos === 1;
const isPrecededBySpace = textAreaRef.current?.value.charAt(startPos - 2) === ' ';
setShowMentionPopover(isAtStart || isPrecededBySpace);
},
[textAreaRef, setShowMentionPopover],
);

View file

@ -5,7 +5,7 @@ import { useGetSearchEnabledQuery } from 'librechat-data-provider/react-query';
import type { ContextType } from '~/common';
import { useAuthContext, useAssistantsMap, useFileMap } from '~/hooks';
import { AssistantsMapContext, FileMapContext } from '~/Providers';
import { Nav, MobileNav } from '~/components/Nav';
import { Nav } from '~/components/Nav';
import store from '~/store';
export default function Root() {
@ -45,7 +45,6 @@ export default function Root() {
<div className="relative z-0 flex h-full w-full overflow-hidden">
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
<div className="relative flex h-full max-w-full flex-1 flex-col overflow-hidden">
<MobileNav setNavVisible={setNavVisible} />
<Outlet context={{ navVisible, setNavVisible } satisfies ContextType} />
</div>
</div>