generated from Labyricorn/labyricorn-project-template
CI / frontend-quality (push) Canceled after 0s
- All 'voicebox'/'Voicebox'/'VOICEBOX' strings replaced with 'talkbox'/'TalkBox'/'TALKBOX' - Port changed from 17493 to 17494 (avoids conflict with upstream VoiceBox) - MCP tool namespace: voicebox.* -> talkbox.* - App bundle ID: sh.voicebox.app -> com.talkbox.app - Binary names: voicebox-server -> talkbox-server, voicebox-mcp -> talkbox-mcp - Docker user/group: voicebox -> talkbox - Database: voicebox.db -> talkbox.db - Env vars: VOICEBOX_* -> TALKBOX_* - Asset files renamed: voicebox-logo.* -> talkbox-logo.*, etc. - External binaries in tauri.conf.json updated to talkbox-server/talkbox-mcp
193 lines
7.1 KiB
TypeScript
193 lines
7.1 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { Loader2, XCircle } from 'lucide-react';
|
|
import { useEffect } from 'react';
|
|
import { useForm } from 'react-hook-form';
|
|
import * as z from 'zod';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormDescription,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from '@/components/ui/form';
|
|
import { Input } from '@/components/ui/input';
|
|
import { useToast } from '@/components/ui/use-toast';
|
|
import { useServerHealth } from '@/lib/hooks/useServer';
|
|
import { usePlatform } from '@/platform/PlatformContext';
|
|
import { useServerStore } from '@/stores/serverStore';
|
|
|
|
const connectionSchema = z.object({
|
|
serverUrl: z.string().url('Please enter a valid URL'),
|
|
});
|
|
|
|
type ConnectionFormValues = z.infer<typeof connectionSchema>;
|
|
|
|
export function ConnectionForm() {
|
|
const platform = usePlatform();
|
|
const serverUrl = useServerStore((state) => state.serverUrl);
|
|
const setServerUrl = useServerStore((state) => state.setServerUrl);
|
|
const keepServerRunningOnClose = useServerStore((state) => state.keepServerRunningOnClose);
|
|
const setKeepServerRunningOnClose = useServerStore((state) => state.setKeepServerRunningOnClose);
|
|
const mode = useServerStore((state) => state.mode);
|
|
const setMode = useServerStore((state) => state.setMode);
|
|
const { toast } = useToast();
|
|
const { data: health, isLoading, error: healthError } = useServerHealth();
|
|
|
|
const form = useForm<ConnectionFormValues>({
|
|
resolver: zodResolver(connectionSchema),
|
|
defaultValues: {
|
|
serverUrl: serverUrl,
|
|
},
|
|
});
|
|
|
|
// Sync form with store when serverUrl changes externally
|
|
useEffect(() => {
|
|
form.reset({ serverUrl });
|
|
}, [serverUrl, form]);
|
|
|
|
const { isDirty } = form.formState;
|
|
|
|
function onSubmit(data: ConnectionFormValues) {
|
|
setServerUrl(data.serverUrl);
|
|
form.reset(data);
|
|
toast({
|
|
title: 'Server URL updated',
|
|
description: `Connected to ${data.serverUrl}`,
|
|
});
|
|
}
|
|
|
|
return (
|
|
<Card role="region" aria-label="Server Connection" tabIndex={0}>
|
|
<CardHeader>
|
|
<CardTitle>Server Connection</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
<FormField
|
|
control={form.control}
|
|
name="serverUrl"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>Server URL</FormLabel>
|
|
<FormControl>
|
|
<Input placeholder="http://127.0.0.1:17494" {...field} />
|
|
</FormControl>
|
|
<FormDescription>Enter the URL of your talkbox backend server</FormDescription>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
|
|
{isDirty && <Button type="submit">Update Connection</Button>}
|
|
</form>
|
|
</Form>
|
|
|
|
{/* Connection status */}
|
|
<div className="mt-4">
|
|
{isLoading ? (
|
|
<div className="flex items-center gap-2">
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
<span className="text-sm text-muted-foreground">Checking connection...</span>
|
|
</div>
|
|
) : healthError ? (
|
|
<div className="flex items-center gap-2">
|
|
<XCircle className="h-4 w-4 text-destructive" />
|
|
<span className="text-sm text-destructive">
|
|
Connection failed: {healthError.message}
|
|
</span>
|
|
</div>
|
|
) : health ? (
|
|
<div className="flex flex-wrap gap-2">
|
|
<Badge
|
|
variant={health.model_loaded || health.model_downloaded ? 'default' : 'secondary'}
|
|
>
|
|
{health.model_loaded || health.model_downloaded ? 'Model Ready' : 'No Model'}
|
|
</Badge>
|
|
<Badge variant={health.gpu_available ? 'default' : 'secondary'}>
|
|
GPU: {health.gpu_available ? 'Available' : 'Not Available'}
|
|
</Badge>
|
|
{health.vram_used_mb != null && health.vram_used_mb > 0 && (
|
|
<Badge variant="outline">VRAM: {health.vram_used_mb.toFixed(0)} MB</Badge>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="mt-6 pt-6 border-t">
|
|
<div className="flex items-start space-x-3">
|
|
<Checkbox
|
|
id="keepServerRunning"
|
|
className="mt-[6px]"
|
|
checked={keepServerRunningOnClose}
|
|
onCheckedChange={(checked: boolean) => {
|
|
setKeepServerRunningOnClose(checked);
|
|
platform.lifecycle.setKeepServerRunning(checked).catch((error) => {
|
|
console.error('Failed to sync setting to Rust:', error);
|
|
});
|
|
toast({
|
|
title: 'Setting updated',
|
|
description: checked
|
|
? 'Server will continue running when app closes'
|
|
: 'Server will stop when app closes',
|
|
});
|
|
}}
|
|
/>
|
|
<div className="space-y-1">
|
|
<label
|
|
htmlFor="keepServerRunning"
|
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
>
|
|
Keep server running when app closes
|
|
</label>
|
|
<p className="text-sm text-muted-foreground">
|
|
When enabled, the server will continue running in the background after closing the
|
|
app. Disabled by default.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{platform.metadata.isTauri && (
|
|
<div className="mt-6 pt-6 border-t">
|
|
<div className="flex items-start space-x-3">
|
|
<Checkbox
|
|
id="allowNetworkAccess"
|
|
className="mt-[6px]"
|
|
checked={mode === 'remote'}
|
|
onCheckedChange={(checked: boolean) => {
|
|
setMode(checked ? 'remote' : 'local');
|
|
toast({
|
|
title: 'Setting updated',
|
|
description: checked
|
|
? 'Network access enabled. Restart the app to apply.'
|
|
: 'Network access disabled. Restart the app to apply.',
|
|
});
|
|
}}
|
|
/>
|
|
<div className="space-y-1">
|
|
<label
|
|
htmlFor="allowNetworkAccess"
|
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
>
|
|
Allow network access
|
|
</label>
|
|
<p className="text-sm text-muted-foreground">
|
|
Makes the server accessible from other devices on your network. Restart the app
|
|
after changing this setting.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|