|
9 | 9 | * Visual language follows ServiceBlockingGate.tsx (bg-stone-950/80 overlay, |
10 | 10 | * bg-stone-900 panel, ocean-500 / coral-500 semantics). |
11 | 11 | */ |
| 12 | +import { isTauri } from '@tauri-apps/api/core'; |
12 | 13 | import debug from 'debug'; |
13 | 14 | import { useCallback, useEffect, useRef, useState } from 'react'; |
14 | 15 |
|
@@ -74,8 +75,17 @@ type TestStatus = |
74 | 75 | | { kind: 'auth' } |
75 | 76 | | { kind: 'unreachable'; reason: string }; |
76 | 77 |
|
| 78 | +// Desktop release artifact URL surfaced on the web build's mode picker so |
| 79 | +// users without a remote core have a clear path to install the app instead |
| 80 | +// of being trapped on the cloud-only form. |
| 81 | +const DESKTOP_DOWNLOAD_URL = 'https://github.com/tinyhumansai/openhuman/releases/latest'; |
| 82 | + |
77 | 83 | function ModePicker({ onConfirm }: PickerProps) { |
78 | | - const [selected, setSelected] = useState<'local' | 'cloud'>('local'); |
| 84 | + // Web build cannot spawn a local sidecar, so the only viable choice is |
| 85 | + // cloud. Default the selection accordingly and hide the local option in |
| 86 | + // the render path below. |
| 87 | + const isDesktop = isTauri(); |
| 88 | + const [selected, setSelected] = useState<'local' | 'cloud'>(isDesktop ? 'local' : 'cloud'); |
79 | 89 | const [cloudUrl, setCloudUrl] = useState(''); |
80 | 90 | const [cloudToken, setCloudToken] = useState(''); |
81 | 91 | const [urlError, setUrlError] = useState<string | null>(null); |
@@ -180,41 +190,65 @@ function ModePicker({ onConfirm }: PickerProps) { |
180 | 190 |
|
181 | 191 | return ( |
182 | 192 | <Panel> |
183 | | - <h2 className="text-xl font-semibold text-white">Choose core mode</h2> |
| 193 | + <h2 className="text-xl font-semibold text-white"> |
| 194 | + {isDesktop ? 'Choose core mode' : 'Connect to your core'} |
| 195 | + </h2> |
184 | 196 | <p className="mt-2 text-sm text-stone-300"> |
185 | | - OpenHuman needs a running core to operate. Choose how you want to connect. |
| 197 | + {isDesktop |
| 198 | + ? 'OpenHuman needs a running core to operate. Choose how you want to connect.' |
| 199 | + : 'OpenHuman on the web connects to a remote core you control. Enter its URL and auth token, or install the desktop app to run one locally.'} |
186 | 200 | </p> |
187 | 201 |
|
| 202 | + {!isDesktop && ( |
| 203 | + <div |
| 204 | + className="mt-4 rounded-xl border border-stone-700 bg-stone-800/60 p-3 text-xs text-stone-300" |
| 205 | + data-testid="web-download-cta"> |
| 206 | + Prefer to run everything on your own device?{' '} |
| 207 | + <a |
| 208 | + href={DESKTOP_DOWNLOAD_URL} |
| 209 | + target="_blank" |
| 210 | + rel="noopener noreferrer" |
| 211 | + className="text-ocean-400 underline hover:text-ocean-300"> |
| 212 | + Download the desktop app |
| 213 | + </a> |
| 214 | + . |
| 215 | + </div> |
| 216 | + )} |
| 217 | + |
188 | 218 | <div className="mt-5 flex flex-col gap-3"> |
189 | | - {/* Local option */} |
190 | | - <button |
191 | | - type="button" |
192 | | - onClick={() => setSelected('local')} |
193 | | - className={`rounded-xl border p-4 text-left transition-colors ${ |
194 | | - selected === 'local' |
195 | | - ? 'border-ocean-500 bg-ocean-500/10 text-white' |
196 | | - : 'border-stone-700 text-stone-300 hover:border-stone-500 hover:bg-stone-800' |
197 | | - }`}> |
198 | | - <div className="font-medium">Local (recommended)</div> |
199 | | - <div className="mt-0.5 text-xs text-stone-400"> |
200 | | - Embedded core runs on this device — fastest, no configuration required. |
201 | | - </div> |
202 | | - </button> |
| 219 | + {/* Local option — desktop only; web builds cannot spawn a sidecar. */} |
| 220 | + {isDesktop && ( |
| 221 | + <button |
| 222 | + type="button" |
| 223 | + onClick={() => setSelected('local')} |
| 224 | + className={`rounded-xl border p-4 text-left transition-colors ${ |
| 225 | + selected === 'local' |
| 226 | + ? 'border-ocean-500 bg-ocean-500/10 text-white' |
| 227 | + : 'border-stone-700 text-stone-300 hover:border-stone-500 hover:bg-stone-800' |
| 228 | + }`}> |
| 229 | + <div className="font-medium">Local (recommended)</div> |
| 230 | + <div className="mt-0.5 text-xs text-stone-400"> |
| 231 | + Embedded core runs on this device — fastest, no configuration required. |
| 232 | + </div> |
| 233 | + </button> |
| 234 | + )} |
203 | 235 |
|
204 | | - {/* Cloud option */} |
205 | | - <button |
206 | | - type="button" |
207 | | - onClick={() => setSelected('cloud')} |
208 | | - className={`rounded-xl border p-4 text-left transition-colors ${ |
209 | | - selected === 'cloud' |
210 | | - ? 'border-ocean-500 bg-ocean-500/10 text-white' |
211 | | - : 'border-stone-700 text-stone-300 hover:border-stone-500 hover:bg-stone-800' |
212 | | - }`}> |
213 | | - <div className="font-medium">Cloud</div> |
214 | | - <div className="mt-0.5 text-xs text-stone-400"> |
215 | | - Connect to a remote core at a custom URL. |
216 | | - </div> |
217 | | - </button> |
| 236 | + {/* Cloud option — always available; the only option on the web build. */} |
| 237 | + {isDesktop && ( |
| 238 | + <button |
| 239 | + type="button" |
| 240 | + onClick={() => setSelected('cloud')} |
| 241 | + className={`rounded-xl border p-4 text-left transition-colors ${ |
| 242 | + selected === 'cloud' |
| 243 | + ? 'border-ocean-500 bg-ocean-500/10 text-white' |
| 244 | + : 'border-stone-700 text-stone-300 hover:border-stone-500 hover:bg-stone-800' |
| 245 | + }`}> |
| 246 | + <div className="font-medium">Cloud</div> |
| 247 | + <div className="mt-0.5 text-xs text-stone-400"> |
| 248 | + Connect to a remote core at a custom URL. |
| 249 | + </div> |
| 250 | + </button> |
| 251 | + )} |
218 | 252 |
|
219 | 253 | {selected === 'cloud' && ( |
220 | 254 | <div className="mt-1 flex flex-col gap-3"> |
|
0 commit comments