File size: 18,354 Bytes
4c3bb95
 
 
b973bea
4c3bb95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34b4396
4c3bb95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ae5f108
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
import React, { useState, useRef } from 'react';
import { Layers, Database, Key, CheckCircle, FileJson, ArrowRight, ShieldCheck, Zap, Sparkles, Mail, Globe, Scale, Share2, Copy, FileText, MonitorPlay, Github, Terminal, Package, Rocket, Server, FolderTree, HelpCircle, AlertCircle, XCircle, Mic, Play, Pause, Loader2, Volume2, Info } from 'lucide-react';
import CodeBlock from './CodeBlock';
import { generateProposalScript, generateSpeech } from '../geminiService';

const DeveloperHub: React.FC = () => {
  const [activeSection, setActiveSection] = useState<'architecture' | 'deployment' | 'compliance' | 'repo' | 'pitch'>('pitch');
  const [presentationMode, setPresentationMode] = useState(false);
  
  // Pitch Studio States
  const [pitchScript, setPitchScript] = useState<string>('');
  const [isGeneratingPitch, setIsGeneratingPitch] = useState(false);
  const [isSpeaking, setIsSpeaking] = useState(false);
  const [pitchType, setPitchType] = useState<'executive' | 'walkthrough'>('executive');

  const handleCreatePitch = async () => {
    setIsGeneratingPitch(true);
    const script = await generateProposalScript(pitchType);
    setPitchScript(script);
    setIsGeneratingPitch(false);
  };

  const handlePlayPitch = async () => {
    if (!pitchScript) return;
    setIsSpeaking(true);
    const base64Audio = await generateSpeech(pitchScript);
    
    if (base64Audio) {
      const audioData = decode(base64Audio);
      const audioCtx = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 24000 });
      const audioBuffer = await decodeAudioData(audioData, audioCtx, 24000, 1);
      
      const source = audioCtx.createBufferSource();
      source.buffer = audioBuffer;
      source.connect(audioCtx.destination);
      source.onended = () => setIsSpeaking(false);
      source.start();
    } else {
      setIsSpeaking(false);
    }
  };

  function decode(base64: string) {
    const binaryString = atob(base64);
    const len = binaryString.length;
    const bytes = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes;
  }

  async function decodeAudioData(
    data: Uint8Array,
    ctx: AudioContext,
    sampleRate: number,
    numChannels: number,
  ): Promise<AudioBuffer> {
    const dataInt16 = new Int16Array(data.buffer);
    const frameCount = dataInt16.length / numChannels;
    const buffer = ctx.createBuffer(numChannels, frameCount, sampleRate);

    for (let channel = 0; channel < numChannels; channel++) {
      const channelData = buffer.getChannelData(channel);
      for (let i = 0; i < frameCount; i++) {
        channelData[i] = dataInt16[i * numChannels + channel] / 32768.0;
      }
    }
    return buffer;
  }

  const dockerfileCode = `FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]`;

  const renderSectionContent = (section: string) => {
    switch(section) {
        case 'pitch': return (
          <div className="space-y-8 animate-in fade-in duration-500">
            <div className="flex items-start justify-between">
              <div>
                <h2 className="text-3xl font-display font-bold text-white mb-2">Pitch Studio</h2>
                <p className="text-gray-400 max-w-2xl">
                  Generate a professional narrative for your proposal video. Use the AI Narrator to provide the voiceover.
                </p>
              </div>
              <div className="p-3 bg-brand-500/20 rounded-2xl border border-brand-500/30">
                <Mic size={32} className="text-brand-500" />
              </div>
            </div>

            <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
              <div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/5 space-y-4">
                <h3 className="text-lg font-bold text-white flex items-center gap-2">
                  <FileText size={18} className="text-brand-400" />
                  Script Config
                </h3>
                
                <div className="flex gap-2 p-1 bg-black/40 rounded-xl border border-white/5 mb-4">
                  <button 
                    onClick={() => setPitchType('executive')}
                    className={`flex-1 py-2 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all ${pitchType === 'executive' ? 'bg-brand-500 text-white' : 'text-gray-500 hover:text-white'}`}
                  >
                    Executive Pitch
                  </button>
                  <button 
                    onClick={() => setPitchType('walkthrough')}
                    className={`flex-1 py-2 text-[10px] font-black uppercase tracking-widest rounded-lg transition-all ${pitchType === 'walkthrough' ? 'bg-brand-500 text-white' : 'text-gray-500 hover:text-white'}`}
                  >
                    Walkthrough V.O.
                  </button>
                </div>

                <button 
                  onClick={handleCreatePitch}
                  disabled={isGeneratingPitch}
                  className="w-full py-4 bg-brand-600 hover:bg-brand-500 text-white rounded-xl font-black uppercase tracking-widest text-[10px] flex items-center justify-center gap-3 transition-all active:scale-95 disabled:opacity-50"
                >
                  {isGeneratingPitch ? <Loader2 className="animate-spin" size={16} /> : <Sparkles size={16} />}
                  Generate {pitchType === 'executive' ? 'Proposal' : 'Walkthrough'} Script
                </button>

                {pitchScript && (
                  <div className="mt-6 space-y-4 animate-in slide-in-from-top-2">
                    <div className="p-4 bg-black/40 rounded-xl border border-white/10 max-h-[250px] overflow-y-auto">
                      <p className="text-xs text-gray-400 leading-relaxed whitespace-pre-wrap italic">
                        "{pitchScript}"
                      </p>
                    </div>
                    <button 
                      onClick={handlePlayPitch}
                      disabled={isSpeaking}
                      className="w-full py-4 bg-white text-black hover:bg-brand-500 hover:text-white rounded-xl font-black uppercase tracking-widest text-[10px] flex items-center justify-center gap-3 transition-all disabled:opacity-50"
                    >
                      {isSpeaking ? <Loader2 className="animate-spin" size={16} /> : <Volume2 size={16} />}
                      Play Narrator
                    </button>
                  </div>
                )}
              </div>

              <div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/5 flex flex-col justify-center items-center text-center space-y-4">
                 <div className="w-16 h-16 rounded-full bg-blue-500/20 flex items-center justify-center border border-blue-500/30 mb-2">
                    <MonitorPlay size={32} className="text-blue-500" />
                 </div>
                 <h3 className="text-lg font-bold text-white">Video Ready</h3>
                 <p className="text-xs text-gray-500 max-w-xs">
                   Turn on your screen recorder, activate "Presentation Mode", and let the AI handle the narration while you navigate the tabs.
                 </p>
                 <div className="flex gap-2">
                    <div className="px-3 py-1 bg-white/5 rounded text-[10px] text-gray-400 font-mono">1080p Export</div>
                    <div className="px-3 py-1 bg-white/5 rounded text-[10px] text-gray-400 font-mono">Dolby Audio</div>
                 </div>
              </div>
            </div>
          </div>
        );
        case 'repo': return (
             <div className="space-y-8 animate-in fade-in duration-500">
                <div>
                   <h2 className="text-2xl font-display font-bold text-white mb-2">Repository Setup</h2>
                   <p className="text-gray-400">
                     GitHub is the standard for hosting the Shib Insider technical ecosystem. 
                   </p>
                </div>
                <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                  <div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
                    <h3 className="text-brand-400 font-bold mb-3 flex items-center gap-2">
                      <Package size={18} /> Frontend Hub
                    </h3>
                    <p className="text-xs text-gray-500 mb-4">React 18 + Vite. Optimized for L2 wallet interactions.</p>
                    <div className="text-[10px] font-mono bg-black/40 p-2 rounded border border-white/5 text-gray-400">
                      git init<br/>
                      git add .<br/>
                      git commit -m "initial protocol"
                    </div>
                  </div>
                  <div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
                    <h3 className="text-blue-400 font-bold mb-3 flex items-center gap-2">
                      <Terminal size={18} /> Backend Node
                    </h3>
                    <p className="text-xs text-gray-500 mb-4">Python + Flask. Handles Beehiiv RSS & DB verification.</p>
                    <div className="text-[10px] font-mono bg-black/40 p-2 rounded border border-white/5 text-gray-400">
                      python -m venv venv<br/>
                      pip install flask feedparser
                    </div>
                  </div>
                </div>
             </div>
        );
        case 'deployment': return (
             <div className="space-y-8 animate-in fade-in duration-500">
                <div>
                  <h2 className="text-2xl font-display font-bold text-white mb-2">Deploying to Hugging Face</h2>
                  <p className="text-gray-400">Host your dashboard on Hugging Face Spaces using the Docker SDK for maximum reliability.</p>
                </div>
                
                <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
                  <div className="lg:col-span-2 space-y-6">
                    <div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/10">
                      <h3 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
                        <FileCode2 size={18} className="text-blue-400" /> Dockerfile
                      </h3>
                      <CodeBlock language="dockerfile" code={dockerfileCode} filename="Dockerfile" />
                    </div>

                    <div className="glass-panel p-6 rounded-2xl bg-white/[0.02] border-white/10">
                      <h3 className="text-lg font-bold text-white mb-4">Step-by-Step Instructions</h3>
                      <ol className="space-y-4">
                        <li className="flex gap-4">
                          <div className="w-6 h-6 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-black shrink-0">1</div>
                          <div>
                            <p className="text-sm font-bold text-white">Create Space</p>
                            <p className="text-xs text-gray-500">Go to HF Spaces, select "New Space", choose <strong>Docker</strong> as the SDK.</p>
                          </div>
                        </li>
                        <li className="flex gap-4">
                          <div className="w-6 h-6 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-black shrink-0">2</div>
                          <div>
                            <p className="text-sm font-bold text-white">Config Secrets</p>
                            <p className="text-xs text-gray-500">Under "Settings" → "Variables and Secrets", add your <code>API_KEY</code> from Google AI Studio.</p>
                          </div>
                        </li>
                        <li className="flex gap-4">
                          <div className="w-6 h-6 rounded-full bg-brand-500 flex items-center justify-center text-[10px] font-black shrink-0">3</div>
                          <div>
                            <p className="text-sm font-bold text-white">Push Files</p>
                            <p className="text-xs text-gray-500">Push your <code>App.tsx</code>, <code>index.html</code>, and <code>Dockerfile</code>. HF will build automatically.</p>
                          </div>
                        </li>
                      </ol>
                    </div>
                  </div>

                  <div className="space-y-6">
                    <div className="p-6 bg-blue-900/10 border border-blue-500/20 rounded-2xl">
                      <h4 className="text-blue-400 font-black text-[10px] uppercase tracking-widest mb-3 flex items-center gap-2">
                        <Info size={14} /> Deployment Tips
                      </h4>
                      <ul className="space-y-3 text-[11px] text-gray-400">
                        <li className="flex gap-2"><div className="w-1 h-1 rounded-full bg-blue-500 mt-1.5 shrink-0"></div> Ensure <code>dist</code> folder matches Docker config.</li>
                        <li className="flex gap-2"><div className="w-1 h-1 rounded-full bg-blue-500 mt-1.5 shrink-0"></div> Set Space to "Public" for subscriber access.</li>
                        <li className="flex gap-2"><div className="w-1 h-1 rounded-full bg-blue-500 mt-1.5 shrink-0"></div> Port 7860 is the default HF port.</li>
                      </ul>
                    </div>
                  </div>
                </div>
             </div>
        );
        case 'compliance': return (
             <div className="space-y-8 animate-in fade-in duration-500">
                <h2 className="text-2xl font-display font-bold text-white">Platform Compliance Strategy</h2>
                <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
                        <h3 className="text-lg font-bold text-brand-400 mb-2">1. Gamified Discovery</h3>
                        <p className="text-sm text-gray-400 leading-relaxed">Frame activity as exploring the newsletter value rather than "pay-per-click" to maintain platform health.</p>
                    </div>
                    <div className="p-6 bg-gray-900/50 rounded-xl border border-gray-700">
                        <h3 className="text-lg font-bold text-green-400 mb-2">2. On-Chain Verifiability</h3>
                        <p className="text-sm text-gray-400 leading-relaxed">Using Shibarium ensures that every reward given is transparent and traceable back to genuine engagement.</p>
                    </div>
                </div>
             </div>
        );
        default: return <div className="text-gray-500">Section details loaded in full codebase.</div>;
    }
  }

  const SidebarButton = ({ section, icon: Icon, label }: { section: typeof activeSection, icon: any, label: string }) => (
    <button
      onClick={() => setActiveSection(section)}
      className={`w-full text-left px-4 py-3 rounded-xl transition-all flex items-center gap-3 ${
        activeSection === section 
          ? 'bg-brand-500/10 text-brand-400 border border-brand-500/20' 
          : 'text-gray-400 hover:bg-gray-800'
      }`}
    >
      <Icon size={18} />
      <span className="font-semibold">{label}</span>
    </button>
  );

  return (
    <div className="grid grid-cols-1 lg:grid-cols-4 gap-8 h-full">
      <div className="lg:col-span-1 flex flex-col gap-6">
        <div className="space-y-2">
            {!presentationMode && (
                <>
                    <SidebarButton section="pitch" icon={Mic} label="Pitch Studio" />
                    <SidebarButton section="repo" icon={Github} label="Repository Setup" />
                    <SidebarButton section="deployment" icon={Rocket} label="Deploy to HF" />
                    <SidebarButton section="compliance" icon={Scale} label="Compliance" />
                </>
            )}
            
            <div className="pt-6 border-t border-gray-800 space-y-3">
                <button
                    onClick={() => setPresentationMode(!presentationMode)}
                    className={`w-full py-3 px-4 rounded-xl font-bold flex items-center justify-center gap-2 transition-all ${
                        presentationMode 
                        ? 'bg-brand-600 text-white shadow-lg' 
                        : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
                    }`}
                >
                    <MonitorPlay size={18} />
                    {presentationMode ? 'Exit Presentation' : 'Presentation Mode'}
                </button>
            </div>
        </div>
      </div>

      <div className="lg:col-span-3">
        <div className="glass-panel p-8 rounded-2xl min-h-[600px] relative overflow-y-auto max-h-[75vh]">
          {presentationMode ? (
              <div className="space-y-16">
                  <div className="pb-4 border-b border-gray-800">
                      <h1 className="text-3xl font-display font-bold text-white mb-2">Technical Specification: Weekly Trail</h1>
                      <p className="text-gray-400">Project Overview and Presentation Mode active.</p>
                  </div>
                  {renderSectionContent('pitch')}
                  <hr className="border-gray-800" />
                  {renderSectionContent('deployment')}
                  <hr className="border-gray-800" />
                  {renderSectionContent('repo')}
              </div>
          ) : (
              renderSectionContent(activeSection)
          )}
        </div>
      </div>
    </div>
  );
};

// Internal icon fix for the file
const FileCode2 = (props: any) => (
  <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 22h14a2 2 0 0 0 2-2V7.5L14.5 2H6a2 2 0 0 0-2 2v4"/><polyline points="14 2 14 8 20 8"/><path d="m3 15 2 2-2 2"/><path d="m9 15-2 2 2 2"/></svg>
);

export default DeveloperHub;