Spaces:
Sleeping
Sleeping
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;
|