@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.
- package/dist/{chunk-2GRFWONP.js → chunk-3L5L67V3.js} +2 -2
- package/dist/{chunk-4JDQXYFI.cjs → chunk-6NY7TKLY.cjs} +14 -14
- package/dist/{chunk-4JDQXYFI.cjs.map → chunk-6NY7TKLY.cjs.map} +1 -1
- package/dist/{chunk-FLSI72M2.cjs → chunk-6R4JGGT5.cjs} +3 -3
- package/dist/{chunk-FLSI72M2.cjs.map → chunk-6R4JGGT5.cjs.map} +1 -1
- package/dist/{chunk-LOSJMXB7.cjs → chunk-A33QVXOY.cjs} +9 -9
- package/dist/{chunk-LOSJMXB7.cjs.map → chunk-A33QVXOY.cjs.map} +1 -1
- package/dist/{chunk-J2Z53KRG.js → chunk-BBPVOSGN.js} +2 -2
- package/dist/{chunk-WNNZFBMS.js → chunk-F2TXZCTG.js} +6 -4
- package/dist/{chunk-WNNZFBMS.js.map → chunk-F2TXZCTG.js.map} +1 -1
- package/dist/{chunk-DMYAVVD7.cjs → chunk-FPBFFSJA.cjs} +7 -7
- package/dist/{chunk-DMYAVVD7.cjs.map → chunk-FPBFFSJA.cjs.map} +1 -1
- package/dist/{chunk-JIAZRPQG.cjs → chunk-G6C56Z3W.cjs} +11 -11
- package/dist/{chunk-JIAZRPQG.cjs.map → chunk-G6C56Z3W.cjs.map} +1 -1
- package/dist/{chunk-FMH3YY4H.js → chunk-HCRASVSB.js} +2 -2
- package/dist/{chunk-QW36SYRQ.js → chunk-HGB574AT.js} +2 -2
- package/dist/{chunk-Y22S7NE3.js → chunk-HRRA45HV.js} +9 -7
- package/dist/chunk-HRRA45HV.js.map +1 -0
- package/dist/{chunk-4IOPSFHF.cjs → chunk-JQUUI5CU.cjs} +37 -37
- package/dist/{chunk-4IOPSFHF.cjs.map → chunk-JQUUI5CU.cjs.map} +1 -1
- package/dist/{chunk-DVBUNH46.cjs → chunk-K47OETPN.cjs} +5 -5
- package/dist/{chunk-DVBUNH46.cjs.map → chunk-K47OETPN.cjs.map} +1 -1
- package/dist/{chunk-BETH6QZC.cjs → chunk-MAEVGBN2.cjs} +75 -73
- package/dist/chunk-MAEVGBN2.cjs.map +1 -0
- package/dist/{chunk-VP4ZY2RA.js → chunk-NAZXVYFV.js} +2169 -2158
- package/dist/chunk-NAZXVYFV.js.map +1 -0
- package/dist/{chunk-PZGOK2OC.js → chunk-NKX7NWLJ.js} +2 -2
- package/dist/{chunk-OLUROGWG.cjs → chunk-PDIV4BY6.cjs} +31 -31
- package/dist/{chunk-OLUROGWG.cjs.map → chunk-PDIV4BY6.cjs.map} +1 -1
- package/dist/{chunk-AKZY45LV.js → chunk-PPRV3CSO.js} +2 -2
- package/dist/{chunk-FT5PZQVK.js → chunk-QY77HY3A.js} +5 -5
- package/dist/{chunk-UL5EGPMV.js → chunk-R4YDVJMY.js} +4 -4
- package/dist/{chunk-F3GGE6DI.cjs → chunk-RBHKEA2G.cjs} +87 -87
- package/dist/{chunk-F3GGE6DI.cjs.map → chunk-RBHKEA2G.cjs.map} +1 -1
- package/dist/{chunk-SEAKHM72.js → chunk-RHIN6A3M.js} +2 -2
- package/dist/{chunk-QDO7Y54S.js → chunk-RHNEWFVD.js} +2 -2
- package/dist/{chunk-CE2GROZZ.cjs → chunk-TTFBD45B.cjs} +641 -630
- package/dist/chunk-TTFBD45B.cjs.map +1 -0
- package/dist/{chunk-QEXFM2QX.js → chunk-VLSRNHHN.js} +2 -2
- package/dist/{chunk-T4O3AEKV.cjs → chunk-YEU5XUMC.cjs} +26 -26
- package/dist/{chunk-T4O3AEKV.cjs.map → chunk-YEU5XUMC.cjs.map} +1 -1
- package/dist/{chunk-WFG72MKN.cjs → chunk-YNBYIMZB.cjs} +28 -26
- package/dist/chunk-YNBYIMZB.cjs.map +1 -0
- package/dist/{chunk-IRZHAWRW.cjs → chunk-YV5LPROO.cjs} +4 -4
- package/dist/{chunk-IRZHAWRW.cjs.map → chunk-YV5LPROO.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/types/entities/onboarding-guide.d.ts +1 -0
- package/dist/components/chat/types/entities/onboarding-guide.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/captions-url.cjs +3 -2
- package/dist/components/features/captions-url.cjs.map +1 -1
- package/dist/components/features/captions-url.d.ts +8 -1
- package/dist/components/features/captions-url.d.ts.map +1 -1
- package/dist/components/features/captions-url.js +3 -2
- package/dist/components/features/captions-url.js.map +1 -1
- package/dist/components/features/entity-video-section.d.ts +7 -1
- package/dist/components/features/entity-video-section.d.ts.map +1 -1
- package/dist/components/features/highlight-config-section.d.ts +4 -6
- package/dist/components/features/highlight-config-section.d.ts.map +1 -1
- package/dist/components/features/highlight-video-combined-section.d.ts +7 -5
- package/dist/components/features/highlight-video-combined-section.d.ts.map +1 -1
- package/dist/components/features/highlight-video-section.d.ts +1 -5
- package/dist/components/features/highlight-video-section.d.ts.map +1 -1
- package/dist/components/features/index.cjs +4 -2
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +1 -0
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +3 -1
- package/dist/components/features/subtitles-editor.d.ts +35 -0
- package/dist/components/features/subtitles-editor.d.ts.map +1 -0
- package/dist/components/features/transcript-summary-editor.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +134 -132
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +13 -11
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +34 -34
- package/dist/components/meeting-scheduler/index.js +3 -3
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/shared/product-release/release-detail-page.d.ts +2 -0
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/dist/types/case-study.d.ts +2 -0
- package/dist/types/case-study.d.ts.map +1 -1
- package/dist/types/customer-interview.d.ts +2 -2
- package/dist/types/customer-interview.d.ts.map +1 -1
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/product-release.d.ts +2 -0
- package/dist/types/product-release.d.ts.map +1 -1
- package/dist/types/supabase.d.ts +3 -0
- package/dist/types/supabase.d.ts.map +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/types/entities/onboarding-guide.ts +1 -0
- package/src/components/features/captions-url.ts +10 -1
- package/src/components/features/entity-video-section.tsx +9 -0
- package/src/components/features/highlight-config-section.tsx +3 -20
- package/src/components/features/highlight-video-combined-section.tsx +25 -8
- package/src/components/features/highlight-video-section.tsx +0 -19
- package/src/components/features/index.ts +1 -0
- package/src/components/features/subtitles-editor.tsx +87 -0
- package/src/components/features/transcript-summary-editor.tsx +14 -33
- package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +2 -0
- package/src/components/shared/product-release/release-detail-page.tsx +4 -0
- package/src/types/case-study.ts +2 -0
- package/src/types/customer-interview.ts +2 -2
- package/src/types/product-release.ts +2 -0
- package/src/types/supabase.ts +3 -0
- package/dist/chunk-BETH6QZC.cjs.map +0 -1
- package/dist/chunk-CE2GROZZ.cjs.map +0 -1
- package/dist/chunk-VP4ZY2RA.js.map +0 -1
- package/dist/chunk-WFG72MKN.cjs.map +0 -1
- package/dist/chunk-Y22S7NE3.js.map +0 -1
- /package/dist/{chunk-2GRFWONP.js.map → chunk-3L5L67V3.js.map} +0 -0
- /package/dist/{chunk-J2Z53KRG.js.map → chunk-BBPVOSGN.js.map} +0 -0
- /package/dist/{chunk-FMH3YY4H.js.map → chunk-HCRASVSB.js.map} +0 -0
- /package/dist/{chunk-QW36SYRQ.js.map → chunk-HGB574AT.js.map} +0 -0
- /package/dist/{chunk-PZGOK2OC.js.map → chunk-NKX7NWLJ.js.map} +0 -0
- /package/dist/{chunk-AKZY45LV.js.map → chunk-PPRV3CSO.js.map} +0 -0
- /package/dist/{chunk-FT5PZQVK.js.map → chunk-QY77HY3A.js.map} +0 -0
- /package/dist/{chunk-UL5EGPMV.js.map → chunk-R4YDVJMY.js.map} +0 -0
- /package/dist/{chunk-SEAKHM72.js.map → chunk-RHIN6A3M.js.map} +0 -0
- /package/dist/{chunk-QDO7Y54S.js.map → chunk-RHNEWFVD.js.map} +0 -0
- /package/dist/{chunk-QEXFM2QX.js.map → chunk-VLSRNHHN.js.map} +0 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 —
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
)}
|
package/src/types/case-study.ts
CHANGED
|
@@ -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
|
package/src/types/supabase.ts
CHANGED
|
@@ -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
|