From 1526f2de26cb9de16e142b4988e18d81a1d86879 Mon Sep 17 00:00:00 2001 From: James Pine Date: Mon, 16 Mar 2026 10:14:47 -0700 Subject: [PATCH] about page + generation folder open and display --- app/src/components/ServerTab/AboutPage.tsx | 135 ++++++++++++++++++ .../components/ServerTab/GenerationPage.tsx | 48 +++++++ app/src/components/ServerTab/ServerTab.tsx | 4 +- app/src/router.tsx | 8 ++ backend/routes/health.py | 2 +- 5 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 app/src/components/ServerTab/AboutPage.tsx diff --git a/app/src/components/ServerTab/AboutPage.tsx b/app/src/components/ServerTab/AboutPage.tsx new file mode 100644 index 00000000..6fff5cb7 --- /dev/null +++ b/app/src/components/ServerTab/AboutPage.tsx @@ -0,0 +1,135 @@ +import { ArrowUpRight } from 'lucide-react'; +import type { CSSProperties, ReactNode } from 'react'; +import { useEffect, useState } from 'react'; +import voiceboxLogo from '@/assets/voicebox-logo.png'; +import { usePlatform } from '@/platform/PlatformContext'; + +function FadeIn({ delay = 0, children }: { delay?: number; children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function AboutPage() { + const platform = usePlatform(); + const [version, setVersion] = useState(''); + + useEffect(() => { + platform.metadata + .getVersion() + .then(setVersion) + .catch(() => setVersion('')); + }, [platform]); + + return ( + <> + +
+
+ + Voicebox + + + +
+

Voicebox

+

+ {version ? `v${version}` : '\u00A0'} +

+
+
+ + +

+ The open-source voice synthesis studio. Clone voices, generate speech, apply effects, + and build voice-powered apps — all running locally on your machine. +

+
+ + +
+ Created by + + Jamie Pine + +
+
+ + +
+ + + Buy me a coffee + + + + + GitHub + + +
+
+ + +

+ Licensed under{' '} + + BSL 1.1 + +

+
+
+
+ + ); +} diff --git a/app/src/components/ServerTab/GenerationPage.tsx b/app/src/components/ServerTab/GenerationPage.tsx index 12ddb01f..4bb162a2 100644 --- a/app/src/components/ServerTab/GenerationPage.tsx +++ b/app/src/components/ServerTab/GenerationPage.tsx @@ -1,9 +1,15 @@ +import { FolderOpen } from 'lucide-react'; +import { useCallback, useEffect, useState } from 'react'; +import { Button } from '@/components/ui/button'; import { Slider } from '@/components/ui/slider'; import { Toggle } from '@/components/ui/toggle'; +import { usePlatform } from '@/platform/PlatformContext'; import { useServerStore } from '@/stores/serverStore'; import { SettingRow, SettingSection } from './SettingRow'; export function GenerationPage() { + const platform = usePlatform(); + const serverUrl = useServerStore((state) => state.serverUrl); const maxChunkChars = useServerStore((state) => state.maxChunkChars); const setMaxChunkChars = useServerStore((state) => state.setMaxChunkChars); const crossfadeMs = useServerStore((state) => state.crossfadeMs); @@ -12,6 +18,32 @@ export function GenerationPage() { const setNormalizeAudio = useServerStore((state) => state.setNormalizeAudio); const autoplayOnGenerate = useServerStore((state) => state.autoplayOnGenerate); const setAutoplayOnGenerate = useServerStore((state) => state.setAutoplayOnGenerate); + const [opening, setOpening] = useState(false); + const [generationsPath, setGenerationsPath] = useState(null); + + useEffect(() => { + fetch(`${serverUrl}/health/filesystem`) + .then((res) => res.json()) + .then((data) => { + const genDir = data.directories?.find((d: { path: string }) => + d.path.includes('generations'), + ); + if (genDir?.path) setGenerationsPath(genDir.path); + }) + .catch(() => {}); + }, [serverUrl]); + + const openGenerationsFolder = useCallback(async () => { + if (!generationsPath) return; + setOpening(true); + try { + await platform.filesystem.openPath(generationsPath); + } catch (e) { + console.error('Failed to open generations folder:', e); + } finally { + setOpening(false); + } + }, [platform, generationsPath]); return (
@@ -84,6 +116,22 @@ export function GenerationPage() { /> } /> + + + + Open + + } + />
); diff --git a/app/src/components/ServerTab/ServerTab.tsx b/app/src/components/ServerTab/ServerTab.tsx index 3aa0933d..b2f78dc3 100644 --- a/app/src/components/ServerTab/ServerTab.tsx +++ b/app/src/components/ServerTab/ServerTab.tsx @@ -11,7 +11,8 @@ interface SettingsTab { | '/settings/generation' | '/settings/gpu' | '/settings/logs' - | '/settings/changelog'; + | '/settings/changelog' + | '/settings/about'; tauriOnly?: boolean; } @@ -21,6 +22,7 @@ const tabs: SettingsTab[] = [ { label: 'GPU', path: '/settings/gpu', tauriOnly: true }, { label: 'Logs', path: '/settings/logs', tauriOnly: true }, { label: 'Changelog', path: '/settings/changelog' }, + { label: 'About', path: '/settings/about' }, ]; export function SettingsLayout() { diff --git a/app/src/router.tsx b/app/src/router.tsx index fd5b7cf2..45876cd3 100644 --- a/app/src/router.tsx +++ b/app/src/router.tsx @@ -10,6 +10,7 @@ import { AudioTab } from '@/components/AudioTab/AudioTab'; import { EffectsTab } from '@/components/EffectsTab/EffectsTab'; import { MainEditor } from '@/components/MainEditor/MainEditor'; import { ModelsTab } from '@/components/ModelsTab/ModelsTab'; +import { AboutPage } from '@/components/ServerTab/AboutPage'; import { ChangelogPage } from '@/components/ServerTab/ChangelogPage'; import { GeneralPage } from '@/components/ServerTab/GeneralPage'; import { GenerationPage } from '@/components/ServerTab/GenerationPage'; @@ -169,6 +170,12 @@ const settingsLogsRoute = createRoute({ component: LogsPage, }); +const settingsAboutRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: '/about', + component: AboutPage, +}); + // Redirect old /server path to /settings const serverRedirectRoute = createRoute({ getParentRoute: () => rootRoute, @@ -192,6 +199,7 @@ const routeTree = rootRoute.addChildren([ settingsGpuRoute, settingsLogsRoute, settingsChangelogRoute, + settingsAboutRoute, ]), serverRedirectRoute, ]); diff --git a/backend/routes/health.py b/backend/routes/health.py index 0053f423..f138e336 100644 --- a/backend/routes/health.py +++ b/backend/routes/health.py @@ -207,7 +207,7 @@ async def filesystem_health(): checks.append( models.DirectoryCheck( - path=str(dir_path), + path=str(dir_path.resolve()), exists=exists, writable=writable, error=error,