From 300b1f13bdcda9bf08c31f905ed944e3d186ece6 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Mon, 21 Jul 2025 23:37:30 +0545 Subject: [PATCH] fix(fastapi): removes seperate schema constraints from system prompt, fix(nextjs): improves layout schema constraints --- .../fastapi/api/v1/ppt/endpoints/outlines.py | 3 + .../api/v1/ppt/endpoints/presentation.py | 3 + servers/fastapi/get_test_schema.py | 12 +- .../generate_presentation_outlines.py | 3 +- .../generate_presentation_structure.py | 47 ++++--- .../utils/llm_calls/generate_slide_content.py | 15 +- .../llm_calls/select_slide_type_on_edit.py | 4 +- servers/fastapi/utils/schema_utils.py | 73 ++++++---- .../api/presentation_to_pptx_model/route.ts | 3 +- .../presentation-layouts/defaultSchemes.ts | 4 +- .../general/BasicInfoSlideLayout.tsx | 4 +- .../general/BulletIconsOnlySlideLayout.tsx | 38 ++--- .../general/BulletWithIconsSlideLayout.tsx | 6 +- .../general/ChartWithBulletsSlideLayout.tsx | 131 +++++++++--------- .../general/IntroSlideLayout.tsx | 4 +- .../general/MetricsSlideLayout.tsx | 84 +++++------ .../general/MetricsWithImageSlideLayout.tsx | 4 +- .../general/NumberedBulletsSlideLayout.tsx | 28 ++-- .../general/TeamSlideLayout.tsx | 26 ++-- 19 files changed, 261 insertions(+), 231 deletions(-) diff --git a/servers/fastapi/api/v1/ppt/endpoints/outlines.py b/servers/fastapi/api/v1/ppt/endpoints/outlines.py index a9a811e3..28ff100b 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/outlines.py +++ b/servers/fastapi/api/v1/ppt/endpoints/outlines.py @@ -32,6 +32,9 @@ async def stream_outlines(presentation_id: str): presentation.language, presentation.summary, ): + # Give control to the event loop + await asyncio.sleep(0) + yield SSEResponse( event="response", data=json.dumps({"type": "chunk", "chunk": chunk}), diff --git a/servers/fastapi/api/v1/ppt/endpoints/presentation.py b/servers/fastapi/api/v1/ppt/endpoints/presentation.py index 0b70d6f0..758a8f40 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/presentation.py +++ b/servers/fastapi/api/v1/ppt/endpoints/presentation.py @@ -218,6 +218,9 @@ async def stream_presentation(presentation_id: str): # This will mutate slide async_assets_generation_tasks.append(process_slide_and_fetch_assets(slide)) + # Give control to the event loop + await asyncio.sleep(0) + yield SSEResponse( event="response", data=json.dumps({"type": "chunk", "chunk": slide.model_dump_json()}), diff --git a/servers/fastapi/get_test_schema.py b/servers/fastapi/get_test_schema.py index 4a17e9e7..0ae458b3 100644 --- a/servers/fastapi/get_test_schema.py +++ b/servers/fastapi/get_test_schema.py @@ -17,8 +17,8 @@ class ContactInfoModel(BaseModel): class ImageModel(BaseModel): - image_url__: str = Field(description="Image URL") - image_prompt__: str = Field(description="Image prompt") + __image_url__: str = Field(description="Image URL") + __image_prompt__: str = Field(description="Image prompt") # First Slide Layout @@ -415,11 +415,5 @@ presentation_layout = PresentationLayoutModel( ], ) -# print(json.dumps(FirstSlideModel.model_json_schema())) -slide_schema = FirstSlideModel.model_json_schema() - -slide_schema = remove_fields_from_schema(slide_schema, ["image_url__"]) -print(slide_schema) - -# print(PresentationOutlineModel.model_json_schema()) +print(json.dumps(StatisticsSlideModel.model_json_schema())) diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index f19c9a06..1bd860eb 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -8,6 +8,7 @@ from utils.llm_provider import ( get_google_llm_client, get_large_model, get_llm_client, + get_nano_model, is_google_selected, ) @@ -83,7 +84,7 @@ async def generate_ppt_outline( language: Optional[str] = None, content: Optional[str] = None, ): - model = get_large_model() + model = get_nano_model() response_model = get_presentation_outline_model_with_n_slides(n_slides) if not is_google_selected(): diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_structure.py b/servers/fastapi/utils/llm_calls/generate_presentation_structure.py index 85deb581..7de9578b 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_structure.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_structure.py @@ -1,6 +1,6 @@ from models.presentation_layout import PresentationLayoutModel from models.presentation_outline_model import PresentationOutlineModel -from utils.llm_provider import get_llm_client, get_small_model +from utils.llm_provider import get_llm_client, get_nano_model, get_small_model from utils.get_dynamic_models import ( get_presentation_structure_model_with_n_slides, ) @@ -14,30 +14,37 @@ def get_prompt(presentation_layout: PresentationLayoutModel, n_slides: int, data { "role": "system", "content": f""" - You're a professional presentation designer. + You're a professional presentation designer with creative freedom to design engaging presentations. + {presentation_layout.to_string()} - # CRITICAL RULES - - NEVER use layout type 1 (bullet points) for more than 30% of slides - - MUST use at least 3 different layout types across presentation - - NO consecutive slides with same layout type + # DESIGN PHILOSOPHY + - Create visually compelling and varied presentations + - Match layout to content purpose and audience needs + - Prioritize engagement over rigid formatting rules - # Selection Strategy - 1. **Ignore bullet point format** - focus on slide PURPOSE - 2. **Match content to layout**: - - Title/intro → Title layouts - - Process/steps → Visual process layouts - - Comparisons → Side-by-side layouts - - Data → Chart/graph layouts - - Concepts → Image + text layouts - - Key messages → Emphasis layouts + # Layout Selection Guidelines + 1. **Content-driven choices**: Let the slide's purpose guide layout selection + - Opening/closing → Title layouts + - Processes/workflows → Visual process layouts + - Comparisons/contrasts → Side-by-side layouts + - Data/metrics → Chart/graph layouts + - Concepts/ideas → Image + text layouts + - Key insights → Emphasis layouts - 3. **Force variety**: If recently used a layout type, pick different one - 4. **Prioritize visual layouts** over text-heavy ones + 2. **Visual variety**: Aim for diverse, engaging presentation flow + - Mix text-heavy and visual-heavy slides naturally + - Use your judgment on when repetition serves the content + - Balance information density across slides - **Think PURPOSE not FORMAT. Make it visually engaging.** + 3. **Audience experience**: Consider how slides work together + - Create natural transitions between topics + - Use layouts that enhance comprehension + - Design for maximum impact and retention - Select layout index for each of the {n_slides} slides. + **Trust your design instincts. Focus on creating the most effective presentation for the content and audience.** + + Select layout index for each of the {n_slides} slides based on what will best serve the presentation's goals. """, }, { @@ -55,7 +62,7 @@ async def generate_presentation_structure( ) -> PresentationStructureModel: client = get_llm_client() - model = get_small_model() + model = get_nano_model() response_model = get_presentation_structure_model_with_n_slides( len(presentation_outline.slides) ) diff --git a/servers/fastapi/utils/llm_calls/generate_slide_content.py b/servers/fastapi/utils/llm_calls/generate_slide_content.py index be699ccb..5346314b 100644 --- a/servers/fastapi/utils/llm_calls/generate_slide_content.py +++ b/servers/fastapi/utils/llm_calls/generate_slide_content.py @@ -6,10 +6,11 @@ from models.presentation_outline_model import SlideOutlineModel from utils.llm_provider import ( get_google_llm_client, get_llm_client, + get_nano_model, get_small_model, is_google_selected, ) -from utils.schema_utils import remove_fields_from_schema, generate_constraint_sentences +from utils.schema_utils import remove_fields_from_schema system_prompt = """ Generate structured slide based on provided title and outline, follow mentioned steps and notes and provide structured output. @@ -36,14 +37,12 @@ def get_user_prompt(title: str, outline: str): """ -def get_prompt_to_generate_slide_content( - title: str, outline: str, schema_constraints: str = "" -): +def get_prompt_to_generate_slide_content(title: str, outline: str): return [ { "role": "system", - "content": system_prompt + f"\n{schema_constraints}", + "content": system_prompt, }, { "role": "user", @@ -55,12 +54,11 @@ def get_prompt_to_generate_slide_content( async def get_slide_content_from_type_and_outline( slide_layout: SlideLayoutModel, outline: SlideOutlineModel ): - model = get_small_model() + model = get_nano_model() response_schema = remove_fields_from_schema( slide_layout.json_schema, ["__image_url__", "__icon_url__"] ) - schema_constraints = generate_constraint_sentences(response_schema) if not is_google_selected(): client = get_llm_client() @@ -69,7 +67,6 @@ async def get_slide_content_from_type_and_outline( messages=get_prompt_to_generate_slide_content( outline.title, outline.body, - schema_constraints, ), response_format={ "type": "json_schema", @@ -87,7 +84,7 @@ async def get_slide_content_from_type_and_outline( model=model, contents=[get_user_prompt(outline.title, outline.body)], config=GenerateContentConfig( - system_instruction=system_prompt + f"\n{schema_constraints}", + system_instruction=system_prompt, response_mime_type="application/json", response_json_schema=response_schema, ), diff --git a/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py b/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py index 671e2778..6fd9ad45 100644 --- a/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py +++ b/servers/fastapi/utils/llm_calls/select_slide_type_on_edit.py @@ -1,7 +1,7 @@ from models.presentation_layout import PresentationLayoutModel, SlideLayoutModel from models.slide_layout_index import SlideLayoutIndex from models.sql.slide import SlideModel -from utils.llm_provider import get_llm_client, get_small_model +from utils.llm_provider import get_llm_client, get_nano_model, get_small_model def get_prompt_to_select_slide_layout( @@ -42,7 +42,7 @@ async def get_slide_layout_from_prompt( ) -> SlideLayoutModel: client = get_llm_client() - model = get_small_model() + model = get_nano_model() slide_layout_ids = list(map(lambda x: x.id, layout.slides)) diff --git a/servers/fastapi/utils/schema_utils.py b/servers/fastapi/utils/schema_utils.py index 94b4642c..0f2ad6d7 100644 --- a/servers/fastapi/utils/schema_utils.py +++ b/servers/fastapi/utils/schema_utils.py @@ -1,7 +1,7 @@ from copy import deepcopy from typing import List -from utils.dict_utils import get_dict_paths_with_key, get_dict_at_path, set_dict_at_path +from utils.dict_utils import get_dict_paths_with_key, get_dict_at_path def resolve_refs(schema, defs): @@ -50,70 +50,93 @@ def remove_fields_from_schema(schema: dict, fields_to_remove: List[str]): return schema +# ? Not used def generate_constraint_sentences(schema: dict) -> str: """ Generate human-readable constraint sentences from a JSON schema. - + Args: schema: JSON schema dictionary - + Returns: String containing constraint sentences separated by newlines """ constraints = [] - + def extract_constraints_recursive(obj, prefix=""): if isinstance(obj, dict): if "properties" in obj: properties = obj["properties"] for prop_name, prop_def in properties.items(): current_path = f"{prefix}.{prop_name}" if prefix else prop_name - + if isinstance(prop_def, dict): prop_type = prop_def.get("type") - + # Handle string constraints if prop_type == "string": min_length = prop_def.get("minLength") max_length = prop_def.get("maxLength") - + if min_length is not None and max_length is not None: - constraints.append(f" - {current_path} should be less than {max_length} characters and greater than {min_length} characters") + constraints.append( + f" - {current_path} should be less than {max_length} characters and greater than {min_length} characters" + ) elif max_length is not None: - constraints.append(f" - {current_path} should be less than {max_length} characters") + constraints.append( + f" - {current_path} should be less than {max_length} characters" + ) elif min_length is not None: - constraints.append(f" - {current_path} should be greater than {min_length} characters") - + constraints.append( + f" - {current_path} should be greater than {min_length} characters" + ) + # Handle array constraints elif prop_type == "array": min_items = prop_def.get("minItems") max_items = prop_def.get("maxItems") - + if min_items is not None and max_items is not None: - constraints.append(f" - {current_path} should have more than {min_items} items and less than {max_items} items") + constraints.append( + f" - {current_path} should have more than {min_items} items and less than {max_items} items" + ) elif max_items is not None: - constraints.append(f" - {current_path} should have less than {max_items} items") + constraints.append( + f" - {current_path} should have less than {max_items} items" + ) elif min_items is not None: - constraints.append(f" - {current_path} should have more than {min_items} items") - + constraints.append( + f" - {current_path} should have more than {min_items} items" + ) + # Recurse into nested objects if prop_type == "object" or "properties" in prop_def: extract_constraints_recursive(prop_def, current_path) - + # Handle array items if they have properties if prop_type == "array" and "items" in prop_def: items_def = prop_def["items"] - if isinstance(items_def, dict) and ("properties" in items_def or items_def.get("type") == "object"): - extract_constraints_recursive(items_def, f"{current_path}[*]") - + if isinstance(items_def, dict) and ( + "properties" in items_def + or items_def.get("type") == "object" + ): + extract_constraints_recursive( + items_def, f"{current_path}[*]" + ) + # Also recurse into other nested structures for key, value in obj.items(): - if key not in ["properties", "type", "minLength", "maxLength", "minItems", "maxItems"] and isinstance(value, dict): + if key not in [ + "properties", + "type", + "minLength", + "maxLength", + "minItems", + "maxItems", + ] and isinstance(value, dict): extract_constraints_recursive(value, prefix) - + # Start extraction from the root schema extract_constraints_recursive(schema) - + return "\n".join(constraints) - - diff --git a/servers/nextjs/app/api/presentation_to_pptx_model/route.ts b/servers/nextjs/app/api/presentation_to_pptx_model/route.ts index 66a2b2da..354d54f4 100644 --- a/servers/nextjs/app/api/presentation_to_pptx_model/route.ts +++ b/servers/nextjs/app/api/presentation_to_pptx_model/route.ts @@ -66,13 +66,12 @@ async function getBrowserAndPage(id: string): Promise<[Browser, Page]> { '--disable-dev-shm-usage', '--disable-gpu', '--disable-web-security', - '--window-size=1920,1080' ], }); const page = await browser.newPage(); - await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 }); + await page.setViewport({ width: 1280, height: 720, deviceScaleFactor: 1 }); await page.goto(`http://localhost/pdf-maker?id=${id}`, { waitUntil: "networkidle0", timeout: 60000, diff --git a/servers/nextjs/presentation-layouts/defaultSchemes.ts b/servers/nextjs/presentation-layouts/defaultSchemes.ts index 4f3c7fde..45e81f92 100644 --- a/servers/nextjs/presentation-layouts/defaultSchemes.ts +++ b/servers/nextjs/presentation-layouts/defaultSchemes.ts @@ -6,7 +6,7 @@ export const ImageSchema = z.object({ }), __image_prompt__: z.string().meta({ description: "Prompt used to generate the image", - }), + }).min(10).max(50), }) export const IconSchema = z.object({ @@ -15,5 +15,5 @@ export const IconSchema = z.object({ }), __icon_query__: z.string().meta({ description: "Query used to search the icon", - }), + }).min(5).max(20), }) \ No newline at end of file diff --git a/servers/nextjs/presentation-layouts/general/BasicInfoSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/BasicInfoSlideLayout.tsx index 27f09c50..0a4132c7 100644 --- a/servers/nextjs/presentation-layouts/general/BasicInfoSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/BasicInfoSlideLayout.tsx @@ -7,10 +7,10 @@ export const layoutName = 'Basic Info' export const layoutDescription = 'A clean slide layout with title, description text, and a supporting image.' const basicInfoSlideSchema = z.object({ - title: z.string().min(3).max(50).default('Product Overview').meta({ + title: z.string().min(3).max(40).default('Product Overview').meta({ description: "Main title of the slide", }), - description: z.string().min(10).max(180).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({ + description: z.string().min(10).max(150).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({ description: "Main description text content", }), image: ImageSchema.default({ diff --git a/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx b/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx index ff17b2b3..da7143c5 100644 --- a/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/BulletIconsOnlySlideLayout.tsx @@ -7,7 +7,7 @@ export const layoutName = 'Bullet Icons Only' export const layoutDescription = 'A slide layout with title, grid of bullet points with icons (no descriptions), and a supporting image.' const bulletIconsOnlySlideSchema = z.object({ - title: z.string().min(3).max(50).default('Solutions').meta({ + title: z.string().min(3).max(40).default('Solutions').meta({ description: "Main title of the slide", }), image: ImageSchema.default({ @@ -20,11 +20,11 @@ const bulletIconsOnlySlideSchema = z.object({ title: z.string().min(2).max(80).meta({ description: "Bullet point title", }), - subtitle: z.string().min(5).max(180).optional().meta({ + subtitle: z.string().min(5).max(150).optional().meta({ description: "Optional short subtitle or brief explanation", }), icon: IconSchema, - })).min(2).max(6).default([ + })).min(2).max(4).default([ { title: 'Custom Software', subtitle: 'We create tailored software to optimize processes and boost efficiency.', @@ -72,7 +72,7 @@ interface BulletIconsOnlySlideLayoutProps { const BulletIconsOnlySlideLayout: React.FC = ({ data: slideData }) => { const bulletPoints = slideData?.bulletPoints || [] - + // Function to determine grid classes based on number of bullets const getGridClasses = (count: number) => { if (count <= 2) { @@ -87,12 +87,12 @@ const BulletIconsOnlySlideLayout: React.FC = ({ return ( <> {/* Import Google Fonts */} - - -
= ({ {/* Decorative Wave Patterns */}
- - + +
- +
- +
@@ -124,19 +124,19 @@ const BulletIconsOnlySlideLayout: React.FC = ({ {/* Bullet Points Grid */}
{bulletPoints.map((bullet, index) => ( -
{/* Icon */}
- {bullet.icon.__icon_query__}
- + {/* Content */}

@@ -158,10 +158,10 @@ const BulletIconsOnlySlideLayout: React.FC = ({ {/* Decorative Elements */}
- +
- +
interface ChartWithBulletsSlideLayoutProps { @@ -106,21 +109,21 @@ const chartConfig = { }; const CHART_COLORS = [ - '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', + '#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6', '#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1' ]; const BULLET_COLORS = [ - '#7F31E9', '#2C78DA', '#F58AAB', '#10b981', '#f59e0b', + '#7F31E9', '#2C78DA', '#F58AAB', '#10b981', '#f59e0b', '#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1' ]; const ChartWithBulletsSlideLayout: React.FC = ({ data: slideData }) => { - const chartData = slideData?.data || []; - const chartType = slideData?.chartType || 'bar'; + const chartData = slideData?.chartData?.data || []; + const chartType = slideData?.chartData?.type; const color = slideData?.color || '#3b82f6'; - const dataKey = slideData?.dataKey || 'value'; - const categoryKey = slideData?.categoryKey || 'name'; + const xAxis = chartType === 'scatter' ? 'x' : 'name'; + const yAxis = chartType === 'scatter' ? 'y' : 'value'; const showLegend = slideData?.showLegend || false; const showTooltip = slideData?.showTooltip || true; const bulletPoints = slideData?.bulletPoints || [] @@ -136,50 +139,50 @@ const ChartWithBulletsSlideLayout: React.FC = return ( - + {showTooltip && } />} {showLegend && } />} - + ); - + case 'line': return ( - + {showTooltip && } />} {showLegend && } />} - ); - + case 'area': return ( - + {showTooltip && } />} {showLegend && } />} - ); - + case 'pie': return ( @@ -191,7 +194,7 @@ const ChartWithBulletsSlideLayout: React.FC = cy="40%" outerRadius={70} fill={color} - dataKey={dataKey} + dataKey={yAxis} label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`} > {chartData.map((entry, index) => ( @@ -200,19 +203,19 @@ const ChartWithBulletsSlideLayout: React.FC = ); - + case 'scatter': return ( - - + + {showTooltip && } />} {showLegend && } />} ); - + default: return
Unsupported chart type
; } @@ -221,12 +224,12 @@ const ChartWithBulletsSlideLayout: React.FC = return ( <> {/* Import Google Fonts */} - - -
= {/* Right Section - Bullet Point Boxes */}
{bulletPoints.map((bullet, index) => ( -
= {/* Icon and Title */}
- {bullet.icon.__icon_query__} @@ -277,7 +280,7 @@ const ChartWithBulletsSlideLayout: React.FC = {bullet.title}

- + {/* Description */}

{bullet.description} diff --git a/servers/nextjs/presentation-layouts/general/IntroSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/IntroSlideLayout.tsx index a5af9844..347d877d 100644 --- a/servers/nextjs/presentation-layouts/general/IntroSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/IntroSlideLayout.tsx @@ -7,10 +7,10 @@ export const layoutName = 'Intro Slide' export const layoutDescription = 'A clean slide layout with title, description text, presenter info, and a supporting image.' const introSlideSchema = z.object({ - title: z.string().min(3).max(50).default('Product Overview').meta({ + title: z.string().min(3).max(40).default('Product Overview').meta({ description: "Main title of the slide", }), - description: z.string().min(10).max(180).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({ + description: z.string().min(10).max(150).default('Our product offers customizable dashboards for real-time reporting and data-driven decisions. It integrates with third-party tools to enhance operations and scales with business growth for improved efficiency.').meta({ description: "Main description text content", }), presenterName: z.string().min(2).max(50).default('John Doe').meta({ diff --git a/servers/nextjs/presentation-layouts/general/MetricsSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/MetricsSlideLayout.tsx index e3a06cb4..266d0fc2 100644 --- a/servers/nextjs/presentation-layouts/general/MetricsSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/MetricsSlideLayout.tsx @@ -10,16 +10,16 @@ const metricsSlideSchema = z.object({ description: "Main title of the slide", }), metrics: z.array(z.object({ - value: z.string().min(1).max(10).meta({ - description: "Metric value (e.g., 150+, 95%, $2M). No long values. Keep simple number." - }), label: z.string().min(2).max(100).meta({ description: "Metric label/title" }), - description: z.string().min(10).max(300).meta({ + value: z.string().min(1).max(10).meta({ + description: "Metric value (e.g., 150+, 95%, $2M). No long values. Keep simple number." + }), + description: z.string().min(10).max(150).meta({ description: "Detailed description of the metric. Explanation of the metric." }), - })).min(2).max(6).default([ + })).min(2).max(3).default([ { value: '150+', label: 'Clients Onboarded', @@ -50,7 +50,7 @@ interface MetricsSlideLayoutProps { const MetricsSlideLayout: React.FC = ({ data: slideData }) => { const metrics = slideData?.metrics || [] - + // Function to determine layout classes based on number of metrics const getLayoutClasses = (count: number) => { if (count === 1) { @@ -67,7 +67,7 @@ const MetricsSlideLayout: React.FC = ({ data: slideData return 'grid grid-cols-2 md:grid-cols-3' } } - + // Function to get individual item classes const getItemClasses = (count: number) => { // All items use same classes now @@ -77,12 +77,12 @@ const MetricsSlideLayout: React.FC = ({ data: slideData return ( <> {/* Import Google Fonts */} - - -

= ({ data: slideData {/* Decorative Wave Patterns */}
- - - + + +
- +
- - - + + +
@@ -121,31 +121,31 @@ const MetricsSlideLayout: React.FC = ({ data: slideData
{/* Metrics Layout - Each metric grouped vertically */}
- {metrics.map((metric, index) => ( -
- {/* Label */} -
- {metric.label} + {metrics.map((metric, index) => ( +
+ {/* Label */} +
+ {metric.label} +
+ + {/* Large Metric Value */} +
+ {metric.value} +
+ + {/* Description Box */} +
+

+ {metric.description} +

+
- - {/* Large Metric Value */} -
- {metric.value} -
- - {/* Description Box */} -
-

- {metric.description} -

-
-
- ))} + ))}
diff --git a/servers/nextjs/presentation-layouts/general/MetricsWithImageSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/MetricsWithImageSlideLayout.tsx index 155e509d..9b7ee8c6 100644 --- a/servers/nextjs/presentation-layouts/general/MetricsWithImageSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/MetricsWithImageSlideLayout.tsx @@ -7,10 +7,10 @@ export const layoutName = 'Metrics with Image' export const layoutDescription = 'A slide layout with supporting image on the left and title, description, and metrics grid on the right. Can be used alternatively with MetricSlide.' const metricsWithImageSlideSchema = z.object({ - title: z.string().min(3).max(50).default('Competitive Advantage').meta({ + title: z.string().min(3).max(40).default('Competitive Advantage').meta({ description: "Main title of the slide", }), - description: z.string().min(10).max(180).default('Ginyard International Co. stands out by offering custom digital solutions tailored to client needs, alongside long-term support to ensure lasting relationships and continuous adaptation.').meta({ + description: z.string().min(10).max(150).default('Ginyard International Co. stands out by offering custom digital solutions tailored to client needs, alongside long-term support to ensure lasting relationships and continuous adaptation.').meta({ description: "Description text below the title", }), image: ImageSchema.default({ diff --git a/servers/nextjs/presentation-layouts/general/NumberedBulletsSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/NumberedBulletsSlideLayout.tsx index ab64c864..d6efa638 100644 --- a/servers/nextjs/presentation-layouts/general/NumberedBulletsSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/NumberedBulletsSlideLayout.tsx @@ -7,7 +7,7 @@ export const layoutName = 'Numbered Bullets' export const layoutDescription = 'A slide layout with large title, supporting image, and numbered bullet points with descriptions.' const numberedBulletsSlideSchema = z.object({ - title: z.string().min(3).max(50).default('Market Validation').meta({ + title: z.string().min(3).max(40).default('Market Validation').meta({ description: "Main title of the slide", }), image: ImageSchema.default({ @@ -20,7 +20,7 @@ const numberedBulletsSlideSchema = z.object({ title: z.string().min(2).max(80).meta({ description: "Bullet point title", }), - description: z.string().min(10).max(180).meta({ + description: z.string().min(10).max(150).meta({ description: "Bullet point description", }), })).min(1).max(4).default([ @@ -59,12 +59,12 @@ const NumberedBulletsSlideLayout: React.FC = ({ return ( <> {/* Import Google Fonts */} - - -
= ({ {String(index + 1).padStart(2, '0')}
- + {/* Content */}

@@ -120,15 +120,15 @@ const NumberedBulletsSlideLayout: React.FC = ({ {/* Decorative Wave Pattern at Bottom */}
- - diff --git a/servers/nextjs/presentation-layouts/general/TeamSlideLayout.tsx b/servers/nextjs/presentation-layouts/general/TeamSlideLayout.tsx index 060cd320..8c7551f6 100644 --- a/servers/nextjs/presentation-layouts/general/TeamSlideLayout.tsx +++ b/servers/nextjs/presentation-layouts/general/TeamSlideLayout.tsx @@ -13,20 +13,20 @@ const teamMemberSchema = z.object({ position: z.string().min(2).max(50).meta({ description: "Job title or position" }), - description: z.string().max(180).meta({ + description: z.string().max(150).meta({ description: "Brief description of the team member (around 100 characters)" }), image: ImageSchema }); const teamSlideSchema = z.object({ - title: z.string().min(3).max(50).default('Our Team Members').meta({ - description: "Main title of the slide", + title: z.string().min(3).max(40).default('Our Team Members').meta({ + description: "Main title of the slide", }), - companyDescription: z.string().min(10).max(180).default('Ginyard International Co. is a leading provider of innovative digital solutions tailored for businesses. Our mission is to empower organizations to achieve their goals through cutting-edge technology and strategic partnerships.').meta({ + companyDescription: z.string().min(10).max(150).default('Ginyard International Co. is a leading provider of innovative digital solutions tailored for businesses. Our mission is to empower organizations to achieve their goals through cutting-edge technology and strategic partnerships.').meta({ description: "Company description or team introduction text", }), - teamMembers: z.array(teamMemberSchema).min(2).max(6).default([ + teamMembers: z.array(teamMemberSchema).min(2).max(4).default([ { name: 'Juliana Silva', position: 'CEO', @@ -78,7 +78,7 @@ interface TeamSlideLayoutProps { const TeamSlideLayout: React.FC = ({ data: slideData }) => { const teamMembers = slideData?.teamMembers || [] - + // Function to determine grid classes based on number of team members const getGridClasses = (count: number) => { if (count <= 2) { @@ -93,12 +93,12 @@ const TeamSlideLayout: React.FC = ({ data: slideData }) => return ( <> {/* Import Google Fonts */} - - -
= ({ data: slideData }) => {/* Decorative Wave Pattern */}
- - + +
@@ -143,7 +143,7 @@ const TeamSlideLayout: React.FC = ({ data: slideData }) => className="w-full h-full object-cover" />
- + {/* Member Info */}