File size: 13,454 Bytes
b8b3edf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251

import React, { useState, useEffect, useMemo } from 'react';
import { 
  Globe, 
  Volume2, 
  Mic2, 
  Settings2, 
  Loader2, 
  Zap, 
  Sparkles, 
  MessageSquare, 
  ArrowRight, 
  Users, 
  ChevronRight, 
  Activity,
  ShieldCheck,
  Search,
  Filter,
  X
} from 'lucide-react';
import { synthesizeSpeech, TTS_VOICES, TTS_LANGUAGES, callGemini } from '../services/geminiService';

const Broadcast: React.FC = () => {
  const [activeTab, setActiveTab] = useState<'single' | 'multi'>('single');
  const [selectedLang, setSelectedLang] = useState(TTS_LANGUAGES.find(l => l.code === 'en')!);
  const [selectedVoice, setSelectedVoice] = useState(TTS_VOICES[0]);
  const [directorNotes, setDirectorNotes] = useState('Professional, authoritative corporate tone.');
  const [transcript, setTranscript] = useState("Greetings, this is a global institutional broadcast via the Lumina Quantum Node. System parity achieved.");
  const [isSynthesizing, setIsSynthesizing] = useState(false);
  
  // Multi-speaker state
  const [msConfig, setMsConfig] = useState({
    speaker1: 'Alex',
    voice1: 'Kore',
    speaker2: 'Lumina',
    voice2: 'Zephyr'
  });

  const [searchTerm, setSearchTerm] = useState('');
  const filteredLangs = useMemo(() => 
    TTS_LANGUAGES.filter(l => l.name.toLowerCase().includes(searchTerm.toLowerCase())), 
  [searchTerm]);

  const handleGenerateNotes = async () => {
    try {
      const response = await callGemini('gemini-3-flash-preview', 
        `Generate professional, detailed director's notes for a TTS reading in ${selectedLang.name}. The theme is "Quantum Financial Hub Status Update". Keep it to 2 sentences. tone should be ${selectedVoice.style}.`
      );
      setDirectorNotes(response.text || '');
    } catch (e) {
      console.error(e);
    }
  };

  const handleBroadcast = async () => {
    setIsSynthesizing(true);
    const success = await synthesizeSpeech({
      text: transcript,
      voiceName: selectedVoice.name,
      directorNotes: directorNotes,
      multiSpeaker: activeTab === 'multi' ? msConfig : undefined
    });
    setIsSynthesizing(false);
  };

  return (
    <div className="space-y-10 animate-in fade-in duration-700 pb-20 max-w-7xl mx-auto">
      {/* Header */}
      <div className="flex flex-col md:flex-row justify-between items-start md:items-end gap-6">
        <div>
          <h2 className="text-4xl font-black text-white italic tracking-tighter uppercase mb-2">Global <span className="text-blue-500 not-italic">Broadcast</span></h2>
          <p className="text-zinc-500 text-[10px] font-black uppercase tracking-[0.3em] flex items-center gap-2">
            <Globe size={12} className="text-blue-500" />
            Neural Voice Synthesis • 80+ Protocols Online
          </p>
        </div>
        <div className="flex bg-black p-1.5 rounded-2xl border border-zinc-900">
          <button 
            onClick={() => setActiveTab('single')}
            className={`px-8 py-2.5 rounded-xl text-[10px] font-black transition-all uppercase tracking-widest flex items-center gap-3 ${activeTab === 'single' ? 'bg-zinc-100 text-black' : 'text-zinc-500 hover:text-zinc-300'}`}
          >
            <Mic2 size={14} /> Single Node
          </button>
          <button 
            onClick={() => setActiveTab('multi')}
            className={`px-8 py-2.5 rounded-xl text-[10px] font-black transition-all uppercase tracking-widest flex items-center gap-3 ${activeTab === 'multi' ? 'bg-zinc-100 text-black' : 'text-zinc-500 hover:text-zinc-300'}`}
          >
            <Users size={14} /> Multi Speaker
          </button>
        </div>
      </div>

      <div className="grid grid-cols-12 gap-8">
        {/* Left: Language & Voice Registry */}
        <div className="col-span-12 lg:col-span-4 space-y-8">
          <div className="bg-zinc-950 border border-zinc-900 rounded-[3rem] p-8 shadow-2xl h-[calc(100vh-350px)] flex flex-col relative overflow-hidden">
             <div className="absolute top-0 right-0 p-6 opacity-[0.02]">
                <Globe size={180} />
             </div>
             
             <div className="relative z-10 mb-8 space-y-6">
                <div className="flex items-center justify-between">
                  <h3 className="text-white font-black text-xs uppercase tracking-widest italic">Protocol Registry</h3>
                  <span className="text-[9px] font-black text-zinc-500 bg-zinc-900 px-3 py-1 rounded-full">{filteredLangs.length} Nodes</span>
                </div>
                <div className="relative">
                  <Search size={14} className="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-600" />
                  <input 
                    value={searchTerm}
                    onChange={(e) => setSearchTerm(e.target.value)}
                    placeholder="Search Language Node..."
                    className="w-full bg-black border border-zinc-800 rounded-xl py-3 pl-11 pr-4 text-white text-[10px] font-black uppercase tracking-widest outline-none focus:border-blue-500 transition-all"
                  />
                </div>
             </div>

             <div className="flex-1 overflow-y-auto custom-scrollbar pr-2 space-y-2 relative z-10">
                {filteredLangs.map(lang => (
                  <button 
                    key={lang.code}
                    onClick={() => setSelectedLang(lang)}
                    className={`w-full flex items-center justify-between p-4 rounded-2xl border transition-all ${selectedLang.code === lang.code ? 'bg-blue-600/10 border-blue-500/50 text-blue-400' : 'bg-black border-zinc-900 text-zinc-600 hover:border-zinc-700'}`}
                  >
                    <span className="text-[10px] font-black uppercase tracking-widest">{lang.name}</span>
                    <span className="text-[9px] mono font-bold opacity-40">{lang.code.toUpperCase()}</span>
                  </button>
                ))}
             </div>
          </div>
        </div>

        {/* Center: Command Center */}
        <div className="col-span-12 lg:col-span-8 space-y-8">
          <div className="bg-zinc-950 border border-zinc-900 rounded-[3.5rem] p-10 shadow-2xl relative overflow-hidden group">
             <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_0%,_#1e1b4b_0%,_transparent_60%)] opacity-30"></div>
             
             <div className="relative z-10 space-y-10">
                <div className="flex items-center justify-between">
                   <div className="flex items-center gap-6">
                      <div className="w-16 h-16 bg-blue-600/10 text-blue-500 border border-blue-500/20 rounded-3xl flex items-center justify-center">
                        <Activity size={32} className="animate-pulse" />
                      </div>
                      <div>
                        <h4 className="text-2xl font-black text-white italic tracking-tighter uppercase leading-none">{selectedLang.name} Link</h4>
                        <p className="text-[10px] text-zinc-500 font-black uppercase tracking-widest mt-2">Active Node: {selectedLang.code.toUpperCase()}_PARITY</p>
                      </div>
                   </div>
                   <div className="flex items-center gap-4">
                      <div className="px-6 py-2 bg-black border border-zinc-900 rounded-full flex items-center gap-3">
                         <div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></div>
                         <span className="text-[9px] font-black text-zinc-500 uppercase tracking-widest">Neural Sync Stable</span>
                      </div>
                   </div>
                </div>

                <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
                   <div className="space-y-6">
                      <div className="space-y-3">
                        <label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Voice Profile</label>
                        <select 
                          value={selectedVoice.name}
                          onChange={(e) => setSelectedVoice(TTS_VOICES.find(v => v.name === e.target.value)!)}
                          className="w-full bg-black border border-zinc-900 rounded-2xl py-4 px-6 text-white text-[11px] font-black uppercase tracking-widest outline-none focus:border-blue-500 transition-all appearance-none cursor-pointer"
                        >
                          {TTS_VOICES.map(v => (
                            <option key={v.name} value={v.name}>{v.name} — {v.style}</option>
                          ))}
                        </select>
                      </div>
                      <div className="space-y-3">
                        <div className="flex justify-between items-end mb-1">
                          <label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Directorial Notes</label>
                          <button onClick={handleGenerateNotes} className="text-[9px] font-black text-blue-500 uppercase tracking-widest hover:text-white transition-colors flex items-center gap-2">
                             <Sparkles size={10} /> Neural Architect
                          </button>
                        </div>
                        <textarea 
                          value={directorNotes}
                          onChange={(e) => setDirectorNotes(e.target.value)}
                          className="w-full bg-black border border-zinc-900 rounded-2xl p-6 text-zinc-300 text-[11px] font-medium leading-relaxed italic h-32 outline-none focus:border-blue-500 transition-all resize-none"
                        />
                      </div>
                   </div>

                   <div className="space-y-3">
                      <label className="text-[10px] font-black text-zinc-600 uppercase tracking-widest ml-1">Transmission Script</label>
                      <div className="relative">
                        <textarea 
                          value={transcript}
                          onChange={(e) => setTranscript(e.target.value)}
                          className="w-full bg-black border-2 border-zinc-900 rounded-[2.5rem] p-8 text-white text-base font-bold leading-relaxed tracking-tight h-[280px] outline-none focus:border-blue-500 transition-all resize-none placeholder:text-zinc-800"
                          placeholder="Input the global broadcast content..."
                        />
                        <div className="absolute bottom-6 right-8 text-[9px] font-black text-zinc-700 uppercase tracking-widest">
                          {transcript.length} Characters
                        </div>
                      </div>
                   </div>
                </div>

                <div className="pt-4">
                  <button 
                    onClick={handleBroadcast}
                    disabled={isSynthesizing || !transcript.trim()}
                    className="w-full py-7 bg-blue-600 hover:bg-blue-500 disabled:bg-zinc-900 disabled:text-zinc-700 text-white rounded-[2.5rem] font-black text-sm uppercase tracking-[0.4em] transition-all flex items-center justify-center gap-6 shadow-2xl shadow-blue-900/40 group"
                  >
                    {isSynthesizing ? (
                      <>
                        <Loader2 className="animate-spin" size={24} />
                        <span>Generating Global Waveform...</span>
                      </>
                    ) : (
                      <>
                        <Zap size={24} className="group-hover:scale-125 transition-transform" />
                        <span>Execute Multi-Node Broadcast</span>
                        <ArrowRight size={20} className="group-hover:translate-x-3 transition-transform" />
                      </>
                    )}
                  </button>
                </div>
             </div>
          </div>

          <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
             <div className="bg-zinc-950 border border-zinc-900 rounded-[2.5rem] p-8 shadow-xl flex items-center gap-6 group hover:border-blue-500/20 transition-all">
                <div className="p-4 bg-blue-600/10 text-blue-500 rounded-2xl group-hover:scale-110 transition-transform">
                   <ShieldCheck size={24} />
                </div>
                <div>
                   <h5 className="text-white font-black text-xs uppercase italic">Verified Logic</h5>
                   <p className="text-[10px] text-zinc-600 font-bold uppercase mt-1">Zero-persistence audio packet shredding enabled.</p>
                </div>
             </div>
             <div className="bg-zinc-950 border border-zinc-900 rounded-[2.5rem] p-8 shadow-xl flex items-center gap-6 group hover:border-emerald-500/20 transition-all">
                <div className="p-4 bg-emerald-500/10 text-emerald-500 rounded-2xl group-hover:scale-110 transition-transform">
                   <Users size={24} />
                </div>
                <div>
                   <h5 className="text-white font-black text-xs uppercase italic">Directorial Access</h5>
                   <p className="text-[10px] text-zinc-600 font-bold uppercase mt-1">Fine-grained accent and pace control active.</p>
                </div>
             </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default Broadcast;