@flamingo-stack/openframe-frontend-core 0.0.542 → 0.0.543-1802.4770.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/{chunk-2GRFWONP.js → chunk-3L5L67V3.js} +2 -2
  2. package/dist/{chunk-4JDQXYFI.cjs → chunk-6NY7TKLY.cjs} +14 -14
  3. package/dist/{chunk-4JDQXYFI.cjs.map → chunk-6NY7TKLY.cjs.map} +1 -1
  4. package/dist/{chunk-FLSI72M2.cjs → chunk-6R4JGGT5.cjs} +3 -3
  5. package/dist/{chunk-FLSI72M2.cjs.map → chunk-6R4JGGT5.cjs.map} +1 -1
  6. package/dist/{chunk-LOSJMXB7.cjs → chunk-A33QVXOY.cjs} +9 -9
  7. package/dist/{chunk-LOSJMXB7.cjs.map → chunk-A33QVXOY.cjs.map} +1 -1
  8. package/dist/{chunk-J2Z53KRG.js → chunk-BBPVOSGN.js} +2 -2
  9. package/dist/{chunk-WNNZFBMS.js → chunk-F2TXZCTG.js} +6 -4
  10. package/dist/{chunk-WNNZFBMS.js.map → chunk-F2TXZCTG.js.map} +1 -1
  11. package/dist/{chunk-DMYAVVD7.cjs → chunk-FPBFFSJA.cjs} +7 -7
  12. package/dist/{chunk-DMYAVVD7.cjs.map → chunk-FPBFFSJA.cjs.map} +1 -1
  13. package/dist/{chunk-JIAZRPQG.cjs → chunk-G6C56Z3W.cjs} +11 -11
  14. package/dist/{chunk-JIAZRPQG.cjs.map → chunk-G6C56Z3W.cjs.map} +1 -1
  15. package/dist/{chunk-FMH3YY4H.js → chunk-HCRASVSB.js} +2 -2
  16. package/dist/{chunk-QW36SYRQ.js → chunk-HGB574AT.js} +2 -2
  17. package/dist/{chunk-Y22S7NE3.js → chunk-HRRA45HV.js} +9 -7
  18. package/dist/chunk-HRRA45HV.js.map +1 -0
  19. package/dist/{chunk-4IOPSFHF.cjs → chunk-JQUUI5CU.cjs} +37 -37
  20. package/dist/{chunk-4IOPSFHF.cjs.map → chunk-JQUUI5CU.cjs.map} +1 -1
  21. package/dist/{chunk-DVBUNH46.cjs → chunk-K47OETPN.cjs} +5 -5
  22. package/dist/{chunk-DVBUNH46.cjs.map → chunk-K47OETPN.cjs.map} +1 -1
  23. package/dist/{chunk-BETH6QZC.cjs → chunk-MAEVGBN2.cjs} +75 -73
  24. package/dist/chunk-MAEVGBN2.cjs.map +1 -0
  25. package/dist/{chunk-VP4ZY2RA.js → chunk-NAZXVYFV.js} +2169 -2158
  26. package/dist/chunk-NAZXVYFV.js.map +1 -0
  27. package/dist/{chunk-PZGOK2OC.js → chunk-NKX7NWLJ.js} +2 -2
  28. package/dist/{chunk-OLUROGWG.cjs → chunk-PDIV4BY6.cjs} +31 -31
  29. package/dist/{chunk-OLUROGWG.cjs.map → chunk-PDIV4BY6.cjs.map} +1 -1
  30. package/dist/{chunk-AKZY45LV.js → chunk-PPRV3CSO.js} +2 -2
  31. package/dist/{chunk-FT5PZQVK.js → chunk-QY77HY3A.js} +5 -5
  32. package/dist/{chunk-UL5EGPMV.js → chunk-R4YDVJMY.js} +4 -4
  33. package/dist/{chunk-F3GGE6DI.cjs → chunk-RBHKEA2G.cjs} +87 -87
  34. package/dist/{chunk-F3GGE6DI.cjs.map → chunk-RBHKEA2G.cjs.map} +1 -1
  35. package/dist/{chunk-SEAKHM72.js → chunk-RHIN6A3M.js} +2 -2
  36. package/dist/{chunk-QDO7Y54S.js → chunk-RHNEWFVD.js} +2 -2
  37. package/dist/{chunk-CE2GROZZ.cjs → chunk-TTFBD45B.cjs} +641 -630
  38. package/dist/chunk-TTFBD45B.cjs.map +1 -0
  39. package/dist/{chunk-QEXFM2QX.js → chunk-VLSRNHHN.js} +2 -2
  40. package/dist/{chunk-T4O3AEKV.cjs → chunk-YEU5XUMC.cjs} +26 -26
  41. package/dist/{chunk-T4O3AEKV.cjs.map → chunk-YEU5XUMC.cjs.map} +1 -1
  42. package/dist/{chunk-WFG72MKN.cjs → chunk-YNBYIMZB.cjs} +28 -26
  43. package/dist/chunk-YNBYIMZB.cjs.map +1 -0
  44. package/dist/{chunk-IRZHAWRW.cjs → chunk-YV5LPROO.cjs} +4 -4
  45. package/dist/{chunk-IRZHAWRW.cjs.map → chunk-YV5LPROO.cjs.map} +1 -1
  46. package/dist/components/case-studies/index.cjs +8 -8
  47. package/dist/components/case-studies/index.js +2 -2
  48. package/dist/components/chat/index.cjs +2 -2
  49. package/dist/components/chat/index.js +1 -1
  50. package/dist/components/chat/types/entities/onboarding-guide.d.ts +1 -0
  51. package/dist/components/chat/types/entities/onboarding-guide.d.ts.map +1 -1
  52. package/dist/components/contact/index.cjs +3 -3
  53. package/dist/components/contact/index.js +2 -2
  54. package/dist/components/docs/index.cjs +5 -5
  55. package/dist/components/docs/index.js +4 -4
  56. package/dist/components/embeds/index.cjs +3 -3
  57. package/dist/components/embeds/index.js +2 -2
  58. package/dist/components/faq/index.cjs +3 -3
  59. package/dist/components/faq/index.js +2 -2
  60. package/dist/components/features/captions-url.cjs +3 -2
  61. package/dist/components/features/captions-url.cjs.map +1 -1
  62. package/dist/components/features/captions-url.d.ts +8 -1
  63. package/dist/components/features/captions-url.d.ts.map +1 -1
  64. package/dist/components/features/captions-url.js +3 -2
  65. package/dist/components/features/captions-url.js.map +1 -1
  66. package/dist/components/features/entity-video-section.d.ts +7 -1
  67. package/dist/components/features/entity-video-section.d.ts.map +1 -1
  68. package/dist/components/features/highlight-config-section.d.ts +4 -6
  69. package/dist/components/features/highlight-config-section.d.ts.map +1 -1
  70. package/dist/components/features/highlight-video-combined-section.d.ts +7 -5
  71. package/dist/components/features/highlight-video-combined-section.d.ts.map +1 -1
  72. package/dist/components/features/highlight-video-section.d.ts +1 -5
  73. package/dist/components/features/highlight-video-section.d.ts.map +1 -1
  74. package/dist/components/features/index.cjs +4 -2
  75. package/dist/components/features/index.cjs.map +1 -1
  76. package/dist/components/features/index.d.ts +1 -0
  77. package/dist/components/features/index.d.ts.map +1 -1
  78. package/dist/components/features/index.js +3 -1
  79. package/dist/components/features/subtitles-editor.d.ts +35 -0
  80. package/dist/components/features/subtitles-editor.d.ts.map +1 -0
  81. package/dist/components/features/transcript-summary-editor.d.ts.map +1 -1
  82. package/dist/components/help-center-pages/index.cjs +22 -22
  83. package/dist/components/help-center-pages/index.js +13 -13
  84. package/dist/components/index.cjs +134 -132
  85. package/dist/components/index.cjs.map +1 -1
  86. package/dist/components/index.js +13 -11
  87. package/dist/components/index.js.map +1 -1
  88. package/dist/components/meeting-scheduler/index.cjs +34 -34
  89. package/dist/components/meeting-scheduler/index.js +3 -3
  90. package/dist/components/navigation/index.cjs +2 -2
  91. package/dist/components/navigation/index.js +1 -1
  92. package/dist/components/onboarding-guides/index.cjs +5 -5
  93. package/dist/components/onboarding-guides/index.js +4 -4
  94. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  95. package/dist/components/related-content/index.cjs +3 -3
  96. package/dist/components/related-content/index.js +2 -2
  97. package/dist/components/shared/product-release/release-detail-page.d.ts +2 -0
  98. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  99. package/dist/components/tickets/index.cjs +6 -6
  100. package/dist/components/tickets/index.js +5 -5
  101. package/dist/components/ui/index.cjs +2 -2
  102. package/dist/components/ui/index.js +1 -1
  103. package/dist/index.cjs +4 -2
  104. package/dist/index.cjs.map +1 -1
  105. package/dist/index.js +3 -1
  106. package/dist/types/case-study.d.ts +2 -0
  107. package/dist/types/case-study.d.ts.map +1 -1
  108. package/dist/types/customer-interview.d.ts +2 -2
  109. package/dist/types/customer-interview.d.ts.map +1 -1
  110. package/dist/types/index.cjs.map +1 -1
  111. package/dist/types/index.js.map +1 -1
  112. package/dist/types/product-release.d.ts +2 -0
  113. package/dist/types/product-release.d.ts.map +1 -1
  114. package/dist/types/supabase.d.ts +3 -0
  115. package/dist/types/supabase.d.ts.map +1 -1
  116. package/dist/utils/index.cjs.map +1 -1
  117. package/dist/utils/index.js.map +1 -1
  118. package/package.json +1 -1
  119. package/src/components/chat/types/entities/onboarding-guide.ts +1 -0
  120. package/src/components/features/captions-url.ts +10 -1
  121. package/src/components/features/entity-video-section.tsx +9 -0
  122. package/src/components/features/highlight-config-section.tsx +3 -20
  123. package/src/components/features/highlight-video-combined-section.tsx +25 -8
  124. package/src/components/features/highlight-video-section.tsx +0 -19
  125. package/src/components/features/index.ts +1 -0
  126. package/src/components/features/subtitles-editor.tsx +87 -0
  127. package/src/components/features/transcript-summary-editor.tsx +14 -33
  128. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +2 -0
  129. package/src/components/shared/product-release/release-detail-page.tsx +4 -0
  130. package/src/types/case-study.ts +2 -0
  131. package/src/types/customer-interview.ts +2 -2
  132. package/src/types/product-release.ts +2 -0
  133. package/src/types/supabase.ts +3 -0
  134. package/dist/chunk-BETH6QZC.cjs.map +0 -1
  135. package/dist/chunk-CE2GROZZ.cjs.map +0 -1
  136. package/dist/chunk-VP4ZY2RA.js.map +0 -1
  137. package/dist/chunk-WFG72MKN.cjs.map +0 -1
  138. package/dist/chunk-Y22S7NE3.js.map +0 -1
  139. /package/dist/{chunk-2GRFWONP.js.map → chunk-3L5L67V3.js.map} +0 -0
  140. /package/dist/{chunk-J2Z53KRG.js.map → chunk-BBPVOSGN.js.map} +0 -0
  141. /package/dist/{chunk-FMH3YY4H.js.map → chunk-HCRASVSB.js.map} +0 -0
  142. /package/dist/{chunk-QW36SYRQ.js.map → chunk-HGB574AT.js.map} +0 -0
  143. /package/dist/{chunk-PZGOK2OC.js.map → chunk-NKX7NWLJ.js.map} +0 -0
  144. /package/dist/{chunk-AKZY45LV.js.map → chunk-PPRV3CSO.js.map} +0 -0
  145. /package/dist/{chunk-FT5PZQVK.js.map → chunk-QY77HY3A.js.map} +0 -0
  146. /package/dist/{chunk-UL5EGPMV.js.map → chunk-R4YDVJMY.js.map} +0 -0
  147. /package/dist/{chunk-SEAKHM72.js.map → chunk-RHIN6A3M.js.map} +0 -0
  148. /package/dist/{chunk-QDO7Y54S.js.map → chunk-RHNEWFVD.js.map} +0 -0
  149. /package/dist/{chunk-QEXFM2QX.js.map → chunk-VLSRNHHN.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.542",
3
+ "version": "0.0.543-1802.4770.1",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -36,6 +36,7 @@ export interface OnboardingGuide {
36
36
  main_video_thumbnail: string | null;
37
37
  youtube_url: string | null;
38
38
  highlight_video_url: string | null;
39
+ highlight_srt_content?: string | null;
39
40
  highlight_video_thumbnail: string | null;
40
41
  highlight_video_duration_ms: number | null;
41
42
  highlight_video_source: string | null;
@@ -18,8 +18,17 @@ export function getCaptionsUrl(
18
18
  entityType: string,
19
19
  entityId: string | number,
20
20
  srtContent?: string | null,
21
+ options?: {
22
+ /**
23
+ * Which SRT column the endpoint should serve. `'highlight'` targets the
24
+ * entity's highlight_srt_content (AI highlight reel captions); omitted
25
+ * means the main video's srt_content.
26
+ */
27
+ variant?: 'highlight'
28
+ },
21
29
  ): string | undefined {
22
30
  if (!srtContent) return undefined
23
31
  const hash = `${srtContent.length}-${srtContent.slice(0, 8).replace(/\s/g, '')}`
24
- return `/api/captions/${entityType}/${entityId}?v=${hash}`
32
+ const variant = options?.variant ? `&variant=${options.variant}` : ''
33
+ return `/api/captions/${entityType}/${entityId}?v=${hash}${variant}`
25
34
  }
@@ -71,6 +71,12 @@ export interface EntityVideoSectionProps {
71
71
  srtContent?: string | null;
72
72
  /** HTTPS URL to a VTT captions file (rendered as native `<track>`). */
73
73
  captionsUrl?: string | null;
74
+ /**
75
+ * HTTPS URL to the HIGHLIGHT video's VTT captions file — same `<track>`
76
+ * mechanism as `captionsUrl`, applied to the Highlights tab / highlight-only
77
+ * player (`getCaptionsUrl(type, id, highlightSrt, { variant: 'highlight' })`).
78
+ */
79
+ highlightCaptionsUrl?: string | null;
74
80
  /** LCP hint — when true, the full-video tab's poster eager-loads. */
75
81
  priority?: boolean;
76
82
  }
@@ -94,6 +100,7 @@ export function EntityVideoSection({
94
100
  MarkdownRenderer,
95
101
  srtContent,
96
102
  captionsUrl,
103
+ highlightCaptionsUrl,
97
104
  priority = false,
98
105
  }: EntityVideoSectionProps) {
99
106
  const hasFullVideo = !!(youtubeUrl || mainVideoUrl);
@@ -145,6 +152,7 @@ export function EntityVideoSection({
145
152
  <Video
146
153
  url={highlightVideoUrl!}
147
154
  poster={highlightVideoThumbnail}
155
+ captionsUrl={highlightCaptionsUrl}
148
156
  layout="centered"
149
157
  />
150
158
  </TabsContent>
@@ -164,6 +172,7 @@ export function EntityVideoSection({
164
172
  <Video
165
173
  url={highlightVideoUrl!}
166
174
  poster={highlightVideoThumbnail}
175
+ captionsUrl={highlightCaptionsUrl}
167
176
  layout="centered"
168
177
  priority={priority}
169
178
  />
@@ -9,10 +9,6 @@ export interface HighlightConfigSectionProps {
9
9
  targetDurationSeconds: number;
10
10
  /** Callback when target duration changes */
11
11
  onTargetDurationChange: (seconds: number) => void;
12
- /** Whether to skip subtitle burning */
13
- skipSubtitleBurning: boolean;
14
- /** Callback when skip subtitle option changes */
15
- onSkipSubtitleBurningChange: (skip: boolean) => void;
16
12
  /** Whether the section is disabled */
17
13
  disabled?: boolean;
18
14
  /** Additional class name */
@@ -23,13 +19,13 @@ export interface HighlightConfigSectionProps {
23
19
  * HighlightConfigSection - Unified component for highlight video configuration
24
20
  *
25
21
  * This component provides a consistent UI for both CustomerInterview and ProductRelease entities,
26
- * including duration selection and subtitle burning options in a styled horizontal layout.
22
+ * including duration selection in a styled horizontal layout. Highlight captions
23
+ * are generated automatically as a selectable text track (same pattern as the
24
+ * main video) — there is no subtitle-burning option anymore.
27
25
  */
28
26
  export function HighlightConfigSection({
29
27
  targetDurationSeconds,
30
28
  onTargetDurationChange,
31
- skipSubtitleBurning,
32
- onSkipSubtitleBurningChange,
33
29
  disabled = false,
34
30
  className = '',
35
31
  }: HighlightConfigSectionProps) {
@@ -55,19 +51,6 @@ export function HighlightConfigSection({
55
51
  </SelectContent>
56
52
  </Select>
57
53
  </div>
58
- <div className="flex items-center gap-2 pt-5">
59
- <input
60
- type="checkbox"
61
- id="skipSubtitleBurning"
62
- checked={skipSubtitleBurning}
63
- onChange={(e) => onSkipSubtitleBurningChange(e.target.checked)}
64
- disabled={disabled}
65
- className="h-4 w-4 rounded border-ods-border bg-ods-bg text-ods-accent focus:ring-ods-accent"
66
- />
67
- <Label htmlFor="skipSubtitleBurning" className="text-h6 cursor-pointer">
68
- Skip subtitle burning
69
- </Label>
70
- </div>
71
54
  </div>
72
55
  </div>
73
56
  );
@@ -5,6 +5,7 @@ import { Sparkles, Upload } from 'lucide-react';
5
5
  import { AIGeneratedBadge } from '../ui/ai-generated-badge';
6
6
  import { AIEnrichSection } from './ai-enrich';
7
7
  import { HighlightConfigSection } from './highlight-config-section';
8
+ import { SubtitlesEditor } from './subtitles-editor';
8
9
  import { Label } from '../ui/label';
9
10
  import { Badge } from '../ui/badge';
10
11
  import { Button } from '../ui/button';
@@ -16,10 +17,6 @@ export interface HighlightVideoCombinedSectionProps {
16
17
  targetDurationSeconds: number;
17
18
  /** Handler for duration changes */
18
19
  onTargetDurationChange: (seconds: number) => void;
19
- /** Whether to skip subtitle burning */
20
- skipSubtitleBurning: boolean;
21
- /** Handler for subtitle burning toggle */
22
- onSkipSubtitleBurningChange: (skip: boolean) => void;
23
20
  /** Whether config section is disabled */
24
21
  configDisabled?: boolean;
25
22
 
@@ -76,6 +73,14 @@ export interface HighlightVideoCombinedSectionProps {
76
73
  emptyMessage?: string;
77
74
  /** Upload progress component (optional) */
78
75
  uploadProgressComponent?: ReactNode;
76
+
77
+ // ===== Highlight Subtitles (SRT) Props =====
78
+ /** Highlight captions SRT content (highlight_srt_content) */
79
+ subtitles?: string;
80
+ /** Callback when highlight subtitles change */
81
+ onSubtitlesChange?: (value: string) => void;
82
+ /** Whether the highlight subtitles were AI generated */
83
+ isSubtitlesAIGenerated?: boolean;
79
84
  /** Video preview component - receives video data, should render the preview */
80
85
  renderVideoPreview: (props: {
81
86
  videoUrl: string;
@@ -102,8 +107,6 @@ export function HighlightVideoCombinedSection({
102
107
  // Config props
103
108
  targetDurationSeconds,
104
109
  onTargetDurationChange,
105
- skipSubtitleBurning,
106
- onSkipSubtitleBurningChange,
107
110
  configDisabled = false,
108
111
  // Generation props
109
112
  onGenerateHighlight,
@@ -132,6 +135,10 @@ export function HighlightVideoCombinedSection({
132
135
  previewLabel = "Highlight Video",
133
136
  emptyMessage = "No highlight video yet. Use AI generation above or upload manually.",
134
137
  uploadProgressComponent,
138
+ // Highlight subtitles props
139
+ subtitles,
140
+ onSubtitlesChange,
141
+ isSubtitlesAIGenerated = false,
135
142
  renderVideoPreview,
136
143
  // Common
137
144
  className = '',
@@ -164,8 +171,6 @@ export function HighlightVideoCombinedSection({
164
171
  <HighlightConfigSection
165
172
  targetDurationSeconds={targetDurationSeconds}
166
173
  onTargetDurationChange={onTargetDurationChange}
167
- skipSubtitleBurning={skipSubtitleBurning}
168
- onSkipSubtitleBurningChange={onSkipSubtitleBurningChange}
169
174
  disabled={configDisabled}
170
175
  />
171
176
 
@@ -229,6 +234,18 @@ export function HighlightVideoCombinedSection({
229
234
  </p>
230
235
  )}
231
236
  </div>
237
+
238
+ {/* 4. Highlight Subtitles (SRT) — the SAME SubtitlesEditor the main
239
+ video's TranscriptSummaryEditor renders, bound to
240
+ highlight_srt_content instead of srt_content. */}
241
+ <SubtitlesEditor
242
+ id="highlight-subtitles"
243
+ subtitles={subtitles}
244
+ onSubtitlesChange={onSubtitlesChange}
245
+ label="Highlight Subtitles (SRT)"
246
+ helperText="SRT captions transcribed from the highlight reel (AssemblyAI). Served as a selectable text track on the highlight player. Editable for fine-tuning."
247
+ isAIGenerated={isSubtitlesAIGenerated}
248
+ />
232
249
  </div>
233
250
  );
234
251
  }
@@ -24,10 +24,6 @@ export interface HighlightVideoSectionProps {
24
24
  targetDurationSeconds: number;
25
25
  /** Callback when target duration changes */
26
26
  onTargetDurationChange: (seconds: number) => void;
27
- /** Whether to skip subtitle burning */
28
- skipSubtitleBurning: boolean;
29
- /** Callback when skip subtitle option changes */
30
- onSkipSubtitleBurningChange: (skip: boolean) => void;
31
27
  /** Callback to trigger highlight generation */
32
28
  onGenerateHighlight: () => void;
33
29
  /** Whether highlight generation is in progress */
@@ -81,8 +77,6 @@ export function HighlightVideoSection({
81
77
  highlightVideoSource,
82
78
  targetDurationSeconds,
83
79
  onTargetDurationChange,
84
- skipSubtitleBurning,
85
- onSkipSubtitleBurningChange,
86
80
  onGenerateHighlight,
87
81
  isGenerating = false,
88
82
  generationProgress,
@@ -152,19 +146,6 @@ export function HighlightVideoSection({
152
146
  </SelectContent>
153
147
  </Select>
154
148
  </div>
155
- <div className="flex items-center gap-2 pt-5">
156
- <input
157
- type="checkbox"
158
- id="skipSubtitleBurning"
159
- checked={skipSubtitleBurning}
160
- onChange={(e) => onSkipSubtitleBurningChange(e.target.checked)}
161
- disabled={disabled}
162
- className="h-4 w-4 rounded border-ods-border bg-ods-bg-surface text-ods-accent focus:ring-ods-accent"
163
- />
164
- <Label htmlFor="skipSubtitleBurning" className="text-h6 cursor-pointer">
165
- Skip subtitle burning
166
- </Label>
167
- </div>
168
149
  </div>
169
150
  </div>
170
151
 
@@ -74,6 +74,7 @@ export * from './use-video-warmup'
74
74
  export * from './captions-url'
75
75
  export * from './video-source-selector'
76
76
  export * from './transcript-summary-editor'
77
+ export * from './subtitles-editor'
77
78
  export * from './highlight-video-section'
78
79
  export * from './highlight-config-section'
79
80
  export * from './entity-summary-editor'
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ import React from 'react';
4
+ import { Label } from '../ui/label';
5
+ import { Textarea } from '../ui/textarea';
6
+ import { Badge } from '../ui/badge';
7
+ import { StandardCcIcon } from '../icons-v2-generated';
8
+
9
+ export interface SubtitlesEditorProps {
10
+ /** SRT subtitle content */
11
+ subtitles?: string;
12
+ /** Callback when subtitles change */
13
+ onSubtitlesChange?: (value: string) => void;
14
+ /** Custom label for subtitles field */
15
+ label?: string;
16
+ /** Custom helper text */
17
+ helperText?: string;
18
+ /** Custom placeholder */
19
+ placeholder?: string;
20
+ /** Minimum height for the textarea */
21
+ minHeight?: number;
22
+ /** Show the AI Generated badge next to the label (when content exists) */
23
+ isAIGenerated?: boolean;
24
+ /** Whether the field is disabled */
25
+ disabled?: boolean;
26
+ /** DOM id for the textarea/label pair (unique per instance on a page) */
27
+ id?: string;
28
+ /** Additional class name */
29
+ className?: string;
30
+ }
31
+
32
+ /**
33
+ * SubtitlesEditor — THE single SRT-editing surface.
34
+ *
35
+ * Extracted from TranscriptSummaryEditor so the MAIN video's subtitles
36
+ * (srt_content) and the HIGHLIGHT reel's subtitles (highlight_srt_content)
37
+ * render through the exact same component: same textarea, same AI badge,
38
+ * same helper-text layout. Renders nothing when neither content nor a
39
+ * change handler is provided (same convention the inline block had).
40
+ */
41
+ export function SubtitlesEditor({
42
+ subtitles,
43
+ onSubtitlesChange,
44
+ label = 'Subtitles (SRT)',
45
+ helperText = 'SRT subtitle content generated from transcription. Editable for fine-tuning.',
46
+ placeholder = '1\n00:00:00,000 --> 00:00:02,500\nHello, welcome to...',
47
+ minHeight = 200,
48
+ isAIGenerated = false,
49
+ disabled = false,
50
+ id = 'subtitles',
51
+ className = '',
52
+ }: SubtitlesEditorProps) {
53
+ if (!subtitles && !onSubtitlesChange) return null;
54
+
55
+ return (
56
+ <div className={className}>
57
+ <div className="mb-2">
58
+ <div className="flex items-center gap-2">
59
+ <Label htmlFor={id}>{label}</Label>
60
+ {isAIGenerated && subtitles && (
61
+ <Badge variant="secondary" className="flex items-center gap-1">
62
+ <StandardCcIcon size={12} />
63
+ AI Generated
64
+ </Badge>
65
+ )}
66
+ </div>
67
+ <p className="text-h6 text-ods-text-secondary mt-1">{helperText}</p>
68
+ </div>
69
+ <div
70
+ className="rounded-lg border border-ods-border bg-ods-card overflow-hidden"
71
+ style={{ minHeight: `${minHeight}px` }}
72
+ >
73
+ <Textarea
74
+ id={id}
75
+ value={subtitles || ''}
76
+ onChange={(e) => onSubtitlesChange?.(e.target.value)}
77
+ placeholder={placeholder}
78
+ disabled={disabled || !onSubtitlesChange}
79
+ className="h-full w-full resize-none border-0 bg-transparent text-ods-text-primary placeholder:text-ods-text-secondary/50 focus:ring-0 focus:outline-none p-4 text-code"
80
+ style={{ minHeight: `${minHeight}px`, lineHeight: '1.6' }}
81
+ />
82
+ </div>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ export default SubtitlesEditor;
@@ -5,9 +5,8 @@ import { Sparkles } from 'lucide-react';
5
5
  import { AIGeneratedBadge } from '../ui/ai-generated-badge';
6
6
  import { Label } from '../ui/label';
7
7
  import { Textarea } from '../ui/textarea';
8
- import { Badge } from '../ui/badge';
9
- import { StandardCcIcon } from '../icons-v2-generated';
10
8
  import { ConfidenceBadge } from './ai-enrich/ConfidenceBadge';
9
+ import { SubtitlesEditor } from './subtitles-editor';
11
10
 
12
11
  export interface TranscriptSummaryEditorProps {
13
12
  /** Video summary text value - AI-generated from video transcription (defaults to empty string if undefined) */
@@ -161,37 +160,19 @@ export function TranscriptSummaryEditor({
161
160
  </div>
162
161
  </div>
163
162
 
164
- {/* Subtitles Editor — only shown when subtitles content exists or handler provided */}
165
- {(subtitles || onSubtitlesChange) && (
166
- <div>
167
- <div className="mb-2">
168
- <div className="flex items-center gap-2">
169
- <Label htmlFor="subtitles">{subtitlesLabel}</Label>
170
- {isAIGenerated && subtitles && (
171
- <Badge variant="secondary" className="flex items-center gap-1">
172
- <StandardCcIcon size={12} />
173
- AI Generated
174
- </Badge>
175
- )}
176
- </div>
177
- <p className="text-h6 text-ods-text-secondary mt-1">{subtitlesHelperText}</p>
178
- </div>
179
- <div
180
- className="rounded-lg border border-ods-border bg-ods-card overflow-hidden"
181
- style={{ minHeight: `${subtitlesMinHeight}px` }}
182
- >
183
- <Textarea
184
- id="subtitles"
185
- value={subtitles || ''}
186
- onChange={(e) => onSubtitlesChange?.(e.target.value)}
187
- placeholder={subtitlesPlaceholder}
188
- disabled={disabled || !onSubtitlesChange}
189
- className="h-full w-full resize-none border-0 bg-transparent text-ods-text-primary placeholder:text-ods-text-secondary/50 focus:ring-0 focus:outline-none p-4 text-code"
190
- style={{ minHeight: `${subtitlesMinHeight}px`, lineHeight: '1.6' }}
191
- />
192
- </div>
193
- </div>
194
- )}
163
+ {/* Subtitles Editor — the shared SubtitlesEditor SSOT (same component
164
+ the highlight section uses for highlight_srt_content). */}
165
+ <SubtitlesEditor
166
+ id="subtitles"
167
+ subtitles={subtitles}
168
+ onSubtitlesChange={onSubtitlesChange}
169
+ label={subtitlesLabel}
170
+ helperText={subtitlesHelperText}
171
+ placeholder={subtitlesPlaceholder}
172
+ minHeight={subtitlesMinHeight}
173
+ isAIGenerated={isAIGenerated}
174
+ disabled={disabled}
175
+ />
195
176
  </div>
196
177
  );
197
178
  }
@@ -138,6 +138,7 @@ export function OnboardingGuideDetailView({
138
138
  }
139
139
 
140
140
  const captionsUrl = getCaptionsUrl('onboarding_guide', guide.id, guide.srt_content)
141
+ const highlightCaptionsUrl = getCaptionsUrl('onboarding_guide', guide.id, guide.highlight_srt_content, { variant: 'highlight' })
141
142
 
142
143
  const videoPoster =
143
144
  guide.main_video_thumbnail ||
@@ -197,6 +198,7 @@ export function OnboardingGuideDetailView({
197
198
  title={guide.title}
198
199
  srtContent={guide.srt_content}
199
200
  captionsUrl={captionsUrl}
201
+ highlightCaptionsUrl={highlightCaptionsUrl}
200
202
  MarkdownRenderer={MarkdownRenderer}
201
203
  />
202
204
  </div>
@@ -88,6 +88,8 @@ export interface VideoDisplaySectionProps {
88
88
  bitesHref?: string;
89
89
  srtContent?: string | null;
90
90
  captionsUrl?: string | null;
91
+ /** VTT captions URL for the highlight reel (variant=highlight). */
92
+ highlightCaptionsUrl?: string | null;
91
93
  }
92
94
 
93
95
  export interface ReleaseDetailPageProps {
@@ -342,6 +344,7 @@ export function ReleaseDetailPage({
342
344
  bitesHref={typeof release.slug === 'string' ? `/releases/${release.slug}` : undefined}
343
345
  srtContent={release?.srt_content as string | null | undefined}
344
346
  captionsUrl={release?.captionsUrl as string | undefined}
347
+ highlightCaptionsUrl={release?.highlightCaptionsUrl as string | undefined}
345
348
  />
346
349
  ) : (
347
350
  <>
@@ -370,6 +373,7 @@ export function ReleaseDetailPage({
370
373
  <Video
371
374
  url={highlightVideoUrl}
372
375
  poster={highlightVideoThumbnail}
376
+ captionsUrl={release?.highlightCaptionsUrl as string | undefined}
373
377
  layout="centered"
374
378
  />
375
379
  )}
@@ -40,6 +40,7 @@ export interface CaseStudy {
40
40
  // NOTE: transcript_words_data is server-only and intentionally NOT typed here.
41
41
  transcript?: string | null
42
42
  srt_content?: string | null
43
+ highlight_srt_content?: string | null
43
44
  video_summary?: string | null
44
45
  custom_instructions?: string | null
45
46
  config?: Record<string, unknown> | null // clip/highlight processing config
@@ -98,6 +99,7 @@ export interface CreateCaseStudyData {
98
99
  // Video processing pipeline (parity with customer_interviews)
99
100
  transcript?: string | null
100
101
  srt_content?: string | null
102
+ highlight_srt_content?: string | null
101
103
  video_summary?: string | null
102
104
  custom_instructions?: string | null
103
105
  config?: Record<string, unknown> | null
@@ -14,8 +14,6 @@ import type { VideoTeaser, Speaker, VideoClip, TranscriptWord, SpeakerMapping, E
14
14
  export interface CustomerInterviewConfig {
15
15
  /** Target duration in seconds for AI-generated highlight video (default: 180) */
16
16
  highlight_target_duration_seconds?: number
17
- /** Skip subtitle burning during highlight video generation */
18
- skipSubtitleBurning?: boolean
19
17
  }
20
18
 
21
19
  export interface CustomerInterview {
@@ -79,6 +77,7 @@ export interface CustomerInterview {
79
77
  // Word-level transcript data for video processing
80
78
  transcript_words_data?: TranscriptWord[]
81
79
  srt_content?: string | null
80
+ highlight_srt_content?: string | null
82
81
 
83
82
  // Incentive mention exclusion ranges (computed during transcription)
84
83
  incentive_excluded_ranges?: ExcludedRange[]
@@ -115,6 +114,7 @@ export interface CreateCustomerInterviewData {
115
114
  video_summary?: string // AI-generated summary from video transcription (Markdown supported)
116
115
  transcript?: string // Markdown supported
117
116
  srt_content?: string | null // SRT subtitle content
117
+ highlight_srt_content?: string | null
118
118
  user_id?: string // Customer UUID
119
119
  main_video_url?: string
120
120
  highlight_video_url?: string | null
@@ -101,6 +101,7 @@ export interface ProductRelease {
101
101
  transcript: string | null
102
102
  transcript_words_data?: TranscriptWord[]
103
103
  srt_content?: string | null
104
+ highlight_srt_content?: string | null
104
105
  video_summary: string | null // AI-generated summary from video transcription
105
106
  video_bites: VideoTeaser[] // JSONB array of extracted video clips
106
107
  highlight_video_url: string | null
@@ -192,6 +193,7 @@ export interface CreateProductReleaseData {
192
193
  main_video_url?: string
193
194
  transcript?: string
194
195
  srt_content?: string | null
196
+ highlight_srt_content?: string | null
195
197
  video_summary?: string // AI-generated summary from video transcription
196
198
  video_bites?: VideoTeaser[]
197
199
  highlight_video_url?: string | null
@@ -409,6 +409,7 @@ export type Database = {
409
409
  highlight_video_source: string | null
410
410
  highlight_video_thumbnail: string | null
411
411
  highlight_video_duration_ms: number | null
412
+ highlight_srt_content: string | null
412
413
  author_id: string | null
413
414
  position: 'left' | 'right'
414
415
  created_at: string
@@ -434,6 +435,7 @@ export type Database = {
434
435
  highlight_video_source?: string | null
435
436
  highlight_video_thumbnail?: string | null
436
437
  highlight_video_duration_ms?: number | null
438
+ highlight_srt_content?: string | null
437
439
  author_id?: string | null
438
440
  created_at?: string
439
441
  updated_at?: string
@@ -457,6 +459,7 @@ export type Database = {
457
459
  highlight_video_source?: string | null
458
460
  highlight_video_thumbnail?: string | null
459
461
  highlight_video_duration_ms?: number | null
462
+ highlight_srt_content?: string | null
460
463
  author_id?: string | null
461
464
  position?: 'left' | 'right'
462
465
  created_at?: string