- {
- Array.from({ length: 8 }).map((_, index) => (
-
- ))
- }
+
+
-
+ {[...Array(15)].map((_, i) => (
+
+ ))}
)
}
diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx
index 3a67a294..a1b9172e 100644
--- a/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx
+++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/layout.tsx
@@ -1,13 +1,12 @@
import React from 'react'
import DashboardSidebar from './Components/DashboardSidebar'
-import DashboardNav from './Components/DashboardNav'
const layout = ({ children }: { children: React.ReactNode }) => {
return (
-
+
diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx
index 33e2a808..c9cc3f37 100644
--- a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx
+++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingPage.tsx
@@ -14,6 +14,7 @@ import LLMProviderSelection from "@/components/LLMSelection";
import Header from "../dashboard/components/Header";
import { LLMConfig } from "@/types/llm_config";
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
+import SettingSideBar from "./SettingSideBar";
// Button state interface
interface ButtonState {
@@ -28,6 +29,8 @@ interface ButtonState {
const SettingsPage = () => {
const router = useRouter();
const pathname = usePathname();
+ const [mode, setMode] = useState<'nanobanana' | 'presenton'>('presenton')
+ const [selectedProvider, setSelectedProvider] = useState<'text-provider' | 'image-provider'>('text-provider')
const userConfigState = useSelector((state: RootState) => state.userConfig);
const [llmConfig, setLlmConfig] = useState
(
userConfigState.llm_config
@@ -155,15 +158,30 @@ const SettingsPage = () => {
return (
-
+
+
+
+
-
+ {mode === 'nanobanana' &&
+
Nano Banana
+ }
+ {mode === 'presenton' &&
}
+
{/* Fixed Bottom Button */}
diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx
new file mode 100644
index 00000000..b2eb2d75
--- /dev/null
+++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+const SettingSideBar = ({ mode, setMode, selectedProvider, setSelectedProvider }: { mode: 'nanobanana' | 'presenton', setMode: (mode: 'nanobanana' | 'presenton') => void, selectedProvider: 'text-provider' | 'image-provider', setSelectedProvider: (provider: 'text-provider' | 'image-provider') => void }) => {
+ return (
+
+
FILTER BY:
+
+
Select Mode
+
+
+
+
+
+
Select Provider
+ {mode === 'presenton' &&
+
+
+
}
+ {
+ mode === 'nanobanana' &&
+
+
+ }
+
+
+ )
+}
+
+export default SettingSideBar
diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx
index 7a972392..92e26ac3 100644
--- a/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx
+++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx
@@ -2,7 +2,7 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import { Card } from "@/components/ui/card";
-import { ExternalLink, Loader2, Plus } from "lucide-react";
+import { ChevronRight, ExternalLink, Loader2, Plus } from "lucide-react";
import { templates } from "@/app/presentation-templates";
import { TemplateWithData, TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils";
import {
@@ -12,11 +12,12 @@ import {
} from "@/app/hooks/useCustomTemplates";
import { CompiledLayout } from "@/app/hooks/compileLayout";
import CreateCustomTemplate from "./CreateCustomTemplate";
+import Link from "next/link";
// Component for rendering custom template card with lazy-loaded previews
export const CustomTemplateCard = React.memo(function CustomTemplateCard({ template }: { template: CustomTemplates }) {
const router = useRouter();
- const { previewLayouts, loading } = useCustomTemplatePreview(`${template.id}`);
+ const { previewLayouts, loading, totalLayouts } = useCustomTemplatePreview(`${template.id}`);
const handleOpen = useCallback(() => {
if (template.id.startsWith('custom-')) {
router.push(`/template-preview/${template.id}`)
@@ -34,7 +35,7 @@ export const CustomTemplateCard = React.memo(function CustomTemplateCard({ templ
- Layouts- {template.layoutCount}
+ Layouts- {totalLayouts}
@@ -157,7 +158,7 @@ const InbuiltTemplateCard = React.memo(function InbuiltTemplateCard({
});
const LayoutPreview = () => {
- const [tab, setTab] = useState<'custom' | 'default'>('custom');
+ const [tab, setTab] = useState<'custom' | 'default'>('default');
const router = useRouter();
const { templates: customTemplates, loading: customLoading } = useCustomTemplateSummaries();
@@ -191,6 +192,33 @@ const LayoutPreview = () => {
return (
+
+
+
+ Templates
+
+
+
+
+
+
+
+ New Template
+ New
+
+
+
+
+
+
diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx
index 4a5ac6fa..3f795157 100644
--- a/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx
+++ b/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx
@@ -28,70 +28,11 @@ LayoutPreview.displayName = 'LayoutPreview';
export const CustomTemplateCard = memo(({ template, onSelectTemplate, selectedTemplate }: { template: CustomTemplates, onSelectTemplate: (template: string) => void, selectedTemplate: string | null }) => {
- const { previewLayouts, loading: customLoading } = useCustomTemplatePreview(template.id);
+ const { previewLayouts, loading: customLoading, totalLayouts } = useCustomTemplatePreview(template.id);
const isSelected = selectedTemplate === template.id;
return (
- //
{
- // onSelectTemplate(template.id);
- // }}
- // >
- //
- //
- //
- // {template.name}
- //
- //
-
-
-
- // {/* Layout previews */}
- //
- // {customLoading ? (
- // // Loading placeholders
- // [...Array(Math.min(4, template.layoutCount))].map((_, index) => (
- //
- //
- //
- // ))
- // ) : previewLayouts && previewLayouts?.length > 0 ? (
- // // Actual layout previews - using memoized component
- // previewLayouts?.slice(0, 4).map((layout: CompiledLayout, index: number) => (
- //
- // ))
- // ) : (
- // // Empty state placeholders
- // [...Array(Math.min(4, template.layoutCount))].map((_, index) => (
- //
- // No preview
- //
- // ))
- // )}
- //
-
-
- //
- // {isSelected && (
- //
- // Selected
- //
- // )}
- //
onSelectTemplate(template.id)}
@@ -99,7 +40,7 @@ export const CustomTemplateCard = memo(({ template, onSelectTemplate, selectedTe
- Layouts- {template.layoutCount}
+ Layouts- {totalLayouts}
diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx
index 78d6afdb..aa82ad1f 100644
--- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx
+++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx
@@ -48,10 +48,10 @@ const OutlinePage: React.FC = () => {
duration={loadingState.duration}
/>
-
-
+
+
-
+
{
+ const LayoutComponent = layout.component;
+ return (
+
+ );
+});
+BuiltInLayoutPreview.displayName = 'BuiltInLayoutPreview';
+
+// Memoized built-in template card
+const BuiltInTemplateCard = memo(({ template, isSelected, onSelect }: {
+ template: TemplateLayoutsWithSettings;
+ isSelected: boolean;
+ onSelect: (template: TemplateLayoutsWithSettings) => void;
+}) => {
+ const previewLayouts = useMemo(() => template.layouts.slice(0, 4), [template.layouts]);
+ const handleClick = useCallback(() => onSelect(template), [onSelect, template]);
+
+ return (
+
+
+ Layouts- {template.layouts.length}
+
+
+
+
+ {previewLayouts.map((layout: TemplateWithData, index: number) => (
+
+ ))}
+
+
+
+
+
+ {template.name}
+
+
+ {template.description}
+
+
+
+
+ );
+});
+BuiltInTemplateCard.displayName = 'BuiltInTemplateCard';
+
interface TemplateSelectionProps {
selectedTemplate: (TemplateLayoutsWithSettings | string) | null;
onSelectTemplate: (template: TemplateLayoutsWithSettings | string) => void;
}
-const TemplateSelection: React.FC = ({
+const TemplateSelection: React.FC = memo(({
selectedTemplate,
onSelectTemplate
}) => {
-
-
useEffect(() => {
-
const existingScript = document.querySelector(
'script[src*="tailwindcss.com"]'
);
@@ -30,146 +97,101 @@ const TemplateSelection: React.FC = ({
script.async = true;
document.head.appendChild(script);
}
-
}, []);
const { templates: customTemplates, loading: customLoading } = useCustomTemplateSummaries();
+ // Stable callback for custom template selection
+ const handleCustomSelect = useCallback(
+ (template: TemplateLayoutsWithSettings | string) => onSelectTemplate(template),
+ [onSelectTemplate]
+ );
+
+ // Stable callback for built-in template selection
+ const handleBuiltInSelect = useCallback(
+ (template: TemplateLayoutsWithSettings) => onSelectTemplate(template),
+ [onSelectTemplate]
+ );
+
+ // Derive the selected custom template id only when selectedTemplate changes
+ const selectedCustomId = useMemo(
+ () => (typeof selectedTemplate === 'string' ? selectedTemplate : null),
+ [selectedTemplate]
+ );
+
+ // Derive the selected built-in template id only when selectedTemplate changes
+ const selectedBuiltInId = useMemo(
+ () => (typeof selectedTemplate !== 'string' ? selectedTemplate?.id ?? null : null),
+ [selectedTemplate]
+ );
+
+ // Memoize the custom templates section
+ const customTemplateCards = useMemo(() => {
+ if (customLoading) {
+ return (
+
+
+ Loading custom templates...
+
+ );
+ }
+ if (customTemplates.length === 0) {
+ return (
+
+ No custom templates yet.
+
+ Custom templates you create will appear here.
+
+
+ );
+ }
+ return (
+
+ {customTemplates.map((template: CustomTemplates) => (
+
+ ))}
+
+ );
+ }, [customLoading, customTemplates, handleCustomSelect, selectedCustomId]);
+
+ // Memoize the built-in templates list
+ const builtInTemplateCards = useMemo(
+ () =>
+ templates.map((template: TemplateLayoutsWithSettings) => (
+
+ )),
+ [selectedBuiltInId, handleBuiltInSelect]
+ );
+
return (
- {/* In Built Templates */}
-
-
In Built Templates
-
- {templates.map((template: TemplateLayoutsWithSettings) => {
- const previewLayouts = template.layouts.slice(0, 4);
-
- return (
-
onSelectTemplate(template)}
- >
-
- Layouts- {template.layouts.length}
-
-
-
-
- {previewLayouts.map((layout: TemplateWithData, index: number) => {
- const LayoutComponent = layout.component;
- return (
-
- );
- })}
-
-
-
-
-
-
- {template.name}
-
-
- {template.description}
-
-
-
-
-
- //
onSelectTemplate(template)}
- // >
- //
- //
- //
- // {template.name}
- //
-
- //
-
- //
- // {template.description}
- //
-
- //
- // {previewLayouts.map((layout: TemplateWithData, index: number) => {
- // const LayoutComponent = layout.component;
- // return (
- //
- // );
- // })}
- //
- //
- // {typeof selectedTemplate !== 'string' && selectedTemplate?.id === template.id && (
- //
- // Selected
- //
- // )}
- //
- );
- })}
-
-
-
{/* Custom AI Templates */}
-
Custom AI Templates
+ Custom
+
+ {customTemplateCards}
+
+ {/* In Built Templates */}
+
+
In Built
+
+ {builtInTemplateCards}
- {customLoading ? (
-
-
- Loading custom templates...
-
- ) : customTemplates.length === 0 ? (
-
- No custom templates yet.
-
- Custom templates you create will appear here.
-
-
- ) : (
-
- {customTemplates.map((template: CustomTemplates) => (
-
-
- ))}
-
- )}
);
-};
+});
+TemplateSelection.displayName = 'TemplateSelection';
export default TemplateSelection;
diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx
index d7d387e9..691cb222 100644
--- a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx
+++ b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationHeader.tsx
@@ -1,17 +1,18 @@
"use client";
import { Button } from "@/components/ui/button";
import {
- SquareArrowOutUpRight,
Play,
Loader2,
Redo2,
Undo2,
RotateCcw,
ArrowRightFromLine,
+ ExternalLink,
+ MoveUpRight,
+ ArrowUpRight,
} from "lucide-react";
import React, { useState } from "react";
-import Wrapper from "@/components/Wrapper";
import { useRouter, usePathname } from "next/navigation";
import {
Popover,
@@ -19,27 +20,21 @@ import {
PopoverTrigger,
} from "@/components/ui/popover";
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
-import { OverlayLoader } from "@/components/ui/overlay-loader";
import { useDispatch, useSelector } from "react-redux";
-import Link from "next/link";
import { RootState } from "@/store/store";
import { toast } from "sonner";
-import Announcement from "@/components/Announcement";
import { PptxPresentationModel } from "@/types/pptx_models";
-import HeaderNav from "../../components/HeaderNab";
-import PDFIMAGE from "@/public/pdf.svg";
-import PPTXIMAGE from "@/public/pptx.svg";
-import Image from "next/image";
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
import { usePresentationUndoRedo } from "../hooks/PresentationUndoRedo";
import ToolTip from "@/components/ToolTip";
import { clearPresentationData } from "@/store/slices/presentationGeneration";
import { clearHistory } from "@/store/slices/undoRedoSlice";
import { Separator } from "@/components/ui/separator";
+import ThemeSelector from "./ThemeSelector";
const PresentationHeader = ({
presentation_id,
@@ -157,28 +152,35 @@ const PresentationHeader = ({
};
const ExportOptions = ({ mobile }: { mobile: boolean }) => (
-
-
-
+
+
Export as
+
+
+
+
+
+
@@ -189,12 +191,13 @@ const PresentationHeader = ({
return (
<>
-
{presentationData?.title || "Presentation"}
+
{presentationData?.title || "Presentation"}
{isPresentationSaving &&
}
+
@@ -246,10 +249,10 @@ const PresentationHeader = ({
}}
disabled={isExporting}
>
- {isExporting ?
: "Export"}
+ {isExporting ?
: "Export"}
-
+
diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/ThemeSelector.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/ThemeSelector.tsx
new file mode 100644
index 00000000..65ecec72
--- /dev/null
+++ b/servers/nextjs/app/(presentation-generator)/presentation/components/ThemeSelector.tsx
@@ -0,0 +1,120 @@
+"use client";
+import React, { useState } from 'react'
+// import { Theme } from '@/app/(presentation-generator)/services/api/types'
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+import { Palette } from 'lucide-react';
+
+import { useDispatch } from 'react-redux';
+// import { updateTheme } from '@/store/slices/presentationGeneration';
+import { useRouter } from 'next/navigation';
+import { useFontLoader } from '../../hooks/useFontLoader';
+const ThemeSelector = ({ presentation_id, current_theme, themes: allThemes }: { presentation_id: string, current_theme: any, themes: any[] }) => {
+ const [currentTheme, setCurrentTheme] = useState(current_theme)
+ const dispatch = useDispatch()
+ const router = useRouter()
+ const applyTheme = async (theme: any) => {
+ const element = document.getElementById('presentation-slides-wrapper')
+ if (!element) return;
+ if (allThemes.length === 0) return;
+ setCurrentTheme(theme)
+ clearTheme()
+ if (!theme.data.colors['graph_0']) { return; }
+ const cssVariables = {
+ '--primary-color': theme.data.colors['primary'],
+ '--background-color': theme.data.colors['background'],
+ '--card-color': theme.data.colors['card'],
+ '--stroke': theme.data.colors['stroke'],
+ '--primary-text': theme.data.colors['primary_text'],
+ '--background-text': theme.data.colors['background_text'],
+ '--graph-0': theme.data.colors['graph_0'],
+ '--graph-1': theme.data.colors['graph_1'],
+ '--graph-2': theme.data.colors['graph_2'],
+ '--graph-3': theme.data.colors['graph_3'],
+ '--graph-4': theme.data.colors['graph_4'],
+ '--graph-5': theme.data.colors['graph_5'],
+ '--graph-6': theme.data.colors['graph_6'],
+ '--graph-7': theme.data.colors['graph_7'],
+ '--graph-8': theme.data.colors['graph_8'],
+ '--graph-9': theme.data.colors['graph_9'],
+ }
+ Object.entries(cssVariables).forEach(([key, value]) => {
+ element.style.setProperty(key, value)
+ })
+ // useFontLoader({ [theme.data.fonts.textFont.name]: theme.data.fonts.textFont.url })
+
+ // Apply fonts to preview container
+ element.style.setProperty('font-family', `"${theme.data.fonts.textFont.name}"`)
+ element.style.setProperty('--heading-font-family', `"${theme.data.fonts.textFont.name}"`)
+
+ // dispatch(updateTheme(theme))
+ }
+ const clearTheme = () => {
+ const element = document.getElementById('presentation-slides-wrapper')
+ if (!element) return;
+ element.style.removeProperty('--primary-color');
+ element.style.removeProperty('--background-color');
+ element.style.removeProperty('--card-color');
+ element.style.removeProperty('--stroke');
+ element.style.removeProperty('--primary-text');
+ element.style.removeProperty('--background-text');
+ element.style.removeProperty('--graph-0');
+ element.style.removeProperty('--graph-1');
+ element.style.removeProperty('--graph-2');
+ element.style.removeProperty('--graph-3');
+ element.style.removeProperty('--graph-4');
+ element.style.removeProperty('--graph-5');
+ element.style.removeProperty('--graph-6');
+ element.style.removeProperty('--graph-7');
+ element.style.removeProperty('--graph-8');
+ element.style.removeProperty('--graph-9');
+ }
+ const resetTheme = async () => {
+ clearTheme();
+
+ // dispatch(updateTheme({} as any))
+ }
+
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {allThemes && allThemes.length > 0 && allThemes.map((t) => (
+
applyTheme(t)}
+ className={`text-left group relative`}
+ >
+
+
+
+ {t.name}
+
+
+ ))}
+
+
+
+ )
+}
+
+export default ThemeSelector
\ No newline at end of file
diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx
index a7977a07..e067a288 100644
--- a/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx
+++ b/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx
@@ -5,8 +5,7 @@ import { Card } from "@/components/ui/card";
import { ExternalLink, Loader2, Plus } from "lucide-react";
import { templates } from "@/app/presentation-templates";
-import type { TemplateLayoutsWithSettings } from "@/app/presentation-templates";
-import { TemplateWithData } from "@/app/presentation-templates/utils";
+import { TemplateLayoutsWithSettings, TemplateWithData } from "@/app/presentation-templates/utils";
import {
useCustomTemplateSummaries,
useCustomTemplatePreview,
diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx
index 55248085..e97e810d 100644
--- a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx
+++ b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx
@@ -46,7 +46,7 @@ const UploadPage = () => {
// State management
const [files, setFiles] = useState([]);
const [config, setConfig] = useState({
- slides: "8",
+ slides: "5",
language: LanguageType.English,
prompt: "",
tone: ToneType.Default,
diff --git a/servers/nextjs/components/LLMSelection.tsx b/servers/nextjs/components/LLMSelection.tsx
index 92c583ac..062b547a 100644
--- a/servers/nextjs/components/LLMSelection.tsx
+++ b/servers/nextjs/components/LLMSelection.tsx
@@ -34,28 +34,7 @@ interface LLMProviderSelectionProps {
) => void;
}
-const LLM_TABS = [
- {
- label: 'OpenAI',
- value: 'openai',
- },
- {
- label: 'Google',
- value: 'google',
- },
- {
- label: 'Anthropic',
- value: 'anthropic',
- },
- {
- label: 'Ollama',
- value: 'ollama',
- },
- {
- label: 'Custom',
- value: 'custom',
- },
-];
+
export default function LLMProviderSelection({
initialLLMConfig,
onConfigChange,
@@ -236,23 +215,9 @@ export default function LLMProviderSelection({
return (
-
- {/* Provider Selection - Fixed Header */}
-
-
- {LLM_TABS.map((tab) => (
-
- ))}
+
-
-
{/* Scrollable Content */}
diff --git a/servers/nextjs/public/providers/image-provider.png b/servers/nextjs/public/providers/image-provider.png
new file mode 100644
index 00000000..c27e0591
Binary files /dev/null and b/servers/nextjs/public/providers/image-provider.png differ
diff --git a/servers/nextjs/public/providers/openai.png b/servers/nextjs/public/providers/openai.png
new file mode 100644
index 00000000..4146ce93
Binary files /dev/null and b/servers/nextjs/public/providers/openai.png differ