Merge branch 'feat/custom_schema_and_layout' into presentation-api-from_custom_schema_and_layout

This commit is contained in:
Saurav Niraula 2025-07-25 23:29:22 +05:45 committed by GitHub
commit f14a856ba4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 312 additions and 296 deletions

View file

@ -12,7 +12,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { PresentationGenerationApi } from "../services/api/presentation-generation";
import { getStaticFileUrl } from "../utils/others";
import { toast } from "sonner";
interface IconsEditorProps {
icon_prompt?: string[] | null;
onClose?: () => void;
@ -53,8 +53,9 @@ const IconsEditor = ({
limit: 40,
});
setIcons(data);
} catch (error) {
} catch (error: any) {
console.error("Error fetching icons:", error);
toast.error(error.message || "Failed to fetch icons. Please try again.");
setIcons([]);
} finally {
setLoading(false);

View file

@ -98,10 +98,10 @@ const ImageEditor = ({
try {
const response = await PresentationGenerationApi.getPreviousGeneratedImages();
setPreviousGeneratedImages(response);
} catch (error) {
} catch (error: any) {
toast.error("Failed to get previous generated images. Please try again.");
console.error("error in getting previous generated images", error);
setError("Failed to get previous generated images. Please try again.");
setError(error.message || "Failed to get previous generated images. Please try again.");
}
}
@ -218,9 +218,9 @@ const ImageEditor = ({
});
setPreviewImages(response);
} catch (err) {
} catch (err: any) {
console.error("Error in image generation", err);
setError("Failed to generate image. Please try again.");
setError(err.message || "Failed to generate image. Please try again.");
} finally {
setIsGenerating(false);
}

View file

@ -149,11 +149,13 @@ const DocumentsPreviewPage: React.FC = () => {
dispatch(setPresentationId(createResponse.id));
router.push("/outline");
} catch (error) {
console.error("Error in presentation creation:", error);
toast.error("Error in presentation creation. Please try again.");
} catch (error: any) {
console.error("Error in radar presentation creation:", error);
toast.error('Error', {
description: error.message || "Error in radar presentation creation.",
});
setShowLoading({
message: "Error in presentation creation.",
message: "Error in radar presentation creation.",
show: true,
duration: 10,
progress: false,

View file

@ -86,10 +86,10 @@ export const usePresentationGeneration = (
dispatch(clearPresentationData());
router.push(`/presentation?id=${presentationId}&stream=true`);
}
} catch (error) {
console.error("Error in data generation", error);
} catch (error: any) {
console.error('Error In Presentation Generation(prepare).', error);
toast.error("Generation Error", {
description: "Failed to generate presentation. Please try again.",
description: error.message || "Error In Presentation Generation(prepare).",
});
} finally {
setLoadingState(DEFAULT_LOADING_STATE);

View file

@ -60,9 +60,11 @@ const SlideContent = ({
dispatch(updateSlide({ index: slide.index, slide: response }));
toast.success("Slide updated successfully");
}
} catch (error) {
console.error("Error updating slide:", error);
toast.error("Failed to update slide. Please try again.");
} catch (error: any) {
console.error("Error in slide editing:", error);
toast.error("Error in slide editing.", {
description: error.message || "Error in slide editing.",
});
} finally {
setIsUpdating(false);
}
@ -70,8 +72,11 @@ const SlideContent = ({
const onDeleteSlide = async () => {
try {
dispatch(deletePresentationSlide(slide.index));
} catch (error) {
} catch (error: any) {
console.error("Error deleting slide:", error);
toast.error("Error deleting slide.", {
description: error.message || "Error deleting slide.",
});
}
};
// Scroll to the new slide when streaming and new slides are being generated

View file

@ -53,6 +53,7 @@ export const useAutoSave = ({
} catch (error) {
console.error('❌ Auto-save failed:', error);
} finally {
setIsSaving(false);
}

View file

@ -0,0 +1,121 @@
// API Error Response Interface
interface ApiErrorResponse {
detail?: string;
message?: string;
error?: string;
}
// API Response Handler Utility
export class ApiResponseHandler {
static async handleResponse(response: Response, defaultErrorMessage: string): Promise<any> {
// Handle successful responses
if (response.ok) {
// Handle 204 No Content responses
if (response.status === 204) {
return true;
}
// Try to parse JSON response
try {
return await response.json();
} catch (error) {
// If JSON parsing fails but response is ok, return empty object
return {};
}
}
// Handle error responses
let errorMessage = defaultErrorMessage;
try {
const errorData: ApiErrorResponse = await response.json();
// Extract error message in order of preference
if (errorData.detail) {
errorMessage = errorData.detail;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (errorData.error) {
errorMessage = errorData.error;
}
} catch (parseError) {
// If JSON parsing fails, use status-based messages
errorMessage = this.getStatusBasedErrorMessage(response.status, defaultErrorMessage);
}
// Throw error with appropriate message
throw new Error(errorMessage);
}
static async handleResponseWithResult(response: Response, defaultErrorMessage: string): Promise<{success: boolean, message?: string}> {
try {
// Handle successful responses
if (response.ok) {
return { success: true };
}
// Handle error responses
let errorMessage = defaultErrorMessage;
try {
const errorData: ApiErrorResponse = await response.json();
// Extract error message in order of preference
if (errorData.detail) {
errorMessage = errorData.detail;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (errorData.error) {
errorMessage = errorData.error;
}
} catch (parseError) {
// If JSON parsing fails, use status-based messages
errorMessage = this.getStatusBasedErrorMessage(response.status, defaultErrorMessage);
}
return {
success: false,
message: errorMessage,
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : defaultErrorMessage,
};
}
}
private static getStatusBasedErrorMessage(status: number, defaultMessage: string): string {
switch (status) {
case 400:
return "Bad request. Please check your input and try again.";
case 401:
return "Unauthorized. Please log in and try again.";
case 403:
return "Access forbidden. You don't have permission to perform this action.";
case 404:
return "Resource not found. The requested item may have been deleted or moved.";
case 409:
return "Conflict. The resource already exists or there's a conflict with the current state.";
case 422:
return "Validation error. Please check your input and try again.";
case 429:
return "Too many requests. Please wait a moment and try again.";
case 500:
return "Internal server error. Please try again later.";
case 502:
return "Bad gateway. The server is temporarily unavailable.";
case 503:
return "Service unavailable. Please try again later.";
case 504:
return "Gateway timeout. The request took too long to process.";
default:
return defaultMessage;
}
}
}
export type { ApiErrorResponse };

View file

@ -1,5 +1,7 @@
import { getHeader, getHeaderForFormData } from "./header";
import { IconSearch, ImageGenerate, ImageSearch, PreviousGeneratedImagesResponse } from "./params";
import { ApiResponseHandler } from "./api-error-handler";
export class PresentationGenerationApi {
static async uploadDoc(documents: File[]) {
const formData = new FormData();
@ -19,20 +21,13 @@ export class PresentationGenerationApi {
}
);
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
const data = await response.json();
return data;
return await ApiResponseHandler.handleResponse(response, "Failed to upload documents");
} catch (error) {
console.error("Upload error:", error);
throw error;
}
}
static async decomposeDocuments(documentKeys: string[]) {
try {
const response = await fetch(
@ -46,28 +41,53 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.status === 200) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to decompose files: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to decompose documents");
} catch (error) {
console.error("Error in Decompose Files", error);
throw error;
}
}
static async createPresentation({
prompt,
n_slides,
file_paths,
language,
}: {
prompt: string;
n_slides: number | null;
file_paths?: string[];
language: string | null;
}) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/create`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify({
prompt,
n_slides,
file_paths,
language,
}),
cache: "no-cache",
}
);
return await ApiResponseHandler.handleResponse(response, "Failed to create presentation");
} catch (error) {
console.error("error in presentation creation", error);
throw error;
}
}
static async editSlide(
slide_id: string,
prompt: string
) {
try {
const response = await fetch(
`/api/v1/ppt/slide/edit`,
{
@ -76,18 +96,12 @@ export class PresentationGenerationApi {
body: JSON.stringify({
id: slide_id,
prompt,
}),
cache: "no-cache",
}
);
if (!response.ok) {
throw new Error("Failed to update slides");
}
const data = await response.json();
return data;
return await ApiResponseHandler.handleResponse(response, "Failed to update slide");
} catch (error) {
console.error("error in slide update", error);
throw error;
@ -105,15 +119,8 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(
`Failed to update presentation content: ${response.statusText}`
);
}
return await ApiResponseHandler.handleResponse(response, "Failed to update presentation content");
} catch (error) {
console.error("error in presentation content update", error);
throw error;
@ -131,41 +138,17 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to generate data: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to prepare presentation");
} catch (error) {
console.error("error in data generation", error);
throw error;
}
}
// IMAGE AND ICON SEARCH
static async imageSearch(imageSearch: ImageSearch) {
try {
const response = await fetch(
`/api/v1/ppt/image/search`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify(imageSearch),
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to search images: ${response.statusText}`);
}
} catch (error) {
console.error("error in image search", error);
throw error;
}
}
static async generateImage(imageGenerate: ImageGenerate) {
try {
const response = await fetch(
@ -176,20 +159,15 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to generate images: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to generate image");
} catch (error) {
console.error("error in image generation", error);
throw error;
}
}
static getPreviousGeneratedImages = async():Promise<PreviousGeneratedImagesResponse[]>=>{
static getPreviousGeneratedImages = async (): Promise<PreviousGeneratedImagesResponse[]> => {
try {
const response = await fetch(
`/api/v1/ppt/images/generated`,
@ -198,17 +176,14 @@ export class PresentationGenerationApi {
headers: getHeader(),
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to get previous generated images: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to get previous generated images");
} catch (error) {
console.error("error in getting previous generated images", error);
throw error;
}
}
static async searchIcons(iconSearch: IconSearch) {
try {
const response = await fetch(
@ -219,41 +194,15 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to search icons: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to search icons");
} catch (error) {
console.error("error in icon search", error);
throw error;
}
}
static async updateDocuments(body: any) {
try {
const response = await fetch(
`/api/v1/ppt/document/update`,
{
method: "POST",
headers: getHeaderForFormData(),
body: body,
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to update documents: ${response.statusText}`);
}
} catch (error) {
console.error("error in document update", error);
throw error;
}
}
// EXPORT PRESENTATION
static async exportAsPPTX(presentationData: any) {
@ -267,100 +216,13 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
return await response.json();
} else {
throw new Error(`Failed to export as pptx: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to export as PowerPoint");
} catch (error) {
console.error("error in pptx export", error);
throw error;
}
}
static async exportAsPDF(presentationData: any) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/export_as_pdf`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify(presentationData),
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to export as pdf: ${response.statusText}`);
}
} catch (error) {
console.error("error in pdf export", error);
throw error;
}
}
static async deleteSlide(presentation_id: string, slide_id: string) {
try {
const response = await fetch(
`/api/v1/ppt/slide/delete?presentation_id=${presentation_id}&slide_id=${slide_id}`,
{
method: "DELETE",
headers: getHeader(),
cache: "no-cache",
}
);
if (response.status === 204) {
return true;
} else {
throw new Error(`Failed to delete slide: ${response.statusText}`);
}
} catch (error) {
console.error("error in slide deletion", error);
throw error;
}
}
static async createPresentation({
prompt,
n_slides,
file_paths,
language,
}: {
prompt: string;
n_slides: number | null;
file_paths?: string[];
language: string | null;
}) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/create`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify({
prompt,
n_slides,
file_paths,
language,
}),
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to get questions: ${response.statusText}`);
}
} catch (error) {
console.error("error in question generation", error);
throw error;
}
}
}
}

View file

@ -162,7 +162,7 @@ const UploadPage = () => {
* Handles errors during presentation generation
*/
const handleGenerationError = (error: any) => {
console.error("Error in presentation generation:", error);
console.error("Error in upload page", error);
setLoadingState({
isLoading: false,
message: "",
@ -170,7 +170,7 @@ const UploadPage = () => {
showProgress: false,
});
toast.error("Error", {
description: "Failed to generate presentation. Please try again.",
description: error.message || "Error in upload page.",
});
};

View file

@ -1,8 +1,7 @@
import {
getHeader,
} from "@/app/(presentation-generator)/services/api/header";
import { ApiResponseHandler } from "@/app/(presentation-generator)/services/api/api-error-handler";
export interface PresentationResponse {
id: string;
@ -33,38 +32,36 @@ export class DashboardApi {
method: "GET",
}
);
if (response.status === 200) {
const data = await response.json();
return data;
} else if (response.status === 404) {
// Handle the special case where 404 means "no presentations found"
if (response.status === 404) {
console.log("No presentations found");
return [];
}
return [];
return await ApiResponseHandler.handleResponse(response, "Failed to fetch presentations");
} catch (error) {
console.error("Error fetching presentations:", error);
throw error;
}
}
static async getPresentation(id: string) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/?id=${id}`,
{
method: "GET",
}
);
if (response.status === 200) {
const data = await response.json();
return data;
}
throw new Error("Presentation not found");
return await ApiResponseHandler.handleResponse(response, "Presentation not found");
} catch (error) {
console.error("Error fetching presentations:", error);
console.error("Error fetching presentation:", error);
throw error;
}
}
static async deletePresentation(presentation_id: string) {
try {
const response = await fetch(
@ -74,17 +71,8 @@ export class DashboardApi {
headers: getHeader(),
}
);
const data = await response.json();
if (response.status === 204) {
return {
success: true,
};
}
return {
success: false,
message: data.detail || "Failed to delete presentation",
};
return await ApiResponseHandler.handleResponseWithResult(response, "Failed to delete presentation");
} catch (error) {
console.error("Error deleting presentation:", error);
return {
@ -93,5 +81,4 @@ export class DashboardApi {
};
}
}
}

View file

@ -4,13 +4,13 @@ import * as z from "zod";
export const layoutId = "intro-pitchdeck-slide";
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.";
"A visually appealing introduction slide for a pitch deck, featuring a large title, company name, date, and contact information with a modern design. This Slide is always the first slide in a pitch deck, setting the tone for the presentation with a clean and professional look.";
const introPitchDeckSchema = z.object({
title: z.string().min(2).max(15).default("Pitch Deck").meta({
description: "Main title of the slide",
}),
description: z.string().default("").meta({
description: "Empty description as per the design",
description: "Description as per the design",
}),
contactNumber: z.string().default("+123-456-7890").meta({
description: "Contact phone number displayed in footer",

View file

@ -13,8 +13,8 @@ const aboutCompanySlideSchema = z.object({
}),
content: z
.string()
.min(50)
.max(500)
.min(25)
.max(400)
.default(
"In the presentation session, the background/introduction can be filled with information that is arranged systematically and effectively with respect to an interesting topic to be used as material for discussion at the opening of the presentation session. The introduction can provide a general overview for those who are listening to your presentation so that the key words on the topic of discussion are emphasized during this background/introductory presentation session.",
)
@ -25,7 +25,7 @@ const aboutCompanySlideSchema = z.object({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Date displayed in header",
description: "Today Date displayed in header",
}),
image: ImageSchema.optional().meta({
description:
@ -53,7 +53,7 @@ const AboutCompanySlideLayout: React.FC<AboutCompanySlideLayoutProps> = ({
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden"
className="w-full rounded-sm max-w-[1280px] shadow-lg aspect-video bg-white relative z-20 mx-auto overflow-hidden"
style={{
fontFamily: "Montserrat, sans-serif",
}}

View file

@ -83,11 +83,11 @@ const problemStatementSlideSchema = z.object({
description:
"List of problem categories with titles, descriptions, and optional icons",
}),
companyName: z.string().min(2).max(50).default("Rimberio").meta({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Date displayed in header",
description: "Today Date displayed in header",
}),
});

View file

@ -11,8 +11,8 @@ const solutionSlideSchema = z.object({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Date displayed in header",
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Today Date displayed in header",
}),
title: z.string().min(3).max(25).default("Businesses struggle").meta({
description: "Main title of the slide",
@ -33,7 +33,7 @@ const solutionSlideSchema = z.object({
title: z.string().min(3).max(30).meta({
description: "Section title",
}),
description: z.string().min(5).max(80).meta({
description: z.string().min(5).max(70).meta({
description: "Section description",
}),
icon: IconSchema.optional().meta({
@ -46,7 +46,8 @@ const solutionSlideSchema = z.object({
.default([
{
title: "Market",
description: "Innovative and widely accepted.",
description:
"Innovative and widely accepted. Innovative and widely accepted. Innovative and widely accepted.",
icon: {
__icon_query__: "market innovation",
__icon_url__:
@ -109,7 +110,7 @@ const SolutionSlideLayout: React.FC<SolutionSlideLayoutProps> = ({
/>
<div
className="w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white relative z-20 mx-auto overflow-hidden border-2 border-gray-800"
className="w-full rounded-sm max-w-[1280px] shadow-lg aspect-video bg-white relative z-20 mx-auto overflow-hidden border-2 border-gray-800"
style={{
fontFamily: "Montserrat, sans-serif",
}}
@ -132,11 +133,11 @@ const SolutionSlideLayout: React.FC<SolutionSlideLayoutProps> = ({
</p>
</div>
{/* Four Small Boxes in a Row */}
<div className="grid grid-cols-2 gap-4 w-full max-w-5xl">
<div className="grid grid-cols-2 gap-4 w-full ">
{sections.map((section, idx) => (
<div
key={idx}
className="flex flex-col items-center text-center bg-[#F5F8FE] rounded-lg shadow px-3 py-4 min-h-[140px] max-h-[160px]"
className="flex flex-col items-center text-center bg-[#F5F8FE] rounded-lg shadow px-3 py-4 "
>
<div className="mb-2">
{section?.icon?.__icon_url__ && (

View file

@ -12,7 +12,7 @@ const productOverviewSlideSchema = z.object({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Date displayed in header",
description: "Today Date displayed in header",
}),
title: z.string().min(3).max(40).default("Product Overview").meta({
description: "Main title of the slide",

View file

@ -11,11 +11,11 @@ const marketSizeSlideSchema = z.object({
title: z.string().min(3).max(15).default("Market Size").meta({
description: "Main slide title",
}),
companyName: z.string().min(3).max(30).default("Rimberio").meta({
description: "Presenter's name",
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().default("June 13, 2038").meta({
description: "Presentation date",
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Today Date displayed in header",
}),
mapImage: ImageSchema.default({
__image_url__:
@ -51,7 +51,11 @@ const marketSizeSlideSchema = z.object({
description:
"The SOM is a smaller fraction of the SAM that is the target of a serviceable and realistically achievable market in the short to medium term.",
},
]),
])
.meta({
description:
"Market statistics including TAM, SAM, and SOM with labels, values, and descriptions.",
}),
description: z
.string()
.default(

View file

@ -14,10 +14,10 @@ export const layoutDescription =
// Make the schema generic: allow any label/value pairs for comparison
const marketValidationSchema = z.object({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name in header",
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Date in header",
description: "Today Date displayed in header",
}),
title: z.string().min(3).max(20).default("Market Validation").meta({
description: "Title of the slide",
@ -58,7 +58,8 @@ const marketValidationSchema = z.object({
{ label: "Liceria & Co.", metricLabel: "Revenue ($K)", value: 1010 },
])
.meta({
description: "Market benchmark data (generic metric)",
description:
"Comparison data for market validation, allowing flexible labels and values",
}),
image: ImageSchema.optional().meta({
description: "Optional decorative image",

View file

@ -16,24 +16,38 @@ export const layoutName = "Company Traction Slide";
export const layoutDescription =
"A slide layout designed to present company traction data, including growth statistics over the years, a chart visualization, and key metrics in a visually appealing format.";
const growthStatsSchema = z.object({
year: z.string(),
})
.catchall(z.number());
const growthStatsSchema = z
.object({
year: z.string(),
})
.catchall(z.number())
.meta({
description:
"Growth statistics for a specific year, with any number of metrics as key-value pairs where keys are metric names and values are numbers.",
});
// growthStats: list of dicts, each dict is { year: string, <metric1>: number, <metric2>: number, ... }
const tractionSchema = z.object({
companyName: z.string().default("presention"),
date: z.string().default("June 13, 2038"),
title: z.string().default("Company Traction"),
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Today Date displayed in header",
}),
title: z.string().default("Company Traction").meta({
description: "Main title of the slide",
}),
description: z
.string()
.min(3)
.max(200)
.default(
"Traction is a period where the company is feeling momentum during its development period. If traction momentum is not harnessed, sales figures can decline and the customer base can shrink. In general, companies will judge success by the amount of revenue and new customers they receive.",
),
)
.meta({
description:
"Main content text describing the company's traction and growth momentum.",
}),
// growthStats is a list of objects, each with a 'year' and any number of metric keys (all numbers)
growthStats: z
.array(growthStatsSchema)
@ -88,7 +102,11 @@ const tractionSchema = z.object({
internetOfThings: 65,
others: 52,
}),
]),
])
.meta({
description:
"Growth statistics for the company, used for chart visualization. Each entry is an object representing a specific year, with the 'year' key as a string (e.g., '2020'), and additional keys for each metric (such as 'artificialIntelligence', 'internetOfThings', 'others'), where the values are numbers representing the metric's value for that year. Example:\n\n[\n { year: '2020', artificialIntelligence: 5, internetOfThings: 10, others: 8 },\n { year: '2021', artificialIntelligence: 10, internetOfThings: 20, others: 15 },\n ...\n]\nThis structure allows the chart to dynamically render multiple series over time, with each metric visualized as a separate line.",
}),
});
export const Schema = tractionSchema;

View file

@ -17,14 +17,22 @@ export const layoutDescription =
"A business model presentation slide displaying CAC metrics and monetization strategy.";
const businessModelSchema = z.object({
companyName: z.string().default("presenton"),
date: z.string().default("June 13, 2038"),
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Today Date displayed in header",
}),
title: z.string().min(3).max(20).default("Business Model"),
description: z
.string()
.default(
"Describe how you monetize, who your customers are, your distribution channels or fee structure. The goal is to give an idea of how this business will sustain your product or service and explain how your company will make money and achieve its goals. This can be shown with graphs, statistics, or charts. Use the Lifetime Value (LTV) and Customer Acquisition Cost (CAC) metrics to provide a clearer picture.",
),
)
.meta({
description:
"Description of the business model, monetization strategy, and customer acquisition costs.",
}),
cacChart: z
.array(
z.object({
@ -32,10 +40,19 @@ const businessModelSchema = z.object({
percentage: z.number().min(0).max(100),
}),
)
.min(2)
.max(5)
.default([
{ label: "Internet of Things", percentage: 70 },
{ label: "Artificial Intelligence", percentage: 60 },
]),
{ label: "Blockchain", percentage: 50 },
{ label: "Cloud Computing", percentage: 40 },
{ label: "Cybersecurity", percentage: 30 },
])
.meta({
description:
"Array of objects representing Customer Acquisition Cost (CAC) metrics for different business segments or channels. Each object should include a 'label' (the name of the segment or channel) and a 'percentage' (the CAC as a percentage value, from 0 to 100). This data is visualized in the bar chart to illustrate the distribution of CAC across various categories.",
}),
});
export const Schema = businessModelSchema;
@ -50,9 +67,9 @@ const BusinessModelSlide: React.FC<Props> = ({ data }) => {
data?.cacChart && Array.isArray(data.cacChart) && data.cacChart.length > 0
? data.cacChart
: [
{ label: "Internet of Things", percentage: 70 },
{ label: "Artificial Intelligence", percentage: 60 },
];
{ label: "Internet of Things", percentage: 70 },
{ label: "Artificial Intelligence", percentage: 60 },
];
return (
<>

View file

@ -24,7 +24,7 @@ const teamMemberSchema = z.object({
});
const modernTeamSlideSchema = z.object({
title: z.string().min(3).max(20).default("Our Team").meta({
title: z.string().min(3).max(15).default("Our Team").meta({
description: "Main title of the slide",
}),
subtitle: z.string().min(10).max(120).optional().meta({
@ -83,11 +83,11 @@ const modernTeamSlideSchema = z.object({
.meta({
description: "List of team members with their information",
}),
companyName: z.string().default("presenton").meta({
description: "Company name to display in the header",
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().default("June 13, 2038").meta({
description: "Date to display in the header",
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Today Date displayed in header",
}),
});

View file

@ -4,20 +4,20 @@ import * as z from "zod";
export const layoutId = "thank-you-slide";
export const layoutName = "Thank You Slide";
export const layoutDescription =
"A simple, plain thank you slide for closing presentations.";
"A simple, plain thank you slide for closing presentations. This is always the last slide in a presentation, providing a clean and professional closing message with company contact information.";
const thankYouSlideSchema = z.object({
title: z.string().min(3).max(40).default("Thank You!").meta({
title: z.string().min(3).max(30).default("Thank You!").meta({
description: "Main thank you message",
}),
subtitle: z.string().min(0).max(100).default("").meta({
description: "Optional subtitle or closing remark",
}),
companyName: z.string().min(2).max(30).default("Rimberio").meta({
companyName: z.string().min(2).max(50).default("presenton").meta({
description: "Company name displayed in header",
}),
date: z.string().min(5).max(30).default("June 13, 2038").meta({
description: "Date displayed in header",
date: z.string().min(5).max(50).default("June 13, 2038").meta({
description: "Today Date displayed in header",
}),
address: z
.string()
@ -72,10 +72,7 @@ const ThankYouSlideLayout: React.FC<ThankYouSlideLayoutProps> = ({ data }) => {
<div className="flex flex-1 flex-col px-16 pb-16 justify-between">
{/* Thank You and description */}
<div className="flex flex-col items-start w-full pt-16">
<h1
className="font-bold text-8xl text-white mb-6 mt-8 text-left w-full"
>
<h1 className="font-bold text-8xl text-white mb-6 mt-8 text-left w-full">
{data?.title || "Thank You!"}
</h1>
{data?.subtitle && (
@ -83,7 +80,6 @@ const ThankYouSlideLayout: React.FC<ThankYouSlideLayoutProps> = ({ data }) => {
{data.subtitle}
</div>
)}
</div>
{/* Footer area */}