@@ -6,23 +6,21 @@ import { ChatPanel } from '@/components/ChatPanel';
66import { AgentPanel } from '@/components/AgentPanel' ;
77import { ModelSelector } from '@/components/ModelSelector' ;
88import { AgentTask , VrlmEvent } from '@/lib/vrlm/types' ;
9- import { PanelRightOpen , PanelRightClose , ArrowLeft , Settings , Eye , EyeOff , AlertTriangle } from 'lucide-react' ;
9+ import { PanelRightOpen , PanelRightClose , ArrowLeft , Settings , CheckCircle , AlertTriangle , ExternalLink } from 'lucide-react' ;
1010import { SimulatedVrlmRuntime } from '@/lib/vrlm/simulated-runtime' ;
1111
1212const STORAGE_KEY = 'agentnetes-settings' ;
1313
1414type Mode = 'real' | 'simulation' ;
1515
1616interface DemoSettings {
17- googleApiKey : string ;
1817 sandboxProvider : 'docker' | 'local' ;
1918 repoUrl : string ;
2019 plannerModel : string ;
2120 workerModel : string ;
2221}
2322
2423const DEFAULT_SETTINGS : DemoSettings = {
25- googleApiKey : '' ,
2624 sandboxProvider : 'docker' ,
2725 repoUrl : 'https://github.com/expressjs/express' ,
2826 plannerModel : 'google/gemini-2.5-flash' ,
@@ -41,9 +39,16 @@ function saveSettings(s: DemoSettings) {
4139}
4240
4341// ── Settings modal ────────────────────────────────────────────────────────────
44- function SettingsModal ( { current, onClose } : { current : DemoSettings ; onClose : ( s : DemoSettings ) => void } ) {
42+ function SettingsModal ( {
43+ current,
44+ apiKeySet,
45+ onClose,
46+ } : {
47+ current : DemoSettings ;
48+ apiKeySet : boolean ;
49+ onClose : ( s : DemoSettings ) => void ;
50+ } ) {
4551 const [ s , setS ] = useState < DemoSettings > ( current ) ;
46- const [ showKey , setShowKey ] = useState ( false ) ;
4752 const set = ( patch : Partial < DemoSettings > ) => setS ( prev => ( { ...prev , ...patch } ) ) ;
4853
4954 function save ( ) {
@@ -63,36 +68,43 @@ function SettingsModal({ current, onClose }: { current: DemoSettings; onClose: (
6368 < div className = "px-6 pt-5 pb-4 border-b border-white/[0.07] flex items-center justify-between" >
6469 < div >
6570 < h2 className = "text-white font-bold text-base" > Runtime Settings</ h2 >
66- < p className = "text-[11px] text-white/35 font-mono mt-0.5" > Saved in your browser · never sent to our servers </ p >
71+ < p className = "text-[11px] text-white/35 font-mono mt-0.5" > Saved in your browser</ p >
6772 </ div >
6873 < button onClick = { ( ) => onClose ( current ) } className = "text-white/30 hover:text-white/60 transition-colors text-xl leading-none" > ✕</ button >
6974 </ div >
7075
7176 < div className = "px-6 py-5 space-y-5" >
7277
73- { /* API Key */ }
74- < div >
75- < label className = "text-[10px] font-mono text-white/35 uppercase tracking-widest block mb-1.5" >
76- Google API Key < span className = "text-red-400/80" > *</ span >
77- </ label >
78- < div className = "flex items-center gap-2" >
79- < input
80- type = { showKey ? 'text' : 'password' }
81- value = { s . googleApiKey }
82- onChange = { e => set ( { googleApiKey : e . target . value } ) }
83- placeholder = "AIzaSy..."
84- autoFocus
85- className = "flex-1 rounded-lg border border-white/15 px-3 py-2 text-sm font-mono outline-none focus:border-purple-500/50 transition-colors"
86- style = { { background : 'var(--bg-subtle)' , color : 'rgb(var(--fg))' } }
87- />
88- < button onClick = { ( ) => setShowKey ( v => ! v ) } className = "text-white/30 hover:text-white/60 transition-colors p-1" >
89- { showKey ? < EyeOff size = { 15 } /> : < Eye size = { 15 } /> }
90- </ button >
78+ { /* API Key status */ }
79+ < div className = { `flex items-start gap-3 rounded-xl px-4 py-3 border ${ apiKeySet ? 'border-green-500/20 bg-green-500/5' : 'border-yellow-500/20 bg-yellow-500/5' } ` } >
80+ { apiKeySet
81+ ? < CheckCircle size = { 15 } className = "text-green-400 mt-0.5 shrink-0" />
82+ : < AlertTriangle size = { 15 } className = "text-yellow-400 mt-0.5 shrink-0" /> }
83+ < div >
84+ < p className = "text-[12px] font-mono font-medium" style = { { color : apiKeySet ? 'rgb(134 239 172)' : 'rgb(253 224 71)' } } >
85+ GOOGLE_API_KEY { apiKeySet ? 'is set' : 'not set' }
86+ </ p >
87+ { ! apiKeySet && (
88+ < div className = "mt-1.5 space-y-1.5" >
89+ < p className = "text-[11px] text-white/40" >
90+ Option 1 — export in your shell, then restart:
91+ </ p >
92+ < div className = "rounded-lg px-3 py-2 font-mono text-[11px] text-green-300/80 border border-white/10" style = { { background : 'var(--bg-subtle)' } } >
93+ export GOOGLE_API_KEY=your_key_here
94+ </ div >
95+ < p className = "text-[11px] text-white/40" >
96+ Option 2 — add to < span className = "font-mono text-white/60" > .env.local</ span > and restart:
97+ </ p >
98+ < div className = "rounded-lg px-3 py-2 font-mono text-[11px] text-green-300/80 border border-white/10" style = { { background : 'var(--bg-subtle)' } } >
99+ GOOGLE_API_KEY=your_key_here
100+ </ div >
101+ < a href = "https://aistudio.google.com/apikey" target = "_blank" rel = "noreferrer"
102+ className = "inline-flex items-center gap-0.5 text-purple-400/70 hover:text-purple-400 transition-colors text-[11px]" >
103+ Get a free key at aistudio.google.com < ExternalLink size = { 10 } />
104+ </ a >
105+ </ div >
106+ ) }
91107 </ div >
92- < a href = "https://aistudio.google.com/apikey" target = "_blank" rel = "noreferrer"
93- className = "text-[11px] text-purple-400/60 hover:text-purple-400 mt-1.5 inline-block transition-colors" >
94- Get a free key at aistudio.google.com →
95- </ a >
96108 </ div >
97109
98110 { /* Repo URL */ }
@@ -105,6 +117,7 @@ function SettingsModal({ current, onClose }: { current: DemoSettings; onClose: (
105117 value = { s . repoUrl }
106118 onChange = { e => set ( { repoUrl : e . target . value } ) }
107119 placeholder = "https://github.com/owner/repo"
120+ autoFocus
108121 className = "w-full rounded-lg border border-white/15 px-3 py-2 text-sm font-mono outline-none focus:border-purple-500/50 transition-colors"
109122 style = { { background : 'var(--bg-subtle)' , color : 'rgb(var(--fg))' } }
110123 />
@@ -158,7 +171,7 @@ function SettingsModal({ current, onClose }: { current: DemoSettings; onClose: (
158171 </ button >
159172 < button
160173 onClick = { save }
161- disabled = { s . googleApiKey . trim ( ) . length < 10 || ! s . repoUrl . trim ( ) . startsWith ( 'http' ) }
174+ disabled = { ! s . repoUrl . trim ( ) . startsWith ( 'http' ) }
162175 className = "px-5 py-2 rounded-lg text-sm font-semibold transition-all disabled:opacity-30 disabled:cursor-not-allowed"
163176 style = { { background : 'linear-gradient(135deg, #a855f7, #ec4899)' , color : '#fff' } } >
164177 Save
@@ -182,12 +195,17 @@ export default function DemoPage() {
182195 const [ showSettings , setShowSettings ] = useState ( false ) ;
183196 const [ mode , setMode ] = useState < Mode > ( 'real' ) ;
184197 const [ settings , setSettings ] = useState < DemoSettings > ( DEFAULT_SETTINGS ) ;
198+ const [ apiKeySet , setApiKeySet ] = useState ( false ) ;
185199 const mounted = useRef ( false ) ;
186200
187201 useEffect ( ( ) => {
188202 if ( mounted . current ) return ;
189203 mounted . current = true ;
190204 setSettings ( loadSettings ( ) ) ;
205+ fetch ( '/api/config' )
206+ . then ( r => r . json ( ) )
207+ . then ( d => setApiKeySet ( ! ! d . googleApiKeySet ) )
208+ . catch ( ( ) => { /* ignore */ } ) ;
191209 } , [ ] ) ;
192210
193211 const processEvent = useCallback ( ( event : VrlmEvent , finalRef : { content : string } ) => {
@@ -244,7 +262,6 @@ export default function DemoPage() {
244262 headers : { 'Content-Type' : 'application/json' } ,
245263 body : JSON . stringify ( {
246264 message,
247- googleApiKey : settings . googleApiKey ,
248265 sandboxProvider : settings . sandboxProvider ,
249266 repoUrl : settings . repoUrl ,
250267 plannerModel : settings . plannerModel ,
@@ -274,14 +291,12 @@ export default function DemoPage() {
274291 }
275292 } , [ mode , settings , processEvent ] ) ;
276293
277- const keySet = settings . googleApiKey . trim ( ) . length > 10 ;
278-
279294 return (
280295 < div className = "h-screen overflow-hidden flex flex-col" style = { { background : 'var(--bg-base)' , color : 'rgb(var(--fg))' } } >
281296
282297 { /* Settings modal */ }
283298 { showSettings && (
284- < SettingsModal current = { settings } onClose = { s => { setSettings ( s ) ; setShowSettings ( false ) ; } } />
299+ < SettingsModal current = { settings } apiKeySet = { apiKeySet } onClose = { s => { setSettings ( s ) ; setShowSettings ( false ) ; } } />
285300 ) }
286301
287302 { /* Header */ }
@@ -310,17 +325,20 @@ export default function DemoPage() {
310325 </ button >
311326 </ div >
312327
313- { /* Settings button (Real mode only) — shows key status */ }
328+ { /* Settings button (Real mode only) */ }
314329 { mode === 'real' && (
315330 < button
316331 onClick = { ( ) => setShowSettings ( true ) }
317332 className = "flex items-center gap-1.5 text-[11px] font-mono border rounded-lg px-2.5 py-1 transition-all"
318- style = { { background : 'var(--bg-subtle)' , color : keySet ? 'rgb(var(--fg))' : undefined } }
333+ style = { { background : 'var(--bg-subtle)' } }
319334 title = "Runtime settings" >
320- < Settings size = { 11 } className = { keySet ? ' text-white/50' : 'text-purple-400/70' } />
321- < span className = { keySet ? ' text-white/45' : 'text-purple-400/70' } >
322- { keySet ? ` ${ settings . sandboxProvider } · $ {settings . repoUrl . replace ( 'https://github.com/' , '' ) } ` : 'Configure' }
335+ < Settings size = { 11 } className = " text-white/50" />
336+ < span className = " text-white/45" >
337+ { settings . sandboxProvider } · { settings . repoUrl . replace ( 'https://github.com/' , '' ) }
323338 </ span >
339+ { ! apiKeySet && (
340+ < span className = "text-yellow-400/70 text-[10px] font-mono" > · no key</ span >
341+ ) }
324342 </ button >
325343 ) }
326344
0 commit comments