diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardNav.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardNav.tsx new file mode 100644 index 00000000..6185cd7f --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardNav.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { ChevronRight } from 'lucide-react'; +import Link from 'next/link'; +import React, { } from 'react' +import { defaultNavItems } from './DashboardSidebar'; +import { usePathname } from 'next/navigation'; + +const DashboardNav = () => { + const pathname = usePathname(); + const activeTab = pathname.split("?")[0].split("/").pop(); + const activeItem = defaultNavItems.find((i: any) => i.key === activeTab); + + + + + + return ( +
+
+

+ + {activeItem?.label ?? (activeTab && activeTab?.charAt(0).toUpperCase() + activeTab?.slice(1))} +

+
+ + + + {activeTab !== "playground" && activeTab !== "theme" && + + New presentation + New + + } + {activeTab === "theme" && + + New Themes + New + + + } +
+
+
+ ) +} + +export default DashboardNav diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardSidebar.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardSidebar.tsx new file mode 100644 index 00000000..7974bc1f --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardSidebar.tsx @@ -0,0 +1,128 @@ +"use client"; + +import React from "react"; +import { LayoutDashboard, Star, Brain, Settings, Palette } from "lucide-react"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import { useRouter } from "next/navigation"; + + + +export const defaultNavItems = [ + { key: "dashboard" as const, label: "Dashboard", icon: LayoutDashboard }, + { key: "templates" as const, label: "Standard", icon: Star }, + { key: "designs" as const, label: "Smart", icon: Brain }, + + + +]; +export const BelongingNavItems = [ + { key: "settings" as const, label: "Settings", icon: Settings }, +] + +const DashboardSidebar = () => { + + + const pathname = usePathname(); + const activeTab = pathname.split("?")[0].split("/").pop(); + const router = useRouter(); + + + + + return ( + + ); +}; + +export default DashboardSidebar; + + diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx new file mode 100644 index 00000000..3026da00 --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx @@ -0,0 +1,111 @@ +"use client"; + +import React, { useState, useEffect } from "react"; + +import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard"; +import { PresentationGrid } from "@/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid"; +import Link from "next/link"; +import { ChevronRight } from "lucide-react"; + + + +const DashboardPage: React.FC = () => { + const [presentations, setPresentations] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadData = async () => { + await fetchPresentations(); + }; + loadData(); + }, []); + + const fetchPresentations = async () => { + try { + setIsLoading(true); + setError(null); + const data = await DashboardApi.getPresentations(); + data.sort( + (a: any, b: any) => + new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() + ); + setPresentations(data); + } catch (err) { + setError(null); + setPresentations([]); + } finally { + setIsLoading(false); + } + }; + + const removePresentation = (presentationId: string) => { + setPresentations((prev: any) => + prev ? prev.filter((p: any) => p.id !== presentationId) : [] + ); + }; + + return ( +
+
+
+

+ + Slide Presentations +

+
+ + + + + + New presentation + New + + + {/* { + + New Themes + New + + + } */} +
+
+
+ +
+
+ ); +}; + +export default DashboardPage; diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/EmptyState.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/EmptyState.tsx new file mode 100644 index 00000000..322a30ed --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/EmptyState.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export const EmptyState = () => { + return ( +
+
+ + + + + + +
+

+ You don't have any presentations yet. +

+

+ Start creating the first one. +

+
+ ); +}; \ No newline at end of file diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx new file mode 100644 index 00000000..7177698b --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx @@ -0,0 +1,31 @@ +"use client"; + +import Wrapper from "@/components/Wrapper"; +import React from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; +const Header = () => { + const pathname = usePathname(); + return ( +
+ +
+
+ {/* {(pathname !== "/upload" && pathname !== "/dashboard") && } */} + trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/dashboard" })}> + Presentation logo + +
+ +
+
+
+ ); +}; + +export default Header; diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationCard.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationCard.tsx new file mode 100644 index 00000000..24e1d9d1 --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationCard.tsx @@ -0,0 +1,105 @@ +'use client' +import React from "react"; + +import { Card } from "@/components/ui/card"; +import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard"; +import { EllipsisVertical, Star, Trash } from "lucide-react"; +import { + Popover, + PopoverTrigger, + PopoverContent, +} from "@/components/ui/popover"; +import { useRouter } from "next/navigation"; +import { toast } from "sonner"; + +import { useFontLoader } from "@/app/(presentation-generator)/hooks/useFontLoader"; +import SlideScale from "@/app/(presentation-generator)/components/PresentationRender"; +import MarkdownRenderer from "@/components/MarkDownRender"; + +export const PresentationCard = ({ + id, + title, + presentation, + onDeleted +}: { + id: string; + title: string; + presentation: any; + onDeleted?: (presentationId: string) => void; +}) => { + const router = useRouter(); + useFontLoader(presentation.fonts || []); + const handlePreview = (e: React.MouseEvent) => { + e.preventDefault(); + router.push(`/presentation?id=${id}&type=standard`); + }; + + const handleDelete = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + + const response = await DashboardApi.deletePresentation(id); + + if (response) { + toast.success("Presentation deleted", { + description: "The presentation has been deleted successfully", + }); + if (onDeleted) { + onDeleted(id); + } + } else { + toast.error("Error deleting presentation"); + } + }; + const firstSlide = presentation?.slides?.[0]; + return ( + +
+ {/*

+ + {presentation.type} +

*/} + + +
+ + +
+ +
+
+
+
+ +
+

