diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardNav.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardNav.tsx new file mode 100644 index 00000000..6185cd7f --- /dev/null +++ b/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/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardSidebar.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardSidebar.tsx new file mode 100644 index 00000000..1629603e --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/Components/DashboardSidebar.tsx @@ -0,0 +1,149 @@ +"use client"; + +import React from "react"; +import { LayoutDashboard, Star, Brain, Settings } 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/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/DashboardPage.tsx new file mode 100644 index 00000000..c24caa9a --- /dev/null +++ b/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/servers/nextjs/app/(presentation-generator)/dashboard/components/EmptyState.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/EmptyState.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/dashboard/components/EmptyState.tsx rename to servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/EmptyState.tsx diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx new file mode 100644 index 00000000..6c1f4c0e --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx @@ -0,0 +1,34 @@ +"use client"; + +import Wrapper from "@/components/Wrapper"; +import React from "react"; +import Link from "next/link"; +import BackBtn from "@/components/BackBtn"; +import { usePathname } from "next/navigation"; +import HeaderNav from "@/app/(presentation-generator)/components/HeaderNab"; +import { Layout, FilePlus2 } from "lucide-react"; +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/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationCard.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationCard.tsx new file mode 100644 index 00000000..8051258e --- /dev/null +++ b/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 "@/app/(presentation-generator)/documents-preview/components/MarkdownRenderer"; + +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/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationGrid.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid.tsx similarity index 53% rename from servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationGrid.tsx rename to servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid.tsx index 1a6ca543..5fa4d938 100644 --- a/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationGrid.tsx +++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationGrid.tsx @@ -3,6 +3,7 @@ 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[]; @@ -41,35 +42,44 @@ export const PresentationGrid = ({ const CreateNewCard = () => (
-
- -
-
-

- Create {type === "slide" ? "New" : "Video"} Presentation -

-

- Start from scratch and bring your ideas to life -

+ New Presentation +
+ + + + + + + + + + + + + +
+

Create New Presentation

+

Get Started

+
); if (isLoading) { return ( -
-
-
+
+
+
-
-
+
+
- {[...Array(3)].map((_, i) => ( + {[...Array(15)].map((_, i) => ( ))}
@@ -105,8 +115,7 @@ export const PresentationGrid = ({ key={presentation.id} id={presentation.id} title={presentation.title} - created_at={presentation.created_at} - slide={presentation.slides[0]} + presentation={presentation} onDeleted={onPresentationDeleted} /> ))} diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationListItem.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationListItem.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationListItem.tsx rename to servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/PresentationListItem.tsx diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/loading.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/loading.tsx new file mode 100644 index 00000000..919a55ad --- /dev/null +++ b/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/servers/nextjs/app/(presentation-generator)/dashboard/page.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/dashboard/page.tsx rename to servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/page.tsx diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/types.ts b/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/types.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/dashboard/types.ts rename to servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/types.ts diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx new file mode 100644 index 00000000..a1b9172e --- /dev/null +++ b/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/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx new file mode 100644 index 00000000..883cb91d --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx @@ -0,0 +1,363 @@ +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 shouldHideImageApiKeyInput = (providerValue: string, providerApiKeyField?: string) => { + if (!providerApiKeyField) return true; + if (providerValue === "comfyui") return false; + return providerApiKeyField === getTextProviderApiField(); + }; + + + + + 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 info message when using same API key as main provider + if (shouldHideImageApiKeyInput(provider.value, provider.apiKeyField)) { + return <>; + } + + // 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 + ) + } + /> + +
+ +
+ ); + })()} + + + )} +
+
+ + {renderQualitySelector(llmConfig, input_field_changed)} + {llmConfig.IMAGE_PROVIDER === "comfyui" &&
+ +
+