fix: captions view ui + use BatchCommand to add text elements in transcription at once

This commit is contained in:
Maze Winther 2026-03-04 21:52:22 +01:00
parent a2580216f8
commit 5638974f4b
1 changed files with 72 additions and 57 deletions

View File

@ -11,6 +11,11 @@ import { useState, useRef } from "react";
import { extractTimelineAudio } from "@/lib/media/mediabunny";
import { useEditor } from "@/hooks/use-editor";
import { DEFAULT_TEXT_ELEMENT } from "@/constants/text-constants";
import {
BatchCommand,
AddTrackCommand,
InsertElementCommand,
} from "@/lib/commands";
import { TRANSCRIPTION_LANGUAGES } from "@/constants/transcription-constants";
import type {
TranscriptionLanguage,
@ -20,7 +25,12 @@ import { transcriptionService } from "@/services/transcription/service";
import { decodeAudioToFloat32 } from "@/lib/media/audio";
import { buildCaptionChunks } from "@/lib/transcription/caption";
import { Spinner } from "@/components/ui/spinner";
import { Label } from "@/components/ui/label";
import {
Section,
SectionContent,
SectionField,
SectionFields,
} from "@/components/editor/panels/properties/section";
export function Captions() {
const [selectedLanguage, setSelectedLanguage] =
@ -60,29 +70,28 @@ export function Captions() {
onProgress: handleProgress,
});
setProcessingStep("Generating captions...");
const captionChunks = buildCaptionChunks({ segments: result.segments });
setProcessingStep("Generating captions...");
const captionChunks = buildCaptionChunks({ segments: result.segments });
const captionTrackId = editor.timeline.addTrack({
type: "text",
index: 0,
});
const addTrackCommand = new AddTrackCommand("text", 0);
const insertCommands = captionChunks.map((caption, i) =>
new InsertElementCommand({
placement: { mode: "explicit", trackId: addTrackCommand.getTrackId() },
element: {
...DEFAULT_TEXT_ELEMENT,
name: `Caption ${i + 1}`,
content: caption.text,
duration: caption.duration,
startTime: caption.startTime,
fontSize: 65,
fontWeight: "bold",
},
})
);
for (let i = 0; i < captionChunks.length; i++) {
const caption = captionChunks[i];
editor.timeline.insertElement({
placement: { mode: "explicit", trackId: captionTrackId },
element: {
...DEFAULT_TEXT_ELEMENT,
name: `Caption ${i + 1}`,
content: caption.text,
duration: caption.duration,
startTime: caption.startTime,
fontSize: 65,
fontWeight: "bold",
},
});
}
editor.command.execute({
command: new BatchCommand([addTrackCommand, ...insertCommands]),
});
} catch (error) {
console.error("Transcription failed:", error);
setError(
@ -108,43 +117,49 @@ export function Captions() {
};
return (
<PanelView title="Captions" ref={containerRef}>
<div className="flex flex-col gap-3">
<Label>Language</Label>
<Select
value={selectedLanguage}
onValueChange={(value) => handleLanguageChange({ value })}
>
<SelectTrigger>
<SelectValue placeholder="Select a language" />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto">Auto detect</SelectItem>
{TRANSCRIPTION_LANGUAGES.map((language) => (
<SelectItem key={language.code} value={language.code}>
{language.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex flex-col gap-4">
{error && (
<div className="bg-destructive/10 border-destructive/20 rounded-md border p-3">
<p className="text-destructive text-sm">{error}</p>
</div>
)}
<PanelView title="Captions" contentClassName="px-0 flex flex-col h-full" ref={containerRef}>
<Section showTopBorder={false} className="flex-1">
<SectionContent className="flex flex-col gap-4 h-full">
<SectionFields>
<SectionField label="Language">
<Select
value={selectedLanguage}
onValueChange={(value) => handleLanguageChange({ value })}
>
<SelectTrigger>
<SelectValue placeholder="Select a language" />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto">Auto detect</SelectItem>
{TRANSCRIPTION_LANGUAGES.map((language) => (
<SelectItem key={language.code} value={language.code}>
{language.name}
</SelectItem>
))}
</SelectContent>
</Select>
</SectionField>
</SectionFields>
{error && (
<div className="bg-destructive/10 border-destructive/20 rounded-md border p-3">
<p className="text-destructive text-sm">{error}</p>
</div>
)}
</SectionContent>
</Section>
<Section showBottomBorder={false} showTopBorder={false}>
<SectionContent>
<Button
className="w-full"
onClick={handleGenerateTranscript}
disabled={isProcessing}
>
{isProcessing && <Spinner className="mr-1" />}
{isProcessing ? processingStep : "Generate transcript"}
</Button>
</div>
className="w-full"
onClick={handleGenerateTranscript}
disabled={isProcessing}
>
{isProcessing && <Spinner className="mr-1" />}
{isProcessing ? processingStep : "Generate transcript"}
</Button>
</SectionContent>
</Section>
</PanelView>
);
}