diff --git a/web/containers/Providers/EventHandler.tsx b/web/containers/Providers/EventHandler.tsx index 5af6d4917..ac793b4ae 100644 --- a/web/containers/Providers/EventHandler.tsx +++ b/web/containers/Providers/EventHandler.tsx @@ -61,6 +61,7 @@ export default function EventHandler({ children }: { children: ReactNode }) { toaster({ title: 'Success!', description: `Model ${model.id} has been started.`, + type: 'success', }) setStateModel(() => ({ state: 'stop', diff --git a/web/containers/Providers/index.tsx b/web/containers/Providers/index.tsx index f9726e43d..dd9069a95 100644 --- a/web/containers/Providers/index.tsx +++ b/web/containers/Providers/index.tsx @@ -82,7 +82,7 @@ const Providers = (props: PropsWithChildren) => { {!isMac && } - + )} diff --git a/web/containers/Toast/index.tsx b/web/containers/Toast/index.tsx index c5e5f03da..7cffa89b9 100644 --- a/web/containers/Toast/index.tsx +++ b/web/containers/Toast/index.tsx @@ -6,7 +6,99 @@ import { twMerge } from 'tailwind-merge' type Props = { title?: string description?: string - type?: 'default' | 'error' | 'success' + type?: 'default' | 'error' | 'success' | 'warning' +} + +const ErrorIcon = () => { + return ( + + + + ) +} + +const WarningIcon = () => { + return ( + + + + ) +} + +const SuccessIcon = () => { + return ( + + + + ) +} + +const DefaultIcon = () => { + return ( + + + + ) +} + +const renderIcon = (type: string) => { + switch (type) { + case 'warning': + return + + case 'error': + return + + case 'success': + return + + default: + return + } } export function toaster(props: Props) { @@ -16,37 +108,52 @@ export function toaster(props: Props) { return (
-
-

- {title} -

-

- {description} -

+
+
{renderIcon(type)}
+
+

{title}

+

{description}

+
+ toast.dismiss(t.id)} + />
- toast.dismiss(t.id)} - />
) }, - { id: 'toast', duration: 3000 } + { id: 'toast', duration: 2000, position: 'top-right' } + ) +} + +export function snackbar(props: Props) { + const { description, type = 'default' } = props + return toast.custom( + (t) => { + return ( +
+
+
{renderIcon(type)}
+

{description}

+ toast.dismiss(t.id)} + /> +
+
+ ) + }, + { id: 'snackbar', duration: 2000, position: 'bottom-center' } ) } diff --git a/web/hooks/useActiveModel.ts b/web/hooks/useActiveModel.ts index 336f0be21..a456d8787 100644 --- a/web/hooks/useActiveModel.ts +++ b/web/hooks/useActiveModel.ts @@ -42,6 +42,7 @@ export function useActiveModel() { toaster({ title: `Model ${modelId} not found!`, description: `Please download the model first.`, + type: 'warning', }) setStateModel(() => ({ state: 'start', diff --git a/web/hooks/useDeleteModel.ts b/web/hooks/useDeleteModel.ts index cd7292997..fa0cfb45e 100644 --- a/web/hooks/useDeleteModel.ts +++ b/web/hooks/useDeleteModel.ts @@ -19,6 +19,7 @@ export default function useDeleteModel() { toaster({ title: 'Model Deletion Successful', description: `The model ${model.id} has been successfully deleted.`, + type: 'success', }) } diff --git a/web/hooks/useDeleteThread.ts b/web/hooks/useDeleteThread.ts index 00ba98b99..88710f777 100644 --- a/web/hooks/useDeleteThread.ts +++ b/web/hooks/useDeleteThread.ts @@ -86,6 +86,7 @@ export default function useDeleteThread() { toaster({ title: 'Thread successfully deleted.', description: `Thread ${threadId} has been successfully deleted.`, + type: 'success', }) } diff --git a/web/hooks/useDownloadState.ts b/web/hooks/useDownloadState.ts index d39ab5e58..37f41d2a1 100644 --- a/web/hooks/useDownloadState.ts +++ b/web/hooks/useDownloadState.ts @@ -26,6 +26,7 @@ const setDownloadStateSuccessAtom = atom(null, (get, set, modelId: string) => { toaster({ title: 'Download Completed', description: `Download ${modelId} completed`, + type: 'success', }) }) @@ -61,6 +62,7 @@ const setDownloadStateCancelledAtom = atom( toaster({ title: 'Cancel Download', description: `Model ${modelId} cancel download`, + type: 'warning', }) return diff --git a/web/hooks/useSendChatMessage.ts b/web/hooks/useSendChatMessage.ts index 71dc99d21..835bdfed4 100644 --- a/web/hooks/useSendChatMessage.ts +++ b/web/hooks/useSendChatMessage.ts @@ -160,7 +160,7 @@ export default function useSendChatMessage() { activeThread.assistants[0].model.id !== selectedModel?.id ) { if (!selectedModel) { - toaster({ title: 'Please select a model' }) + toaster({ title: 'Please select a model', type: 'warning' }) return } const assistantId = activeThread.assistants[0].assistant_id ?? '' diff --git a/web/screens/Chat/index.tsx b/web/screens/Chat/index.tsx index cfd47ad39..887d9d035 100644 --- a/web/screens/Chat/index.tsx +++ b/web/screens/Chat/index.tsx @@ -5,7 +5,7 @@ import { useDropzone } from 'react-dropzone' import { useAtomValue, useSetAtom } from 'jotai' -import { UploadCloudIcon, XIcon } from 'lucide-react' +import { UploadCloudIcon } from 'lucide-react' import { twMerge } from 'tailwind-merge' @@ -15,6 +15,8 @@ import ModelStart from '@/containers/Loader/ModelStart' import { currentPromptAtom, fileUploadAtom } from '@/containers/Providers/Jotai' import { showLeftSideBarAtom } from '@/containers/Providers/KeyListener' +import { snackbar } from '@/containers/Toast' + import { queuedMessageAtom, reloadModelAtom } from '@/hooks/useSendChatMessage' import ChatBody from '@/screens/Chat/ChatBody' @@ -112,8 +114,13 @@ const ChatScreen: React.FC = () => { }, }) - // TODO @faisal change this until we have sneakbar component useEffect(() => { + if (dragRejected.code) { + snackbar({ + description: renderError(dragRejected.code), + type: 'error', + }) + } setTimeout(() => { if (dragRejected.code) { setDragRejected({ code: '' }) @@ -134,33 +141,6 @@ const ChatScreen: React.FC = () => { className="relative flex h-full w-full flex-col overflow-auto bg-background outline-none" {...getRootProps()} > - {dragRejected.code !== '' && ( -
-
- - - -

{renderError(dragRejected.code)}

- setDragRejected({ code: '' })} - /> -
-
- )} - {dragOver && (
{ toaster({ title: 'Logs cleared', description: 'All logs have been cleared.', + type: 'success', }) }