-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTranscript.tsx
More file actions
127 lines (119 loc) · 6.08 KB
/
Copy pathTranscript.tsx
File metadata and controls
127 lines (119 loc) · 6.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import React, { useMemo, useEffect, useRef } from 'react';
interface DebateTurn {
speaker: 'user' | 'ai';
text: string;
}
interface TranscriptProps {
transcript: string;
isPlaying: boolean;
isGeneratingAudio: boolean;
onPlayPause: () => void;
isDebating: boolean;
isConnectingDebate: boolean;
onToggleDebate: () => void;
debateHistory: DebateTurn[];
}
const Transcript: React.FC<TranscriptProps> = ({
transcript,
isPlaying,
isGeneratingAudio,
onPlayPause,
isDebating,
isConnectingDebate,
onToggleDebate,
debateHistory,
}) => {
const debateEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
debateEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [debateHistory]);
const formattedTranscript = useMemo(() => {
if (!transcript) return null;
return transcript.split('\n').map((line, index) => {
const trimmedLine = line.trim();
if (trimmedLine.length === 0) {
return <div key={index} className="h-4" />;
}
if (/^[A-Z\s]+$/.test(trimmedLine) && !trimmedLine.includes('(') && trimmedLine.length < 20) {
return <p key={index} className="font-bold text-cyan-300 mt-4 mb-1">{trimmedLine}</p>;
}
if (trimmedLine.startsWith('(') && trimmedLine.endsWith(')')) {
return <p key={index} className="italic text-stone-400">{trimmedLine}</p>;
}
return <p key={index} className="text-stone-200 pl-8">{trimmedLine}</p>;
});
}, [transcript]);
const getDebateButtonText = () => {
if (isConnectingDebate) return 'Connecting...';
if (isDebating) return 'End Debate';
return 'Start Debate';
};
return (
<div className="bg-stone-800 p-6 md:p-8 rounded-lg shadow-2xl overflow-y-auto h-full font-mono flex flex-col">
<div className="flex items-center justify-between mb-6 border-b-2 border-stone-600 pb-3 flex-shrink-0">
<h2 className="text-2xl font-bold text-stone-300">Podcast Transcript</h2>
{transcript && (
<div className="flex items-center gap-2">
<button
onClick={onToggleDebate}
disabled={isConnectingDebate || isGeneratingAudio}
className="px-4 py-2 text-sm font-semibold text-white bg-rose-600 rounded-md shadow-lg transition-all duration-200 ease-in-out hover:bg-rose-500 focus:outline-none focus:ring-2 focus:ring-rose-400 focus:ring-opacity-75 disabled:bg-stone-600 disabled:cursor-not-allowed flex items-center justify-center min-w-[120px]"
>
{isConnectingDebate && (
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
)}
{getDebateButtonText()}
</button>
<button
onClick={onPlayPause}
disabled={isGeneratingAudio || isDebating}
className="px-3 py-2 text-sm font-semibold text-white bg-cyan-600 rounded-full shadow-lg transition-all duration-200 ease-in-out hover:bg-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-400 focus:ring-opacity-75 disabled:bg-stone-600 disabled:cursor-not-allowed flex items-center justify-center w-10 h-10"
aria-label={isPlaying ? "Pause transcript" : "Play transcript"}
>
{isGeneratingAudio ? (
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : isPlaying ? (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
<path fillRule="evenodd" d="M6.75 5.25a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-1.5 0V6a.75.75 0 0 1 .75-.75Zm10.5 0a.75.75 0 0 1 .75.75v12a.75.75 0 0 1-1.5 0V6a.75.75 0 0 1 .75-.75Z" clipRule="evenodd" />
</svg>
) : (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6">
<path fillRule="evenodd" d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.647c1.295.748 1.295 2.538 0 3.286L7.279 20.99c-1.25.72-2.779-.217-2.779-1.643V5.653Z" clipRule="evenodd" />
</svg>
)}
</button>
</div>
)}
</div>
<div className="overflow-y-auto flex-grow pr-2">
{formattedTranscript}
{isDebating && (
<div className="mt-8 pt-4 border-t-2 border-dashed border-stone-600">
<h3 className="text-lg font-bold text-rose-400 mb-4 text-center">LIVE DEBATE</h3>
<div className="space-y-4">
{debateHistory.map((turn, index) => (
<div key={index} className={`flex flex-col ${turn.speaker === 'user' ? 'items-end' : 'items-start'}`}>
<div className={`rounded-lg px-4 py-2 max-w-[80%] ${turn.speaker === 'user' ? 'bg-cyan-800 text-white' : 'bg-stone-700 text-stone-200'}`}>
<span className="font-bold block text-sm mb-1">{turn.speaker === 'user' ? 'You' : 'Podcast AI'}</span>
<p className="whitespace-pre-wrap">{turn.text}</p>
</div>
</div>
))}
<div ref={debateEndRef} />
</div>
<div className="text-center text-stone-400 mt-4 h-6">
{isDebating && !isConnectingDebate && <p className="animate-pulse">Listening...</p>}
</div>
</div>
)}
</div>
</div>
);
};
export default Transcript;