Shinhati2023 commited on
Commit
77cce7b
·
verified ·
1 Parent(s): 07df536

Update client/src/App.jsx

Browse files
Files changed (1) hide show
  1. client/src/App.jsx +117 -67
client/src/App.jsx CHANGED
@@ -1,23 +1,40 @@
1
- import React, { useState, useEffect } from 'react';
2
  import { createClient } from '@supabase/supabase-js';
3
 
4
  // --- CONFIG ---
5
  const SUPABASE_URL = "https://whpciwshxecjvalksicw.supabase.co";
6
- const SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndocGNpd3NoeGVjanZhbGtzaWN3Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzAxNDQyNjYsImV4cCI6MjA4NTcyMDI2Nn0.TwWAlQCUstvGykPhaDhPAVpTyg2MV7eltG7JFvjZ-zU"; // <--- PASTE KEY HERE
7
 
8
  const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
9
 
 
10
  const AI_MODELS = [
11
- { id: 'flux-schnell', name: 'FLUX.1 (Fast)', type: 'image' },
12
- { id: 'flux-dev', name: 'FLUX.1 (HD)', type: 'image' },
 
 
 
 
 
 
 
 
 
 
13
  ];
14
 
15
  function App() {
16
  const [session, setSession] = useState(null);
17
  const [prompt, setPrompt] = useState("");
18
- const [selectedModel, setSelectedModel] = useState(AI_MODELS[0].id);
 
19
  const [generating, setGenerating] = useState(false);
20
  const [imageSrc, setImageSrc] = useState(null);
 
 
 
 
 
21
 
22
  useEffect(() => {
23
  supabase.auth.getSession().then(({ data: { session } }) => setSession(session));
@@ -30,15 +47,43 @@ function App() {
30
  if (error) alert("Login failed: " + error.message);
31
  };
32
 
 
 
 
 
 
 
 
 
 
33
  const handleGenerate = async () => {
34
  if (!prompt) return;
35
  setGenerating(true);
36
- // Clear previous image so we can see the bear
37
  setImageSrc(null);
38
 
39
  try {
40
  if (!window.puter.auth.isSignedIn()) await window.puter.auth.signIn();
41
- const imgElement = await window.puter.ai.txt2img(prompt);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  setImageSrc(imgElement.src);
43
  } catch (e) {
44
  alert("Error: " + e.message);
@@ -47,7 +92,6 @@ function App() {
47
  }
48
  };
49
 
50
- // --- LOGIN SCREEN ---
51
  if (!session) {
52
  return (
53
  <div style={{
@@ -56,103 +100,109 @@ function App() {
56
  background: 'radial-gradient(circle at center, #1a1a1a 0%, #000 100%)',
57
  padding: '20px', textAlign: 'center'
58
  }}>
59
-
60
- {/* BRANDING */}
61
- <div className="logo-glass">
62
- <div className="logo-text">TEK IMAGE GEN</div>
63
- </div>
64
  <div className="sub-brand">TEKTREY BUILDs</div>
65
-
66
- {/* INFO TEXT */}
67
- <p style={{
68
- color: '#888', fontSize: '0.85rem', maxWidth: '300px',
69
- lineHeight: '1.4', marginBottom: '30px', fontFamily: 'monospace'
70
- }}>
71
- Generate images unlimitedly but consider 3 every 5mins. Enjoy.
72
  </p>
73
-
74
- {/* THE DREAM BUTTON */}
75
- <button onClick={handleLogin} style={{padding: '18px 50px', fontSize: '1.2rem'}}>
76
- LET'S DREAM
77
- </button>
78
  </div>
79
  );
80
  }
81
 
82
- // --- GENERATION HUB ---
83
  return (
84
  <div style={{
85
  minHeight: '100vh', display: 'flex', flexDirection: 'column',
86
- maxWidth: '100%', margin: '0 auto', paddingBottom: '120px'
87
  }}>
88
 
89
  {/* HEADER */}
90
  <div style={{
91
  padding: '15px 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
92
  background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(10px)',
93
- position: 'sticky', top: 0, zIndex: 10, borderBottom: '1px solid #333'
94
  }}>
95
- <div className="logo-glass" style={{padding: '5px 15px', borderRadius: '8px', marginBottom: 0, animation: 'none'}}>
96
- <span style={{fontSize: '0.9rem', fontWeight: 'bold', color: 'white'}}>TEK IMAGE GEN</span>
 
 
 
 
 
 
 
 
97
  </div>
98
-
99
- <select
100
- value={selectedModel}
101
- onChange={(e) => setSelectedModel(e.target.value)}
102
- style={{padding: '8px', fontSize: '0.8rem', width: '120px'}}
103
- >
104
- {AI_MODELS.map(m => <option key={m.id} value={m.id} style={{color:'black'}}>{m.name}</option>)}
105
- </select>
106
  </div>
107
 
108
- {/* VIEWPORT (BEAR vs IMAGE) */}
109
  <div style={{flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px', flexDirection: 'column'}}>
110
-
111
  {generating ? (
112
- /* 🐻 THE DANCING BEAR 🐻 */
113
  <div style={{textAlign: 'center'}}>
114
  <div className="dancing-bear">🐻</div>
115
  <p style={{marginTop: '20px', color: '#55ff55', letterSpacing: '2px', animation: 'pulse 1s infinite'}}>DREAMING...</p>
116
  </div>
117
  ) : imageSrc ? (
118
- /* THE RESULT */
119
  <div className="glass-panel" style={{padding: '10px', borderRadius: '16px', maxWidth: '100%'}}>
120
- <img src={imageSrc} alt="Generated" style={{width: '100%', borderRadius: '8px', display: 'block'}} />
121
  </div>
122
  ) : (
123
- /* EMPTY STATE */
124
  <div style={{textAlign: 'center', opacity: 0.3}}>
125
  <div style={{fontSize: '50px', marginBottom: '10px'}}>❖</div>
126
- <p>READY FOR INPUT</p>
127
  </div>
128
  )}
129
  </div>
130
 
131
- {/* INPUT DOCK */}
132
  <div style={{
133
  position: 'fixed', bottom: 0, left: 0, right: 0,
134
- padding: '15px 20px 30px 20px',
135
- background: 'linear-gradient(to top, #000 90%, transparent)',
136
- display: 'flex', alignItems: 'flex-end', gap: '10px', zIndex: 20
137
  }}>
138
- <textarea
139
- value={prompt} onChange={e => setPrompt(e.target.value)}
140
- placeholder="COMMAND: Cyberpunk street..."
141
- style={{
142
- flex: 1, padding: '15px', fontSize: '16px', height: '50px',
143
- borderRadius: '25px', resize: 'none'
144
- }}
145
- />
146
- <button
147
- onClick={handleGenerate} disabled={generating}
148
- style={{
149
- height: '54px', width: '54px', borderRadius: '50%',
150
- display: 'flex', alignItems: 'center', justifyContent: 'center',
151
- background: generating ? '#333' : '#55ff55'
152
- }}
153
- >
154
- {generating ? '...' : ''}
155
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  </div>
157
  </div>
158
  );
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
  import { createClient } from '@supabase/supabase-js';
3
 
4
  // --- CONFIG ---
5
  const SUPABASE_URL = "https://whpciwshxecjvalksicw.supabase.co";
6
+ const SUPABASE_ANON_KEY = "PASTE_YOUR_ANON_KEY_HERE"; // <--- PASTE KEY HERE
7
 
8
  const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
9
 
10
+ // UPDATED MODEL LIST (Includes Klien 2)
11
  const AI_MODELS = [
12
+ { id: 'black-forest-labs/FLUX.1-schnell', name: 'FLUX.1 (Fast)' },
13
+ { id: 'black-forest-labs/FLUX.1-dev', name: 'FLUX.1 (HD)' },
14
+ { id: 'black-forest-labs/FLUX.2-klein-4B', name: 'FLUX.2 Klein (Fast/Edit)' }, // Good for edits
15
+ { id: 'black-forest-labs/FLUX.2-klein-9B', name: 'FLUX.2 Klein (Pro/Edit)' },
16
+ ];
17
+
18
+ const ASPECT_RATIOS = [
19
+ { id: '1:1', name: 'Square (1:1)', promptSuffix: '--ar 1:1' },
20
+ { id: '16:9', name: 'Landscape (16:9)', promptSuffix: '--ar 16:9' },
21
+ { id: '9:16', name: 'Portrait (9:16)', promptSuffix: '--ar 9:16' },
22
+ { id: '21:9', name: 'Cinema (21:9)', promptSuffix: '--ar 21:9' },
23
+ { id: '3:2', name: 'Photo (3:2)', promptSuffix: '--ar 3:2' },
24
  ];
25
 
26
  function App() {
27
  const [session, setSession] = useState(null);
28
  const [prompt, setPrompt] = useState("");
29
+ const [selectedModel, setSelectedModel] = useState(AI_MODELS[2].id); // Default to Klein
30
+ const [selectedRatio, setSelectedRatio] = useState(ASPECT_RATIOS[0].id);
31
  const [generating, setGenerating] = useState(false);
32
  const [imageSrc, setImageSrc] = useState(null);
33
+
34
+ // NEW: Image Input for Editing
35
+ const [inputImage, setInputImage] = useState(null); // Blob for API
36
+ const [previewUrl, setPreviewUrl] = useState(null); // URL for UI
37
+ const fileInputRef = useRef(null);
38
 
39
  useEffect(() => {
40
  supabase.auth.getSession().then(({ data: { session } }) => setSession(session));
 
47
  if (error) alert("Login failed: " + error.message);
48
  };
49
 
50
+ // Handle File Upload
51
+ const handleFileChange = (e) => {
52
+ const file = e.target.files[0];
53
+ if (file) {
54
+ setInputImage(file);
55
+ setPreviewUrl(URL.createObjectURL(file));
56
+ }
57
+ };
58
+
59
  const handleGenerate = async () => {
60
  if (!prompt) return;
61
  setGenerating(true);
 
62
  setImageSrc(null);
63
 
64
  try {
65
  if (!window.puter.auth.isSignedIn()) await window.puter.auth.signIn();
66
+
67
+ const ratioConfig = ASPECT_RATIOS.find(r => r.id === selectedRatio);
68
+ const finalPrompt = `${prompt} ${ratioConfig.promptSuffix}`;
69
+
70
+ let imgElement;
71
+
72
+ // MODE A: IMAGE EDITING (img2img)
73
+ if (inputImage) {
74
+ // Puter txt2img accepts input_image for editing
75
+ imgElement = await window.puter.ai.txt2img(finalPrompt, {
76
+ model: selectedModel,
77
+ input_image: inputImage
78
+ });
79
+ }
80
+ // MODE B: TEXT TO IMAGE
81
+ else {
82
+ imgElement = await window.puter.ai.txt2img(finalPrompt, {
83
+ model: selectedModel
84
+ });
85
+ }
86
+
87
  setImageSrc(imgElement.src);
88
  } catch (e) {
89
  alert("Error: " + e.message);
 
92
  }
93
  };
94
 
 
95
  if (!session) {
96
  return (
97
  <div style={{
 
100
  background: 'radial-gradient(circle at center, #1a1a1a 0%, #000 100%)',
101
  padding: '20px', textAlign: 'center'
102
  }}>
103
+ <div className="logo-glass"><div className="logo-text">TEK IMAGE GEN</div></div>
 
 
 
 
104
  <div className="sub-brand">TEKTREY BUILDs</div>
105
+ <p style={{color: '#888', fontSize: '0.85rem', marginBottom: '30px', fontFamily: 'monospace'}}>
106
+ Generate unlimitedly. Edit Images. 3 every 5mins.
 
 
 
 
 
107
  </p>
108
+ <button onClick={handleLogin} style={{padding: '18px 50px', fontSize: '1.2rem'}}>LET'S DREAM</button>
 
 
 
 
109
  </div>
110
  );
111
  }
112
 
 
113
  return (
114
  <div style={{
115
  minHeight: '100vh', display: 'flex', flexDirection: 'column',
116
+ maxWidth: '100%', margin: '0 auto', paddingBottom: '140px'
117
  }}>
118
 
119
  {/* HEADER */}
120
  <div style={{
121
  padding: '15px 20px', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
122
  background: 'rgba(0,0,0,0.6)', backdropFilter: 'blur(10px)',
123
+ position: 'sticky', top: 0, zIndex: 10, borderBottom: '1px solid #333', gap: '10px'
124
  }}>
125
+ <div className="logo-glass" style={{padding: '5px 15px', borderRadius: '8px', marginBottom: 0, animation: 'none', flexShrink: 0}}>
126
+ <span style={{fontSize: '0.9rem', fontWeight: 'bold', color: 'white'}}>TEK</span>
127
+ </div>
128
+ <div style={{display: 'flex', gap: '8px'}}>
129
+ <select value={selectedRatio} onChange={(e) => setSelectedRatio(e.target.value)} style={{padding: '8px', fontSize: '0.75rem', width: 'auto', maxWidth: '80px'}}>
130
+ {ASPECT_RATIOS.map(r => <option key={r.id} value={r.id} style={{color:'black'}}>{r.name}</option>)}
131
+ </select>
132
+ <select value={selectedModel} onChange={(e) => setSelectedModel(e.target.value)} style={{padding: '8px', fontSize: '0.75rem', width: 'auto', maxWidth: '100px'}}>
133
+ {AI_MODELS.map(m => <option key={m.id} value={m.id} style={{color:'black'}}>{m.name}</option>)}
134
+ </select>
135
  </div>
 
 
 
 
 
 
 
 
136
  </div>
137
 
138
+ {/* VIEWPORT */}
139
  <div style={{flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '20px', flexDirection: 'column'}}>
 
140
  {generating ? (
 
141
  <div style={{textAlign: 'center'}}>
142
  <div className="dancing-bear">🐻</div>
143
  <p style={{marginTop: '20px', color: '#55ff55', letterSpacing: '2px', animation: 'pulse 1s infinite'}}>DREAMING...</p>
144
  </div>
145
  ) : imageSrc ? (
 
146
  <div className="glass-panel" style={{padding: '10px', borderRadius: '16px', maxWidth: '100%'}}>
147
+ <img src={imageSrc} alt="Generated" style={{maxWidth: '100%', maxHeight: '60vh', borderRadius: '8px'}} />
148
  </div>
149
  ) : (
 
150
  <div style={{textAlign: 'center', opacity: 0.3}}>
151
  <div style={{fontSize: '50px', marginBottom: '10px'}}>❖</div>
152
+ <p>READY</p>
153
  </div>
154
  )}
155
  </div>
156
 
157
+ {/* INPUT DOCK & UPLOAD */}
158
  <div style={{
159
  position: 'fixed', bottom: 0, left: 0, right: 0,
160
+ padding: '10px 20px 30px 20px',
161
+ background: 'linear-gradient(to top, #000 95%, transparent)',
162
+ zIndex: 20
163
  }}>
164
+ {/* HOLO BOX (Mini Uploader) */}
165
+ {previewUrl && (
166
+ <div style={{position: 'absolute', bottom: '90px', left: '20px', zIndex: 30}}>
167
+ <div className="holo-box" style={{padding: '5px', marginBottom: 0, display: 'inline-block'}}>
168
+ <img src={previewUrl} style={{height: '60px', borderRadius: '4px'}} />
169
+ <button onClick={() => {setPreviewUrl(null); setInputImage(null);}} style={{position: 'absolute', top: -5, right: -5, padding: '2px 6px', fontSize: '10px', background: 'red'}}>X</button>
170
+ </div>
171
+ </div>
172
+ )}
173
+
174
+ <div style={{display: 'flex', alignItems: 'flex-end', gap: '10px'}}>
175
+ {/* UPLOAD BUTTON */}
176
+ <div onClick={() => fileInputRef.current.click()} style={{
177
+ height: '50px', width: '50px', borderRadius: '50%', background: '#333',
178
+ display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', border: '1px solid #555'
179
+ }}>
180
+ <span style={{fontSize: '24px'}}>📷</span>
181
+ </div>
182
+ <input type="file" ref={fileInputRef} onChange={handleFileChange} style={{display: 'none'}} accept="image/*" />
183
+
184
+ {/* TEXT INPUT */}
185
+ <textarea
186
+ value={prompt} onChange={e => setPrompt(e.target.value)}
187
+ placeholder={previewUrl ? "COMMAND: Change background to..." : "COMMAND: Cyberpunk street..."}
188
+ style={{
189
+ flex: 1, padding: '15px', fontSize: '16px', height: '50px',
190
+ borderRadius: '25px', resize: 'none'
191
+ }}
192
+ />
193
+
194
+ {/* RUN BUTTON */}
195
+ <button
196
+ onClick={handleGenerate} disabled={generating}
197
+ style={{
198
+ height: '54px', width: '54px', borderRadius: '50%',
199
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
200
+ background: generating ? '#333' : '#55ff55'
201
+ }}
202
+ >
203
+ {generating ? '...' : '▶'}
204
+ </button>
205
+ </div>
206
  </div>
207
  </div>
208
  );