Skip to content

Commit 78925e5

Browse files
committed
refactor: enhance agent connection prompt and local gateway discovery
- Updated the AgentConnectPrompt component to improve user experience during connection attempts, including clearer messaging for connection status. - Simplified the connection logic by removing unnecessary localStorage handling and introducing a manual connection option. - Enhanced the GatewayProvider to automatically discover a local gateway and store its URL in localStorage if available. - Improved error handling and user feedback for connection failures.
1 parent 4f90aa1 commit 78925e5

2 files changed

Lines changed: 92 additions & 79 deletions

File tree

components/agent-panel.tsx

Lines changed: 73 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,13 @@ interface ChatMessage {
4040
}
4141

4242
function AgentConnectPrompt() {
43-
const { status, error, connect, gatewayUrl } = useGateway()
44-
const [url, setUrl] = useState('')
43+
const { status, error, connect } = useGateway()
44+
const [showManual, setShowManual] = useState(false)
45+
const [url, setUrl] = useState('ws://localhost:18789')
4546
const [password, setPassword] = useState('')
46-
const [showPassword, setShowPassword] = useState(false)
4747

4848
const isConnecting = status === 'connecting' || status === 'authenticating'
4949

50-
useEffect(() => {
51-
try {
52-
const savedUrl = localStorage.getItem('code-flow:gateway-url')
53-
if (savedUrl && !url) setUrl(savedUrl)
54-
} catch {}
55-
if (gatewayUrl && !url) setUrl(gatewayUrl)
56-
}, [gatewayUrl])
57-
5850
const handleConnect = () => {
5951
if (!url.trim()) return
6052
connect(url.trim(), password)
@@ -64,81 +56,84 @@ function AgentConnectPrompt() {
6456
<div className="flex flex-col items-center justify-center text-center py-8 px-4">
6557
<div className="relative mb-5">
6658
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[color-mix(in_srgb,var(--brand)_20%,transparent)] to-[color-mix(in_srgb,var(--brand)_6%,transparent)] border border-[color-mix(in_srgb,var(--brand)_25%,transparent)] flex items-center justify-center shadow-lg">
67-
<Icon icon="lucide:cpu" width={28} height={28} className="text-[var(--brand)]" />
59+
<Icon icon={isConnecting ? 'lucide:loader-2' : 'lucide:cpu'} width={28} height={28} className={`text-[var(--brand)] ${isConnecting ? 'animate-spin' : ''}`} />
6860
</div>
69-
<span className={`absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 rounded-full border-2 border-[var(--sidebar-bg)] ${
61+
<span className={`absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 rounded-full border-2 border-[var(--bg)] ${
7062
isConnecting ? 'bg-[var(--warning,#eab308)] animate-pulse' : 'bg-[var(--text-disabled)]'
7163
}`} />
7264
</div>
7365

74-
<h3 className="text-[16px] font-semibold text-[var(--text-primary)] mb-1.5">Connect to Gateway</h3>
75-
<p className="text-[13px] text-[var(--text-tertiary)] leading-relaxed mb-5 max-w-[280px]">
76-
Your OpenClaw gateway powers the AI agent. Connect to enable completions, chat, and slash commands.
77-
</p>
78-
79-
<div className="w-full max-w-[300px] space-y-2.5">
80-
<input
81-
type="text"
82-
value={url || 'ws://openclaw.local:18789'}
83-
onChange={e => setUrl(e.target.value)}
84-
onKeyDown={e => { if (e.key === 'Enter') handleConnect() }}
85-
placeholder="ws://openclaw.local:18789"
86-
className="w-full px-3 py-2.5 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-[13px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--border-focus)] transition-colors"
87-
disabled={isConnecting}
88-
/>
89-
<div className="relative">
90-
<input
91-
type={showPassword ? 'text' : 'password'}
92-
value={password}
93-
onChange={e => setPassword(e.target.value)}
94-
onKeyDown={e => { if (e.key === 'Enter') handleConnect() }}
95-
placeholder="Password (optional)"
96-
className="w-full px-3 py-2.5 pr-9 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-[13px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--border-focus)] transition-colors"
97-
disabled={isConnecting}
98-
/>
99-
<button
100-
onClick={() => setShowPassword(v => !v)}
101-
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] cursor-pointer p-0.5"
102-
tabIndex={-1}
103-
>
104-
<Icon icon={showPassword ? 'lucide:eye-off' : 'lucide:eye'} width={14} height={14} />
105-
</button>
106-
</div>
107-
<button
108-
onClick={handleConnect}
109-
disabled={!url.trim() || isConnecting}
110-
className="w-full flex items-center justify-center gap-2 py-2.5 rounded-lg text-[13px] font-medium transition-all cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed"
111-
style={{
112-
backgroundColor: 'var(--brand)',
113-
color: 'var(--brand-contrast, #fff)',
114-
}}
115-
>
116-
{isConnecting ? (
117-
<Icon icon="lucide:loader-2" width={14} height={14} className="animate-spin" />
118-
) : (
119-
<Icon icon="lucide:plug" width={14} height={14} />
66+
{isConnecting ? (
67+
<>
68+
<h3 className="text-[16px] font-semibold text-[var(--text-primary)] mb-1.5">Connecting…</h3>
69+
<p className="text-[13px] text-[var(--text-tertiary)]">Looking for OpenClaw gateway</p>
70+
</>
71+
) : (
72+
<>
73+
<h3 className="text-[16px] font-semibold text-[var(--text-primary)] mb-1.5">Gateway not found</h3>
74+
<p className="text-[13px] text-[var(--text-tertiary)] leading-relaxed mb-4 max-w-[280px]">
75+
Make sure OpenClaw is running on this machine.
76+
</p>
77+
78+
<div className="space-y-2.5 w-full max-w-[280px]">
79+
<button
80+
onClick={() => connect('ws://localhost:18789', '')}
81+
className="w-full flex items-center justify-center gap-2 py-2.5 rounded-lg text-[13px] font-medium transition-all cursor-pointer"
82+
style={{ backgroundColor: 'var(--brand)', color: 'var(--brand-contrast, #fff)' }}
83+
>
84+
<Icon icon="lucide:refresh-cw" width={14} height={14} />
85+
Retry connection
86+
</button>
87+
88+
<button
89+
onClick={() => setShowManual(v => !v)}
90+
className="w-full flex items-center justify-center gap-1.5 py-2 text-[12px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors cursor-pointer"
91+
>
92+
<Icon icon="lucide:settings-2" width={12} height={12} />
93+
{showManual ? 'Hide' : 'Manual connection'}
94+
</button>
95+
96+
{showManual && (
97+
<div className="space-y-2 pt-1">
98+
<input
99+
type="text"
100+
value={url}
101+
onChange={e => setUrl(e.target.value)}
102+
onKeyDown={e => { if (e.key === 'Enter') handleConnect() }}
103+
placeholder="ws://localhost:18789"
104+
className="w-full px-3 py-2 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-[12px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--border-focus)]"
105+
/>
106+
<input
107+
type="password"
108+
value={password}
109+
onChange={e => setPassword(e.target.value)}
110+
onKeyDown={e => { if (e.key === 'Enter') handleConnect() }}
111+
placeholder="Password (if set)"
112+
className="w-full px-3 py-2 rounded-lg bg-[var(--bg)] border border-[var(--border)] text-[12px] font-mono text-[var(--text-primary)] placeholder:text-[var(--text-disabled)] outline-none focus:border-[var(--border-focus)]"
113+
/>
114+
<button
115+
onClick={handleConnect}
116+
disabled={!url.trim()}
117+
className="w-full py-2 rounded-lg text-[12px] font-medium bg-[var(--bg-subtle)] text-[var(--text-secondary)] hover:bg-[var(--bg-tertiary)] transition-colors cursor-pointer disabled:opacity-50"
118+
>
119+
Connect
120+
</button>
121+
</div>
122+
)}
123+
</div>
124+
125+
{error && (
126+
<div className="flex items-start gap-2 mt-3 text-[11px] text-[var(--color-deletions)] max-w-[280px] text-left">
127+
<Icon icon="lucide:alert-circle" width={12} height={12} className="shrink-0 mt-0.5" />
128+
<span>{error}</span>
129+
</div>
120130
)}
121-
{isConnecting ? 'Connecting…' : 'Connect'}
122-
</button>
123-
</div>
124131

125-
{status === 'error' && error && (
126-
<div className="flex items-start gap-2 mt-4 text-[12px] text-[var(--color-deletions)] max-w-[300px] text-left">
127-
<Icon icon="lucide:alert-circle" width={14} height={14} className="shrink-0 mt-0.5" />
128-
<span className="leading-relaxed">{error}</span>
129-
</div>
132+
<p className="mt-5 text-[11px] text-[var(--text-disabled)]">
133+
Run <code className="px-1 py-0.5 bg-[var(--bg-secondary)] rounded text-[var(--brand)]">openclaw gateway start</code> to start
134+
</p>
135+
</>
130136
)}
131-
132-
<div className="mt-6 space-y-2 text-[12px] text-[var(--text-disabled)] max-w-[260px]">
133-
<div className="flex items-center gap-2">
134-
<Icon icon="lucide:shield" width={13} height={13} />
135-
<span>Runs locally. Code never leaves your machine</span>
136-
</div>
137-
<div className="flex items-center gap-2">
138-
<Icon icon="lucide:zap" width={13} height={13} />
139-
<span>Works with any LLM provider</span>
140-
</div>
141-
</div>
142137
</div>
143138
)
144139
}

context/gateway-context.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,28 @@ export function GatewayProvider({ children }: { children: React.ReactNode }) {
448448
localStorage.setItem(STORAGE_URL, config.url)
449449
localStorage.setItem(STORAGE_PASS, config.password)
450450
doConnect(config.url, config.password)
451+
return
451452
}
452453
} catch {
453-
// Config not found or parse error — that's fine, gateway is optional
454+
// Config not found or parse error — fall through to localhost discovery
455+
}
456+
}
457+
458+
// 3. Auto-discover local gateway at default port (no password)
459+
if (cancelled) return
460+
try {
461+
const localUrl = 'ws://localhost:18789'
462+
const probe = await fetch('http://localhost:18789/health', { signal: AbortSignal.timeout(2000) }).catch(() => null)
463+
if (cancelled) return
464+
if (probe && probe.ok) {
465+
credentialsRef.current = { url: localUrl, password: '' }
466+
setGatewayUrl(localUrl)
467+
localStorage.setItem(STORAGE_URL, localUrl)
468+
localStorage.setItem(STORAGE_PASS, '')
469+
doConnect(localUrl, '')
454470
}
471+
} catch {
472+
// No local gateway found — user will see the connect prompt
455473
}
456474
})()
457475

0 commit comments

Comments
 (0)