mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
style: adjust icon scale, favicon, azure icon; chore: convert files to TSX; ci: unit tests for generation buttons (#987)
* some jsx to tsx and added 3 new test * test(stop) * new librechat and azure icon, small fix * fix(tsc error) * fix(tsc error) Endpoint Item
This commit is contained in:
parent
3137f467a8
commit
be71a1947b
21 changed files with 187 additions and 56 deletions
Binary file not shown.
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 709 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
|
@ -22,7 +22,7 @@ export default function DeleteButton({ conversationId, renaming, retainView, tit
|
|||
|
||||
useEffect(() => {
|
||||
if (deleteConvoMutation.isSuccess) {
|
||||
if (currentConversation?.conversationId == conversationId) {
|
||||
if ((currentConversation as { conversationId?: string }).conversationId == conversationId) {
|
||||
newConversation();
|
||||
}
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react';
|
||||
import RenameIcon from '../svg/RenameIcon';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
|
||||
export default function RenameButton({ renaming, renameHandler, onRename, twcss }) {
|
||||
const handler = renaming ? onRename : renameHandler;
|
||||
const classProp = { className: 'p-1 hover:text-white' };
|
||||
if (twcss) {
|
||||
classProp.className = twcss;
|
||||
}
|
||||
return (
|
||||
<button {...classProp} onClick={handler}>
|
||||
{renaming ? <CheckMark /> : <RenameIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
28
client/src/components/Conversations/RenameButton.tsx
Normal file
28
client/src/components/Conversations/RenameButton.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React, { ReactElement } from 'react';
|
||||
import RenameIcon from '../svg/RenameIcon';
|
||||
import CheckMark from '../svg/CheckMark';
|
||||
|
||||
interface RenameButtonProps {
|
||||
renaming: boolean;
|
||||
renameHandler: () => void;
|
||||
onRename: () => void;
|
||||
twcss?: string;
|
||||
}
|
||||
|
||||
export default function RenameButton({
|
||||
renaming,
|
||||
renameHandler,
|
||||
onRename,
|
||||
twcss,
|
||||
}: RenameButtonProps): ReactElement {
|
||||
const handler = renaming ? onRename : renameHandler;
|
||||
const classProp: { className?: string } = { className: 'p-1 hover:text-white' };
|
||||
if (twcss) {
|
||||
classProp.className = twcss;
|
||||
}
|
||||
return (
|
||||
<button {...classProp} onClick={handler}>
|
||||
{renaming ? <CheckMark /> : <RenameIcon />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Plugin, GPTIcon, AnthropicIcon } from '~/components/svg';
|
||||
import { Plugin, GPTIcon, AnthropicIcon, AzureMinimalIcon } from '~/components/svg';
|
||||
import { useAuthContext } from '~/hooks';
|
||||
import { cn } from '~/utils';
|
||||
import { IconProps } from '~/common';
|
||||
|
|
@ -34,12 +34,12 @@ const Icon: React.FC<IconProps> = (props) => {
|
|||
} else {
|
||||
const endpointIcons = {
|
||||
azureOpenAI: {
|
||||
icon: <GPTIcon size={size * 0.7} />,
|
||||
icon: <AzureMinimalIcon size={size * 0.55} />,
|
||||
bg: 'linear-gradient(0.375turn, #61bde2, #4389d0)',
|
||||
name: 'ChatGPT',
|
||||
},
|
||||
openAI: {
|
||||
icon: <GPTIcon size={size * 0.7} />,
|
||||
icon: <GPTIcon size={size * 0.55} />,
|
||||
bg:
|
||||
typeof model === 'string' && model.toLowerCase().includes('gpt-4')
|
||||
? '#AB68FF'
|
||||
|
|
@ -52,7 +52,7 @@ const Icon: React.FC<IconProps> = (props) => {
|
|||
name: 'Plugins',
|
||||
},
|
||||
google: { icon: <img src="/assets/google-palm.svg" alt="Palm Icon" />, name: 'PaLM2' },
|
||||
anthropic: { icon: <AnthropicIcon size={size * 0.7} />, bg: '#d09a74', name: 'Claude' },
|
||||
anthropic: { icon: <AnthropicIcon size={size * 0.55} />, bg: '#d09a74', name: 'Claude' },
|
||||
bingAI: {
|
||||
icon: jailbreak ? (
|
||||
<img src="/assets/bingai-jb.png" alt="Bing Icon" />
|
||||
|
|
@ -62,7 +62,7 @@ const Icon: React.FC<IconProps> = (props) => {
|
|||
name: jailbreak ? 'Sydney' : 'BingAI',
|
||||
},
|
||||
chatGPTBrowser: {
|
||||
icon: <GPTIcon size={size * 0.7} />,
|
||||
icon: <GPTIcon size={size * 0.55} />,
|
||||
bg:
|
||||
typeof model === 'string' && model.toLowerCase().includes('gpt-4')
|
||||
? '#AB68FF'
|
||||
|
|
@ -73,7 +73,7 @@ const Icon: React.FC<IconProps> = (props) => {
|
|||
default: { icon: <GPTIcon size={size * 0.7} />, bg: 'grey', name: 'UNKNOWN' },
|
||||
};
|
||||
|
||||
const { icon, bg, name } = endpointIcons[endpoint ?? ''] ?? endpointIcons.default;
|
||||
const { icon, bg, name } = endpointIcons[endpoint ?? 'default'];
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ export default function ModelItem({
|
|||
error: false,
|
||||
className: 'mr-2',
|
||||
message: false,
|
||||
isCreatedByUser: false,
|
||||
});
|
||||
|
||||
const userProvidesKey = endpointsConfig?.[endpoint]?.userProvide;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import EndpointItem from './EndpointItem';
|
||||
|
||||
export default function EndpointItems({ endpoints, onSelect, selectedEndpoint }) {
|
||||
interface EndpointItemsProps {
|
||||
endpoints: string[];
|
||||
onSelect: (endpoint: string) => void;
|
||||
selectedEndpoint: string;
|
||||
}
|
||||
|
||||
export default function EndpointItems({ endpoints, selectedEndpoint }: EndpointItemsProps) {
|
||||
return (
|
||||
<>
|
||||
{endpoints.map((endpoint) => (
|
||||
|
|
@ -8,7 +14,6 @@ export default function EndpointItems({ endpoints, onSelect, selectedEndpoint })
|
|||
isSelected={selectedEndpoint === endpoint}
|
||||
key={endpoint}
|
||||
value={endpoint}
|
||||
onSelect={onSelect}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -17,6 +17,7 @@ export default function PresetItem({
|
|||
model: preset?.model,
|
||||
error: false,
|
||||
className: 'mr-2',
|
||||
isCreatedByUser: false,
|
||||
});
|
||||
|
||||
const getPresetTitle = () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import { render, fireEvent } from '@testing-library/react';
|
||||
import Button from '../Button';
|
||||
|
||||
describe('Button', () => {
|
||||
it('renders with the correct type and children', () => {
|
||||
const { getByTestId, getByText } = render(
|
||||
<Button
|
||||
type="regenerate"
|
||||
onClick={() => {
|
||||
('');
|
||||
}}
|
||||
>
|
||||
Regenerate
|
||||
</Button>,
|
||||
);
|
||||
expect(getByTestId('regenerate-generation-button')).toBeInTheDocument();
|
||||
expect(getByText('Regenerate')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onClick when clicked', () => {
|
||||
const handleClick = jest.fn();
|
||||
const { getByText } = render(
|
||||
<Button type="continue" onClick={handleClick}>
|
||||
Continue
|
||||
</Button>,
|
||||
);
|
||||
fireEvent.click(getByText('Continue'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { render, fireEvent } from '@testing-library/react';
|
||||
import Continue from '../Continue';
|
||||
|
||||
describe('Continue', () => {
|
||||
it('should render the Continue button', () => {
|
||||
const { getByText } = render(
|
||||
<Continue
|
||||
onClick={() => {
|
||||
('');
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(getByText('Continue')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClick when the button is clicked', () => {
|
||||
const handleClick = jest.fn();
|
||||
const { getByText } = render(<Continue onClick={handleClick} />);
|
||||
fireEvent.click(getByText('Continue'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { render, fireEvent } from '@testing-library/react';
|
||||
import Regenerate from '../Regenerate';
|
||||
|
||||
describe('Regenerate', () => {
|
||||
it('should render the Regenerate button', () => {
|
||||
const { getByText } = render(
|
||||
<Regenerate
|
||||
onClick={() => {
|
||||
('');
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(getByText('Regenerate')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClick when the button is clicked', () => {
|
||||
const handleClick = jest.fn();
|
||||
const { getByText } = render(<Regenerate onClick={handleClick} />);
|
||||
fireEvent.click(getByText('Regenerate'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { render, fireEvent } from '@testing-library/react';
|
||||
import Stop from '../Stop';
|
||||
|
||||
describe('Stop', () => {
|
||||
it('should render the Stop button', () => {
|
||||
const { getByText } = render(
|
||||
<Stop
|
||||
onClick={() => {
|
||||
('');
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
expect(getByText('Stop')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClick when the button is clicked', () => {
|
||||
const handleClick = jest.fn();
|
||||
const { getByText } = render(<Stop onClick={handleClick} />);
|
||||
fireEvent.click(getByText('Stop'));
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -94,6 +94,7 @@ export default function Message({
|
|||
...conversation,
|
||||
...message,
|
||||
model: message?.model ?? conversation?.model,
|
||||
size: 38,
|
||||
});
|
||||
|
||||
if (message?.bg && searchResult) {
|
||||
|
|
@ -135,7 +136,7 @@ export default function Message({
|
|||
<>
|
||||
<div {...props} onWheel={handleScroll} onTouchMove={handleScroll}>
|
||||
<div className="relative m-auto flex gap-4 p-4 text-base md:max-w-2xl md:gap-6 md:py-6 lg:max-w-2xl lg:px-0 xl:max-w-3xl">
|
||||
<div className="relative flex h-[30px] w-[30px] flex-col items-end text-right text-xs md:text-sm">
|
||||
<div className="relative flex h-[40px] w-[40px] flex-col items-end text-right text-xs md:text-sm">
|
||||
{typeof icon === 'string' && /[^\\x00-\\x7F]+/.test(icon as string) ? (
|
||||
<span className=" direction-rtl w-40 overflow-x-scroll">{icon}</span>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ const ClearConvos = ({ open, onOpenChange }) => {
|
|||
const [confirmClear, setConfirmClear] = useState(false);
|
||||
const localize = useLocalize();
|
||||
|
||||
// Clear all conversations
|
||||
const clearConvos = useCallback(() => {
|
||||
if (confirmClear) {
|
||||
console.log('Clearing conversations...');
|
||||
|
|
@ -22,6 +23,7 @@ const ClearConvos = ({ open, onOpenChange }) => {
|
|||
}
|
||||
}, [confirmClear, clearConvosMutation]);
|
||||
|
||||
// Refresh conversations after clearing
|
||||
useEffect(() => {
|
||||
if (clearConvosMutation.isSuccess) {
|
||||
refreshConversations();
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import { forwardRef } from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
const NavLink = forwardRef((props, ref) => {
|
||||
const { svg, text, clickHandler, className = '' } = props;
|
||||
const defaultProps = {};
|
||||
|
||||
defaultProps.className = cn(
|
||||
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10',
|
||||
className,
|
||||
);
|
||||
|
||||
if (clickHandler) {
|
||||
defaultProps.onClick = clickHandler;
|
||||
}
|
||||
|
||||
return (
|
||||
<a {...defaultProps} ref={ref}>
|
||||
{svg()}
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
export default NavLink;
|
||||
35
client/src/components/Nav/NavLink.tsx
Normal file
35
client/src/components/Nav/NavLink.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { FC, forwardRef } from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
interface Props {
|
||||
svg: () => JSX.Element;
|
||||
text: string;
|
||||
clickHandler?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const NavLink: FC<Props> = forwardRef<HTMLAnchorElement, Props>((props, ref) => {
|
||||
const { svg, text, clickHandler, className = '' } = props;
|
||||
const defaultProps: {
|
||||
className: string;
|
||||
onClick?: () => void;
|
||||
} = {
|
||||
className: cn(
|
||||
'flex cursor-pointer items-center gap-3 rounded-md py-3 px-3 text-sm text-white transition-colors duration-200 hover:bg-gray-500/10',
|
||||
className,
|
||||
),
|
||||
};
|
||||
|
||||
if (clickHandler) {
|
||||
defaultProps.onClick = clickHandler;
|
||||
}
|
||||
|
||||
return (
|
||||
<a {...defaultProps} ref={ref}>
|
||||
{svg()}
|
||||
{text}
|
||||
</a>
|
||||
);
|
||||
});
|
||||
|
||||
export default NavLink;
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
/* eslint-disable indent */
|
||||
import React from 'react';
|
||||
import { cn } from '~/utils/';
|
||||
|
||||
export default function AzureMinimalIcon({ size = 25, className = 'h-4 w-4' }) {
|
||||
const height = size;
|
||||
const width = size;
|
||||
|
||||
export default function AzureMinimalIcon() {
|
||||
return (
|
||||
<svg
|
||||
stroke="currentColor"
|
||||
|
|
@ -10,9 +13,9 @@ export default function AzureMinimalIcon() {
|
|||
viewBox="0 0 24 24"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="h-4 w-4"
|
||||
height="1em"
|
||||
width="1em"
|
||||
className={cn(className, '')}
|
||||
width={width}
|
||||
height={height}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="m8.0458 0.81981a1.1197 1.1197 0 0 0-1.0608 0.76184l-6.7912 20.123a1.1178 1.1178 0 0 0 1.0592 1.4751h5.4647a1.1197 1.1197 0 0 0 1.0608-0.7615l1.3528-4.0084-2.3684-2.2107a0.51536 0.51536 0 0 1 0.35193-0.8923h3.0639l1.8213-5.3966-2.8111-8.3294a1.1181 1.1181 0 0 0-1.0595-0.76049h-0.0836z" />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue