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
+
+ {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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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,