Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ release/
*.tsbuildinfo

# Python
backend/python/
backend/.venv/
backend/__pycache__/
backend/**/__pycache__/
Expand Down
62 changes: 33 additions & 29 deletions frontend/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Loader2, AlertCircle, Settings, FileText } from 'lucide-react'
import { I18nProvider, useT } from './lib/i18n'
import { ApiClient, type ApiSuccessOf } from './lib/api-client'
import { ProjectProvider } from './contexts/ProjectContext'
import { ViewProvider, useView } from './contexts/ViewContext'
Expand All @@ -24,6 +25,7 @@ type LtxRecommendation = ApiSuccessOf<'getLtxRecommendation'>
type LtxUpgradeRecommendation = Extract<LtxRecommendation, { status: 'upgrade' }>

function AppContent() {
const { t } = useT()
const { currentView } = useView()
const { connected, processStatus, isLoading: backendLoading } = useBackend()
const { settings, saveLtxApiKey, saveFalApiKey, forceApiGenerations, isLoaded, runtimePolicyLoaded } = useAppSettings()
Expand Down Expand Up @@ -73,8 +75,8 @@ function AppContent() {
const requiredKeys = Array.isArray(detail.requiredKeys) ? detail.requiredKeys : ['ltx']
setApiGatewayRequest({
requiredKeys,
title: detail.title ?? 'Connect API Keys',
description: detail.description ?? 'Add the required API keys to continue.',
title: detail.title ?? t('app.connectApiKeys'),
description: detail.description ?? t('app.connectApiKeysDesc'),
blocking: detail.blocking ?? false,
includeOptionalMissing: detail.includeOptionalMissing ?? false,
})
Expand Down Expand Up @@ -335,9 +337,9 @@ function AppContent() {
<div className="rounded-lg border border-zinc-700 bg-zinc-900/95 px-6 py-4 text-center shadow-xl">
<div className="flex items-center justify-center gap-2 text-zinc-100">
<Loader2 className="h-4 w-4 animate-spin" />
<span className="font-medium">Reconnecting...</span>
<span className="font-medium">{t('app.reconnecting')}</span>
</div>
<p className="mt-2 text-sm text-zinc-400">The backend process stopped unexpectedly. Attempting to restart...</p>
<p className="mt-2 text-sm text-zinc-400">{t('app.reconnectingDesc')}</p>
</div>
</div>
) : null
Expand All @@ -352,8 +354,8 @@ function AppContent() {
if (shouldBlockForLtxKey && apiGatewayRequest === null) {
setApiGatewayRequest({
requiredKeys: ['ltx'],
title: 'Connect API Keys',
description: 'This app is configured for API-only generation. Add your API key to continue.',
title: t('app.connectApiKeys'),
description: t('app.apiOnlyNotice'),
blocking: true,
includeOptionalMissing: true,
})
Expand Down Expand Up @@ -433,14 +435,14 @@ function AppContent() {
<div className="w-full max-w-5xl rounded-xl border border-zinc-700 bg-zinc-900/80 p-6 shadow-2xl">
<div className="text-center">
<AlertCircle className="h-12 w-12 text-red-500 mx-auto mb-4" />
<h2 className="text-xl font-semibold text-foreground mb-2">The backend process crashed and could not be restarted</h2>
<p className="text-muted-foreground mb-4">Review the logs below and restart the application.</p>
<h2 className="text-xl font-semibold text-foreground mb-2">{t('app.crashed')}</h2>
<p className="text-muted-foreground mb-4">{t('app.crashedDesc')}</p>
</div>
<div className="h-[50vh]">
<LogViewer isOpen={true} onClose={() => {}} embedded={true} />
</div>
<div className="mt-4 flex justify-center">
<Button onClick={() => window.location.reload()}>Restart Application</Button>
<Button onClick={() => window.location.reload()}>{t('app.restart')}</Button>
</div>
</div>
</div>
Expand All @@ -460,8 +462,8 @@ function AppContent() {
<div className="h-screen bg-background flex items-center justify-center">
<div className="text-center">
<Loader2 className="h-12 w-12 text-primary animate-spin mx-auto mb-4" />
<h2 className="text-xl font-semibold text-foreground mb-2">Starting LTX Desktop...</h2>
<p className="text-muted-foreground">Initializing the inference engine</p>
<h2 className="text-xl font-semibold text-foreground mb-2">{t('app.loading')}</h2>
<p className="text-muted-foreground">{t('app.loadingSub')}</p>
</div>
</div>
{restartingOverlay}
Expand Down Expand Up @@ -518,14 +520,14 @@ function AppContent() {
<button
onClick={() => setIsLogViewerOpen(true)}
className="h-8 w-8 flex items-center justify-center rounded-md text-zinc-400 hover:text-white hover:bg-zinc-800 transition-colors"
title="View Backend Logs"
title={t('app.viewLogs')}
>
<FileText className="h-4 w-4" />
</button>
<button
onClick={() => setIsSettingsOpen(true)}
className="h-8 w-8 flex items-center justify-center rounded-md text-zinc-400 hover:text-white hover:bg-zinc-800 transition-colors"
title="Settings"
title={t('app.settings')}
>
<Settings className="h-4 w-4" />
</button>
Expand All @@ -545,8 +547,8 @@ function AppContent() {
isOpen={shouldShowGateway}
blocking={apiGatewayRequest?.blocking}
onClose={() => setApiGatewayRequest(null)}
title={apiGatewayRequest?.title ?? 'Connect API Keys'}
description={apiGatewayRequest?.description ?? 'Add the required API keys to continue.'}
title={apiGatewayRequest?.title ?? t('app.connectApiKeys')}
description={apiGatewayRequest?.description ?? t('app.connectApiKeysDesc')}
sections={gatewaySections}
/>
{ltxUpgradeRecommendation && (
Expand All @@ -561,7 +563,7 @@ function AppContent() {
<div className="fixed inset-0 z-40 flex items-center justify-center bg-black/70 backdrop-blur-sm">
<div className="flex items-center gap-2 text-sm text-zinc-200">
<Loader2 className="h-4 w-4 animate-spin" />
Loading settings...
{t('app.loadingSettings')}
</div>
</div>
)}
Expand All @@ -570,15 +572,15 @@ function AppContent() {
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/70 backdrop-blur-sm">
<div className="flex items-center gap-2 text-sm text-zinc-200">
<Loader2 className="h-4 w-4 animate-spin" />
Finalizing setup...
{t('app.finalizingSetup')}
</div>
</div>
)}

{isForcedFirstRun && firstRunFinalizeError && (
<div className="fixed inset-0 z-[61] flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
<div className="w-full max-w-md rounded-xl border border-zinc-700 bg-zinc-900 p-5 text-zinc-100">
<h3 className="text-base font-semibold">Setup finalization failed</h3>
<h3 className="text-base font-semibold">{t('app.setupFailed')}</h3>
<p className="mt-2 text-sm text-zinc-300">{firstRunFinalizeError}</p>
<div className="mt-4 flex justify-end">
<Button
Expand All @@ -588,7 +590,7 @@ function AppContent() {
})
}}
>
Retry
{t('app.retry')}
</Button>
</div>
</div>
Expand All @@ -602,15 +604,17 @@ function AppContent() {

export default function App() {
return (
<ProjectProvider>
<ViewProvider>
<KeyboardShortcutsProvider>
<AppSettingsProvider>
<AppContent />
<KeyboardShortcutsModal />
</AppSettingsProvider>
</KeyboardShortcutsProvider>
</ViewProvider>
</ProjectProvider>
<I18nProvider>
<ProjectProvider>
<ViewProvider>
<KeyboardShortcutsProvider>
<AppSettingsProvider>
<AppContent />
<KeyboardShortcutsModal />
</AppSettingsProvider>
</KeyboardShortcutsProvider>
</ViewProvider>
</ProjectProvider>
</I18nProvider>
)
}
12 changes: 7 additions & 5 deletions frontend/components/ApiGatewayModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react'
import { KeyRound, X, Zap } from 'lucide-react'
import { useT } from '../lib/i18n'
import { ApiKeyHelperRow, LtxApiKeyInput } from './LtxApiKeyInput'

export type ApiKeyType = 'ltx' | 'fal'
Expand Down Expand Up @@ -47,6 +48,7 @@ export function ApiGatewayModal({
sections,
blocking = false,
}: ApiGatewayModalProps) {
const { t } = useT()
const [values, setValues] = useState<Record<ApiKeyType, string>>({ ltx: '', fal: '' })
const [isSaving, setIsSaving] = useState<Record<ApiKeyType, boolean>>({ ltx: false, fal: false })
const [errors, setErrors] = useState<Record<ApiKeyType, string | null>>({ ltx: null, fal: null })
Expand Down Expand Up @@ -153,13 +155,13 @@ export function ApiGatewayModal({
<Icon className={`h-4 w-4 ${meta.iconClass}`} />
<h3 className="text-sm font-semibold text-white">{section.title}</h3>
<span className={`text-[10px] px-1.5 py-0.5 rounded ${section.required ? meta.chipClass : 'bg-zinc-800 text-zinc-500'}`}>
{section.required ? 'Required' : 'Optional'}
{section.required ? t('apiGateway.required') : t('settings.optional')}
</span>
</div>
<p className="text-xs text-zinc-500 mt-1">{section.description}</p>
</div>
<div className={`text-xs px-2 py-1 rounded inline-flex items-center gap-1.5 ${configured ? 'bg-green-500/10 text-green-400' : 'bg-zinc-800 text-zinc-500'}`}>
{configured ? 'Configured' : 'Not set'}
{configured ? t('apiGateway.configured') : t('apiGateway.notSet')}
</div>
</div>

Expand All @@ -178,11 +180,11 @@ export function ApiGatewayModal({
disabled={!canSubmit}
className="px-3 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-500 disabled:bg-zinc-700 disabled:text-zinc-500 disabled:cursor-not-allowed transition-colors whitespace-nowrap"
>
{saving ? 'Saving...' : 'Save Key'}
{saving ? t('apiGateway.saving') : t('settings.saveKey')}
</button>
</div>
<ApiKeyHelperRow
label={section.getKeyLabel ?? 'Get API key'}
label={section.getKeyLabel ?? t('apiGateway.getKey')}
onOpenKey={section.onGetKey}
/>
</div>
Expand All @@ -200,7 +202,7 @@ export function ApiGatewayModal({

{blocking && requiredMissing && (
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-200">
Required API keys are missing. Add them to continue.
{t('apiGateway.missing')}
</div>
)}

Expand Down
Loading