Files
platform/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/AIHarnessesSection.tsx
T
pastilhas 44f5246e38 server settings keyed (352 -> 267)
ServerSettings reaches zero: OCR, STT, TTS, SMTP, maintenance and the AI harnesses.

Two more hand-rolled plurals became real plural entries -- maintenance said
"commit" with a ternary on length === 1, and both read correctly at 0, 1 and 2 in
both locales now. The three Cancel buttons in AIHarnessesSection take ui.button.cancel
rather than adding a fourth app-level copy of the word, which is what phase 1 put it
there for.

Scanner: a single token carrying digits and punctuation is a name, not a sentence --
the OCR placeholder is the example model file Qwen2.5-VL-7B-Instruct-q4_k_m.gguf.
Prose has spaces, and requiring whitespace to be absent is what keeps this from
eating real one-word labels. Verified the delta is exactly that one string.

Note for the owner: another session has src/servers/api/sub-agents/router.ts in
flight with three type errors. Untouched by me and excluded from my gate; the full
tree will not typecheck clean until they land it.
2026-08-23 05:04:22 +00:00

606 lines
27 KiB
TypeScript

import { useState } from 'react';
import { Plus, Save, Trash2, RefreshCw, Loader2, X, Pencil } from 'lucide-react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
CommandDialog,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { useClient } from 'hooks/useClient';
import { useTranslation } from '@/lib/i18n';
type VersionInfo = { version: string | null; path: string | null; globalPath: string | null };
type StoredApiKeys = { keys: { provider: string; value: string }[] };
type LocalProviderEntry = {
id: string;
name: string;
url: string;
apiType: 'ollama' | 'openai-compatible' | 'lmstudio';
auth?: { type: 'api-key' | 'basic' };
};
type ProbeResult = {
success: boolean;
apiType?: LocalProviderEntry['apiType'];
name?: string;
needsAuth?: boolean;
authType?: 'api-key' | 'basic' | 'unknown';
models?: string[];
error?: string;
};
type ChatProvider = { key: string; providerId: string };
type ProbeState = { step: 'url' } | { step: 'probing' } | { step: 'auth'; probe: ProbeResult } | { step: 'saving' };
export const AIHarnessesSection = () => {
const { t, tUi } = useTranslation();
const client = useClient();
const queryClient = useQueryClient();
const [installing, setInstalling] = useState(false);
const { data: CHAT_PROVIDERS = [] as ChatProvider[] } = useQuery({
queryKey: ['CHAT_PROVIDERS_PROVIDERS'],
queryFn: () => client.get<ChatProvider[]>('/server-settings/chat-providers/providers'),
});
const { data: piMonoVersion, isLoading: piMonoLoading } = useQuery({
queryKey: ['CHAT_PROVIDERS_VERSION'],
queryFn: () => client.get<VersionInfo>('/server-settings/chat-providers/version'),
});
const { data: piMonoKeys } = useQuery({
queryKey: ['CHAT_PROVIDERS_API_KEYS'],
queryFn: () => client.get<StoredApiKeys>('/server-settings/chat-providers/api-keys'),
enabled: !!piMonoVersion?.version,
});
const { data: localProviders = [] as LocalProviderEntry[] } = useQuery({
queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'],
queryFn: () => client.get<LocalProviderEntry[]>('/server-settings/chat-providers/local-providers'),
enabled: !!piMonoVersion?.version,
});
const { data: localHealth = {} as Record<string, boolean> } = useQuery({
queryKey: ['CHAT_PROVIDERS_LOCAL_HEALTH'],
queryFn: () => client.get<Record<string, boolean>>('/server-settings/chat-providers/local-providers/health'),
enabled: localProviders.length > 0,
refetchInterval: 15_000,
});
const [keyInputs, setKeyInputs] = useState<Record<string, string>>({});
const [savingKey, setSavingKey] = useState<string | null>(null);
const [commandOpen, setCommandOpen] = useState(false);
const [editingProvider, setEditingProvider] = useState<string | null>(null);
// Local provider connection flow
const [addingLocal, setAddingLocal] = useState(false);
const [localName, setLocalName] = useState('');
const [localUrl, setLocalUrl] = useState('');
const [probeState, setProbeState] = useState<ProbeState>({ step: 'url' });
const [authApiKey, setAuthApiKey] = useState('');
const [authUsername, setAuthUsername] = useState('');
const [authPassword, setAuthPassword] = useState('');
const resetLocalForm = () => {
setAddingLocal(false);
setLocalName('');
setLocalUrl('');
setProbeState({ step: 'url' });
setAuthApiKey('');
setAuthUsername('');
setAuthPassword('');
};
const storedKeys: StoredApiKeys['keys'] = piMonoKeys?.keys ?? [];
const storedProviderIds = new Set(storedKeys.map((k) => k.provider));
const connectedProviders = CHAT_PROVIDERS.filter((p) => storedProviderIds.has(p.providerId));
const unconnectedProviders = CHAT_PROVIDERS.filter((p) => !storedProviderIds.has(p.providerId));
const { data: remoteHealth = {} as Record<string, boolean | null> } = useQuery({
queryKey: ['CHAT_PROVIDERS_REMOTE_HEALTH'],
queryFn: () => client.get<Record<string, boolean | null>>('/server-settings/chat-providers/api-keys/health'),
enabled: connectedProviders.length > 0,
refetchInterval: 30_000,
});
const getStoredMasked = (providerId: string) => storedKeys.find((k) => k.provider === providerId)?.value ?? '';
const saveApiKey = async (providerId: string) => {
const value = keyInputs[providerId];
if (value === undefined) return;
setSavingKey(providerId);
try {
await client.put('/server-settings/chat-providers/api-keys', { provider: providerId, value });
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_API_KEYS'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_REMOTE_HEALTH'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
setKeyInputs((prev) => {
const next = { ...prev };
delete next[providerId];
return next;
});
setEditingProvider(null);
} finally {
setSavingKey(null);
}
};
const disconnectProvider = async (provider: (typeof CHAT_PROVIDERS)[number]) => {
await client.put('/server-settings/chat-providers/api-keys', { provider: provider.providerId, value: '' });
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_API_KEYS'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_REMOTE_HEALTH'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
if (editingProvider === provider.key) setEditingProvider(null);
};
const installAgent = async () => {
setInstalling(true);
try {
const result = await client.post<VersionInfo>('/server-settings/chat-providers/install');
queryClient.setQueryData(['CHAT_PROVIDERS_VERSION'], result);
} finally {
setInstalling(false);
}
};
const handleProbe = async (
auth?: { type: 'api-key'; apiKey: string } | { type: 'basic'; username: string; password: string },
) => {
setProbeState({ step: 'probing' });
try {
const result = await client.post<ProbeResult>('/server-settings/chat-providers/local-providers/probe', {
url: localUrl.trim(),
auth,
});
if (!result.success) {
toast.error(result.error ?? 'Could not detect API type');
setProbeState({ step: 'url' });
return;
}
if (result.needsAuth) {
setProbeState({ step: 'auth', probe: result });
return;
}
// No auth needed — save directly
await saveLocalProvider(result, auth);
} catch {
toast.error(t('settings.server.harnesses.connectFailed'));
setProbeState({ step: 'url' });
}
};
const handleAuthSubmit = async (probe: ProbeResult) => {
const auth =
probe.authType === 'basic'
? { type: 'basic' as const, username: authUsername, password: authPassword }
: { type: 'api-key' as const, apiKey: authApiKey };
// Re-probe with credentials to verify they work
setProbeState({ step: 'probing' });
try {
const result = await client.post<ProbeResult>('/server-settings/chat-providers/local-providers/probe', {
url: localUrl.trim(),
auth,
});
if (!result.success) {
toast.error(result.error ?? 'Could not connect with provided credentials');
setProbeState({ step: 'auth', probe });
return;
}
if (result.needsAuth) {
toast.error(t('settings.server.harnesses.authFailed'));
setProbeState({ step: 'auth', probe });
return;
}
await saveLocalProvider(result, auth);
} catch {
toast.error(t('settings.server.harnesses.connectFailed'));
setProbeState({ step: 'auth', probe });
}
};
const saveLocalProvider = async (
probe: ProbeResult,
auth?: { type: 'api-key'; apiKey: string } | { type: 'basic'; username: string; password: string },
) => {
setProbeState({ step: 'saving' });
try {
await client.post('/server-settings/chat-providers/local-providers', {
url: localUrl.trim(),
name: localName.trim() || probe.name,
apiType: probe.apiType,
auth,
});
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_HEALTH'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
toast.success(t('settings.server.harnesses.connectedTo', { name: probe.name ?? '' }));
resetLocalForm();
} catch {
toast.error(t('settings.server.harnesses.saveFailed'));
setProbeState({ step: 'url' });
}
};
const submitLocalUrl = (ev?: React.FormEvent) => {
ev?.preventDefault();
if (probeState.step === 'url' && localUrl.trim()) handleProbe();
};
const submitAuth = (ev?: React.FormEvent) => {
ev?.preventDefault();
if (probeState.step !== 'auth') return;
const ready = probeState.probe.authType === 'basic' ? authUsername && authPassword : authApiKey;
if (ready) handleAuthSubmit(probeState.probe);
};
const submitApiKey = (ev: React.FormEvent, providerId: string) => {
ev.preventDefault();
if (keyInputs[providerId]) saveApiKey(providerId);
};
const removeLocalProvider = async (id: string) => {
await client.delete(`/server-settings/chat-providers/local-providers/${id}`);
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_PROVIDERS'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_PROVIDERS_LOCAL_HEALTH'] });
queryClient.invalidateQueries({ queryKey: ['CHAT_MODELS'] });
};
return (
<div className="flex flex-col gap-4">
<div>
<div className="flex items-center gap-3">
<span className="text-sm font-medium text-duck-dark">{t('settings.server.harnesses.piMono')}</span>
{piMonoLoading ? (
<span className="text-xs text-duck-dark/50">{t('settings.server.harnesses.checking')}</span>
) : piMonoVersion?.version ? (
<div className="flex items-center gap-2">
<span className="text-xs text-duck-dark/50">{piMonoVersion.version}</span>
<button
type="button"
onClick={installAgent}
disabled={installing}
className="p-0.5 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30"
title={t('settings.server.harnesses.updatePiMono')}
>
<RefreshCw className={`h-3 w-3 text-duck-teal ${installing ? 'animate-spin' : ''}`} />
</button>
</div>
) : (
<Button
size="sm"
className="h-6 text-xs bg-duck-teal text-duck-yellow hover:bg-duck-teal/90"
onClick={installAgent}
disabled={installing}
>
{installing ? 'Installing...' : 'Install'}
</Button>
)}
</div>
{piMonoVersion?.version && (
<>
{/* Local Providers */}
<div className="mt-6 text-xs text-duck-dark/50">
<span className="text-sm font-medium text-duck-dark dark:text-foreground">
{t('settings.server.harnesses.localProviders')}
</span>
<div className="mt-3 flex flex-col gap-2">
{localProviders.map((lp: LocalProviderEntry) => (
<div key={lp.id} className="flex items-center gap-2">
<span
className={`inline-block h-2 w-2 shrink-0 rounded-full ${lp.id in localHealth ? (localHealth[lp.id] ? 'bg-success' : 'bg-red-500') : 'bg-duck-dark/20'}`}
title={
lp.id in localHealth
? localHealth[lp.id]
? 'Online'
: 'Offline'
: t('settings.server.harnesses.checking')
}
/>
<span className="text-duck-dark/70 dark:text-foreground/70 font-medium">{lp.name}</span>
<span className="text-duck-dark/40 dark:text-foreground/40 truncate">{lp.url}</span>
<button
type="button"
onClick={() => removeLocalProvider(lp.id)}
className="shrink-0 p-0.5 rounded hover:bg-red-500/10 cursor-pointer transition-colors"
title={t('settings.server.harnesses.removeProvider')}
>
<Trash2 className="h-3 w-3 text-duck-dark/30 hover:text-red-500" />
</button>
</div>
))}
{addingLocal ? (
<div className="flex flex-col gap-2 max-w-sm">
{/* Step 1: Name + URL inputs stacked */}
<form onSubmit={submitLocalUrl} className="contents">
<Input
type="text"
className="h-8 text-xs"
placeholder={t('settings.server.harnesses.namePlaceholder')}
value={localName}
onChange={(ev) => setLocalName(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Escape') resetLocalForm();
}}
disabled={probeState.step !== 'url'}
autoFocus
/>
<Input
type="url"
className="h-8 text-xs"
placeholder="http://localhost:11434"
value={localUrl}
onChange={(ev) => setLocalUrl(ev.target.value)}
onKeyDown={(ev) => {
if (ev.key === 'Escape') resetLocalForm();
}}
disabled={probeState.step !== 'url'}
/>
<div className="flex items-center gap-2">
{probeState.step === 'url' && (
<Button
type="submit"
size="sm"
className="h-7 text-xs bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!localUrl.trim()}
>
{t('settings.server.harnesses.connect')}
</Button>
)}
{(probeState.step === 'probing' || probeState.step === 'saving') && (
<Loader2 className="h-4 w-4 animate-spin text-duck-teal shrink-0" />
)}
<button
type="button"
onClick={resetLocalForm}
className="text-duck-dark/40 hover:text-duck-dark/70 dark:text-foreground/40 dark:hover:text-foreground/70 cursor-pointer transition-colors text-xs"
>
{tUi('button.cancel')}
</button>
</div>
</form>
{/* Step 2: Auth form (if needed) */}
{probeState.step === 'auth' && (
<form
onSubmit={submitAuth}
className="flex flex-col gap-2 rounded-md border border-duck-dark/10 dark:border-foreground/10 p-3"
>
<span className="text-duck-dark/70 dark:text-foreground/70 font-medium">
{probeState.probe.name} requires authentication
</span>
{probeState.probe.authType === 'basic' ? (
<>
<Input
type="text"
className="h-8 text-xs"
placeholder={t('settings.server.harnesses.username')}
value={authUsername}
onChange={(ev) => setAuthUsername(ev.target.value)}
autoFocus
/>
<Input
type="password"
className="h-8 text-xs"
placeholder={t('settings.server.harnesses.password')}
value={authPassword}
onChange={(ev) => setAuthPassword(ev.target.value)}
/>
<Button
type="submit"
size="sm"
className="h-7 text-xs w-fit bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!authUsername || !authPassword}
>
{t('settings.server.harnesses.authenticate')}
</Button>
</>
) : (
<>
<Input
type="password"
className="h-8 text-xs"
placeholder={t('settings.server.harnesses.apiKey')}
value={authApiKey}
onChange={(ev) => setAuthApiKey(ev.target.value)}
autoFocus
/>
<Button
type="submit"
size="sm"
className="h-7 text-xs w-fit bg-duck-dark text-white hover:bg-duck-dark/90 dark:bg-foreground dark:text-background dark:hover:bg-foreground/90 cursor-pointer disabled:opacity-40"
disabled={!authApiKey}
>
{t('settings.server.harnesses.authenticate')}
</Button>
</>
)}
</form>
)}
</div>
) : (
<Button
type="button"
variant="outline"
size="sm"
className="w-fit cursor-pointer"
onClick={() => setAddingLocal(true)}
>
<Plus className="h-3.5 w-3.5 mr-1.5" />
{t('settings.server.harnesses.connectLocal')}
</Button>
)}
</div>
</div>
{/* Remote Providers */}
<div className="mt-6 text-xs text-duck-dark/50">
<span className="text-sm font-medium text-duck-dark dark:text-foreground">
{t('settings.server.harnesses.remoteProviders')}
</span>
<div className="mt-3 flex flex-col gap-2">
{connectedProviders.map((provider) => (
<div key={provider.providerId} className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<span
className={`inline-block h-2 w-2 shrink-0 rounded-full ${
remoteHealth[provider.providerId] === true
? 'bg-success'
: remoteHealth[provider.providerId] === false
? 'bg-red-500'
: 'bg-duck-dark/20'
}`}
title={
remoteHealth[provider.providerId] === true
? 'Valid'
: remoteHealth[provider.providerId] === false
? 'Invalid'
: 'Unknown'
}
/>
<span className="text-duck-dark/70 dark:text-foreground/70 font-medium">{provider.key}</span>
<button
type="button"
onClick={() => setEditingProvider(editingProvider === provider.key ? null : provider.key)}
className="shrink-0 p-0.5 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors"
title={t('settings.server.harnesses.editKey')}
>
<Pencil className="h-3 w-3 text-duck-dark/30 hover:text-duck-teal" />
</button>
<button
type="button"
onClick={() => disconnectProvider(provider)}
className="shrink-0 p-0.5 rounded hover:bg-red-500/10 cursor-pointer transition-colors"
title={t('settings.server.harnesses.disconnect')}
>
<Trash2 className="h-3 w-3 text-duck-dark/30 hover:text-red-500" />
</button>
</div>
{editingProvider === provider.key && (
<form
onSubmit={(ev) => submitApiKey(ev, provider.providerId)}
className="flex items-center gap-2 pl-4"
>
<Input
type="password"
className="h-7 text-xs flex-1"
placeholder={getStoredMasked(provider.providerId) || t('settings.server.harnesses.enterKey')}
value={keyInputs[provider.providerId] ?? ''}
onChange={(ev) =>
setKeyInputs((prev) => ({ ...prev, [provider.providerId]: ev.target.value }))
}
onKeyDown={(ev) => {
if (ev.key === 'Escape') setEditingProvider(null);
}}
autoFocus
/>
<button
type="submit"
disabled={keyInputs[provider.providerId] === undefined || savingKey === provider.providerId}
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30 disabled:cursor-default"
title={t('settings.server.harnesses.saveKey')}
>
<Save className="h-3.5 w-3.5 text-duck-teal" />
</button>
<button
type="button"
onClick={() => setEditingProvider(null)}
className="text-duck-dark/40 hover:text-duck-dark/70 dark:text-foreground/40 dark:hover:text-foreground/70 cursor-pointer transition-colors text-xs"
>
{tUi('button.cancel')}
</button>
</form>
)}
</div>
))}
{editingProvider &&
(() => {
const provider = CHAT_PROVIDERS.find((p) => p.key === editingProvider);
if (!provider || connectedProviders.some((cp) => cp.providerId === provider.providerId))
return null;
return (
<form
key={provider.providerId}
onSubmit={(ev) => submitApiKey(ev, provider.providerId)}
className="flex items-center gap-2"
>
<label className="w-36 text-duck-dark/70 dark:text-foreground/70 shrink-0 truncate font-medium text-[11px]">
{provider.key}
</label>
<Input
type="password"
className="h-7 text-xs flex-1"
placeholder={t('settings.server.harnesses.enterKey')}
value={keyInputs[provider.providerId] ?? ''}
onChange={(ev) =>
setKeyInputs((prev) => ({ ...prev, [provider.providerId]: ev.target.value }))
}
onKeyDown={(ev) => {
if (ev.key === 'Escape') setEditingProvider(null);
}}
autoFocus
/>
<button
type="submit"
disabled={keyInputs[provider.providerId] === undefined || savingKey === provider.providerId}
className="shrink-0 p-1 rounded hover:bg-duck-teal/10 cursor-pointer transition-colors disabled:opacity-30 disabled:cursor-default"
title={t('settings.server.harnesses.saveKey')}
>
<Save className="h-3.5 w-3.5 text-duck-teal" />
</button>
<button
type="button"
onClick={() => setEditingProvider(null)}
className="text-duck-dark/40 hover:text-duck-dark/70 dark:text-foreground/40 dark:hover:text-foreground/70 cursor-pointer transition-colors text-xs"
>
{tUi('button.cancel')}
</button>
</form>
);
})()}
{unconnectedProviders.length > 0 && (
<Button
type="button"
variant="outline"
size="sm"
className="mt-1 w-fit cursor-pointer"
onClick={() => setCommandOpen(true)}
>
<Plus className="h-3.5 w-3.5 mr-1.5" />
{t('settings.server.harnesses.connectProvider')}
</Button>
)}
</div>
<CommandDialog open={commandOpen} onOpenChange={setCommandOpen}>
<CommandInput placeholder={t('settings.server.harnesses.search')} />
<CommandList>
<CommandEmpty>{t('settings.server.harnesses.none')}</CommandEmpty>
<CommandGroup heading="Available Providers">
{unconnectedProviders.map((provider) => (
<CommandItem
key={provider.key}
onSelect={() => {
setEditingProvider(provider.key);
setCommandOpen(false);
}}
>
<span>{provider.key}</span>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</CommandDialog>
</div>
</>
)}
</div>
</div>
);
};