diff --git a/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx b/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx
index f23f1a5c..bbaefa31 100644
--- a/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx
+++ b/servers/nextjs/app/(presentation-generator)/components/NewSlide.tsx
@@ -8,13 +8,13 @@ import { Trash2 } from 'lucide-react';
import { toast } from 'sonner';
interface NewSlideProps {
setShowNewSlideSelection: (show: boolean) => void;
- group: string;
+ templateID: string;
index: number;
presentationId: string;
}
const NewSlide = ({
setShowNewSlideSelection,
- group,
+ templateID,
index,
presentationId,
}: NewSlideProps) => {
@@ -25,7 +25,7 @@ const NewSlide = ({
id: uuidv4(),
index: index,
content: sampleData,
- layout_group: group,
+ layout_group: templateID,
layout: id,
presentation: presentationId,
};
@@ -36,10 +36,8 @@ const NewSlide = ({
toast.error("Error adding new slide");
}
};
- const { getFullDataByGroup, loading } = useLayout();
-
- const fullData = getFullDataByGroup(group);
-
+ const { getFullDataByTemplateID, loading } = useLayout();
+ const fullData = getFullDataByTemplateID(templateID);
if (loading) {
return (
@@ -73,7 +71,7 @@ const NewSlide = ({
return (
handleNewSlide(sampleData, layoutId)}
- key={`${group}-${index}`}
+ key={`${layoutId}-${index}`}
className=" relative cursor-pointer overflow-hidden aspect-video"
>
diff --git a/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx b/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx
index e466711a..4d04e500 100644
--- a/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx
+++ b/servers/nextjs/app/(presentation-generator)/components/PresentationMode.tsx
@@ -9,7 +9,7 @@ import {
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Slide } from "../types/slide";
-import { useGroupLayouts } from "../hooks/useGroupLayouts";
+import { useTemplateLayouts } from "../hooks/useTemplateLayouts";
interface PresentationModeProps {
@@ -33,7 +33,7 @@ const PresentationMode: React.FC
= ({
onSlideChange,
}) => {
- const { renderSlideContent } = useGroupLayouts();
+ const { renderSlideContent } = useTemplateLayouts();
// Modify the handleKeyPress to prevent default behavior
const handleKeyPress = useCallback(
(event: KeyboardEvent) => {
diff --git a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx
index d8452d03..9e8e4ea5 100644
--- a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx
+++ b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx
@@ -11,14 +11,19 @@ import { toast } from "sonner";
import * as z from "zod";
import { useDispatch } from "react-redux";
import { setLayoutLoading } from "@/store/slices/presentationGeneration";
+
import * as Babel from "@babel/standalone";
import * as Recharts from "recharts";
+import * as d3 from 'd3';
+
+import { getHeader } from "../services/api/header";
export interface LayoutInfo {
id: string;
name?: string;
description?: string;
json_schema: any;
- groupName: string;
+ templateID: string;
+ templateName?: string;
}
export interface FullDataInfo {
name: string;
@@ -26,43 +31,41 @@ export interface FullDataInfo {
schema: any;
sampleData: any;
fileName: string;
- groupName: string;
+ templateID: string;
layoutId: string;
}
-export interface GroupSetting {
+export interface TemplateSetting {
description: string;
ordered: boolean;
default?: boolean;
}
-export interface GroupedLayoutsResponse {
- groupName: string;
+export interface TemplateResponse {
+ templateID: string;
+ templateName?: string;
files: string[];
- settings: GroupSetting | null;
+ settings: TemplateSetting | null;
}
export interface LayoutData {
layoutsById: Map;
- layoutsByGroup: Map>;
- groupSettings: Map;
- fileMap: Map;
- groupedLayouts: Map;
+ layoutsByTemplateID: Map>;
+ templateSettings: Map;
+ fileMap: Map;
+ templateLayouts: Map;
layoutSchema: LayoutInfo[];
- fullDataByGroup: Map;
+ fullDataByTemplateID: Map;
}
export interface LayoutContextType {
getLayoutById: (layoutId: string) => LayoutInfo | null;
- getLayoutByIdAndGroup: (
- layoutId: string,
- groupName: string
- ) => LayoutInfo | null;
- getLayoutsByGroup: (groupName: string) => LayoutInfo[];
- getGroupSetting: (groupName: string) => GroupSetting | null;
- getAllGroups: () => string[];
+
+ getLayoutsByTemplateID: (templateID: string) => LayoutInfo[];
+ getTemplateSetting: (templateID: string) => TemplateSetting | null;
+ getAllTemplateIDs: () => string[];
getAllLayouts: () => LayoutInfo[];
- getFullDataByGroup: (groupName: string) => FullDataInfo[];
+ getFullDataByTemplateID: (templateID: string) => FullDataInfo[];
loading: boolean;
error: string | null;
getLayout: (layoutId: string) => React.ComponentType<{ data: any }> | null;
@@ -76,8 +79,8 @@ const LayoutContext = createContext(undefined);
const layoutCache = new Map>();
-const createCacheKey = (groupName: string, fileName: string): string =>
- `${groupName}/${fileName}`;
+const createCacheKey = (templateID: string, fileName: string): string =>
+ `${templateID}/${fileName}`;
// Extract Babel compilation logic into a utility function
const compileCustomLayout = (layoutCode: string, React: any, z: any) => {
@@ -102,11 +105,15 @@ const compileCustomLayout = (layoutCode: string, React: any, z: any) => {
"React",
"_z",
"Recharts",
+
`
const z = _z;
+
+ const useRef= React.useRef;
+ const useEffect= React.useEffect;
// Expose commonly used Recharts components to compiled layouts
- const { ResponsiveContainer, LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, PieChart, Pie, Cell, AreaChart, Area, RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ComposedChart, ScatterChart, Scatter, FunnelChart, Funnel, TreemapChart, Treemap, SankeyChart, Sankey, RadialBarChart, RadialBar, ReferenceLine, ReferenceDot, ReferenceArea, Brush, ErrorBar, LabelList, Label } = Recharts || {};
-
+ const { ResponsiveContainer, LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, PieChart, Pie, Cell, AreaChart, Area, RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ComposedChart, ScatterChart, Scatter, FunnelChart, Funnel, TreemapChart, Treemap, SankeyChart, Sankey, RadialBarChart, RadialBar, ReferenceLine, ReferenceDot, ReferenceArea, Brush, ErrorBar, LabelList, Label } = Recharts || {};
+
${compiled}
/* everything declared in the string is in scope here */
@@ -134,45 +141,46 @@ export const LayoutProvider: React.FC<{
const [customTemplateFonts, setCustomTemplateFonts] = useState
-
- {/* In Built Templates */}
-
-
-
In Built Templates
-
- {inBuiltGroups.map((group) => {
- const isCustom = group.groupName.toLowerCase().startsWith("custom-");
- const meta = summaryMap[group.groupName];
- const displayName = isCustom && meta?.name ? meta.name : group.groupName;
- const displayDescription = isCustom && meta?.description ? meta.description : group.settings.description;
- const layoutGroup = getFullDataByGroup(group.groupName);
- return (
-
{
- trackEvent(MixpanelEvent.Navigation, { from: pathname, to: `/template-preview/${group.groupName}` });
- router.push(`/template-preview/${group.groupName}`)
- }}
- >
-
-
-
-
- {displayName}
-
-
-
- {group.layouts.length}
-
-
-
-
-
- {displayDescription}
-
-
- {layoutGroup &&
- layoutGroup?.slice(0, 4).map((layout: any, index: number) => {
- const {
- component: LayoutComponent,
- sampleData,
- layoutId,
- groupName,
- } = layout;
- return (
-
- );
- })}
-
-
-
- );
- })}
-
-
-
-
{/* Custom Templates */}
-
+
Custom AI Templates
+
- {customGroupsSorted.length > 0 ? (
- customGroupsSorted.map((group) => {
- const meta = summaryMap[group.groupName];
- const displayName = meta?.name ? meta.name : group.groupName;
- const displayDescription = meta?.description ? meta.description : group.settings.description;
+ {customTemplatesSorted.length > 0 ? (
+ customTemplatesSorted.map((template) => {
+ const meta = summaryMap[template.templateID];
+
+ const displayName = meta?.name ? meta.name : template.templateID;
+ const displayDescription = meta?.description ? meta.description : template.settings.description;
+ const layoutTemplate = getFullDataByTemplateID(template.templateID);
return (
{
- trackEvent(MixpanelEvent.Navigation, { from: pathname, to: `/template-preview/${group.groupName}` });
- router.push(`/template-preview/${group.groupName}`)
+ trackEvent(MixpanelEvent.Navigation, { from: pathname, to: `/template-preview/${template.templateID}` });
+ router.push(`/template-preview/${template.templateID}`)
}}
>
@@ -194,21 +138,45 @@ const LayoutPreview = () => {
{displayName}
+
- {group.layouts.length}
+ {template.layouts.length}
-
+
+
ID: {template.templateID}
+
{
+ navigator.clipboard.writeText(template.templateID);
+ toast.success("Copied to clipboard");
+ }} />
+
+
{displayDescription}
-
-
- {group.layouts.length} layout
- {group.layouts.length !== 1 ? "s" : ""}
-
+
+ {layoutTemplate &&
+ layoutTemplate?.slice(0, 4).map((layout: any, index: number) => {
+ const {
+ component: LayoutComponent,
+ sampleData,
+ layoutId,
+ templateID,
+ } = layout;
+ return (
+
+ );
+ })}
@@ -225,14 +193,14 @@ const LayoutPreview = () => {
- Create
+ Create Custom Template
- Create your first custom AI template
+ Create your first custom template
@@ -240,6 +208,73 @@ const LayoutPreview = () => {
+
+ {/* In Built Templates */}
+
+
+
Inbuilt Templates
+
+ {inBuiltTemplates.map((template) => {
+ const isCustom = template.templateID.toLowerCase().startsWith("custom-");
+ const meta = summaryMap[template.templateID];
+ const displayName = isCustom && meta?.name ? meta.name : template.templateID;
+ const displayDescription = isCustom && meta?.description ? meta.description : template.settings.description;
+ const layoutTemplate = getFullDataByTemplateID(template.templateID);
+ return (
+
{
+ trackEvent(MixpanelEvent.Navigation, { from: pathname, to: `/template-preview/${template.templateID}` });
+ router.push(`/template-preview/${template.templateID}`)
+ }}
+ >
+
+
+
+ {displayName}
+
+
+
+ {template.layouts.length}
+
+
+
+
+
+ {displayDescription}
+
+
+ {layoutTemplate &&
+ layoutTemplate?.slice(0, 4).map((layout: any, index: number) => {
+ const {
+ component: LayoutComponent,
+ sampleData,
+ layoutId,
+ templateID,
+ } = layout;
+ return (
+
+ );
+ })}
+
+
+
+ );
+ })}
+
+
+
+
+
);
diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/types/index.ts b/servers/nextjs/app/(presentation-generator)/template-preview/types/index.ts
index bff16a77..84361e75 100644
--- a/servers/nextjs/app/(presentation-generator)/template-preview/types/index.ts
+++ b/servers/nextjs/app/(presentation-generator)/template-preview/types/index.ts
@@ -5,26 +5,28 @@ export interface LayoutInfo {
schema: any
sampleData: any
fileName: string
- groupName: string
+ templateID: string
layoutId: string
}
-export interface GroupSetting {
+export interface TemplateSetting {
description: string;
ordered: boolean;
default?: boolean;
}
-export interface LayoutGroup {
- groupName: string
+export interface TemplateResponse {
+ templateID: string
+ templateName?: string
layouts: LayoutInfo[]
- settings: GroupSetting
+ settings: TemplateSetting | null
}
-export interface GroupedLayoutsResponse {
- groupName: string
- files: string[]
- settings: GroupSetting | null
+export interface TemplateResponse {
+ templateName?: string
+ templateID: string
+ files: string[],
+ settings: TemplateSetting | null
}
export interface LoadingState {
diff --git a/servers/nextjs/app/api/templates/route.ts b/servers/nextjs/app/api/templates/route.ts
index 427f5260..db73a7a4 100644
--- a/servers/nextjs/app/api/templates/route.ts
+++ b/servers/nextjs/app/api/templates/route.ts
@@ -1,57 +1,77 @@
import { NextResponse } from 'next/server'
import { promises as fs } from 'fs'
import path from 'path'
-import { GroupSetting } from '@/app/(presentation-generator)/template-preview/types'
+import { TemplateSetting } from '@/app/(presentation-generator)/template-preview/types'
export async function GET() {
- try {
- const layoutsDirectory = path.join(process.cwd(), 'presentation-templates')
- const items = await fs.readdir(layoutsDirectory, { withFileTypes: true })
+ try {
+ // Get the path to the presentation-templates directory
+ const templatesDirectory = path.join(process.cwd(), 'presentation-templates')
+
+ // Read all directories in the presentation-templates directory
+ const items = await fs.readdir(templatesDirectory, { withFileTypes: true })
+
+ // Filter for directories (layout templates) and exclude files
+ const templateDirectories = items
+ .filter(item => item.isDirectory())
+ .map(dir => dir.name)
+
+ const allLayouts: {templateName: string, templateID: string; files: string[]; settings: TemplateSetting | null }[] = []
+
+ // Scan each template directory for layout files and settings
+ for (const templateName of templateDirectories) {
+ try {
+ const templatePath = path.join(templatesDirectory, templateName)
+ const templateFiles = await fs.readdir(templatePath)
+
+ // Filter for .tsx files and exclude any non-layout files
+ const layoutFiles = templateFiles.filter(file =>
+ file.endsWith('.tsx') &&
+ !file.startsWith('.') &&
+ !file.includes('.test.') &&
+ !file.includes('.spec.') &&
+ file !== 'settings.json'
+ )
+
+ // Read settings.json if it exists
+ let settings: TemplateSetting | null = null
+ const settingsPath = path.join(templatePath, 'settings.json')
+ try {
+ const settingsContent = await fs.readFile(settingsPath, 'utf-8')
+ settings = JSON.parse(settingsContent) as TemplateSetting
+ } catch (settingsError) {
+
+ console.warn(`No settings.json found for template ${templateName} or invalid JSON`)
+ // Provide default settings if settings.json is missing or invalid
+ settings = {
+ description: `${templateName} presentation layouts`,
+ ordered: false,
+ default: false
+ }
+
+ }
- const groupDirectories = items.filter(item => item.isDirectory()).map(dir => dir.name)
-
- const allLayouts: { groupName: string; files: string[]; settings: GroupSetting | null }[] = []
-
- for (const groupName of groupDirectories) {
- try {
- const groupPath = path.join(layoutsDirectory, groupName)
- const groupFiles = await fs.readdir(groupPath)
-
- const layoutFiles = groupFiles.filter(file =>
- file.endsWith('.tsx') &&
- !file.startsWith('.') &&
- !file.includes('.test.') &&
- !file.includes('.spec.') &&
- file !== 'settings.json'
+ if (layoutFiles.length > 0) {
+ allLayouts.push({
+ templateName: templateName,
+ templateID: templateName,
+ files: layoutFiles,
+ settings: settings
+ })
+ }
+ } catch (error) {
+ console.error(`Error reading template directory ${templateName}:`, error)
+ // Continue with other templates even if one fails
+ }
+ }
+
+
+ return NextResponse.json(allLayouts)
+ } catch (error) {
+ console.error('Error reading presentation-templates directory:', error)
+ return NextResponse.json(
+ { error: 'Failed to read presentation-templates directory' },
+ { status: 500 }
)
-
- let settings: GroupSetting | null = null
- const settingsPath = path.join(groupPath, 'settings.json')
- try {
- const settingsContent = await fs.readFile(settingsPath, 'utf-8')
- settings = JSON.parse(settingsContent) as GroupSetting
- } catch {
- settings = {
- description: `${groupName} presentation templates`,
- ordered: false,
- default: false,
- }
- }
-
- if (layoutFiles.length > 0) {
- allLayouts.push({ groupName, files: layoutFiles, settings })
- }
- } catch (error) {
- console.error(`Error reading group directory ${groupName}:`, error)
- }
}
-
- return NextResponse.json(allLayouts)
- } catch (error) {
- console.error('Error reading presentation-templates directory:', error)
- return NextResponse.json(
- { error: 'Failed to read presentation-templates directory' },
- { status: 500 }
- )
- }
}
\ No newline at end of file