From df0d4851b495402f388473b636fbda64675399c2 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Thu, 24 Jul 2025 22:18:30 +0545 Subject: [PATCH 01/10] docs(readme) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b183a7ce..42727476 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ * ✅ **API Presentation Generation** — Host as API to generate presentations over requests * ✅ **Ollama Support** — Run open-source models locally with Ollama integration * ✅ **OpenAI API Compatibility** — Use any OpenAI-compatible API endpoint with your own models +* ✅ **Versatile Image Generation** — Choose from DALL-E 3, Gemini Flash, Pexels, or Pixabay for your visuals * ✅ **Runs Locally** — All code runs on your device * ✅ **Privacy-First** — No tracking, no data stored by us * ✅ **Flexible** — Generate presentations from prompts or outlines @@ -74,7 +75,7 @@ You may want to directly provide your API KEYS as environment variables and keep You can also set the following environment variables to customize the image generation provider and API keys: - **IMAGE_PROVIDER=[pexels/pixabay/gemini_flash/dall-e-3]**: Select the image provider of your choice. - - Defaults to **dall-e-3** for OpenAI models and **gemini_flash** for Google models if not set. + - Defaults to **dall-e-3** for OpenAI models, **gemini_flash** for Google models if not set. - **PEXELS_API_KEY=[Your Pexels API Key]**: Required if using **pexels** as the image provider. - **PIXABAY_API_KEY=[Your Pixabay API Key]**: Required if using **pixabay** as the image provider. - **GOOGLE_API_KEY=[Your Google API Key]**: Required if using **gemini_flash** as the image provider. From 7f9c0f61e96695b381a38e4321527e0e6a1f80c8 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Thu, 24 Jul 2025 23:18:27 +0545 Subject: [PATCH 02/10] chore(nextjs): changes names of files and directory in presentation-layouts, also changes the examples --- ...leComponent.tsx => ExampleSlideLayout.tsx} | 2 +- .../ExampleSlideLayoutStructure.tsx | 32 +++++++++++++++++ .../presentation-layouts/SlideStructure.tsx | 36 ------------------- 3 files changed, 33 insertions(+), 37 deletions(-) rename servers/nextjs/presentation-layouts/{ExampleComponent.tsx => ExampleSlideLayout.tsx} (97%) create mode 100644 servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx delete mode 100644 servers/nextjs/presentation-layouts/SlideStructure.tsx diff --git a/servers/nextjs/presentation-layouts/ExampleComponent.tsx b/servers/nextjs/presentation-layouts/ExampleSlideLayout.tsx similarity index 97% rename from servers/nextjs/presentation-layouts/ExampleComponent.tsx rename to servers/nextjs/presentation-layouts/ExampleSlideLayout.tsx index dd1dc820..60ab9776 100644 --- a/servers/nextjs/presentation-layouts/ExampleComponent.tsx +++ b/servers/nextjs/presentation-layouts/ExampleSlideLayout.tsx @@ -47,7 +47,7 @@ export const Schema = z.object({ type SchemaType = z.infer; -export default function ExampleComponent({ data }: { data: SchemaType }) { +export default function ExampleSlideLayout({ data }: { data: SchemaType }) { const { title, subtitle, metrics, chartImage, trendIcon } = data; return (
diff --git a/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx b/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx new file mode 100644 index 00000000..ce92d946 --- /dev/null +++ b/servers/nextjs/presentation-layouts/ExampleSlideLayoutStructure.tsx @@ -0,0 +1,32 @@ +import * as z from "zod"; +// Note: +// If you want to use images and icons, you must use ImageSchema and IconSchema +// Images and icons are the only media types supported for PDF and PPTX exports +import { ImageSchema, IconSchema } from "./defaultSchemes"; + + +// Schema definition +export const Schema = z.object({ + // Notes: + // Schema fields + // Each field must have a default value (this is important for Layout Preview) + // Each field must have a meta description + // Each field must have a minimum and maximum length + // Each array field must have a minimum and maximum number of items +}) + +// Type inference +type SchemaType = z.infer; + + +// Component definition +const SlideComponent = ({ data }: { data: Partial }) => { + // Notes: + // Must have consistent aspect ratio (16:9) and max-width of 1280px. + // Validate each data field before rendering using && operator or optional chaining. + // These layouts are exported as PDF and PPTX so they must be optimized for both formats. + // Content must properly fit in the container, specify min and max constraints in the schema. + // You can check out ExampleComponent.tsx for more details. +}; + +export default SlideComponent; \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/SlideStructure.tsx b/servers/nextjs/presentation-layouts/SlideStructure.tsx deleted file mode 100644 index 907244f0..00000000 --- a/servers/nextjs/presentation-layouts/SlideStructure.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import * as z from "zod"; -// Note: -// If you want to use Image and Icon Must Use these Schemas. -// Image and icons are only media support for PDF and PPTX. -import { ImageSchema, IconSchema } from "./defaultSchemes"; - - -// Schema definition -export const Schema = z.object({ - // Note: - // Schema fields - // Each fields must have a default value (this is important for Layout-preview) - // Each fields must have a meta description - // Each fields must have a min and max length, length must support the layout design. - // Each Array fields must have a min and max length - -}) - -// Type inference -type SchemaType = z.infer; - - -// Component definition -const SlideStructure = ({ data }: { data: Partial }) => { - // Note: - // Data is already parse so not need to parse again. - // Must have consistent aspect ratio (16:9) and max-width 1280px. - // Validate each data before rendering using && operator or optional chaining - // These layout are exported as PDF and PPTX so must be optimized for both. - // Component design, layout, styles, etc. - // Content must be properly fit in the container, if need adjust the lenght of the content and items in Schema. - // See ExampleComponent.tsx for more details. - // Font Size must fit the desing and and the content must be properly fit in the container. -}; - -export default SlideStructure; \ No newline at end of file From 1f8b9a70cd34128beb81cb0e4724fe90f4286567 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Thu, 24 Jul 2025 23:45:48 +0545 Subject: [PATCH 03/10] chore: Change isDefault to default --- .../(presentation-generator)/context/LayoutContext.tsx | 4 ++-- .../outline/components/LayoutSelection.tsx | 8 ++++---- .../app/(presentation-generator)/outline/types/index.ts | 2 +- servers/nextjs/app/api/layouts/route.ts | 2 +- .../app/layout-preview/hooks/useGroupLayoutLoader.ts | 2 +- .../nextjs/app/layout-preview/hooks/useLayoutLoader.ts | 2 +- servers/nextjs/app/layout-preview/page.tsx | 2 +- servers/nextjs/app/layout-preview/types/index.ts | 2 +- servers/nextjs/presentation-layouts/classic/setting.json | 2 +- servers/nextjs/presentation-layouts/general/setting.json | 2 +- .../presentation-layouts/modern/1IntroSlideLayout.tsx | 2 +- servers/nextjs/presentation-layouts/modern/setting.json | 2 +- .../nextjs/presentation-layouts/professional/setting.json | 2 +- 13 files changed, 17 insertions(+), 17 deletions(-) diff --git a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx index 59476f2d..6d40b623 100644 --- a/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx +++ b/servers/nextjs/app/(presentation-generator)/context/LayoutContext.tsx @@ -16,7 +16,7 @@ export interface LayoutInfo { export interface GroupSetting { description: string; ordered: boolean; - isDefault?: boolean; + default?: boolean; } export interface GroupedLayoutsResponse { @@ -87,7 +87,7 @@ export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children }) const settings = groupData.settings || { description: `${groupData.groupName} presentation layouts`, ordered: false, - isDefault: false + default: false }; groupSettingsMap.set(groupData.groupName, settings); diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx index 285b4422..0824fa1c 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx @@ -32,14 +32,14 @@ const LayoutSelection: React.FC = ({ name: groupName, description: settings?.description || `${groupName} presentation layouts`, ordered: settings?.ordered || false, - isDefault: settings?.isDefault || false, + default: settings?.default || false, }; }); // Sort groups to put default first, then by name return Groups.sort((a, b) => { - if (a.isDefault && !b.isDefault) return -1; - if (!a.isDefault && b.isDefault) return 1; + if (a.default && !b.default) return -1; + if (!a.default && b.default) return 1; return a.name.localeCompare(b.name); }); }, [getAllGroups, getLayoutsByGroup, getGroupSetting]); @@ -47,7 +47,7 @@ const LayoutSelection: React.FC = ({ // Auto-select first group when groups are loaded useEffect(() => { if (layoutGroups.length > 0 && !selectedLayoutGroup) { - const defaultGroup = layoutGroups.find(g => g.isDefault) || layoutGroups[0]; + const defaultGroup = layoutGroups.find(g => g.default) || layoutGroups[0]; const slides = getLayoutsByGroup(defaultGroup.id); onSelectLayoutGroup({ diff --git a/servers/nextjs/app/(presentation-generator)/outline/types/index.ts b/servers/nextjs/app/(presentation-generator)/outline/types/index.ts index a311f447..d3067f3e 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/types/index.ts +++ b/servers/nextjs/app/(presentation-generator)/outline/types/index.ts @@ -3,7 +3,7 @@ export interface LayoutGroup { name: string; description: string; ordered: boolean; - isDefault?: boolean; + default?: boolean; slides?: any } diff --git a/servers/nextjs/app/api/layouts/route.ts b/servers/nextjs/app/api/layouts/route.ts index fbd73104..5ae25c0b 100644 --- a/servers/nextjs/app/api/layouts/route.ts +++ b/servers/nextjs/app/api/layouts/route.ts @@ -46,7 +46,7 @@ export async function GET() { settings = { description: `${groupName} presentation layouts`, ordered: false, - isDefault: false + default: false } } diff --git a/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts b/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts index 7825bc3c..793387b0 100644 --- a/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts +++ b/servers/nextjs/app/layout-preview/hooks/useGroupLayoutLoader.ts @@ -65,7 +65,7 @@ export const useGroupLayoutLoader = (groupSlug: string): UseGroupLayoutLoaderRet const groupSettings: GroupSetting = targetGroupData.settings ? targetGroupData.settings : { description: `${targetGroupData.groupName} presentation layouts`, ordered: false, - isDefault: false + default: false } for (const fileName of targetGroupData.files) { try { diff --git a/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts b/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts index 8154f07e..686e360e 100644 --- a/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts +++ b/servers/nextjs/app/layout-preview/hooks/useLayoutLoader.ts @@ -40,7 +40,7 @@ export const useLayoutLoader = (): UseLayoutLoaderReturn => { const groupSettings: GroupSetting = groupData.settings ? groupData.settings : { description: `${groupData.groupName} presentation layouts`, ordered: false, - isDefault: false + default: false } for (const fileName of groupData.files) { diff --git a/servers/nextjs/app/layout-preview/page.tsx b/servers/nextjs/app/layout-preview/page.tsx index a7186109..4f1d114c 100644 --- a/servers/nextjs/app/layout-preview/page.tsx +++ b/servers/nextjs/app/layout-preview/page.tsx @@ -68,7 +68,7 @@ const LayoutPreview = () => { {group.layouts.length} layout{group.layouts.length !== 1 ? 's' : ''} - {group.settings.isDefault && ( + {group.settings.default && ( Default diff --git a/servers/nextjs/app/layout-preview/types/index.ts b/servers/nextjs/app/layout-preview/types/index.ts index f784952f..bff16a77 100644 --- a/servers/nextjs/app/layout-preview/types/index.ts +++ b/servers/nextjs/app/layout-preview/types/index.ts @@ -12,7 +12,7 @@ export interface LayoutInfo { export interface GroupSetting { description: string; ordered: boolean; - isDefault?: boolean; + default?: boolean; } export interface LayoutGroup { diff --git a/servers/nextjs/presentation-layouts/classic/setting.json b/servers/nextjs/presentation-layouts/classic/setting.json index d6a15682..1f0c2195 100644 --- a/servers/nextjs/presentation-layouts/classic/setting.json +++ b/servers/nextjs/presentation-layouts/classic/setting.json @@ -1,5 +1,5 @@ { "description": "Default layout for presentations", "ordered": false, - "isDefault": true + "default": true } \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/general/setting.json b/servers/nextjs/presentation-layouts/general/setting.json index 8a00ff22..724e0635 100644 --- a/servers/nextjs/presentation-layouts/general/setting.json +++ b/servers/nextjs/presentation-layouts/general/setting.json @@ -1,5 +1,5 @@ { "description": "General purpose layouts for common presentation elements", "ordered": false, - "isDefault": false + "default": false } \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx b/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx index 85f7ceba..ef9eaee8 100644 --- a/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/modern/1IntroSlideLayout.tsx @@ -6,7 +6,7 @@ export const layoutName = "Intro Pitch Deck Slide"; export const layoutDescription = "A visually appealing introduction slide for a pitch deck, featuring a large title, company name, date, and contact information with a modern design."; const introPitchDeckSchema = z.object({ - title: z.string().min(2).max(15).default("Pitch Deck and badu").meta({ + title: z.string().min(2).max(15).default("Pitch Deck").meta({ description: "Main title of the slide", }), description: z.string().default("").meta({ diff --git a/servers/nextjs/presentation-layouts/modern/setting.json b/servers/nextjs/presentation-layouts/modern/setting.json index b88e362b..93e8470a 100644 --- a/servers/nextjs/presentation-layouts/modern/setting.json +++ b/servers/nextjs/presentation-layouts/modern/setting.json @@ -1,5 +1,5 @@ { "description": "Modern white and blue business pitch deck layouts with clean, professional design", "ordered": false, - "isDefault": false + "default": false } diff --git a/servers/nextjs/presentation-layouts/professional/setting.json b/servers/nextjs/presentation-layouts/professional/setting.json index 00660bc1..4eeda989 100644 --- a/servers/nextjs/presentation-layouts/professional/setting.json +++ b/servers/nextjs/presentation-layouts/professional/setting.json @@ -1,5 +1,5 @@ { "description": "Professional presentation layouts with clean design and flexible content fields. Suitable for business pitches, organizational overviews, product presentations, and various corporate communications.", "ordered": false, - "isDefault": false + "default": false } \ No newline at end of file From 30ae6ebff20a8c377dc14d8843b5a935cb4bc814 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Fri, 25 Jul 2025 02:19:12 +0545 Subject: [PATCH 04/10] chore(nextjs): wraps slide layouts with 16:9 box --- servers/nextjs/app/layout-preview/[slug]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/servers/nextjs/app/layout-preview/[slug]/page.tsx b/servers/nextjs/app/layout-preview/[slug]/page.tsx index 00dee152..fd093d1f 100644 --- a/servers/nextjs/app/layout-preview/[slug]/page.tsx +++ b/servers/nextjs/app/layout-preview/[slug]/page.tsx @@ -100,7 +100,7 @@ const GroupLayoutPreview = () => {
{/* Layout Content */} -
+
From 4d08a05f09f8ecba90970ea3ae8961c4ddc07c52 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Fri, 25 Jul 2025 02:43:34 +0545 Subject: [PATCH 05/10] fix(fastapi): changes google genai stream to create with stream=True to solve json decode error --- .../utils/llm_calls/generate_presentation_outlines.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index b35d84f5..90be3f9e 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -87,14 +87,13 @@ async def generate_ppt_outline( if not is_google_selected(): client = get_llm_client() - async with client.beta.chat.completions.stream( + async for response in await client.chat.completions.create( model=model, messages=get_prompt_template(prompt, n_slides, language, content), + stream=True, response_format=response_model, - ) as stream: - async for event in stream: - if isinstance(event, ContentDeltaEvent): - yield event.delta + ): + yield response.choices[0].delta else: client = get_google_llm_client() From af00557fd5401edb2d52a13cf9b7cf708f3c4648 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Fri, 25 Jul 2025 03:31:10 +0545 Subject: [PATCH 06/10] feat(fastapi): adds endpoint to edit slide layout using its html --- servers/fastapi/api/v1/ppt/endpoints/slide.py | 38 +++++++- servers/fastapi/models/sql/slide.py | 2 + servers/fastapi/utils/llm_calls/edit_slide.py | 4 +- .../utils/llm_calls/edit_slide_html.py | 93 +++++++++++++++++++ 4 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 servers/fastapi/utils/llm_calls/edit_slide_html.py diff --git a/servers/fastapi/api/v1/ppt/endpoints/slide.py b/servers/fastapi/api/v1/ppt/endpoints/slide.py index a6f9ee9a..c473fc03 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/slide.py +++ b/servers/fastapi/api/v1/ppt/endpoints/slide.py @@ -1,10 +1,11 @@ -from typing import Annotated +from typing import Annotated, Optional from fastapi import APIRouter, Body, HTTPException from models.sql.presentation import PresentationModel from models.sql.slide import SlideModel from services.database import get_sql_session from utils.llm_calls.edit_slide import get_edited_slide_content +from utils.llm_calls.edit_slide_html import get_edited_slide_html from utils.llm_calls.select_slide_type_on_edit import get_slide_layout_from_prompt from utils.process_slides import process_old_and_new_slides_and_fetch_assets from utils.randomizers import get_random_uuid @@ -14,10 +15,7 @@ SLIDE_ROUTER = APIRouter(prefix="/slide", tags=["Slide"]) @SLIDE_ROUTER.post("/edit") -async def edit_slide( - id: Annotated[str, Body()], - prompt: Annotated[str, Body()] -): +async def edit_slide(id: Annotated[str, Body()], prompt: Annotated[str, Body()]): with get_sql_session() as sql_session: slide = sql_session.get(SlideModel, id) @@ -53,3 +51,33 @@ async def edit_slide( sql_session.refresh(slide) return slide + + +@SLIDE_ROUTER.post("/edit-html", response_model=SlideModel) +async def edit_slide_html( + id: Annotated[str, Body()], + prompt: Annotated[str, Body()], + html: Annotated[Optional[str], Body()] = None, +): + with get_sql_session() as sql_session: + slide = sql_session.get(SlideModel, id) + if not slide: + raise HTTPException(status_code=404, detail="Slide not found") + + html_to_edit = html or slide.html_content + if not html_to_edit: + raise HTTPException(status_code=400, detail="No HTML to edit") + + edited_slide_html = await get_edited_slide_html(prompt, html_to_edit) + + # Always assign a new unique id to the slide + # This is to ensure that the nextjs can track slide updates + slide.id = get_random_uuid() + + with get_sql_session() as sql_session: + sql_session.add(slide) + slide.html_content = edited_slide_html + sql_session.commit() + sql_session.refresh(slide) + + return slide diff --git a/servers/fastapi/models/sql/slide.py b/servers/fastapi/models/sql/slide.py index 2195350f..76ad01c5 100644 --- a/servers/fastapi/models/sql/slide.py +++ b/servers/fastapi/models/sql/slide.py @@ -1,3 +1,4 @@ +from typing import Optional from sqlmodel import SQLModel, Field, Column, JSON from utils.randomizers import get_random_uuid @@ -10,3 +11,4 @@ class SlideModel(SQLModel, table=True): layout: str index: int content: dict = Field(sa_column=Column(JSON)) + html_content: Optional[str] diff --git a/servers/fastapi/utils/llm_calls/edit_slide.py b/servers/fastapi/utils/llm_calls/edit_slide.py index 17d7e4a6..20c87c53 100644 --- a/servers/fastapi/utils/llm_calls/edit_slide.py +++ b/servers/fastapi/utils/llm_calls/edit_slide.py @@ -7,8 +7,8 @@ from models.sql.slide import SlideModel from google.genai.types import GenerateContentConfig from utils.llm_provider import ( get_google_llm_client, + get_large_model, get_llm_client, - get_small_model, is_google_selected, ) from utils.schema_utils import remove_fields_from_schema @@ -58,7 +58,7 @@ async def get_edited_slide_content( slide: SlideModel, language: Optional[str] = None, ): - model = get_small_model() + model = get_large_model() response_schema = remove_fields_from_schema( slide_layout.json_schema, ["__image_url__", "__icon_url__"] ) diff --git a/servers/fastapi/utils/llm_calls/edit_slide_html.py b/servers/fastapi/utils/llm_calls/edit_slide_html.py new file mode 100644 index 00000000..b20f3cf7 --- /dev/null +++ b/servers/fastapi/utils/llm_calls/edit_slide_html.py @@ -0,0 +1,93 @@ +import asyncio +from typing import Optional +from google.genai.types import GenerateContentConfig +from utils.llm_provider import ( + get_google_llm_client, + get_large_model, + is_google_selected, + get_llm_client, +) + +system_prompt = """ + You are an expert HTML slide editor. Your task is to modify slide HTML content based on user prompts while maintaining proper structure, styling, and functionality. + + Guidelines: + 1. **Preserve Structure**: Maintain the overall HTML structure, including essential containers, classes, and IDs + 2. **Content Updates**: Modify text, images, lists, and other content elements as requested + 3. **Style Consistency**: Keep existing CSS classes and styling unless specifically asked to change them + 4. **Responsive Design**: Ensure modifications work across different screen sizes + 5. **Accessibility**: Maintain proper semantic HTML and accessibility attributes + 6. **Clean Output**: Return only the modified HTML without explanations unless errors occur + + Common Edit Types: + - Text content changes (headings, paragraphs, lists) + - Image updates (src, alt text, captions) + - Layout modifications (adding/removing sections) + - Style adjustments (colors, fonts, spacing via classes) + - Interactive elements (buttons, links, forms) + + Error Handling: + - If the HTML structure is invalid, fix it while making requested changes + - If a request would break functionality, suggest an alternative approach + - For unclear prompts, make reasonable assumptions and note any ambiguities + + Output Format: + Return the complete modified HTML. If the original HTML contains