/* * 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; 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.'; }