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 b9e8517d..0ffc6a6a 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py +++ b/servers/fastapi/api/v1/ppt/endpoints/slide_to_html.py @@ -76,6 +76,7 @@ class GetLayoutsResponse(BaseModel): layouts: list[LayoutData] message: Optional[str] = None template: Optional[dict] = None + fonts: Optional[List[str]] = None class PresentationSummary(BaseModel): @@ -820,6 +821,13 @@ async def get_layouts( for layout in layouts_db ] + # Aggregate unique fonts across all layouts + aggregated_fonts: set[str] = set() + for layout in layouts_db: + if layout.fonts: + aggregated_fonts.update([f for f in layout.fonts if isinstance(f, str)]) + fonts_list = sorted(list(aggregated_fonts)) if aggregated_fonts else None + # Fetch template meta template_meta = await session.get(TemplateModel, presentation_id) template = None @@ -836,6 +844,7 @@ async def get_layouts( layouts=layouts, message=f"Retrieved {len(layouts)} layout(s) for presentation {presentation_id}", template=template, + fonts=fonts_list, ) except HTTPException: diff --git a/servers/fastapi/models/sql/template.py b/servers/fastapi/models/sql/template.py new file mode 100644 index 00000000..8a1e7457 --- /dev/null +++ b/servers/fastapi/models/sql/template.py @@ -0,0 +1,13 @@ +from datetime import datetime +from typing import Optional +from sqlalchemy import Column, DateTime +from sqlmodel import SQLModel, Field + + +class TemplateModel(SQLModel, table=True): + __tablename__ = "templates" + + id: str = Field(primary_key=True, description="UUID for the template (matches presentation_id)") + name: str = Field(description="Human friendly template name") + description: Optional[str] = Field(default=None, description="Optional template description") + created_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now)) \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useLayoutSaving.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useLayoutSaving.ts index 7c4bf06d..14d8bd06 100644 --- a/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useLayoutSaving.ts +++ b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useLayoutSaving.ts @@ -2,11 +2,12 @@ import { useState, useCallback } from "react"; import { toast } from "sonner"; import { v4 as uuidv4 } from "uuid"; import { ApiResponseHandler } from "@/app/(presentation-generator)/services/api/api-error-handler"; -import { ProcessedSlide, UploadedFont } from "../types"; +import { ProcessedSlide, UploadedFont, FontData } from "../types"; export const useLayoutSaving = ( slides: ProcessedSlide[], UploadedFonts: UploadedFont[], + fontsData: FontData | null, refetch: () => void, setSlides: React.Dispatch> ) => { @@ -90,8 +91,10 @@ export const useLayoutSaving = ( const reactComponents: any[] = []; const presentationId = uuidv4(); - // Get all uploaded font URLs - const FontUrls = UploadedFonts.map((font) => font.fontUrl); + // Collect uploaded font URLs and Google Fonts CSS URLs + const uploadedFontUrls = UploadedFonts.map((font) => font.fontUrl); + const googleFontCssUrls = fontsData?.internally_supported_fonts?.map(f => f.google_fonts_url).filter(Boolean) || []; + const FontUrls = Array.from(new Set([...(uploadedFontUrls || []), ...googleFontCssUrls])); console.log("FontUrls", FontUrls); for (let i = 0; i < slides.length; i++) { @@ -186,7 +189,7 @@ export const useLayoutSaving = ( } finally { setIsSavingLayout(false); } - }, [slides, UploadedFonts, refetch, closeSaveModal, setSlides]); + }, [slides, UploadedFonts, fontsData, refetch, closeSaveModal, setSlides]); return { isSavingLayout, diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx index 640762b3..de9a32f1 100644 --- a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx @@ -35,6 +35,7 @@ const CustomTemplatePage = () => { const { isSavingLayout, isModalOpen, openSaveModal, closeSaveModal, saveLayout } = useLayoutSaving( slides, UploadedFonts, + fontsData, refetch, setSlides ); diff --git a/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx index a5031fe4..72cdb37b 100644 --- a/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx @@ -33,6 +33,7 @@ const GroupLayoutPreview = () => { const [currentFonts, setCurrentFonts] = useState(undefined); const [isSaving, setIsSaving] = useState(false); const [layoutsMap, setLayoutsMap] = useState>({}); + const [templateMeta, setTemplateMeta] = useState<{ name?: string; description?: string } | null>(null); const injectFonts = (fontUrls: string[]) => { fontUrls.forEach((fontUrl) => { @@ -55,7 +56,7 @@ const GroupLayoutPreview = () => { const loadCustomLayouts = async () => { if (!isCustom) return; try { - const res = await fetch(`/api/v1/ppt/layout-management/get-layouts/${presentationId}`); + const res = await fetch(`/api/v1/ppt/template-management/get-templates/${presentationId}`); if (!res.ok) return; const data = await res.json(); const map: Record = {}; @@ -68,12 +69,13 @@ const GroupLayoutPreview = () => { }; } setLayoutsMap(map); - // Inject all fonts used by this custom group's layouts - // const allFonts: string[] = []; - // Object.values(map).forEach((entry) => { - // (entry.fonts || []).forEach((f) => allFonts.push(f)); - // }); - injectFonts(map[0].fonts || []); + // Set template meta and inject aggregated fonts if provided + if (data?.template) { + setTemplateMeta({ name: data.template.name, description: data.template.description }); + } + if (Array.isArray(data?.fonts) && data.fonts.length) { + injectFonts(data.fonts); + } } catch (e) { // noop } @@ -157,7 +159,7 @@ const GroupLayoutPreview = () => { }, ], }; - const res = await fetch(`/api/v1/ppt/layout-management/save-layouts`, { + const res = await fetch(`/api/v1/ppt/template-management/save-templates`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload),