Files
bifrost/ui/app/workspace/logs/views/transcriptionView.tsx
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

158 lines
5.7 KiB
TypeScript

import { Badge } from "@/components/ui/badge";
import { CodeEditor } from "@/components/ui/codeEditor";
import { BifrostTranscribe, TranscriptionInput } from "@/lib/types/logs";
import { Clock, FileAudio, Mic } from "lucide-react";
import AudioPlayer from "./audioPlayer";
interface TranscriptionViewProps {
transcriptionInput?: TranscriptionInput;
transcriptionOutput?: BifrostTranscribe;
isStreaming?: boolean;
}
export default function TranscriptionView({ transcriptionInput, transcriptionOutput, isStreaming }: TranscriptionViewProps) {
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = (seconds % 60).toFixed(1);
return `${mins}:${secs.padStart(4, "0")}`;
};
return (
<div className="space-y-4">
{/* Transcription Input */}
{transcriptionInput && (
<div className="w-full rounded-sm border">
<div className="flex items-center gap-2 border-b px-6 py-2 text-sm font-medium">
<FileAudio className="h-4 w-4" />
Transcription Input
</div>
<div className="space-y-4 p-6">
<div className="text-muted-foreground mb-2 text-xs font-medium">AUDIO FILE</div>
{/* Audio Controls */}
<AudioPlayer src={transcriptionInput.file} />
</div>
</div>
)}
{/* Transcription Output */}
{(transcriptionOutput || isStreaming) && (
<div className="w-full rounded-sm border">
<div className="flex items-center gap-2 border-b px-6 py-2 text-sm font-medium">
<Mic className="h-4 w-4" />
Transcription Output
</div>
<div className="space-y-4 p-6">
{!transcriptionOutput && isStreaming ? (
<div className="font-mono text-xs">Output was streamed and is not available.</div>
) : (
<>
{/* Main Transcription Text */}
<div>
<div className="font-mono text-xs">{transcriptionOutput?.text}</div>
</div>
{/* Basic Information */}
{(transcriptionOutput?.task || transcriptionOutput?.language || transcriptionOutput?.duration) && (
<div className="grid grid-cols-3 gap-4">
{transcriptionOutput?.task && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">TASK</div>
<div className="font-mono text-xs">{transcriptionOutput.task}</div>
</div>
)}
{transcriptionOutput?.language && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">DETECTED LANGUAGE</div>
<div className="font-mono text-xs">{transcriptionOutput.language}</div>
</div>
)}
{transcriptionOutput?.duration && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">DURATION</div>
<div className="font-mono text-xs">{transcriptionOutput.duration.toFixed(1)}s</div>
</div>
)}
</div>
)}
{/* Words with Timing */}
{transcriptionOutput?.words && transcriptionOutput.words.length > 0 && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">WORD-LEVEL TIMING</div>
<div className="max-h-40 overflow-y-auto">
<div className="flex flex-wrap gap-2">
{transcriptionOutput.words.map((word, index) => (
<div
key={index}
className="inline-flex items-center gap-1 rounded border px-2 py-1 text-xs"
title={`${formatTime(word.start)} - ${formatTime(word.end)}`}
>
<span>{word.word}</span>
<span className="text-muted-foreground text-xs">{formatTime(word.start)}</span>
</div>
))}
</div>
</div>
</div>
)}
{/* Segments */}
{transcriptionOutput?.segments && transcriptionOutput.segments.length > 0 && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">SEGMENTS</div>
<div className="max-h-60 space-y-2 overflow-y-auto">
{transcriptionOutput.segments.map((segment) => (
<div key={segment.id} className="rounded border p-3">
<div className="mb-2 flex items-center justify-between">
<Badge variant="outline" className="text-xs">
Segment {segment.id}
</Badge>
<div className="text-muted-foreground flex items-center gap-1 text-xs">
<Clock className="h-3 w-3" />
{formatTime(segment.start)} - {formatTime(segment.end)}
</div>
</div>
<div className="text-sm">{segment.text}</div>
<div className="text-muted-foreground mt-2 flex gap-4 text-xs">
<span>Avg LogProb: {segment.avg_logprob.toFixed(3)}</span>
<span>No Speech: {(segment.no_speech_prob * 100).toFixed(1)}%</span>
<span>Temp: {segment.temperature.toFixed(1)}</span>
</div>
</div>
))}
</div>
</div>
)}
{/* Log Probabilities */}
{transcriptionOutput?.logprobs && transcriptionOutput.logprobs.length > 0 && (
<div>
<div className="text-muted-foreground mb-2 text-xs font-medium">LOG PROBABILITIES</div>
<CodeEditor
className="z-0 w-full"
shouldAdjustInitialHeight={true}
maxHeight={200}
wrap={true}
code={JSON.stringify(transcriptionOutput.logprobs, null, 2)}
lang="json"
readonly={true}
options={{
scrollBeyondLastLine: false,
collapsibleBlocks: true,
lineNumbers: "off",
alwaysConsumeMouseWheel: false,
}}
/>
</div>
)}
</>
)}
</div>
</div>
)}
</div>
);
}