Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect, useRef } from 'react'; | |
| import { createClient } from '@supabase/supabase-js'; | |
| // --- CONFIG --- | |
| const SUPABASE_URL = "https://whpciwshxecjvalksicw.supabase.co"; | |
| const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndocGNpd3NoeGVjanZhbGtzaWN3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAxNDQyNjYsImV4cCI6MjA4NTcyMDI2Nn0.TwWAlQCUstvGykPhaDhPAVpTyg2MV7eltG7JFvjZ-zU"; // <--- PASTE KEY HERE | |
| const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY); | |
| const AI_MODELS = [ | |
| { id: 'openai/dall-e-3', name: 'TEK 3 (Best)' }, | |
| { id: 'google/imagen-4.0-preview', name: 'TEK 4 HD (Real)' }, | |
| { id: 'gemini-2.5-flash-image', name: 'TEK NANO HD (Fast)' }, | |
| ]; | |
| const ASPECT_RATIOS = [ | |
| { id: '1:1', name: 'Square' }, | |
| { id: '16:9', name: 'Wide' }, | |
| { id: '9:16', name: 'Portrait' }, | |
| ]; | |
| function App() { | |
| const [session, setSession] = useState(null); | |
| const [prompt, setPrompt] = useState(""); | |
| const [selectedModel, setSelectedModel] = useState(AI_MODELS[0].id); | |
| const [selectedRatio, setSelectedRatio] = useState(ASPECT_RATIOS[0].id); | |
| const [generating, setGenerating] = useState(false); | |
| const [imageSrc, setImageSrc] = useState(null); | |
| const [inputImage, setInputImage] = useState(null); | |
| const [previewUrl, setPreviewUrl] = useState(null); | |
| const fileInputRef = useRef(null); | |
| const [deferredPrompt, setDeferredPrompt] = useState(null); | |
| const [showInstallBanner, setShowInstallBanner] = useState(false); | |
| const [isIOS, setIsIOS] = useState(false); | |
| useEffect(() => { | |
| supabase.auth.getSession().then(({ data: { session } }) => setSession(session)); | |
| const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => setSession(session)); | |
| const isIosDevice = /ipad|iphone|ipod/.test(navigator.userAgent.toLowerCase()) && !window.MSStream; | |
| const isInStandaloneMode = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; | |
| if (isIosDevice && !isInStandaloneMode) { | |
| setIsIOS(true); | |
| setTimeout(() => setShowInstallBanner(true), 3000); | |
| } | |
| window.addEventListener('beforeinstallprompt', (e) => { | |
| e.preventDefault(); | |
| setDeferredPrompt(e); | |
| if (!isInStandaloneMode) setShowInstallBanner(true); | |
| }); | |
| return () => subscription.unsubscribe(); | |
| }, []); | |
| const handleInstallClick = async () => { | |
| if (deferredPrompt) { | |
| deferredPrompt.prompt(); | |
| setDeferredPrompt(null); | |
| setShowInstallBanner(false); | |
| } | |
| }; | |
| const handleLogin = async () => { | |
| const { error } = await supabase.auth.signInAnonymously(); | |
| if (error) alert("Login failed: " + error.message); | |
| }; | |
| const handleFileChange = (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| setInputImage(file); | |
| setPreviewUrl(URL.createObjectURL(file)); | |
| } | |
| }; | |
| const handleGenerate = async () => { | |
| if (!prompt) return; | |
| setGenerating(true); | |
| setImageSrc(null); | |
| try { | |
| if (!window.puter.auth.isSignedIn()) await window.puter.auth.signIn(); | |
| const ratioText = selectedRatio === '1:1' ? '' : ` --ar ${selectedRatio}`; | |
| const finalPrompt = prompt + ratioText; | |
| let imgElement; | |
| if (inputImage && !selectedModel.includes('dall-e')) { | |
| imgElement = await window.puter.ai.txt2img(finalPrompt, { model: selectedModel, image: inputImage }); | |
| } else { | |
| if (inputImage) alert("Note: TEK 3 ignores input images for now."); | |
| imgElement = await window.puter.ai.txt2img(finalPrompt, { model: selectedModel }); | |
| } | |
| setImageSrc(imgElement.src); | |
| } catch (e) { | |
| alert("Error: " + (e.message || "Failed")); | |
| } finally { | |
| setGenerating(false); | |
| } | |
| }; | |
| // --- NEW START SCREEN (TEK DREAMS) --- | |
| if (!session) { | |
| return ( | |
| <div style={{ | |
| height: '100vh', display: 'flex', flexDirection: 'column', | |
| alignItems: 'center', justifyContent: 'center' | |
| }}> | |
| {/* The Outer Glass Box */} | |
| <div className="dream-stack-outer"> | |
| {/* The Inner Glass Box */} | |
| <div className="dream-stack-middle"> | |
| <div className="dream-title">TEK DREAMS</div> | |
| <button onClick={handleLogin} className="dream-btn"> | |
| Enter | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| // --- MAIN APP --- | |
| return ( | |
| <div style={{ | |
| minHeight: '100vh', display: 'flex', flexDirection: 'column', | |
| paddingBottom: '150px', boxSizing: 'border-box' | |
| }}> | |
| {/* HEADER */} | |
| <div style={{ | |
| padding: '15px 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', | |
| position: 'sticky', top: 0, zIndex: 10, gap: '20px' | |
| }}> | |
| <div className="logo-expand-container"> | |
| <span className="logo-main">TEK</span> | |
| <span className="logo-hidden">BUILD</span> | |
| </div> | |
| <div style={{display: 'flex', gap: '8px', flexShrink: 0}}> | |
| <select className="glass-pill" value={selectedRatio} onChange={(e) => setSelectedRatio(e.target.value)}> | |
| {ASPECT_RATIOS.map(r => <option key={r.id} value={r.id}>{r.name}</option>)} | |
| </select> | |
| <select className="glass-pill" value={selectedModel} onChange={(e) => setSelectedModel(e.target.value)} style={{maxWidth:'130px'}}> | |
| {AI_MODELS.map(m => <option key={m.id} value={m.id}>{m.name}</option>)} | |
| </select> | |
| </div> | |
| </div> | |
| {/* VIEWPORT */} | |
| <div style={{ | |
| flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', | |
| padding: '20px', flexDirection: 'column', minHeight: 0 | |
| }}> | |
| {generating ? ( | |
| <div className="glass-panel" style={{padding: '20px', borderRadius:'50%'}}> | |
| <div style={{fontSize:'40px', animation:'spin 2s infinite'}}>⏳</div> | |
| </div> | |
| ) : imageSrc ? ( | |
| <div className="glass-panel" style={{padding: '10px', maxWidth: '100%', maxHeight: '100%', display:'flex'}}> | |
| <img src={imageSrc} alt="Generated" style={{maxWidth: '100%', maxHeight: '65vh', borderRadius: '16px', objectFit:'contain'}} /> | |
| </div> | |
| ) : ( | |
| <div style={{opacity: 0.1, transform:'scale(0.8)'}}>❖</div> | |
| )} | |
| </div> | |
| {/* PWA BANNER */} | |
| {showInstallBanner && ( | |
| <div className="pwa-banner"> | |
| {isIOS ? ( | |
| <div style={{display:'flex', alignItems:'center', gap:'10px'}}> | |
| <span style={{fontSize:'24px'}}>📲</span> | |
| <div style={{textAlign:'left', fontSize:'14px'}}>Tap Share then "Add to Home Screen"</div> | |
| <button onClick={() => setShowInstallBanner(false)} style={{background:'none', border:'none', color:'#aaa', fontSize:'20px'}}>×</button> | |
| </div> | |
| ) : ( | |
| <div style={{display:'flex', alignItems:'center', justifyContent:'space-between', width:'100%'}}> | |
| <span style={{fontSize:'14px', fontWeight:'500'}}>Install App?</span> | |
| <div style={{display:'flex', gap:'10px'}}> | |
| <button onClick={() => setShowInstallBanner(false)} className="glass-pill" style={{padding:'6px 12px', background:'transparent'}}>Later</button> | |
| <button onClick={handleInstallClick} className="run-btn" style={{borderRadius:'20px', padding:'6px 15px', height:'auto', width:'auto', fontSize:'14px'}}>Install</button> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| {/* INPUT DOCK */} | |
| <div style={{ | |
| position: 'fixed', bottom: 0, left: 0, right: 0, | |
| padding: '10px 20px 30px 20px', zIndex: 20, | |
| paddingBottom: 'calc(30px + env(safe-area-inset-bottom))' | |
| }}> | |
| <div style={{ | |
| textAlign: 'center', fontSize: '12px', color: 'rgba(255,255,255,0.6)', | |
| marginBottom: '10px', letterSpacing: '1px', textTransform: 'uppercase' | |
| }}> | |
| {generating ? "Dreaming..." : (imageSrc ? "Done" : "Ready to dream")} | |
| </div> | |
| {previewUrl && ( | |
| <div style={{position: 'absolute', bottom: '110px', left: '20px'}}> | |
| <div className="holo-box" style={{padding:'5px', marginBottom:0, display:'inline-block'}}> | |
| <img src={previewUrl} style={{height: '60px', borderRadius: '4px'}} /> | |
| <button onClick={() => {setPreviewUrl(null); setInputImage(null);}} style={{position: 'absolute', top: -8, right: -8, borderRadius:'50%', width:'20px', height:'20px', border:'none', background:'red', color:'white', fontSize:'12px', display:'flex', alignItems:'center', justifyContent:'center'}}>×</button> | |
| </div> | |
| </div> | |
| )} | |
| <div className="glass-panel" style={{ | |
| padding: '10px', display: 'flex', alignItems: 'flex-end', gap: '10px', | |
| borderRadius: '32px', background: 'rgba(0,0,0,0.4)' | |
| }}> | |
| <div onClick={() => fileInputRef.current.click()} style={{ | |
| height: '48px', width: '48px', borderRadius: '50%', background: 'rgba(255,255,255,0.1)', | |
| display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', flexShrink: 0 | |
| }}> | |
| <span style={{fontSize: '20px'}}>📷</span> | |
| </div> | |
| <input type="file" ref={fileInputRef} onChange={handleFileChange} style={{display: 'none'}} accept="image/*" /> | |
| <textarea | |
| value={prompt} onChange={e => setPrompt(e.target.value)} | |
| placeholder="Describe your image..." | |
| style={{ | |
| flex: 1, padding: '14px', fontSize: '16px', height: '50px', | |
| borderRadius: '24px', resize: 'none', border:'none', background:'transparent' | |
| }} | |
| /> | |
| <button className="run-btn" onClick={handleGenerate} disabled={generating} style={{height: '48px', width: '48px', flexShrink: 0}}> | |
| {generating ? '...' : '➜'} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default App; | |