@@ -14,17 +14,67 @@ export const appName = "Paint by Text";
1414export const appSubtitle = "Edit your photos using written instructions, with the help of an AI." ;
1515export const appMetaDescription = "Edit your photos using written instructions, with the help of an AI." ;
1616
17+ function TokenModal ( { onTokenSet } ) {
18+ const [ token , setToken ] = useState ( "" ) ;
19+ return (
20+ < div className = "fixed inset-0 z-50 flex items-center justify-center pointer-events-auto" >
21+ < form
22+ className = "w-full max-w-md mx-auto p-8 bg-white rounded-xl shadow-2xl border border-gray-200 relative animate-in fade-in"
23+ onSubmit = { e => {
24+ e . preventDefault ( ) ;
25+ if ( token ) {
26+ localStorage . setItem ( "replicateApiToken" , token ) ;
27+ onTokenSet ( token ) ;
28+ }
29+ } }
30+ aria-modal = "true"
31+ role = "dialog"
32+ >
33+ < h2 className = "text-2xl font-bold mb-2 text-center" > Enter your Replicate API token</ h2 >
34+ < p className = "mb-4 text-center text-gray-600" >
35+ Get a free token at { " " }
36+ < a
37+ href = "https://replicate.com/account/api-tokens?new-token-name=paint-by-text-kontext"
38+ target = "_blank"
39+ rel = "noopener noreferrer"
40+ className = "underline text-blue-600"
41+ >
42+ replicate.com/account/api-tokens
43+ </ a >
44+ </ p >
45+ < input
46+ className = "w-full border rounded p-3 mb-4 text-lg"
47+ type = "text"
48+ value = { token }
49+ onChange = { e => setToken ( e . target . value ) }
50+ placeholder = "r8_..."
51+ required
52+ autoFocus
53+ />
54+ < button className = "w-full bg-black text-white px-4 py-3 rounded text-lg font-semibold" type = "submit" >
55+ Start painting
56+ </ button >
57+ </ form >
58+ </ div >
59+ ) ;
60+ }
61+
1762export default function Home ( ) {
1863 const [ events , setEvents ] = useState ( [ ] ) ;
1964 const [ predictions , setPredictions ] = useState ( [ ] ) ;
2065 const [ error , setError ] = useState ( null ) ;
2166 const [ isProcessing , setIsProcessing ] = useState ( false ) ;
2267 const [ seed ] = useState ( getRandomSeed ( ) ) ;
2368 const [ initialPrompt , setInitialPrompt ] = useState ( seed . prompt ) ;
69+ const [ apiToken , setApiToken ] = useState ( null ) ;
70+ // Removed showTokenForm state
2471
2572 // set the initial image from a random seed
2673 useEffect ( ( ) => {
2774 setEvents ( [ { image : seed . image } ] ) ;
75+ const storedToken = localStorage . getItem ( "replicateApiToken" ) ;
76+ if ( storedToken ) setApiToken ( storedToken ) ;
77+ // Removed setShowTokenForm
2878 } , [ seed . image ] ) ;
2979
3080 const handleImageDropped = async ( image ) => {
@@ -39,6 +89,7 @@ export default function Home() {
3989
4090 const handleSubmit = async ( e ) => {
4191 e . preventDefault ( ) ;
92+ if ( ! apiToken ) return ;
4293
4394 const prompt = e . target . prompt . value ;
4495 const lastImage = events . findLast ( ( ev ) => ev . image ) ?. image ;
@@ -60,6 +111,7 @@ export default function Home() {
60111 method : "POST" ,
61112 headers : {
62113 "Content-Type" : "application/json" ,
114+ "x-replicate-api-token" : apiToken ,
63115 } ,
64116 body : JSON . stringify ( body ) ,
65117 } ) ;
@@ -75,7 +127,9 @@ export default function Home() {
75127 prediction . status !== "failed"
76128 ) {
77129 await sleep ( 500 ) ;
78- const response = await fetch ( "/api/predictions/" + prediction . id ) ;
130+ const response = await fetch ( "/api/predictions/" + prediction . id , {
131+ headers : { "x-replicate-api-token" : apiToken } ,
132+ } ) ;
79133 prediction = await response . json ( ) ;
80134 if ( response . status !== 200 ) {
81135 setError ( prediction . detail ) ;
@@ -105,8 +159,19 @@ export default function Home() {
105159 setInitialPrompt ( seed . prompt ) ;
106160 } ;
107161
162+ const handleTokenSet = ( token ) => {
163+ setApiToken ( token ) ;
164+ // Removed setShowTokenForm
165+ } ;
166+
167+ const handleLogout = ( ) => {
168+ localStorage . removeItem ( "replicateApiToken" ) ;
169+ setApiToken ( null ) ;
170+ // Removed setShowTokenForm
171+ } ;
172+
108173 return (
109- < div >
174+ < div className = "relative" >
110175 < Head >
111176 < title > { appName } </ title >
112177 < meta name = "description" content = { appMetaDescription } />
@@ -115,42 +180,52 @@ export default function Home() {
115180 < meta property = "og:image" content = "https://paintbytext.chat/opengraph.jpg" />
116181 </ Head >
117182
118- < main className = "container max-w-[700px] mx-auto p-5" >
119- < hgroup >
120- < h1 className = "text-center text-5xl font-bold m-6" > { appName } </ h1 >
121- < p className = "text-center text-xl opacity-60 m-6" >
122- { appSubtitle }
123- </ p >
124- </ hgroup >
125-
126- < Messages
127- events = { events }
128- isProcessing = { isProcessing }
129- onUndo = { ( index ) => {
130- setInitialPrompt ( events [ index - 1 ] . prompt ) ;
131- setEvents (
132- events . slice ( 0 , index - 1 ) . concat ( events . slice ( index + 1 ) )
133- ) ;
134- } }
135- />
136-
137- < PromptForm
138- initialPrompt = { initialPrompt }
139- isFirstPrompt = { events . length === 1 }
140- onSubmit = { handleSubmit }
141- disabled = { isProcessing }
142- />
143-
144- < div className = "mx-auto w-full" >
145- { error && < p className = "bold text-red-500 pb-5" > { error } </ p > }
146- </ div >
147-
148- < Footer
149- events = { events }
150- startOver = { startOver }
151- handleImageDropped = { handleImageDropped }
152- />
183+ < main className = { `container max-w-[700px] mx-auto p-5 transition-filter duration-300 ${ ! apiToken ? 'filter blur-sm brightness-75 pointer-events-none select-none' : '' } ` } >
184+ { ! apiToken ? null : (
185+ < >
186+ < div className = "flex justify-end mb-4" >
187+ < button className = "text-sm underline text-blue-600" onClick = { handleLogout } >
188+ Log out / Change token
189+ </ button >
190+ </ div >
191+ < hgroup >
192+ < h1 className = "text-center text-5xl font-bold m-6" > { appName } </ h1 >
193+ < p className = "text-center text-xl opacity-60 m-6" >
194+ { appSubtitle }
195+ </ p >
196+ </ hgroup >
197+
198+ < Messages
199+ events = { events }
200+ isProcessing = { isProcessing }
201+ onUndo = { ( index ) => {
202+ setInitialPrompt ( events [ index - 1 ] . prompt ) ;
203+ setEvents (
204+ events . slice ( 0 , index - 1 ) . concat ( events . slice ( index + 1 ) )
205+ ) ;
206+ } }
207+ />
208+
209+ < PromptForm
210+ initialPrompt = { initialPrompt }
211+ isFirstPrompt = { events . length === 1 }
212+ onSubmit = { handleSubmit }
213+ disabled = { isProcessing }
214+ />
215+
216+ < div className = "mx-auto w-full" >
217+ { error && < p className = "bold text-red-500 pb-5" > { error } </ p > }
218+ </ div >
219+
220+ < Footer
221+ events = { events }
222+ startOver = { startOver }
223+ handleImageDropped = { handleImageDropped }
224+ />
225+ </ >
226+ ) }
153227 </ main >
228+ { ! apiToken && < TokenModal onTokenSet = { handleTokenSet } /> }
154229 </ div >
155230 ) ;
156231}
0 commit comments