refactor(Nextjs): Remove toast add sonner with different varient & colors
This commit is contained in:
parent
b2210cdaac
commit
e04fdc5558
26 changed files with 241 additions and 746 deletions
|
|
@ -18,12 +18,12 @@ import { Button } from "@/components/ui/button";
|
|||
import { Label } from "@/components/ui/label";
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { Camera, Loader2, Plus } from "lucide-react";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { useSelector } from "react-redux";
|
||||
import { RootState } from "@/store/store";
|
||||
import { getStaticFileUrl, isDarkColor } from "../../utils/others";
|
||||
import { defaultFooterProperties, useFooterContext } from "../../context/footerContext";
|
||||
import { FooterProperties } from "../../services/footerService";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const SlideFooter: React.FC = () => {
|
||||
const [showEditor, setShowEditor] = useState<boolean>(false);
|
||||
|
|
@ -43,17 +43,13 @@ const SlideFooter: React.FC = () => {
|
|||
const handleSave = async () => {
|
||||
await saveFooterProperties(footerProperties);
|
||||
setIsPropertyChanged(false);
|
||||
toast({
|
||||
title: "Footer properties saved successfully",
|
||||
});
|
||||
toast.success("Footer properties saved successfully");
|
||||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
await resetFooterProperties();
|
||||
setFooterProperties(defaultFooterProperties);
|
||||
toast({
|
||||
title: "Footer properties reset to default",
|
||||
});
|
||||
toast.success("Footer properties reset to default");
|
||||
};
|
||||
|
||||
const updateProperty = (path: string, value: any): void => {
|
||||
|
|
@ -186,9 +182,7 @@ const SlideFooter: React.FC = () => {
|
|||
if (!file) return;
|
||||
|
||||
if (!file.type.startsWith("image/")) {
|
||||
toast({
|
||||
title: "Please Upload An Image File",
|
||||
});
|
||||
toast.error("Please Upload An Image File");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -208,10 +202,7 @@ const SlideFooter: React.FC = () => {
|
|||
}));
|
||||
} catch (error) {
|
||||
console.error("Error converting image:", error);
|
||||
toast({
|
||||
title: "Error uploading image",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Error uploading image");
|
||||
} finally {
|
||||
setIsUploading({ ...isUploading, white: false });
|
||||
}
|
||||
|
|
@ -225,9 +216,7 @@ const SlideFooter: React.FC = () => {
|
|||
if (!file) return;
|
||||
|
||||
if (!file.type.startsWith("image/")) {
|
||||
toast({
|
||||
title: "Please Upload An Image File",
|
||||
});
|
||||
toast.error("Please Upload An Image File");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -247,10 +236,7 @@ const SlideFooter: React.FC = () => {
|
|||
}));
|
||||
} catch (error) {
|
||||
console.error("Error converting image:", error);
|
||||
toast({
|
||||
title: "Error uploading image",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Error uploading image");
|
||||
} finally {
|
||||
setIsUploading({ ...isUploading, dark: false });
|
||||
}
|
||||
|
|
@ -277,10 +263,8 @@ const SlideFooter: React.FC = () => {
|
|||
|
||||
const handleSheetClose = () => {
|
||||
if (isPropertyChanged) {
|
||||
toast({
|
||||
title: "Unsaved Changes",
|
||||
toast.error("Unsaved Changes", {
|
||||
description: "Please save changes before closing the editor",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import * as z from 'zod';
|
||||
|
||||
export interface LayoutInfo {
|
||||
|
|
@ -98,8 +98,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||
const module = await import(`@/presentation-layouts/${groupData.groupName}/${file}`);
|
||||
|
||||
if (!module.default) {
|
||||
toast({
|
||||
title: `${file} has no default export`,
|
||||
toast.error(`${file} has no default export`, {
|
||||
description: 'Please ensure the layout file exports a default component',
|
||||
});
|
||||
console.warn(`❌ ${file} has no default export`);
|
||||
|
|
@ -107,8 +106,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children })
|
|||
}
|
||||
|
||||
if (!module.Schema) {
|
||||
toast({
|
||||
title: `${file} has no Schema export`,
|
||||
toast.error(`${file} has no Schema export`, {
|
||||
description: 'Please ensure the layout file exports a Schema',
|
||||
});
|
||||
console.warn(`❌ ${file} has no Schema export`);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import { useRouter } from "next/navigation";
|
||||
import { RootState } from "@/store/store";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import MarkdownRenderer from "./MarkdownRenderer";
|
||||
import { getIconFromFile } from "../../utils/others";
|
||||
import { ChevronRight, PanelRightOpen, X } from "lucide-react";
|
||||
|
|
@ -122,11 +122,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
});
|
||||
} catch (error) {
|
||||
console.error('Error reading files:', error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to read document content",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to read document content");
|
||||
}
|
||||
setDownloadingDocuments([]);
|
||||
}
|
||||
|
|
@ -155,11 +151,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
router.push("/outline");
|
||||
} catch (error) {
|
||||
console.error("Error in presentation creation:", error);
|
||||
toast({
|
||||
title: "Error in presentation creation.",
|
||||
description: "Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Error in presentation creation. Please try again.");
|
||||
setShowLoading({
|
||||
message: "Error in presentation creation.",
|
||||
show: true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { setOutlines, SlideOutline } from "@/store/slices/presentationGeneration";
|
||||
import { jsonrepair } from "jsonrepair";
|
||||
import { StreamState } from "../types/index";
|
||||
|
|
@ -56,11 +56,7 @@ export const useOutlineStreaming = (presentationId: string | null) => {
|
|||
eventSource.close();
|
||||
} catch (error) {
|
||||
console.error("Error parsing accumulated chunks:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to parse presentation data",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to parse presentation data");
|
||||
eventSource.close();
|
||||
}
|
||||
accumulatedChunks = "";
|
||||
|
|
@ -76,19 +72,11 @@ export const useOutlineStreaming = (presentationId: string | null) => {
|
|||
eventSource.onerror = () => {
|
||||
setStreamState({ isStreaming: false, isLoading: false });
|
||||
eventSource.close();
|
||||
toast({
|
||||
title: "Connection Error",
|
||||
description: "Failed to connect to the server. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to connect to the server. Please try again.");
|
||||
};
|
||||
} catch (error) {
|
||||
setStreamState({ isStreaming: false, isLoading: false });
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to initialize connection",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to initialize connection");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useCallback } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { clearPresentationData, setPresentationData, SlideOutline } from "@/store/slices/presentationGeneration";
|
||||
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
|
|
@ -27,19 +27,15 @@ export const usePresentationGeneration = (
|
|||
|
||||
const validateInputs = useCallback(() => {
|
||||
if (!outlines || outlines.length === 0) {
|
||||
toast({
|
||||
title: "No Outlines",
|
||||
toast.error("No Outlines", {
|
||||
description: "Please wait for outlines to load before generating presentation",
|
||||
variant: "destructive",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!selectedLayoutGroup) {
|
||||
toast({
|
||||
title: "Select Layout Group",
|
||||
toast.error("Select Layout Group", {
|
||||
description: "Please select a layout group before generating presentation",
|
||||
variant: "destructive",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
|
@ -101,10 +97,8 @@ export const usePresentationGeneration = (
|
|||
}
|
||||
} catch (error) {
|
||||
console.error("Error in data generation", error);
|
||||
toast({
|
||||
title: "Generation Error",
|
||||
toast.error("Generation Error", {
|
||||
description: "Failed to generate presentation. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setLoadingState(DEFAULT_LOADING_STATE);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||
import { DashboardApi } from "@/app/dashboard/api/dashboard";
|
||||
|
||||
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
|
||||
|
||||
|
||||
|
|
@ -40,11 +40,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
setContentLoading(false);
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to load presentation",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to load presentation");
|
||||
console.error("Error fetching user slides:", error);
|
||||
setContentLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import Link from "next/link";
|
||||
|
||||
import { RootState } from "@/store/store";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import Modal from "./Modal";
|
||||
|
||||
|
|
@ -75,11 +75,9 @@ const Header = ({
|
|||
} catch (error) {
|
||||
console.error("Export failed:", error);
|
||||
setShowLoader(false);
|
||||
toast({
|
||||
title: "Having trouble exporting!",
|
||||
toast.error("Having trouble exporting!", {
|
||||
description:
|
||||
"We are having trouble exporting your presentation. Please try again.",
|
||||
variant: "default",
|
||||
});
|
||||
} finally {
|
||||
setShowLoader(false);
|
||||
|
|
@ -111,11 +109,9 @@ const Header = ({
|
|||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast({
|
||||
title: "Having trouble exporting!",
|
||||
toast.error("Having trouble exporting!", {
|
||||
description:
|
||||
"We are having trouble exporting your presentation. Please try again.",
|
||||
variant: "default",
|
||||
});
|
||||
} finally {
|
||||
setShowLoader(false);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from "@/components/ui/popover";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { SendHorizontal } from "lucide-react";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
||||
import ToolTip from "@/components/ToolTip";
|
||||
import { RootState } from "@/store/store";
|
||||
|
|
@ -45,11 +45,7 @@ const SlideContent = ({
|
|||
) as HTMLInputElement;
|
||||
const value = element?.value;
|
||||
if (!value?.trim()) {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Please enter a prompt before submitting",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Please enter a prompt before submitting");
|
||||
return;
|
||||
}
|
||||
setIsUpdating(true);
|
||||
|
|
@ -64,18 +60,11 @@ const SlideContent = ({
|
|||
if (response) {
|
||||
console.log("response", response);
|
||||
dispatch(updateSlide({ index: slide.index, slide: response }));
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Slide updated successfully",
|
||||
});
|
||||
toast.success("Slide updated successfully");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error updating slide:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to update slide. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to update slide. Please try again.");
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const useAutoSave = ({
|
|||
debounceMs = 2000,
|
||||
enabled = true,
|
||||
}: UseAutoSaveOptions = {}) => {
|
||||
const { presentationData } = useSelector(
|
||||
const { presentationData, isStreaming, isLoading } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
);
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ export const useAutoSave = ({
|
|||
|
||||
// Effect to trigger auto-save when presentation data changes
|
||||
useEffect(() => {
|
||||
if (!enabled || !presentationData) return;
|
||||
if (!enabled || !presentationData || isStreaming || isLoading) return;
|
||||
|
||||
// Trigger debounced save
|
||||
debouncedSave(presentationData);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useCallback } from "react";
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { DashboardApi } from "@/app/dashboard/api/dashboard";
|
||||
import { setPresentationData } from "@/store/slices/presentationGeneration";
|
||||
|
||||
|
|
@ -11,7 +11,6 @@ export const usePresentationData = (
|
|||
) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
||||
const fetchUserSlides = useCallback(async () => {
|
||||
try {
|
||||
const data = await DashboardApi.getPresentation(presentationId);
|
||||
|
|
@ -21,20 +20,17 @@ export const usePresentationData = (
|
|||
}
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to load presentation",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Failed to load presentation");
|
||||
console.error("Error fetching user slides:", error);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [presentationId, dispatch, setLoading, setError]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchUserSlides();
|
||||
}, [fetchUserSlides]);
|
||||
|
||||
return {
|
||||
fetchUserSlides,
|
||||
|
||||
};
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setPresentationData, setStreaming } from "@/store/slices/presentationGeneration";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { clearPresentationData, setPresentationData, setStreaming } from "@/store/slices/presentationGeneration";
|
||||
import { jsonrepair } from "jsonrepair";
|
||||
import { RootState } from "@/store/store";
|
||||
|
||||
export const usePresentationStreaming = (
|
||||
presentationId: string,
|
||||
|
|
@ -10,6 +11,8 @@ export const usePresentationStreaming = (
|
|||
setError: (error: boolean) => void,
|
||||
fetchUserSlides: () => void
|
||||
) => {
|
||||
const { presentationData } = useSelector((state: RootState) => state.presentationGeneration);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const previousSlidesLength = useRef(0);
|
||||
|
||||
|
|
@ -19,6 +22,7 @@ export const usePresentationStreaming = (
|
|||
|
||||
const initializeStream = async () => {
|
||||
dispatch(setStreaming(true));
|
||||
dispatch(clearPresentationData());
|
||||
|
||||
eventSource = new EventSource(
|
||||
`/api/v1/ppt/presentation/stream?presentation_id=${presentationId}`
|
||||
|
|
@ -98,7 +102,9 @@ export const usePresentationStreaming = (
|
|||
if (stream) {
|
||||
initializeStream();
|
||||
} else {
|
||||
fetchUserSlides();
|
||||
if(!presentationData || presentationData.slides.length === 0){
|
||||
fetchUserSlides();
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { File, X, Upload } from 'lucide-react'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import { toast } from 'sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface FileWithId extends File {
|
||||
|
|
@ -17,7 +17,6 @@ interface SupportingDocProps {
|
|||
const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => {
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const { toast } = useToast()
|
||||
|
||||
// Convert Files to FileWithId with proper type checking
|
||||
const filesWithIds: FileWithId[] = files.map(file => {
|
||||
|
|
@ -57,19 +56,15 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => {
|
|||
|
||||
const invalidFiles = droppedFiles.filter(file => !validTypes.includes(file.type));
|
||||
if (invalidFiles.length > 0) {
|
||||
toast({
|
||||
title: 'Invalid file type',
|
||||
toast.error('Invalid file type', {
|
||||
description: 'Please upload only PDF, TXT, PPTX, or DOCX files',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasPdf && droppedFiles.some(file => file.type === 'application/pdf')) {
|
||||
toast({
|
||||
title: 'Multiple PDF files are not allowed',
|
||||
toast.error('Multiple PDF files are not allowed', {
|
||||
description: 'Please select only one PDF file',
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -82,8 +77,7 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => {
|
|||
const updatedFiles = [...files, ...validFiles]
|
||||
onFilesChange(updatedFiles)
|
||||
|
||||
toast({
|
||||
title: 'Files selected',
|
||||
toast.success('Files selected', {
|
||||
description: `${validFiles.length} file(s) have been added`,
|
||||
})
|
||||
}
|
||||
|
|
@ -102,8 +96,7 @@ const SupportingDoc = ({ files, onFilesChange }: SupportingDocProps) => {
|
|||
const updatedFiles = [...files, ...validFiles]
|
||||
onFilesChange(updatedFiles)
|
||||
|
||||
toast({
|
||||
title: 'Files selected',
|
||||
toast.success('Files selected', {
|
||||
description: `${validFiles.length} file(s) have been added`,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
import React from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
const ToastTesting = () => {
|
||||
return (
|
||||
<div className="p-8 space-y-4">
|
||||
<h2 className="text-2xl font-bold mb-6">Toast Testing - All Variants</h2>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{/* Success Toast */}
|
||||
<Button
|
||||
onClick={() => toast.success('Success! Operation completed successfully', {
|
||||
description: 'Your data has been saved.',
|
||||
duration: 4000,
|
||||
richColors: true,
|
||||
})}
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
Success Toast
|
||||
</Button>
|
||||
|
||||
{/* Error Toast */}
|
||||
<Button
|
||||
onClick={() => toast.error('Error! Something went wrong', {
|
||||
description: 'Please try again later.',
|
||||
duration: 5000,
|
||||
richColors: true,
|
||||
})}
|
||||
className="bg-red-600 hover:bg-red-700"
|
||||
>
|
||||
Error Toast
|
||||
</Button>
|
||||
|
||||
{/* Info Toast */}
|
||||
<Button
|
||||
onClick={() => toast.info('Information', {
|
||||
description: 'Here is some useful information for you.',
|
||||
duration: 4000,
|
||||
richColors: true,
|
||||
})}
|
||||
className="bg-blue-600 hover:bg-blue-700"
|
||||
>
|
||||
Info Toast
|
||||
</Button>
|
||||
|
||||
{/* Warning Toast */}
|
||||
<Button
|
||||
onClick={() => toast.warning('Warning! Please be careful', {
|
||||
description: 'This action cannot be undone.',
|
||||
duration: 4000,
|
||||
richColors: true,
|
||||
})}
|
||||
className="bg-yellow-600 hover:bg-yellow-700"
|
||||
>
|
||||
Warning Toast
|
||||
</Button>
|
||||
|
||||
{/* Loading Toast */}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const loadingToast = toast.loading('Processing...', {
|
||||
description: 'Please wait while we process your request.',
|
||||
});
|
||||
|
||||
// Simulate loading completion after 3 seconds
|
||||
setTimeout(() => {
|
||||
toast.dismiss(loadingToast);
|
||||
toast.success('Processing completed!');
|
||||
}, 3000);
|
||||
}}
|
||||
className="bg-indigo-600 hover:bg-indigo-700"
|
||||
>
|
||||
Loading Toast
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
{/* Promise Toast */}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Math.random() > 0.5 ? resolve('Success!') : reject('Failed!');
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
toast.promise(promise, {
|
||||
loading: 'Uploading file...',
|
||||
success: 'File uploaded successfully!',
|
||||
error: 'Failed to upload file',
|
||||
});
|
||||
}}
|
||||
className="bg-teal-600 hover:bg-teal-700"
|
||||
>
|
||||
Promise Toast
|
||||
</Button>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ToastTesting
|
||||
|
|
@ -20,12 +20,12 @@ import { LanguageType, PresentationConfig } from "../type";
|
|||
import SupportingDoc from "./SupportingDoc";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
||||
import { OverlayLoader } from "@/components/ui/overlay-loader";
|
||||
import Wrapper from "@/components/Wrapper";
|
||||
import { setPptGenUploadState } from "@/store/slices/presentationGenUpload";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
import ToastTesting from "./ToastTesting";
|
||||
|
||||
// Types for loading state
|
||||
interface LoadingState {
|
||||
|
|
@ -39,7 +39,6 @@ interface LoadingState {
|
|||
const UploadPage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
const { toast } = useToast();
|
||||
|
||||
// State management
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
|
|
@ -72,18 +71,12 @@ const UploadPage = () => {
|
|||
*/
|
||||
const validateConfiguration = (): boolean => {
|
||||
if (!config.language || !config.slides) {
|
||||
toast({
|
||||
title: "Please select number of Slides & Language",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Please select number of Slides & Language");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!config.prompt.trim() && files.length === 0) {
|
||||
toast({
|
||||
title: "No Prompt or Document Provided",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("No Prompt or Document Provided");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -177,10 +170,8 @@ const UploadPage = () => {
|
|||
duration: 0,
|
||||
showProgress: false,
|
||||
});
|
||||
toast({
|
||||
title: "Error",
|
||||
toast.error("Error", {
|
||||
description: "Failed to generate presentation. Please try again.",
|
||||
variant: "destructive",
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -200,6 +191,7 @@ const UploadPage = () => {
|
|||
onConfigChange={handleConfigChange}
|
||||
/>
|
||||
</div>
|
||||
<ToastTesting />
|
||||
<div className="relative">
|
||||
<PromptInput
|
||||
value={config.prompt}
|
||||
|
|
|
|||
|
|
@ -52,170 +52,10 @@ export function removeUUID(fileName: string) {
|
|||
}
|
||||
|
||||
|
||||
export function generateRandomId(): string {
|
||||
const length = 36;
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_';
|
||||
let id = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * chars.length);
|
||||
id += chars[randomIndex];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export const getFontLink = (fontName: string) => {
|
||||
if (!fontName) {
|
||||
return { link: '', name: '' };
|
||||
}
|
||||
|
||||
if (fontName.includes('instrument')) {
|
||||
return { link: 'https://fonts.google.com/specimen/Instrument+Sans', name: 'Instrument Sans' }
|
||||
}
|
||||
if (fontName.includes('fraunces')) {
|
||||
return { link: 'https://fonts.google.com/specimen/Fraunces', name: 'Fraunces' }
|
||||
}
|
||||
if (fontName.includes('montserrat')) {
|
||||
return { link: 'https://fonts.google.com/specimen/Montserrat', name: 'Montserrat' }
|
||||
}
|
||||
if (fontName.includes('inria-serif')) {
|
||||
return { link: 'https://fonts.google.com/specimen/Inria+Serif', name: 'Inria Serif' }
|
||||
}
|
||||
if (fontName.includes('inter')) {
|
||||
return { link: 'https://fonts.google.com/specimen/Inter', name: 'Inter' }
|
||||
}
|
||||
else {
|
||||
return { link: '', name: '' };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const numberTranslations: any = {
|
||||
// Key languages
|
||||
"English (English)": ["01", "02", "03", "04", "05"],
|
||||
"English(English)": ["01", "02", "03", "04", "05"],
|
||||
English: ["01", "02", "03", "04", "05"],
|
||||
"Spanish (Español)": ["01", "02", "03", "04", "05"],
|
||||
"French (Français)": ["01", "02", "03", "04", "05"],
|
||||
"German (Deutsch)": ["01", "02", "03", "04", "05"],
|
||||
"Portuguese (Português)": ["01", "02", "03", "04", "05"],
|
||||
"Italian (Italiano)": ["01", "02", "03", "04", "05"],
|
||||
"Dutch (Nederlands)": ["01", "02", "03", "04", "05"],
|
||||
"Russian (Русский)": ["01", "02", "03", "04", "05"],
|
||||
"Chinese (Simplified & Traditional - 中文, 汉语/漢語)": [
|
||||
"一",
|
||||
"二",
|
||||
"三",
|
||||
"四",
|
||||
"五",
|
||||
],
|
||||
"Japanese (日本語)": ["一", "二", "三", "四", "五"],
|
||||
"Korean (한국어)": ["일", "이", "삼", "사", "오"],
|
||||
"Arabic (العربية)": ["١", "٢", "٣", "٤", "٥"],
|
||||
"Hindi (हिन्दी)": ["०१", "०२", "०३", "०४", "०५"],
|
||||
"Bengali (বাংলা)": ["০১", "০২", "০৩", "০৪", "০৫"],
|
||||
|
||||
// European Languages
|
||||
"Polish (Polski)": ["01", "02", "03", "04", "05"],
|
||||
"Czech (Čeština)": ["01", "02", "03", "04", "05"],
|
||||
"Slovak (Slovenčina)": ["01", "02", "03", "04", "05"],
|
||||
"Hungarian (Magyar)": ["01", "02", "03", "04", "05"],
|
||||
"Romanian (Română)": ["01", "02", "03", "04", "05"],
|
||||
"Bulgarian (Български)": ["01", "02", "03", "04", "05"],
|
||||
"Greek (Ελληνικά)": ["α΄", "β΄", "γ΄", "δ΄", "ε΄"],
|
||||
"Serbian (Српски)": ["01", "02", "03", "04", "05"],
|
||||
"Croatian (Hrvatski)": ["01", "02", "03", "04", "05"],
|
||||
"Bosnian (Bosanski)": ["01", "02", "03", "04", "05"],
|
||||
"Slovenian (Slovenščina)": ["01", "02", "03", "04", "05"],
|
||||
"Finnish (Suomi)": ["01", "02", "03", "04", "05"],
|
||||
"Swedish (Svenska)": ["01", "02", "03", "04", "05"],
|
||||
"Danish (Dansk)": ["01", "02", "03", "04", "05"],
|
||||
"Norwegian (Norsk)": ["01", "02", "03", "04", "05"],
|
||||
"Icelandic (Íslenska)": ["01", "02", "03", "04", "05"],
|
||||
"Lithuanian (Lietuvių)": ["01", "02", "03", "04", "05"],
|
||||
"Latvian (Latviešu)": ["01", "02", "03", "04", "05"],
|
||||
"Estonian (Eesti)": ["01", "02", "03", "04", "05"],
|
||||
"Maltese (Malti)": ["01", "02", "03", "04", "05"],
|
||||
"Welsh (Cymraeg)": ["01", "02", "03", "04", "05"],
|
||||
"Irish (Gaeilge)": ["01", "02", "03", "04", "05"],
|
||||
"Scottish Gaelic (Gàidhlig)": ["01", "02", "03", "04", "05"],
|
||||
|
||||
// Middle Eastern and Central Asian Languages
|
||||
"Hebrew (עברית)": ["א׳", "ב׳", "ג׳", "ד׳", "ה׳"],
|
||||
"Persian/Farsi (فارسی)": ["۱", "۲", "۳", "۴", "۵"],
|
||||
"Turkish (Türkçe)": ["01", "02", "03", "04", "05"],
|
||||
"Kurdish (Kurdî / کوردی)": ["١", "٢", "٣", "٤", "٥"],
|
||||
"Pashto (پښتو)": ["١", "٢", "٣", "٤", "٥"],
|
||||
"Dari (دری)": ["١", "٢", "٣", "٤", "٥"],
|
||||
"Uzbek (Oʻzbek)": ["01", "02", "03", "04", "05"],
|
||||
"Kazakh (Қазақша)": ["01", "02", "03", "04", "05"],
|
||||
"Tajik (Тоҷикӣ)": ["01", "02", "03", "04", "05"],
|
||||
"Turkmen (Türkmençe)": ["01", "02", "03", "04", "05"],
|
||||
"Azerbaijani (Azərbaycan dili)": ["01", "02", "03", "04", "05"],
|
||||
|
||||
// South Asian Languages
|
||||
"Urdu (اردو)": ["١", "٢", "٣", "٤", "٥"],
|
||||
"Tamil (தமிழ்)": ["௧", "௨", "௩", "௪", "௫"],
|
||||
"Telugu (తెలుగు)": ["౧", "౨", "౩", "౪", "౫"],
|
||||
"Marathi (मराठी)": ["०१", "०२", "०३", "०४", "०५"],
|
||||
"Punjabi (ਪੰਜਾਬੀ / پنجابی)": ["੦੧", "੦੨", "੦੩", "੦੪", "੦੫"],
|
||||
"Gujarati (ગુજરાતી)": ["૦૧", "૦૨", "૦૩", "૦૪", "૦૫"],
|
||||
"Malayalam (മലയാളം)": ["൧", "൨", "൩", "൪", "൫"],
|
||||
"Kannada (ಕನ್ನಡ)": ["೧", "೨", "೩", "೪", "೫"],
|
||||
"Odia (ଓଡ଼ିଆ)": ["୧", "୨", "୩", "୪", "୫"],
|
||||
"Sinhala (සිංහල)": ["෧", "෨", "෩", "෪", "෫"],
|
||||
"Nepali (नेपाली)": ["०१", "०२", "०३", "०४", "०५"],
|
||||
|
||||
// East and Southeast Asian Languages
|
||||
"Thai (ไทย)": ["๑", "๒", "๓", "๔", "๕"],
|
||||
"Vietnamese (Tiếng Việt)": ["01", "02", "03", "04", "05"],
|
||||
"Lao (ລາວ)": ["໑", "໒", "໓", "໔", "໕"],
|
||||
"Khmer (ភាសាខ្មែរ)": ["១", "២", "៣", "៤", "៥"],
|
||||
"Burmese (မြန်မာစာ)": ["၁", "၂", "၃", "၄", "၅"],
|
||||
"Tagalog/Filipino (Tagalog/Filipino)": ["01", "02", "03", "04", "05"],
|
||||
"Javanese (Basa Jawa)": ["01", "02", "03", "04", "05"],
|
||||
"Sundanese (Basa Sunda)": ["01", "02", "03", "04", "05"],
|
||||
"Malay (Bahasa Melayu)": ["01", "02", "03", "04", "05"],
|
||||
"Mongolian (Монгол)": ["01", "02", "03", "04", "05"],
|
||||
|
||||
// African Languages
|
||||
"Swahili (Kiswahili)": ["01", "02", "03", "04", "05"],
|
||||
"Hausa (Hausa)": ["01", "02", "03", "04", "05"],
|
||||
"Yoruba (Yoruba)": ["01", "02", "03", "04", "05"],
|
||||
"Igbo (Igbo)": ["01", "02", "03", "04", "05"],
|
||||
"Amharic (አማርኛ)": ["፩", "፪", "፫", "፬", "፭"],
|
||||
"Zulu (isiZulu)": ["01", "02", "03", "04", "05"],
|
||||
"Xhosa (isiXhosa)": ["01", "02", "03", "04", "05"],
|
||||
"Shona (ChiShona)": ["01", "02", "03", "04", "05"],
|
||||
"Somali (Soomaaliga)": ["01", "02", "03", "04", "05"],
|
||||
|
||||
// Indigenous and Lesser-Known Languages
|
||||
"Basque (Euskara)": ["01", "02", "03", "04", "05"],
|
||||
"Catalan (Català)": ["01", "02", "03", "04", "05"],
|
||||
"Galician (Galego)": ["01", "02", "03", "04", "05"],
|
||||
"Quechua (Runasimi)": ["01", "02", "03", "04", "05"],
|
||||
"Nahuatl (Nāhuatl)": ["01", "02", "03", "04", "05"],
|
||||
"Hawaiian (ʻŌlelo Hawaiʻi)": ["01", "02", "03", "04", "05"],
|
||||
"Maori (Te Reo Māori)": ["01", "02", "03", "04", "05"],
|
||||
"Tahitian (Reo Tahiti)": ["01", "02", "03", "04", "05"],
|
||||
"Samoan (Gagana Samoa)": ["01", "02", "03", "04", "05"],
|
||||
};
|
||||
|
||||
export const ThemeImagePrompt = {
|
||||
light:
|
||||
"Classy and modern with a corporate and minimalist touch. Tone is serious yet elegant, using a palette of light, white, and cool gray colors.",
|
||||
dark: "Luxurious and futuristic with a simple, clean design. Professional yet elegant using a color scheme of dark, black, and high contrast.",
|
||||
faint_yellow:
|
||||
"Fresh young creatively vibrant style, utilizing a playful mixture of light colors like orange, salmon, and pastel purple, all set against a warm gradient.",
|
||||
cream:
|
||||
"Elegant with a classic and professional look. Subtle and minimalist using a warm palette of cream, beige, and light beige colors.",
|
||||
royal_blue:
|
||||
"Playful and creative, bold and loud with a futuristic touch, using a gradient of vibrant colors including blue, purple, and royal blue.",
|
||||
light_red:
|
||||
"Fun and organic with a playful and inspirational aesthetic, featuring pastel colors like pink, coral, and orange for a vibrant and warm feel.",
|
||||
dark_pink:
|
||||
" Inspirational and creative with a youthful and playful tone, featuring light, pastel colors including blue, pink, and purple, all blending in a vibrant gradient.",
|
||||
custom: "",
|
||||
};
|
||||
|
||||
|
||||
export function sanitizeFilename(input: string, replacement = '') {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
PopoverContent,
|
||||
} from "@/components/ui/popover";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { useGroupLayouts } from "@/app/(presentation-generator)/hooks/useGroupLayouts";
|
||||
|
||||
export const PresentationCard = ({
|
||||
|
|
@ -39,28 +39,20 @@ export const PresentationCard = ({
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
toast({
|
||||
title: "Deleting presentation",
|
||||
toast.loading("Deleting presentation", {
|
||||
description: "Please wait while we delete the presentation",
|
||||
variant: "default",
|
||||
});
|
||||
const response = await DashboardApi.deletePresentation(id);
|
||||
|
||||
if (response) {
|
||||
toast({
|
||||
title: "Presentation deleted",
|
||||
toast.success("Presentation deleted", {
|
||||
description: "The presentation has been deleted successfully",
|
||||
variant: "default",
|
||||
});
|
||||
if (onDeleted) {
|
||||
onDeleted(id);
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to delete presentation",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error("Error deleting presentation");
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { useState, useEffect, useRef } from 'react'
|
||||
|
||||
import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface UseGroupLayoutLoaderReturn {
|
||||
layoutGroup: LayoutGroup | null
|
||||
|
|
@ -42,8 +42,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
|
|||
|
||||
const response = await fetch('/api/layouts')
|
||||
if (!response.ok) {
|
||||
toast({
|
||||
title: 'Error loading layouts',
|
||||
toast.error('Error loading layouts', {
|
||||
description: response.statusText,
|
||||
})
|
||||
return
|
||||
|
|
@ -75,8 +74,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
|
|||
const module = await import(`@/presentation-layouts/${targetGroupData.groupName}/${layoutName}`)
|
||||
|
||||
if (!module.default) {
|
||||
toast({
|
||||
title: `${layoutName} has no default export`,
|
||||
toast.error(`${layoutName} has no default export`, {
|
||||
description: 'Please ensure the layout file exports a default component',
|
||||
})
|
||||
console.warn(`${layoutName} has no default export`)
|
||||
|
|
@ -84,8 +82,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
|
|||
}
|
||||
|
||||
if (!module.Schema) {
|
||||
toast({
|
||||
title: `${layoutName} is missing required Schema export`,
|
||||
toast.error(`${layoutName} is missing required Schema export`, {
|
||||
description: 'Please ensure the layout file exports a Schema',
|
||||
})
|
||||
console.error(`${layoutName} is missing required Schema export`)
|
||||
|
|
@ -139,8 +136,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet
|
|||
}
|
||||
|
||||
if (groupLayouts.length === 0) {
|
||||
toast({
|
||||
title: 'No valid layouts found',
|
||||
toast.error('No valid layouts found', {
|
||||
description: `No valid layouts found in "${groupSlug}" group.`,
|
||||
})
|
||||
setError(`No valid layouts found in "${groupSlug}" group.`)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
|
||||
import { LayoutInfo, LayoutGroup, GroupedLayoutsResponse, GroupSetting } from '../types'
|
||||
import { toast } from '@/hooks/use-toast'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
interface UseLayoutLoaderReturn {
|
||||
layoutGroups: LayoutGroup[]
|
||||
|
|
@ -25,8 +25,7 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
|
|||
|
||||
const response = await fetch('/api/layouts')
|
||||
if (!response.ok) {
|
||||
toast({
|
||||
title: 'Error loading layouts',
|
||||
toast.error('Error loading layouts', {
|
||||
description: response.statusText,
|
||||
})
|
||||
return
|
||||
|
|
@ -50,21 +49,16 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
|
|||
const module = await import(`@/presentation-layouts/${groupData.groupName}/${layoutName}`)
|
||||
|
||||
if (!module.default) {
|
||||
toast({
|
||||
title: `${layoutName} has no default export`,
|
||||
toast.error(`${layoutName} has no default export`, {
|
||||
description: 'Please ensure the layout file exports a default component',
|
||||
|
||||
})
|
||||
console.warn(`${layoutName} has no default export`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (!module.Schema) {
|
||||
toast({
|
||||
title: `${layoutName} is missing required Schema export`,
|
||||
toast.error(`${layoutName} is missing required Schema export`, {
|
||||
description: 'Please ensure the layout file exports a Schema',
|
||||
|
||||
|
||||
})
|
||||
console.error(`${layoutName} is missing required Schema export`)
|
||||
continue
|
||||
|
|
@ -130,10 +124,8 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => {
|
|||
}
|
||||
|
||||
if (allLayouts.length === 0) {
|
||||
toast({
|
||||
title: 'No valid layouts found',
|
||||
toast.error('No valid layouts found', {
|
||||
description: 'Make sure your layout files export both a default component and a Schema.',
|
||||
|
||||
})
|
||||
setError('No valid layouts found. Make sure your layout files export both a default component and a Schema.')
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ import localFont from "next/font/local";
|
|||
import { Fraunces, Montserrat, Inria_Serif, Roboto, Instrument_Sans } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { Providers } from "./providers";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { FooterProvider } from "./(presentation-generator)/context/footerContext";
|
||||
import { LayoutProvider } from "./(presentation-generator)/context/LayoutContext";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
const fraunces = Fraunces({
|
||||
subsets: ["latin"],
|
||||
|
|
@ -105,13 +105,11 @@ export default function RootLayout({
|
|||
<Providers>
|
||||
<LayoutProvider>
|
||||
<FooterProvider>
|
||||
|
||||
|
||||
{children}
|
||||
</FooterProvider>
|
||||
</LayoutProvider>
|
||||
</Providers>
|
||||
<Toaster />
|
||||
<Toaster position="top-center" />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react";
|
|||
import Header from "../dashboard/components/Header";
|
||||
import Wrapper from "@/components/Wrapper";
|
||||
import { Settings, Key, Loader2, Check, ChevronsUpDown } from "lucide-react";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import { RootState } from "@/store/store";
|
||||
import { useSelector } from "react-redux";
|
||||
import { handleSaveLLMConfig } from "@/utils/storeHelpers";
|
||||
|
|
@ -156,22 +156,16 @@ const SettingsPage = () => {
|
|||
setIsLoading(true);
|
||||
await pullOllamaModels();
|
||||
}
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Configuration saved successfully",
|
||||
});
|
||||
toast.success("Configuration saved successfully");
|
||||
setIsLoading(false);
|
||||
router.back();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to save configuration",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to save configuration"
|
||||
);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -284,23 +278,18 @@ const SettingsPage = () => {
|
|||
const isModelAvailable = data.includes(llmConfig.CUSTOM_MODEL);
|
||||
if (!isModelAvailable) {
|
||||
setLlmConfig({ ...llmConfig, CUSTOM_MODEL: "" });
|
||||
toast({
|
||||
title: "Model Unavailable",
|
||||
description: `The selected model "${llmConfig.CUSTOM_MODEL}" is no longer available. Please select a different model.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(
|
||||
`The selected model "${llmConfig.CUSTOM_MODEL}" is no longer available. Please select a different model.`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching custom models:", error);
|
||||
// Don't set customModelsChecked to true on error, so the button remains visible
|
||||
setCustomModels([]);
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
"Failed to fetch available models. Please check your URL and API key.",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(
|
||||
"Failed to fetch available models. Please check your URL and API key."
|
||||
);
|
||||
} finally {
|
||||
setCustomModelsLoading(false);
|
||||
}
|
||||
|
|
@ -385,15 +374,15 @@ const SettingsPage = () => {
|
|||
key={provider}
|
||||
onClick={() => changeProvider(provider)}
|
||||
className={`relative p-4 rounded-lg border-2 transition-all duration-200 ${llmConfig.LLM === provider
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-blue-200 hover:bg-gray-50"
|
||||
? "border-blue-500 bg-blue-50"
|
||||
: "border-gray-200 hover:border-blue-200 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<span
|
||||
className={`font-medium text-center ${llmConfig.LLM === provider
|
||||
? "text-blue-700"
|
||||
: "text-gray-700"
|
||||
? "text-blue-700"
|
||||
: "text-gray-700"
|
||||
}`}
|
||||
>
|
||||
{provider === "openai"
|
||||
|
|
@ -770,8 +759,8 @@ const SettingsPage = () => {
|
|||
customModelsLoading || !llmConfig.CUSTOM_LLM_URL
|
||||
}
|
||||
className={`w-full py-2.5 px-4 rounded-lg transition-all duration-200 border-2 font-semibold ${customModelsLoading || !llmConfig.CUSTOM_LLM_URL
|
||||
? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500"
|
||||
: "bg-white border-blue-600 text-blue-600 hover:bg-blue-50 focus:ring-2 focus:ring-blue-500/20"
|
||||
? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500"
|
||||
: "bg-white border-blue-600 text-blue-600 hover:bg-blue-50 focus:ring-2 focus:ring-blue-500/20"
|
||||
}`}
|
||||
>
|
||||
{customModelsLoading ? (
|
||||
|
|
@ -804,8 +793,8 @@ const SettingsPage = () => {
|
|||
customModelsLoading || !llmConfig.CUSTOM_LLM_URL
|
||||
}
|
||||
className={`w-full py-2.5 px-4 rounded-lg transition-all duration-200 border-2 font-semibold ${customModelsLoading || !llmConfig.CUSTOM_LLM_URL
|
||||
? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500"
|
||||
: "bg-white border-gray-600 text-gray-600 hover:bg-gray-50 focus:ring-2 focus:ring-gray-500/20"
|
||||
? "bg-gray-100 border-gray-300 cursor-not-allowed text-gray-500"
|
||||
: "bg-white border-gray-600 text-gray-600 hover:bg-gray-50 focus:ring-2 focus:ring-gray-500/20"
|
||||
}`}
|
||||
>
|
||||
{customModelsLoading ? (
|
||||
|
|
@ -995,10 +984,10 @@ const SettingsPage = () => {
|
|||
(llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL)
|
||||
}
|
||||
className={`mt-8 w-full font-semibold py-3 px-4 rounded-lg transition-all duration-500 ${isLoading ||
|
||||
(llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL) ||
|
||||
(llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL)
|
||||
? "bg-gray-400 cursor-not-allowed"
|
||||
: "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200"
|
||||
(llmConfig.LLM === "ollama" && !llmConfig.OLLAMA_MODEL) ||
|
||||
(llmConfig.LLM === "custom" && !llmConfig.CUSTOM_MODEL)
|
||||
? "bg-gray-400 cursor-not-allowed"
|
||||
: "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200"
|
||||
} text-white`}
|
||||
>
|
||||
{isLoading ? (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import React from 'react'
|
||||
import SettingPage from './SettingPage'
|
||||
|
||||
export const metadata = {
|
||||
title: 'Settings | Presenton',
|
||||
description: 'Settings page',
|
||||
}
|
||||
const page = () => {
|
||||
|
||||
return (
|
||||
<SettingPage />
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Info,
|
||||
ExternalLink,
|
||||
|
|
@ -276,22 +276,20 @@ export default function Home() {
|
|||
setIsLoading(true);
|
||||
await pullOllamaModels();
|
||||
}
|
||||
toast({
|
||||
title: "Success",
|
||||
description: "Configuration saved successfully",
|
||||
});
|
||||
toast.success("Configuration saved successfully");
|
||||
setIsLoading(false);
|
||||
router.push("/upload");
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to save configuration",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to save configuration",
|
||||
{
|
||||
description:
|
||||
"Failed to save configuration",
|
||||
}
|
||||
);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -413,12 +411,13 @@ export default function Home() {
|
|||
console.error("Error fetching custom models:", error);
|
||||
// Don't set customModelsChecked to true on error, so the button remains visible
|
||||
setCustomModels([]);
|
||||
toast({
|
||||
title: "Error",
|
||||
description:
|
||||
"Failed to fetch available models. Please check your URL and API key.",
|
||||
variant: "destructive",
|
||||
});
|
||||
toast.error(
|
||||
"Failed to fetch available models. Please check your URL and API key.",
|
||||
{
|
||||
description:
|
||||
"Failed to fetch available models. Please check your URL and API key.",
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
setCustomModelsLoading(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
duration={2000}
|
||||
richColors={true}
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
|
|
|
|||
|
|
@ -1,129 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||
|
||||
const ToastProvider = ToastPrimitives.Provider
|
||||
|
||||
const ToastViewport = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Viewport
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed top-2 z-[100] flex max-h-screen w-full flex-col-reverse p-4 right-0 sm:flex-col md:max-w-[420px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||
|
||||
const toastVariants = cva(
|
||||
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border bg-green-50 text-green-700 ",
|
||||
success: "border bg-green-50 text-green-700 border bg-background text-foreground",
|
||||
destructive:
|
||||
"destructive group border-destructive bg-destructive/90 text-destructive-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const Toast = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||
VariantProps<typeof toastVariants>
|
||||
>(({ className, variant, ...props }, ref) => {
|
||||
return (
|
||||
<ToastPrimitives.Root
|
||||
ref={ref}
|
||||
className={cn(toastVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
Toast.displayName = ToastPrimitives.Root.displayName
|
||||
|
||||
const ToastAction = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Action
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastAction.displayName = ToastPrimitives.Action.displayName
|
||||
|
||||
const ToastClose = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Close>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
|
||||
className
|
||||
)}
|
||||
toast-close=""
|
||||
{...props}
|
||||
>
|
||||
<Cross2Icon className="h-4 w-4" />
|
||||
</ToastPrimitives.Close>
|
||||
))
|
||||
ToastClose.displayName = ToastPrimitives.Close.displayName
|
||||
|
||||
const ToastTitle = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Title
|
||||
ref={ref}
|
||||
className={cn("text-sm font-bold [&+div]:text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
||||
|
||||
const ToastDescription = React.forwardRef<
|
||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ToastPrimitives.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm font-medium opacity-90", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
||||
|
||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
||||
|
||||
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
||||
|
||||
export {
|
||||
type ToastProps,
|
||||
type ToastActionElement,
|
||||
ToastProvider,
|
||||
ToastViewport,
|
||||
Toast,
|
||||
ToastTitle,
|
||||
ToastDescription,
|
||||
ToastClose,
|
||||
ToastAction,
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
"use client"
|
||||
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import {
|
||||
Toast,
|
||||
ToastClose,
|
||||
ToastDescription,
|
||||
ToastProvider,
|
||||
ToastTitle,
|
||||
ToastViewport,
|
||||
} from "@/components/ui/toast"
|
||||
|
||||
export function Toaster() {
|
||||
const { toasts } = useToast()
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
{toasts.map(function ({ id, title, description, action, ...props }) {
|
||||
return (
|
||||
<Toast key={id} {...props}>
|
||||
<div className="grid gap-1">
|
||||
{title && <ToastTitle>{title}</ToastTitle>}
|
||||
{description && (
|
||||
<ToastDescription>{description}</ToastDescription>
|
||||
)}
|
||||
</div>
|
||||
{action}
|
||||
<ToastClose />
|
||||
</Toast>
|
||||
)
|
||||
})}
|
||||
<ToastViewport />
|
||||
</ToastProvider>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
"use client"
|
||||
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
|
||||
import type {
|
||||
ToastActionElement,
|
||||
ToastProps,
|
||||
} from "@/components/ui/toast"
|
||||
|
||||
const TOAST_LIMIT = 1
|
||||
const TOAST_REMOVE_DELAY = 1000000
|
||||
|
||||
type ToasterToast = ToastProps & {
|
||||
id: string
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
action?: ToastActionElement
|
||||
}
|
||||
|
||||
const actionTypes = {
|
||||
ADD_TOAST: "ADD_TOAST",
|
||||
UPDATE_TOAST: "UPDATE_TOAST",
|
||||
DISMISS_TOAST: "DISMISS_TOAST",
|
||||
REMOVE_TOAST: "REMOVE_TOAST",
|
||||
} as const
|
||||
|
||||
let count = 0
|
||||
|
||||
function genId() {
|
||||
count = (count + 1) % Number.MAX_SAFE_INTEGER
|
||||
return count.toString()
|
||||
}
|
||||
|
||||
type ActionType = typeof actionTypes
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: ActionType["ADD_TOAST"]
|
||||
toast: ToasterToast
|
||||
}
|
||||
| {
|
||||
type: ActionType["UPDATE_TOAST"]
|
||||
toast: Partial<ToasterToast>
|
||||
}
|
||||
| {
|
||||
type: ActionType["DISMISS_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
}
|
||||
| {
|
||||
type: ActionType["REMOVE_TOAST"]
|
||||
toastId?: ToasterToast["id"]
|
||||
}
|
||||
|
||||
interface State {
|
||||
toasts: ToasterToast[]
|
||||
}
|
||||
|
||||
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
|
||||
|
||||
const addToRemoveQueue = (toastId: string) => {
|
||||
if (toastTimeouts.has(toastId)) {
|
||||
return
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
toastTimeouts.delete(toastId)
|
||||
dispatch({
|
||||
type: "REMOVE_TOAST",
|
||||
toastId: toastId,
|
||||
})
|
||||
}, TOAST_REMOVE_DELAY)
|
||||
|
||||
toastTimeouts.set(toastId, timeout)
|
||||
}
|
||||
|
||||
export const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case "ADD_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
|
||||
}
|
||||
|
||||
case "UPDATE_TOAST":
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === action.toast.id ? { ...t, ...action.toast } : t
|
||||
),
|
||||
}
|
||||
|
||||
case "DISMISS_TOAST": {
|
||||
const { toastId } = action
|
||||
|
||||
// ! Side effects ! - This could be extracted into a dismissToast() action,
|
||||
// but I'll keep it here for simplicity
|
||||
if (toastId) {
|
||||
addToRemoveQueue(toastId)
|
||||
} else {
|
||||
state.toasts.forEach((toast) => {
|
||||
addToRemoveQueue(toast.id)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.map((t) =>
|
||||
t.id === toastId || toastId === undefined
|
||||
? {
|
||||
...t,
|
||||
open: false,
|
||||
}
|
||||
: t
|
||||
),
|
||||
}
|
||||
}
|
||||
case "REMOVE_TOAST":
|
||||
if (action.toastId === undefined) {
|
||||
return {
|
||||
...state,
|
||||
toasts: [],
|
||||
}
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
toasts: state.toasts.filter((t) => t.id !== action.toastId),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listeners: Array<(state: State) => void> = []
|
||||
|
||||
let memoryState: State = { toasts: [] }
|
||||
|
||||
function dispatch(action: Action) {
|
||||
memoryState = reducer(memoryState, action)
|
||||
listeners.forEach((listener) => {
|
||||
listener(memoryState)
|
||||
})
|
||||
}
|
||||
|
||||
type Toast = Omit<ToasterToast, "id">
|
||||
|
||||
function toast({ ...props }: Toast) {
|
||||
const id = genId()
|
||||
|
||||
const update = (props: ToasterToast) =>
|
||||
dispatch({
|
||||
type: "UPDATE_TOAST",
|
||||
toast: { ...props, id },
|
||||
})
|
||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
|
||||
|
||||
dispatch({
|
||||
type: "ADD_TOAST",
|
||||
toast: {
|
||||
...props,
|
||||
id,
|
||||
open: true,
|
||||
onOpenChange: (open) => {
|
||||
if (!open) dismiss()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
id: id,
|
||||
dismiss,
|
||||
update,
|
||||
}
|
||||
}
|
||||
|
||||
function useToast() {
|
||||
const [state, setState] = React.useState<State>(memoryState)
|
||||
|
||||
React.useEffect(() => {
|
||||
listeners.push(setState)
|
||||
return () => {
|
||||
const index = listeners.indexOf(setState)
|
||||
if (index > -1) {
|
||||
listeners.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}, [state])
|
||||
|
||||
return {
|
||||
...state,
|
||||
toast,
|
||||
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
|
||||
}
|
||||
}
|
||||
|
||||
export { useToast, toast }
|
||||
Loading…
Add table
Reference in a new issue