22 * Settings Page
33 * Application configuration
44 */
5+ import { useState } from 'react' ;
56import {
67 Sun ,
78 Moon ,
@@ -11,18 +12,26 @@ import {
1112 ExternalLink ,
1213 Key ,
1314 Download ,
15+ Copy ,
1416} from 'lucide-react' ;
1517import { Button } from '@/components/ui/button' ;
1618import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '@/components/ui/card' ;
1719import { Label } from '@/components/ui/label' ;
1820import { Switch } from '@/components/ui/switch' ;
1921import { Separator } from '@/components/ui/separator' ;
2022import { Badge } from '@/components/ui/badge' ;
23+ import { Input } from '@/components/ui/input' ;
24+ import { toast } from 'sonner' ;
2125import { useSettingsStore } from '@/stores/settings' ;
2226import { useGatewayStore } from '@/stores/gateway' ;
2327import { useUpdateStore } from '@/stores/update' ;
2428import { ProvidersSettings } from '@/components/settings/ProvidersSettings' ;
2529import { UpdateSettings } from '@/components/settings/UpdateSettings' ;
30+ type ControlUiInfo = {
31+ url : string ;
32+ token : string ;
33+ port : number ;
34+ } ;
2635
2736export function Settings ( ) {
2837 const {
@@ -35,16 +44,60 @@ export function Settings() {
3544 autoDownloadUpdate,
3645 setAutoDownloadUpdate,
3746 devModeUnlocked,
47+ setDevModeUnlocked,
3848 } = useSettingsStore ( ) ;
3949
4050 const { status : gatewayStatus , restart : restartGateway } = useGatewayStore ( ) ;
4151 const currentVersion = useUpdateStore ( ( state ) => state . currentVersion ) ;
52+ const [ controlUiInfo , setControlUiInfo ] = useState < ControlUiInfo | null > ( null ) ;
4253
4354 // Open developer console
44- const openDevConsole = ( ) => {
45- window . electron . openExternal ( 'http://localhost:18789' ) ;
55+ const openDevConsole = async ( ) => {
56+ try {
57+ const result = await window . electron . ipcRenderer . invoke ( 'gateway:getControlUiUrl' ) as {
58+ success : boolean ;
59+ url ?: string ;
60+ token ?: string ;
61+ port ?: number ;
62+ error ?: string ;
63+ } ;
64+ if ( result . success && result . url && result . token && typeof result . port === 'number' ) {
65+ setControlUiInfo ( { url : result . url , token : result . token , port : result . port } ) ;
66+ window . electron . openExternal ( result . url ) ;
67+ } else {
68+ console . error ( 'Failed to get Dev Console URL:' , result . error ) ;
69+ }
70+ } catch ( err ) {
71+ console . error ( 'Error opening Dev Console:' , err ) ;
72+ }
4673 } ;
47-
74+
75+ const refreshControlUiInfo = async ( ) => {
76+ try {
77+ const result = await window . electron . ipcRenderer . invoke ( 'gateway:getControlUiUrl' ) as {
78+ success : boolean ;
79+ url ?: string ;
80+ token ?: string ;
81+ port ?: number ;
82+ } ;
83+ if ( result . success && result . url && result . token && typeof result . port === 'number' ) {
84+ setControlUiInfo ( { url : result . url , token : result . token , port : result . port } ) ;
85+ }
86+ } catch {
87+ // Ignore refresh errors
88+ }
89+ } ;
90+
91+ const handleCopyGatewayToken = async ( ) => {
92+ if ( ! controlUiInfo ?. token ) return ;
93+ try {
94+ await navigator . clipboard . writeText ( controlUiInfo . token ) ;
95+ toast . success ( 'Gateway token copied' ) ;
96+ } catch ( error ) {
97+ toast . error ( `Failed to copy token: ${ String ( error ) } ` ) ;
98+ }
99+ } ;
100+
48101 return (
49102 < div className = "space-y-6 p-6" >
50103 < div >
@@ -198,31 +251,85 @@ export function Settings() {
198251 </ div >
199252 </ CardContent >
200253 </ Card >
254+
255+ { /* Advanced */ }
256+ < Card >
257+ < CardHeader >
258+ < CardTitle > Advanced</ CardTitle >
259+ < CardDescription > Power-user options</ CardDescription >
260+ </ CardHeader >
261+ < CardContent className = "space-y-4" >
262+ < div className = "flex items-center justify-between" >
263+ < div >
264+ < Label > Developer Mode</ Label >
265+ < p className = "text-sm text-muted-foreground" >
266+ Show developer tools and shortcuts
267+ </ p >
268+ </ div >
269+ < Switch
270+ checked = { devModeUnlocked }
271+ onCheckedChange = { setDevModeUnlocked }
272+ />
273+ </ div >
274+ </ CardContent >
275+ </ Card >
201276
202277 { /* Developer */ }
203278 { devModeUnlocked && (
204279 < Card >
205280 < CardHeader >
206281 < CardTitle > Developer</ CardTitle >
207- < CardDescription > Advanced options for developers</ CardDescription >
208- </ CardHeader >
209- < CardContent className = "space-y-4" >
210- < div className = "space-y-2" >
211- < Label > OpenClaw Console</ Label >
282+ < CardDescription > Advanced options for developers</ CardDescription >
283+ </ CardHeader >
284+ < CardContent className = "space-y-4" >
285+ < div className = "space-y-2" >
286+ < Label > OpenClaw Console</ Label >
287+ < p className = "text-sm text-muted-foreground" >
288+ Access the native OpenClaw management interface
289+ </ p >
290+ < Button variant = "outline" onClick = { openDevConsole } >
291+ < Terminal className = "h-4 w-4 mr-2" />
292+ Open Developer Console
293+ < ExternalLink className = "h-3 w-3 ml-2" />
294+ </ Button >
295+ < p className = "text-xs text-muted-foreground" >
296+ Opens the Control UI with gateway token injected
297+ </ p >
298+ < div className = "space-y-2 pt-2" >
299+ < Label > Gateway Token</ Label >
212300 < p className = "text-sm text-muted-foreground" >
213- Access the native OpenClaw management interface
214- </ p >
215- < Button variant = "outline" onClick = { openDevConsole } >
216- < Terminal className = "h-4 w-4 mr-2" />
217- Open Developer Console
218- < ExternalLink className = "h-3 w-3 ml-2" />
219- </ Button >
220- < p className = "text-xs text-muted-foreground" >
221- Opens http://localhost:18789 in your browser
301+ Paste this into Control UI settings if prompted
222302 </ p >
303+ < div className = "flex gap-2" >
304+ < Input
305+ readOnly
306+ value = { controlUiInfo ?. token || '' }
307+ placeholder = "Token unavailable"
308+ className = "font-mono"
309+ />
310+ < Button
311+ type = "button"
312+ variant = "outline"
313+ onClick = { refreshControlUiInfo }
314+ disabled = { ! devModeUnlocked }
315+ >
316+ < RefreshCw className = "h-4 w-4 mr-2" />
317+ Load
318+ </ Button >
319+ < Button
320+ type = "button"
321+ variant = "outline"
322+ onClick = { handleCopyGatewayToken }
323+ disabled = { ! controlUiInfo ?. token }
324+ >
325+ < Copy className = "h-4 w-4 mr-2" />
326+ Copy
327+ </ Button >
328+ </ div >
223329 </ div >
224- </ CardContent >
225- </ Card >
330+ </ div >
331+ </ CardContent >
332+ </ Card >
226333 ) }
227334
228335 { /* About */ }
0 commit comments