diff --git a/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py b/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py
index 67539938..bb9f3157 100644
--- a/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py
+++ b/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py
@@ -79,6 +79,7 @@ class GetLayoutsResponse(BaseModel):
class PresentationSummary(BaseModel):
presentation_id: str
layout_count: int
+ last_updated_at: Optional[datetime] = None
class GetPresentationSummaryResponse(BaseModel):
@@ -830,21 +831,13 @@ async def get_presentations_summary(
):
"""
Get summary of all presentations with their layout counts.
-
- Args:
- session: Database session
-
- Returns:
- GetPresentationSummaryResponse with list of presentations and their layout counts
-
- Raises:
- HTTPException: 500 for server errors
"""
try:
- # Query to get presentation_id and count of layouts grouped by presentation_id
+ # Query to get presentation_id, count of layouts, and MAX(updated_at)
stmt = select(
PresentationLayoutCodeModel.presentation_id,
- func.count(PresentationLayoutCodeModel.id).label('layout_count')
+ func.count(PresentationLayoutCodeModel.id).label('layout_count'),
+ func.max(PresentationLayoutCodeModel.updated_at).label('last_updated_at')
).group_by(PresentationLayoutCodeModel.presentation_id)
result = await session.execute(stmt)
@@ -854,7 +847,8 @@ async def get_presentations_summary(
presentations = [
PresentationSummary(
presentation_id=row.presentation_id,
- layout_count=row.layout_count
+ layout_count=row.layout_count,
+ last_updated_at=row.last_updated_at
)
for row in presentation_data
]
diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx
index 652e1c33..640762b3 100644
--- a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx
+++ b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx
@@ -2,7 +2,7 @@
import React from "react";
import FontManager from "./components/FontManager";
-import Header from "@/components/Header";
+import Header from "../dashboard/components/Header";
import { useLayout } from "../context/LayoutContext";
import { useCustomLayout } from "./hooks/useCustomLayout";
import { useFontManagement } from "./hooks/useFontManagement";
@@ -80,8 +80,13 @@ const CustomTemplatePage = () => {
Upload your PPTX file to extract slides and convert them to
- interactive HTML layouts
+ template which then can be used to generate AI presentations.
+
+
+ AI template generation can take around 5 minutes per slide.
+
+
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx b/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx
index 3e127e94..c7ad86dd 100644
--- a/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx
+++ b/servers/nextjs/app/(presentation-generator)/dashboard/components/DashboardPage.tsx
@@ -5,9 +5,9 @@ import React, { useState, useEffect } from "react";
import Wrapper from "@/components/Wrapper";
import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard";
import { PresentationGrid } from "@/app/(presentation-generator)/dashboard/components/PresentationGrid";
-import Header from "./Header";
+import Header from "@/app/(presentation-generator)/dashboard/components/Header";
const DashboardPage: React.FC = () => {
const [presentations, setPresentations] = useState(null);
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/dashboard/components/Header.tsx
index c6b1a507..3708a494 100644
--- a/servers/nextjs/app/(presentation-generator)/dashboard/components/Header.tsx
+++ b/servers/nextjs/app/(presentation-generator)/dashboard/components/Header.tsx
@@ -6,7 +6,7 @@ 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 } from "lucide-react";
+import { Layout, FilePlus2 } from "lucide-react";
const Header = () => {
const pathname = usePathname();
return (
@@ -24,6 +24,15 @@ const Header = () => {
+
+
+
Create Template
+
= ({
loading
} = useLayout();
+ const [summaryMap, setSummaryMap] = React.useState
>({});
+
+ useEffect(() => {
+ // Fetch custom templates summary to get last_updated_at for sorting
+ fetch("/api/v1/ppt/layout-management/summary")
+ .then(res => res.json())
+ .then(data => {
+ const map: Record = {};
+ if (data && Array.isArray(data.presentations)) {
+ for (const p of data.presentations) {
+ // groups are named custom-
+ map[`custom-${p.presentation_id}`] = p.last_updated_at ? new Date(p.last_updated_at).getTime() : 0;
+ }
+ }
+ setSummaryMap(map);
+ })
+ .catch(() => setSummaryMap({}));
+ }, []);
+
const layoutGroups: LayoutGroup[] = React.useMemo(() => {
const groups = getAllGroups();
if (groups.length === 0) return [];
const Groups: LayoutGroup[] = groups.map(groupName => {
-
const settings = getGroupSetting(groupName);
return {
id: groupName,
name: groupName,
- description: settings?.description || `${groupName} presentation layouts`,
+ description: settings?.description || `${groupName} presentation templates`,
ordered: settings?.ordered || false,
default: settings?.default || false,
};
@@ -44,6 +62,16 @@ const LayoutSelection: React.FC = ({
});
}, [getAllGroups, getLayoutsByGroup, getGroupSetting]);
+ const inBuiltGroups = React.useMemo(
+ () => layoutGroups.filter(g => !g.name.toLowerCase().startsWith("custom-")),
+ [layoutGroups]
+ );
+ const customGroups = React.useMemo(() => {
+ const unsorted = layoutGroups.filter(g => g.name.toLowerCase().startsWith("custom-"));
+ // Sort by last_updated_at desc using summaryMap
+ return unsorted.sort((a, b) => (summaryMap[b.name] || 0) - (summaryMap[a.name] || 0));
+ }, [layoutGroups, summaryMap]);
+
// Auto-select first group when groups are loaded
useEffect(() => {
if (layoutGroups.length > 0 && !selectedLayoutGroup) {
@@ -116,16 +144,37 @@ const LayoutSelection: React.FC = ({
}
return (
-
-
- {layoutGroups.map((group) => (
-
- ))}
+
+ {/* In Built Templates */}
+
+
In Built Templates
+
+ {inBuiltGroups.map((group) => (
+
+ ))}
+
+
+
+ {/* Custom AI Templates */}
+
+
+
Custom AI Templates
+
+
+ {customGroups.map((group) => (
+
+ ))}
+
);
diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx
index 1aadf884..85135158 100644
--- a/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx
+++ b/servers/nextjs/app/(presentation-generator)/template-preview/page.tsx
@@ -37,6 +37,13 @@ const LayoutPreview = () => {
settings: getGroupSetting(groupName) || { description: "", ordered: false },
}));
+ const inBuiltGroups = layoutGroups.filter(
+ (g) => !g.groupName.toLowerCase().startsWith("custom-")
+ );
+ const customGroups = layoutGroups.filter((g) =>
+ g.groupName.toLowerCase().startsWith("custom-")
+ );
+
// Handle loading state
if (loading) {
return
;
@@ -65,17 +72,16 @@ const LayoutPreview = () => {
- {/* Group Navigation Cards */}
-
-
+ {/* In Built Templates */}
+
+
+
In Built Templates
- {layoutGroups.map((group) => (
+ {inBuiltGroups.map((group) => (
- router.push(`/template-preview/${group.groupName}`)
- }
+ onClick={() => router.push(`/template-preview/${group.groupName}`)}
>
@@ -106,28 +112,71 @@ const LayoutPreview = () => {
))}
-
router.push(`/custom-template`)}
- >
-
-
-
- Create a new custom layout
-
-
-
-
- {/* */}
+
+
+ {/* Custom Templates */}
+
+
+
+
Custom AI Templates
+
+
+ {customGroups.length > 0 ? (
+ customGroups.map((group) => (
+
router.push(`/template-preview/${group.groupName}`)}
+ >
+
+
+
+ {group.groupName}
+
+
+
+ {group.layouts.length}
+
+
+
+
+
+ {group.settings.description}
+
+
+
+ {group.layouts.length} layout
+ {group.layouts.length !== 1 ? "s" : ""}
+
+
+
+
+ ))
+ ) : (
+
router.push(`/custom-template`)}
+ >
+
+
+
+ Create your first custom AI template
+
+
+
+ )}
+
+
+
);
diff --git a/servers/nextjs/components/Header.tsx b/servers/nextjs/components/Header.tsx
index ea324d3a..947e51ab 100644
--- a/servers/nextjs/components/Header.tsx
+++ b/servers/nextjs/components/Header.tsx
@@ -2,7 +2,7 @@
import React from "react";
import Link from "next/link";
-import { Layout } from "lucide-react";
+import { Layout, Plus } from "lucide-react";
const Header: React.FC = () => {
return (
@@ -14,6 +14,10 @@ const Header: React.FC = () => {