Merge branch 'fix/catalogue-install-capability' into fix/community-integration

# Conflicts:
#	CHANGELOG.md
#	docs/install/troubleshooting.md
This commit is contained in:
Palash Debnath
2026-09-17 12:38:56 +05:30
53 changed files with 221 additions and 61 deletions
@@ -18,6 +18,7 @@ export function EngineInstall({ id }: { id: string }) {
queryFn: () =>
apiJson<{
installed: boolean;
install_allowed?: boolean;
job: null | {
state: string;
steps: { name?: string; state: string }[];
@@ -36,8 +37,15 @@ export function EngineInstall({ id }: { id: string }) {
<Button
size="sm"
variant="outline"
disabled={running || status.data?.installed}
disabled={
running ||
status.isPending ||
status.isError ||
status.data?.installed ||
status.data?.install_allowed === false
}
onClick={async () => {
if (status.data?.install_allowed === false) return;
setStarting(true);
setFailed(null);
setDismissedFailure(false);
@@ -54,6 +62,11 @@ export function EngineInstall({ id }: { id: string }) {
<DownloadIcon />
{t(running ? 'modelMaintenance.installing' : 'modelMaintenance.install')}
</Button>
{status.data?.install_allowed === false && (
<p className="max-w-sm text-xs text-muted-foreground">
{t('engines.localInstallRequired')}
</p>
)}
{!dismissedFailure && (failed || status.isError || status.data?.job?.state === 'failed') && (
<SettingsActionError
className="max-w-sm text-left"
@@ -107,7 +107,41 @@ it('confirms a diarisation engine selection after the backend accepts it', async
);
fireEvent.click(await screen.findByRole('button', { name: 'modelSettings.select' }));
await waitFor(() =>
expect(mock.toast.success).toHaveBeenCalledWith('settings.engine_switched'),
await waitFor(() => expect(mock.toast.success).toHaveBeenCalledWith('settings.engine_switched'));
});
it('explains remote native installation restrictions before a POST', async () => {
mock.api.mockImplementation((path: string) =>
Promise.resolve(
path === '/engines/diarisation'
? {
active: 'pyannote',
options: [
{
id: 'audiocpp-sortformer',
label: 'Sortformer',
model_installed: true,
runtime_installed: false,
installed: false,
},
],
}
: {
installed: false,
supported: true,
install_allowed: false,
job: { state: 'idle', progress: 0 },
},
),
);
render(
<QueryClientProvider client={new QueryClient()}>
<DiarisationSettings />
</QueryClientProvider>,
);
expect(await screen.findByText('engines.localInstallRequired')).toBeInTheDocument();
const button = screen.getByRole('button', { name: 'modelMaintenance.install' });
expect(button).toBeDisabled();
fireEvent.click(button);
expect(mock.api.mock.calls.some(([, init]) => init?.method === 'POST')).toBe(false);
});
@@ -61,6 +61,7 @@ export function DiarisationSettings() {
apiJson<{
installed: boolean;
supported: boolean;
install_allowed?: boolean;
job: { state: string; progress: number; error?: string | null };
}>('/engines/audiocpp/runtime/install/status'),
refetchInterval: (state) => (state.state.data?.job.state === 'running' ? 1_500 : 10_000),
@@ -75,6 +76,7 @@ export function DiarisationSettings() {
}, [client, runtime.data?.installed]);
const runtimeRunning = busy || runtime.data?.job.state === 'running';
const installRuntime = async () => {
if (runtime.data?.install_allowed === false) return;
setBusy(true);
setFailed(null);
try {
@@ -152,7 +154,12 @@ export function DiarisationSettings() {
<Button
size="sm"
variant="outline"
disabled={runtimeRunning}
disabled={runtimeRunning || runtime.data?.install_allowed === false}
title={
runtime.data?.install_allowed === false
? t('engines.localInstallRequired')
: undefined
}
onClick={() => void installRuntime()}
>
<DownloadIcon />
@@ -163,6 +170,13 @@ export function DiarisationSettings() {
: t('modelMaintenance.install')}
</Button>
)}
{option.id === 'audiocpp-sortformer' &&
!option.runtime_installed &&
runtime.data?.install_allowed === false && (
<p className="max-w-sm text-xs text-muted-foreground">
{t('engines.localInstallRequired')}
</p>
)}
{option.installed && (
<Button
size="sm"
@@ -380,11 +394,13 @@ export function ModelSettings({
name: engine.display_name,
available: engine.available,
detail:
engine.id === selected
? engineState.active_model
: !engine.available
? engine.install_hint || engine.reason || engine.hint || undefined
: undefined,
engine.local_install_required && !engine.available
? t('engines.localInstallRequired')
: engine.id === selected
? engineState.active_model
: !engine.available
? engine.install_hint || engine.reason || engine.hint || undefined
: undefined,
models: engine.curated_models,
installable: engine.one_click_install,
setupSnippet: !engine.available ? engine.setup_snippet || undefined : undefined,
@@ -2492,7 +2492,8 @@
"none_ready_title": "تحويل النص إلى كلام غير جاهز",
"none_ready_body": "يمكن لـ VoiceStudio إعداد أفضل خيار متوافق مع هذا الجهاز تلقائيًا.",
"active": "المحرك: {{name}}",
"none": "لا يوجد محرك نشط"
"none": "لا يوجد محرك نشط",
"localInstallRequired": "ثبّت هذا المحرك على جهاز الخادم باستخدام localhost أو دليل الإعداد. التثبيت عن بُعد معطّل."
},
"performanceProfile": {
"requiresEngine": "ثبّت {{engine}} وحدده لضبط السرعة والجودة.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "Sprachausgabe ist nicht bereit",
"none_ready_body": "VoiceStudio kann automatisch die beste kompatible Option für dieses Gerät einrichten.",
"active": "Engine: {{name}}",
"none": "Keine Engine aktiv"
"none": "Keine Engine aktiv",
"localInstallRequired": "Installiere diese Engine auf dem Backend-Rechner über localhost oder die Einrichtungsanleitung. Die Ferninstallation ist deaktiviert."
},
"performanceProfile": {
"requiresEngine": "Installiere und wähle {{engine}}, um Geschwindigkeit und Qualität anzupassen.",
@@ -114,7 +114,8 @@
"recheck": "Re-check",
"rechecking": "Re-checking…",
"failed": "failed",
"latencyMs": "{{ms}} ms"
"latencyMs": "{{ms}} ms",
"localInstallRequired": "Install this engine on the backend computer using localhost or its setup guide. Remote installation is disabled."
},
"clone": {
"title": "Voice cloning",
@@ -2486,7 +2486,8 @@
"none_ready_title": "La síntesis de voz no está lista",
"none_ready_body": "VoiceStudio puede configurar automáticamente la mejor opción compatible con este dispositivo.",
"active": "Motor: {{name}}",
"none": "Ningún motor activo"
"none": "Ningún motor activo",
"localInstallRequired": "Instala este motor en el equipo del servidor mediante localhost o su guía de configuración. La instalación remota está desactivada."
},
"performanceProfile": {
"requiresEngine": "Instala y selecciona {{engine}} para ajustar la velocidad y la calidad.",
@@ -2486,7 +2486,8 @@
"none_ready_title": "La synthèse vocale nest pas prête",
"none_ready_body": "VoiceStudio peut configurer automatiquement la meilleure option compatible avec cet appareil.",
"active": "Moteur : {{name}}",
"none": "Aucun moteur actif"
"none": "Aucun moteur actif",
"localInstallRequired": "Installez ce moteur sur lordinateur du serveur via localhost ou son guide de configuration. Linstallation à distance est désactivée."
},
"performanceProfile": {
"requiresEngine": "Installez et sélectionnez {{engine}} pour régler la vitesse et la qualité.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "वाक् संश्लेषण तैयार नहीं है",
"none_ready_body": "VoiceStudio इस डिवाइस के लिए सबसे अच्छा संगत विकल्प अपने आप सेट कर सकता है।",
"active": "इंजन: {{name}}",
"none": "कोई इंजन सक्रिय नहीं है"
"none": "कोई इंजन सक्रिय नहीं है",
"localInstallRequired": "इस इंजन को बैकएंड कंप्यूटर पर localhost या सेटअप गाइड से इंस्टॉल करें। रिमोट इंस्टॉलेशन बंद है।"
},
"performanceProfile": {
"requiresEngine": "गति और गुणवत्ता समायोजित करने के लिए {{engine}} इंस्टॉल करके चुनें।",
@@ -2484,7 +2484,8 @@
"none_ready_title": "Sintesis ucapan belum siap",
"none_ready_body": "VoiceStudio dapat menyiapkan opsi kompatibel terbaik untuk perangkat ini secara otomatis.",
"active": "Mesin: {{name}}",
"none": "Tidak ada mesin aktif"
"none": "Tidak ada mesin aktif",
"localInstallRequired": "Instal mesin ini di komputer backend melalui localhost atau panduan penyiapannya. Instalasi jarak jauh dinonaktifkan."
},
"performanceProfile": {
"requiresEngine": "Instal dan pilih {{engine}} untuk menyesuaikan kecepatan dan kualitas.",
@@ -2486,7 +2486,8 @@
"none_ready_title": "La sintesi vocale non è pronta",
"none_ready_body": "VoiceStudio può configurare automaticamente lopzione compatibile migliore per questo dispositivo.",
"active": "Motore: {{name}}",
"none": "Nessun motore attivo"
"none": "Nessun motore attivo",
"localInstallRequired": "Installa questo motore sul computer del backend tramite localhost o la guida alla configurazione. Linstallazione remota è disabilitata."
},
"performanceProfile": {
"requiresEngine": "Installa e seleziona {{engine}} per regolare velocità e qualità.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "音声合成の準備ができていません",
"none_ready_body": "VoiceStudio がこのデバイスに最適な互換オプションを自動で設定できます。",
"active": "エンジン:{{name}}",
"none": "有効なエンジンがありません"
"none": "有効なエンジンがありません",
"localInstallRequired": "バックエンドのコンピューターで localhost またはセットアップガイドを使ってこのエンジンをインストールしてください。リモートインストールは無効です。"
},
"performanceProfile": {
"requiresEngine": "速度と品質を調整するには、{{engine}} をインストールして選択してください。",
@@ -2484,7 +2484,8 @@
"none_ready_title": "음성 합성이 준비되지 않았습니다",
"none_ready_body": "VoiceStudio가 이 기기에 가장 적합한 호환 옵션을 자동으로 설정할 수 있습니다.",
"active": "엔진: {{name}}",
"none": "활성 엔진 없음"
"none": "활성 엔진 없음",
"localInstallRequired": "백엔드 컴퓨터에서 localhost 또는 설정 가이드를 사용하여 이 엔진을 설치하세요. 원격 설치는 비활성화되어 있습니다."
},
"performanceProfile": {
"requiresEngine": "속도와 품질을 조절하려면 {{engine}}을 설치하고 선택하세요.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "Spraaksynthese is niet gereed",
"none_ready_body": "VoiceStudio kan automatisch de beste compatibele optie voor dit apparaat instellen.",
"active": "Engine: {{name}}",
"none": "Geen engine actief"
"none": "Geen engine actief",
"localInstallRequired": "Installeer deze engine op de backendcomputer via localhost of de installatiehandleiding. Installatie op afstand is uitgeschakeld."
},
"performanceProfile": {
"requiresEngine": "Installeer en selecteer {{engine}} om snelheid en kwaliteit af te stellen.",
@@ -2488,7 +2488,8 @@
"none_ready_title": "Synteza mowy nie jest gotowa",
"none_ready_body": "VoiceStudio może automatycznie skonfigurować najlepszą zgodną opcję dla tego urządzenia.",
"active": "Silnik: {{name}}",
"none": "Brak aktywnego silnika"
"none": "Brak aktywnego silnika",
"localInstallRequired": "Zainstaluj ten silnik na komputerze zaplecza przez localhost lub zgodnie z instrukcją konfiguracji. Instalacja zdalna jest wyłączona."
},
"performanceProfile": {
"requiresEngine": "Zainstaluj i wybierz {{engine}}, aby dostosować szybkość i jakość.",
@@ -2486,7 +2486,8 @@
"none_ready_title": "A síntese de voz não está pronta",
"none_ready_body": "O VoiceStudio pode configurar automaticamente a melhor opção compatível com este dispositivo.",
"active": "Motor: {{name}}",
"none": "Nenhum motor ativo"
"none": "Nenhum motor ativo",
"localInstallRequired": "Instale este motor no computador do backend pelo localhost ou pelo guia de configuração. A instalação remota está desativada."
},
"performanceProfile": {
"requiresEngine": "Instale e selecione {{engine}} para ajustar a velocidade e a qualidade.",
@@ -2488,7 +2488,8 @@
"none_ready_title": "Синтез речи не готов",
"none_ready_body": "VoiceStudio может автоматически настроить лучший совместимый вариант для этого устройства.",
"active": "Движок: {{name}}",
"none": "Нет активного движка"
"none": "Нет активного движка",
"localInstallRequired": "Установите этот движок на компьютере сервера через localhost или по инструкции. Удалённая установка отключена."
},
"performanceProfile": {
"requiresEngine": "Установите и выберите {{engine}}, чтобы настроить скорость и качество.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "Talsyntesen är inte redo",
"none_ready_body": "VoiceStudio kan automatiskt konfigurera det bästa kompatibla alternativet för den här enheten.",
"active": "Motor: {{name}}",
"none": "Ingen motor aktiv"
"none": "Ingen motor aktiv",
"localInstallRequired": "Installera motorn på backenddatorn via localhost eller installationsguiden. Fjärrinstallation är avstängd."
},
"performanceProfile": {
"requiresEngine": "Installera och välj {{engine}} för att justera hastighet och kvalitet.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "การสังเคราะห์เสียงยังไม่พร้อม",
"none_ready_body": "VoiceStudio สามารถตั้งค่าตัวเลือกที่เข้ากันได้ดีที่สุดสำหรับอุปกรณ์นี้โดยอัตโนมัติ",
"active": "เอนจิน: {{name}}",
"none": "ไม่มีเอนจินที่ใช้งานอยู่"
"none": "ไม่มีเอนจินที่ใช้งานอยู่",
"localInstallRequired": "ติดตั้งเอนจินนี้บนคอมพิวเตอร์แบ็กเอนด์ผ่าน localhost หรือคู่มือการตั้งค่า การติดตั้งจากระยะไกลถูกปิดใช้งาน"
},
"performanceProfile": {
"requiresEngine": "ติดตั้งและเลือก {{engine}} เพื่อปรับความเร็วและคุณภาพ",
@@ -2484,7 +2484,8 @@
"none_ready_title": "Konuşma sentezi hazır değil",
"none_ready_body": "VoiceStudio bu cihaz için en iyi uyumlu seçeneği otomatik olarak ayarlayabilir.",
"active": "Motor: {{name}}",
"none": "Etkin motor yok"
"none": "Etkin motor yok",
"localInstallRequired": "Bu motoru arka uç bilgisayarında localhost veya kurulum kılavuzu üzerinden yükleyin. Uzaktan kurulum devre dışıdır."
},
"performanceProfile": {
"requiresEngine": "Hız ve kaliteyi ayarlamak için {{engine}} yükleyip seçin.",
@@ -2488,7 +2488,8 @@
"none_ready_title": "Синтез мовлення не готовий",
"none_ready_body": "VoiceStudio може автоматично налаштувати найкращий сумісний варіант для цього пристрою.",
"active": "Рушій: {{name}}",
"none": "Немає активного рушія"
"none": "Немає активного рушія",
"localInstallRequired": "Установіть цей рушій на комп’ютері сервера через localhost або за інструкцією. Віддалене встановлення вимкнено."
},
"performanceProfile": {
"requiresEngine": "Установіть і виберіть {{engine}}, щоб налаштувати швидкість і якість.",
@@ -2484,7 +2484,8 @@
"none_ready_title": "Tổng hợp giọng nói chưa sẵn sàng",
"none_ready_body": "VoiceStudio có thể tự động thiết lập tùy chọn tương thích tốt nhất cho thiết bị này.",
"active": "Bộ máy: {{name}}",
"none": "Không có bộ máy đang hoạt động"
"none": "Không có bộ máy đang hoạt động",
"localInstallRequired": "Cài đặt công cụ này trên máy chủ qua localhost hoặc hướng dẫn thiết lập. Cài đặt từ xa đã bị tắt."
},
"performanceProfile": {
"requiresEngine": "Cài đặt và chọn {{engine}} để điều chỉnh tốc độ và chất lượng.",
@@ -2488,7 +2488,8 @@
"none_ready_title": "语音合成尚未就绪",
"none_ready_body": "VoiceStudio 可以自动为此设备设置最佳兼容选项。",
"active": "引擎:{{name}}",
"none": "没有启用的引擎"
"none": "没有启用的引擎",
"localInstallRequired": "请在后端计算机上通过 localhost 或安装指南安装此引擎。远程安装已禁用。"
},
"performanceProfile": {
"requiresEngine": "安装并选择 {{engine}} 以调整速度和质量。",
@@ -2484,7 +2484,8 @@
"none_ready_title": "語音合成尚未就緒",
"none_ready_body": "VoiceStudio 可以自動為此裝置設定最佳相容選項。",
"active": "引擎:{{name}}",
"none": "沒有啟用的引擎"
"none": "沒有啟用的引擎",
"localInstallRequired": "請在後端電腦上透過 localhost 或安裝指南安裝此引擎。遠端安裝已停用。"
},
"performanceProfile": {
"requiresEngine": "安裝並選取 {{engine}} 以調整速度與品質。",
@@ -66,6 +66,7 @@ export interface EngineBackend {
setup_snippet?: string | null;
docs_url?: string | null;
one_click_install?: boolean;
local_install_required?: boolean;
license_required?: boolean;
license_accepted?: boolean;
effective_device?: string;