Create standalone browser port of LTX Athena with authenticated native API transport
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { WebProjectBackup } from './components/WebProjectBackup'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { ProjectProvider } from './contexts/ProjectContext'
|
||||
import { ViewProvider, useView } from './contexts/ViewContext'
|
||||
import { KeyboardShortcutsProvider } from './contexts/KeyboardShortcutsContext'
|
||||
import { AppSettingsProvider, useAppSettings } from './contexts/AppSettingsContext'
|
||||
import { DevFlagsProvider } from './contexts/DevFlagsContext'
|
||||
import { useGenerationRecoveryWatcher } from './hooks/use-generation-recovery-watcher'
|
||||
import { Home } from './views/Home'
|
||||
import { Project } from './views/Project'
|
||||
import { webRequest } from './lib/web-platform'
|
||||
import { useBackend } from './hooks/use-backend'
|
||||
import { DevPanel } from './components/DevPanel'
|
||||
import { KeyboardShortcutsModal } from './components/KeyboardShortcutsModal'
|
||||
|
||||
function Workspace() {
|
||||
const { currentView, goHome } = useView()
|
||||
const { connected, isLoading } = useBackend()
|
||||
const { isLoaded, runtimePolicyLoaded } = useAppSettings()
|
||||
useGenerationRecoveryWatcher()
|
||||
const ready = connected && isLoaded && runtimePolicyLoaded
|
||||
return <>
|
||||
<div className={`px-4 py-2 text-sm ${ready ? 'bg-emerald-950 text-emerald-200' : 'bg-amber-950 text-amber-200'}`} role="status">
|
||||
{ready ? 'LTX backend connected · generation runs on the remote server' : isLoading ? 'Checking LTX connection…' : 'LTX is not ready. Activate Video mode in Athena Deck. The connection is checked automatically.'}
|
||||
</div>
|
||||
{currentView === 'home' ? <Home /> : ready ? <Project /> : <div className="p-12"><p>Projects are retained. Generation becomes available when the LTX backend and its settings are reachable.</p><button className="mt-4 underline" onClick={goHome}>Back to projects</button></div>}
|
||||
<KeyboardShortcutsModal /><DevPanel />
|
||||
</>
|
||||
}
|
||||
export default function App() {
|
||||
const [session, setSession] = useState<{ backendUrl: string } | null>(null)
|
||||
const [checking, setChecking] = useState(true)
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState('')
|
||||
const [message, setMessage] = useState('')
|
||||
const [help, setHelp] = useState(false)
|
||||
useEffect(() => { void webRequest<{ backendUrl: string }>('/web/session').then(setSession).catch(() => {}).finally(() => setChecking(false)) }, [])
|
||||
useEffect(() => { const listener = (event: Event) => setMessage((event as CustomEvent<string>).detail); window.addEventListener('web-status', listener); return () => window.removeEventListener('web-status', listener) }, [])
|
||||
useEffect(() => { const listener = () => setHelp(true); window.addEventListener('open-settings', listener); return () => window.removeEventListener('open-settings', listener) }, [])
|
||||
if (checking) return <div className="p-12">Loading LTX DeskWEB…</div>
|
||||
if (!session) return <main className="min-h-screen flex items-center justify-center bg-zinc-950 text-white">
|
||||
<form className="w-full max-w-md p-8 border border-zinc-700 rounded-xl" onSubmit={async event => {
|
||||
event.preventDefault(); setError('')
|
||||
try { await webRequest('/web/login', { password }); setPassword(''); setSession(await webRequest('/web/session')) } catch (e) { setError(e instanceof Error ? e.message : String(e)) }
|
||||
}}>
|
||||
<h1 className="text-2xl font-bold">LTX DeskWEB</h1><p className="my-4 text-zinc-400">Browser workspace for your LTX backend.</p>
|
||||
<label htmlFor="password">Access password</label><input id="password" type="password" autoComplete="current-password" required value={password} onChange={e => setPassword(e.target.value)} className="mt-2 mb-4 w-full p-3 bg-zinc-800 rounded" />
|
||||
<button className="bg-blue-600 rounded px-4 py-2" type="submit">Sign in</button><p role="alert" className="mt-4 text-red-300">{error}</p>
|
||||
</form>
|
||||
</main>
|
||||
return <>
|
||||
<header className="flex justify-between items-center px-4 py-2 bg-zinc-900 border-b border-zinc-700 text-sm">
|
||||
<strong>LTX DeskWEB · Preview</strong><div className="flex gap-4"><button onClick={() => setHelp(!help)}>Connection & information</button><button onClick={async () => { await webRequest('/web/logout', {}); location.reload() }}>Sign out</button></div>
|
||||
</header>
|
||||
{help && <aside className="p-5 bg-zinc-900 border-b border-zinc-700 space-y-2">
|
||||
<p>Native LTX API via: <code>{session.backendUrl}</code>. Change the address and token in the container configuration.</p>
|
||||
<p>Video mode is managed in Athena Deck. This web app never starts or installs a model.</p>
|
||||
<p>Preview: projects are saved in this browser. Use “Back up projects” before clearing browser data. Timeline rendering, desktop folder dialogs and Hugging Face desktop login are not yet ported.</p>
|
||||
<WebProjectBackup />
|
||||
<button className="ml-4 underline" onClick={() => setHelp(false)}>Close</button>
|
||||
</aside>}
|
||||
{message && <div className="p-3 bg-blue-950 flex justify-between" role="status">{message}<button onClick={() => setMessage('')}>Dismiss</button></div>}
|
||||
<AppSettingsProvider><ProjectProvider><ViewProvider><KeyboardShortcutsProvider><DevFlagsProvider><Workspace /></DevFlagsProvider></KeyboardShortcutsProvider></ViewProvider></ProjectProvider></AppSettingsProvider>
|
||||
</>
|
||||
}
|
||||
Reference in New Issue
Block a user