+ {new Date(presentation?.created_at).toLocaleDateString()} +

+ +
+ + e.stopPropagation()}> + + + + + + +
+ +
+
+
+ ); +}; diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid.tsx new file mode 100644 index 00000000..0edad0fb --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid.tsx @@ -0,0 +1,124 @@ +import React from "react"; +import { PresentationCard } from "./PresentationCard"; +import { PlusIcon } from "@radix-ui/react-icons"; +import { useRouter } from "next/navigation"; +import { PresentationResponse } from "@/app/(presentation-generator)/services/api/dashboard"; +import { ArrowRight } from "lucide-react"; + +interface PresentationGridProps { + presentations: PresentationResponse[]; + type: "slide" | "video"; + isLoading?: boolean; + error?: string | null; + onPresentationDeleted?: (presentationId: string) => void; +} + +export const PresentationGrid = ({ + presentations, + type, + isLoading = false, + error = null, + onPresentationDeleted, +}: PresentationGridProps) => { + const router = useRouter(); + const handleCreateNewPresentation = () => { + if (type === "slide") { + router.push("/upload"); + } else { + router.push("/editor"); + } + }; + + const ShimmerCard = () => ( +
+
+
+
+
+
+
+ ); + + const CreateNewCard = () => ( +
+ New Presentation +
+ + + + + + + + + + + + + +
+

Create New Presentation

+

Get Started

+
+
+
+ ); + + if (isLoading) { + return ( +
+
+
+
+
+
+
+
+
+
+ {[...Array(15)].map((_, i) => ( + + ))} +
+ ); + } + + if (error) { + return ( +
+ +
+
+

{error}

+ +
+
+
+ ); + } + + return ( +
+ + {presentations && + presentations.length > 0 && + presentations.map((presentation) => ( + + ))} +
+ ); +}; diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationListItem.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationListItem.tsx new file mode 100644 index 00000000..54958ce0 --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationListItem.tsx @@ -0,0 +1,44 @@ +import React from 'react'; + +import { Card, CardContent } from "@/components/ui/card"; +import { Presentation } from '../types'; + +export const PresentationListItem: React.FC = ({ + title, + date, + thumbnail, + type +}) => { + return ( + + +
+ {title} +
+ +
+

{title}

+
+ {/* {formatDistanceToNow(new Date(date), { addSuffix: true })} */} +
+
+ +
+ {type === 'video' ? ( + + + + ) : ( + + + + )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/loading.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/loading.tsx new file mode 100644 index 00000000..919a55ad --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/loading.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +const loading = () => { + return ( +
+
+
+
+
+
+
+
+
+
+ {[...Array(15)].map((_, i) => ( +
+
+
+
+
+
+
+ ))} +
+ ) +} + +export default loading diff --git a/electron/servers/nextjs/app/(presentation-generator)/dashboard/page.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx similarity index 100% rename from electron/servers/nextjs/app/(presentation-generator)/dashboard/page.tsx rename to electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/types.ts b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/types.ts new file mode 100644 index 00000000..d4b1ce8f --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/types.ts @@ -0,0 +1,16 @@ +export interface Presentation { + id: string; + title: string; + date: string; + thumbnail: string; + type: 'video' | 'slide'; +} + +export interface PresentationFilter { + type?: 'video' | 'slide'; + search?: string; + dateRange?: { + start: Date; + end: Date; + }; +} \ No newline at end of file diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx new file mode 100644 index 00000000..a1b9172e --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import DashboardSidebar from './Components/DashboardSidebar' + +const layout = ({ children }: { children: React.ReactNode }) => { + return ( +
+ +
+ + {children} +
+
+ ) +} + +export default layout \ No newline at end of file diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx new file mode 100644 index 00000000..31bc11ea --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx @@ -0,0 +1,355 @@ +import ToolTip from '@/components/ToolTip' +import { Button } from '@/components/ui/button' +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command' +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' +import { Select, SelectItem, SelectContent, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Switch } from '@/components/ui/switch' +import { cn } from '@/lib/utils' +import { LLMConfig } from '@/types/llm_config' +import { DALLE_3_QUALITY_OPTIONS, GPT_IMAGE_1_5_QUALITY_OPTIONS, IMAGE_PROVIDERS } from '@/utils/providerConstants' +import { Check, ChevronUp, Eye, EyeOff } from 'lucide-react' +import React, { useState } from 'react' + +const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setLlmConfig: (config: any) => void }) => { + const [openImageProviderSelect, setOpenImageProviderSelect] = useState(false); + const [showApiKey, setShowApiKey] = useState(false); + const isImageGenerationDisabled = llmConfig.DISABLE_IMAGE_GENERATION ?? false; + const handleChangeImageGenerationDisabled = (value: boolean) => { + setLlmConfig((prev: any) => ({ + ...prev, + DISABLE_IMAGE_GENERATION: value + })); + } + const input_field_changed = (value: string, field: string) => { + setLlmConfig((prev: any) => ({ + ...prev, + [field]: value + })); + setOpenImageProviderSelect(false); + } + + const getFieldValue = (field?: string) => { + if (!field) return ""; + return (llmConfig as Record)[field] || ""; + }; + + const updateFieldValue = (field: string | undefined, value: string) => { + if (!field) return; + setLlmConfig((prev: any) => ({ + ...prev, + [field]: value, + })); + }; + + const getTextProviderApiField = () => { + if (llmConfig.LLM === "openai") return "OPENAI_API_KEY"; + if (llmConfig.LLM === "google") return "GOOGLE_API_KEY"; + if (llmConfig.LLM === "anthropic") return "ANTHROPIC_API_KEY"; + return ""; + }; + + + + + + const renderQualitySelector = (llmConfig: LLMConfig, input_field_changed: (value: string, field: string) => void) => { + if (llmConfig.IMAGE_PROVIDER === "dall-e-3") { + return ( +
+ +
+ + +
+
+ ); + } + + if (llmConfig.IMAGE_PROVIDER === "gpt-image-1.5") { + return ( +
+ +
+ + +
+
+ ); + } + + return null; + }; + + + + + return ( +
+ {/* API Key Input */} +
+ +
+ handleChangeImageGenerationDisabled(!checked)} + /> +
+ +
+
+ + +
+
+ image-markup +
+

Image Generation Settings

+

+ Choosing where images come from +

+
+
+ +
+ + {!isImageGenerationDisabled && ( + <> + {/* Image Provider Selection */} +
+ +
+ + + + + + + + + No provider found. + + {Object.values(IMAGE_PROVIDERS).map( + (provider, index) => ( + { + input_field_changed(value, "IMAGE_PROVIDER"); + setOpenImageProviderSelect(false); + }} + > + +
+
+
+ + {provider.label} + +
+ + {provider.description} + +
+
+
+ ) + )} +
+
+
+
+
+
+
+ + + + {/* Dynamic API Key Input for Image Provider */} + {llmConfig.IMAGE_PROVIDER && + IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER] && + (() => { + const provider = IMAGE_PROVIDERS[llmConfig.IMAGE_PROVIDER]; + + + + // Show ComfyUI configuration + if (provider.value === "comfyui") { + return ( +
+
+ +
+ { + input_field_changed( + e.target.value, + "COMFYUI_URL" + ); + }} + /> +
+ +
+ +
+ ); + } + + // Show API key input for other providers + return ( +
+ +
+ + updateFieldValue( + provider.apiKeyField, + e.target.value + ) + } + /> + +
+ +
+ ); + })()} + + + )} +
+ {!isImageGenerationDisabled &&
+ + {renderQualitySelector(llmConfig, input_field_changed)} + {llmConfig.IMAGE_PROVIDER === "comfyui" &&
+ +
+