Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions field-logic/src/components/TranscriptViewer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, it, expect } from "vitest";
import { formatTimestamp } from "./TranscriptViewer";

describe("TranscriptViewer", () => {
describe("formatTimestamp", () => {
it("formats 0 seconds correctly", () => {
expect(formatTimestamp(0)).toBe("00:00");
});

it("formats seconds less than a minute correctly", () => {
expect(formatTimestamp(45)).toBe("00:45");
});

it("formats minutes and seconds correctly", () => {
expect(formatTimestamp(65)).toBe("01:05");
});

it("formats longer durations correctly (ignoring hours)", () => {
expect(formatTimestamp(3665)).toBe("01:05");
});
});
});
128 changes: 72 additions & 56 deletions field-logic/src/components/TranscriptViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,74 +1,90 @@
import React from 'react';
import React from "react";

interface TranscriptSegment {
start: number;
end: number;
text: string;
confidence: number;
flagged?: boolean;
start: number;
end: number;
text: string;
confidence: number;
flagged?: boolean;
}

interface TranscriptData {
meta: {
file: string;
duration: number;
engine: string;
};
segments: TranscriptSegment[];
meta: {
file: string;
duration: number;
engine: string;
};
segments: TranscriptSegment[];
}

interface Props {
data: TranscriptData;
data: TranscriptData;
}

export const formatTimestamp = (start: number): string => {
return new Date(start * 1000).toISOString().substring(14, 19);
};

export const TranscriptViewer: React.FC<Props> = ({ data }) => {
const handleDeepLink = (e: React.MouseEvent, start: number) => {
e.preventDefault();
const file = data.meta.file;
// Construct custom protocol link
const url = `fieldlogic://open?file=${file}&t=${start}`;
window.location.href = url;
};
const handleDeepLink = (e: React.MouseEvent, start: number) => {
e.preventDefault();
const file = data.meta.file;
// Construct custom protocol link
const url = `fieldlogic://open?file=${file}&t=${start}`;
window.location.href = url;
};

return (
<div className="transcript-viewer" style={{ maxWidth: '800px', margin: '0 auto', fontFamily: 'monospace' }}>
<h2>Transcription Review: {data.meta.file}</h2>
return (
<div
className="transcript-viewer"
style={{ maxWidth: "800px", margin: "0 auto", fontFamily: "monospace" }}
>
<h2>Transcription Review: {data.meta.file}</h2>

<div className="segments">
{data.segments.map((seg, idx) => (
<div
key={idx}
style={{
padding: '8px',
borderBottom: '1px solid #eee',
backgroundColor: seg.confidence < 0.6 ? '#fef3c7' : 'transparent'
}}
>
<span style={{ color: '#64748b', marginRight: '10px', userSelect: 'none' }}>
[{new Date(seg.start * 1000).toISOString().substr(14, 5)}]
</span>
<div className="segments">
{data.segments.map((seg, idx) => (
<div
key={idx}
style={{
padding: "8px",
borderBottom: "1px solid #eee",
backgroundColor: seg.confidence < 0.6 ? "#fef3c7" : "transparent",
}}
>
<span
style={{
color: "#64748b",
marginRight: "10px",
userSelect: "none",
}}
>
[{formatTimestamp(seg.start)}]
</span>

<span style={{ marginRight: '10px' }}>{seg.text}</span>
<span style={{ marginRight: "10px" }}>{seg.text}</span>

<div style={{ float: 'right' }}>
{/* Primary: In-browser audio (mock) */}
<button onClick={() => console.log('Seek audio to', seg.start)} style={{ marginRight: '5px' }}>
â–¶
</button>
<div style={{ float: "right" }}>
{/* Primary: In-browser audio (mock) */}
<button
onClick={() => console.log("Seek audio to", seg.start)}
style={{ marginRight: "5px" }}
>
â–¶
</button>

{/* Secondary: VLC Deep Link */}
<a
href="#"
onClick={(e) => handleDeepLink(e, seg.start)}
title="Open in VLC"
style={{ textDecoration: 'none' }}
>
🚀
</a>
</div>
</div>
))}
{/* Secondary: VLC Deep Link */}
<a
href="#"
onClick={(e) => handleDeepLink(e, seg.start)}
title="Open in VLC"
style={{ textDecoration: "none" }}
>
🚀
</a>
</div>
</div>
);
</div>
))}
</div>
</div>
);
};