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