Powerpoint_AI / lib /generated-presentation.ts
Reubencf's picture
Readme updated
45b3fab
/*
* generated-presentation.ts
* Purpose: Defines the typed shape of generated presentation responses and helpers for normalizing them into SlideSpec data.
* Used by: GoogleSlidesEditor and generation API consumers.
* Depends on: SlideSpec types and slide-prompt layout normalization.
*/
import type { SlideSpec } from '@/data/templates';
import { normalizeLayout } from '@/lib/slide-prompt';
export interface PresentationApiColumn {
heading?: string;
text?: string;
}
export interface PresentationApiSlide {
id?: string;
title?: string;
subtitle?: string;
content?: string | string[];
columns?: PresentationApiColumn[];
imageUrl?: string;
imageKeyword?: string;
layout?: string;
}
export interface PresentationGenerateSuccessResponse {
presentationName?: string;
slides: PresentationApiSlide[];
generationSource: 'model';
}
export interface PresentationGenerateErrorResponse {
error: string;
details?: string;
}
function toStringArray(value: unknown): string[] {
if (Array.isArray(value)) {
return value
.filter((entry): entry is string => typeof entry === 'string')
.map((entry) => entry.trim())
.filter(Boolean);
}
if (typeof value === 'string' && value.trim()) {
return [value.trim()];
}
return [];
}
function normalizeColumns(
value: unknown,
content: unknown
): Array<{ heading: string; text: string }> | undefined {
if (Array.isArray(value) && value.length > 0) {
const columns = value
.map((entry, index) => {
if (!entry || typeof entry !== 'object') return null;
const headingValue = (entry as { heading?: unknown }).heading;
const textValue = (entry as { text?: unknown }).text;
const heading = typeof headingValue === 'string' && headingValue.trim()
? headingValue.trim()
: `Column ${index + 1}`;
const text = typeof textValue === 'string' ? textValue.trim() : '';
if (!heading && !text) return null;
return { heading, text };
})
.filter((entry): entry is { heading: string; text: string } => Boolean(entry))
.slice(0, 3);
if (columns.length > 0) {
return columns;
}
}
const contentItems = toStringArray(content).slice(0, 3);
if (!contentItems.length) return undefined;
return contentItems.map((text, index) => ({
heading: `Pillar ${index + 1}`,
text,
}));
}
export function getPresentationSlideContentLines(content: unknown): string[] {
return toStringArray(content);
}
export function mapPresentationSlidesToSlideSpecs(
slides: PresentationApiSlide[],
templateId: string
): SlideSpec[] {
const totalSlides = slides.length;
return slides.map((slide, index) => {
const layout = normalizeLayout(typeof slide.layout === 'string' ? slide.layout : '', index, totalSlides);
const contentLines = getPresentationSlideContentLines(slide.content);
const subtitle = typeof slide.subtitle === 'string' && slide.subtitle.trim()
? slide.subtitle.trim()
: ((layout === 'title_subtitle' || layout === 'thank_you') ? contentLines[0] : undefined);
return {
id: typeof slide.id === 'string' && slide.id.trim() ? slide.id : `slide-${index + 1}`,
templateId,
layout,
title: typeof slide.title === 'string' && slide.title.trim() ? slide.title.trim() : `Slide ${index + 1}`,
subtitle,
body: layout === 'title_and_text' || layout === 'image_and_text'
? (contentLines.length ? [{ text: contentLines.join(' ') }] : undefined)
: undefined,
items: layout === 'agenda' || layout === 'references'
? contentLines.map((text) => ({ text }))
: undefined,
columns: layout === 'three_columns'
? normalizeColumns(slide.columns, slide.content)
: undefined,
imageUrl: typeof slide.imageUrl === 'string' && slide.imageUrl.trim() ? slide.imageUrl : undefined,
};
});
}
export function isPresentationGenerateSuccessResponse(
value: unknown
): value is PresentationGenerateSuccessResponse {
if (!value || typeof value !== 'object') return false;
const candidate = value as Partial<PresentationGenerateSuccessResponse>;
return candidate.generationSource === 'model' && Array.isArray(candidate.slides);
}
export function getPresentationGenerateErrorMessage(value: unknown): string {
if (!value || typeof value !== 'object') {
return 'Failed to generate slides.';
}
const candidate = value as PresentationGenerateErrorResponse;
if (typeof candidate.details === 'string' && candidate.details.trim()) {
return candidate.details;
}
if (typeof candidate.error === 'string' && candidate.error.trim()) {
return candidate.error;
}
return 'Failed to generate slides.';
}