File size: 4,748 Bytes
45b3fab
 
 
 
 
 
 
1eb0838
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
 * 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.';
}