implement modular backend setup scripts, new frontend components, and expanded server services

This commit is contained in:
techjarves committed 2026-06-22 10:10:29 +05:30
1 parent bd89699994
commit ffec37fd0a
27 files changed
+309 -149

No files matched your search

+2
View File
@@ -169,6 +169,7 @@ function App() {
return {
language: parsed.language || "auto",
threads: Math.max(1, Math.min(32, Number(parsed.threads) || Math.max(1, Math.min(8, navigator.hardwareConcurrency || 4)))),
backendPreference: ["auto", "vulkan", "metal", "cpu"].includes(parsed.backendPreference) ? parsed.backendPreference : "auto",
translate: parsed.translate === true,
};
} catch (_) {}
@@ -176,6 +177,7 @@ function App() {
return {
language: "auto",
threads: Math.max(1, Math.min(8, navigator.hardwareConcurrency || 4)),
backendPreference: "auto",
translate: false,
};
});
+18 -1
View File
@@ -964,7 +964,7 @@ function Settings({
<SectionHeader
icon={Volume2}
title="Speech Transcription"
count={3}
count={4}
color="#10b981"
isExpanded={expandedSections.speech}
onToggle={() => toggleSection("speech")}
@@ -982,6 +982,23 @@ function Settings({
Transcription Settings
</div>
<div className="m3-field-group">
<div className="m3-text-field">
<label className="m3-text-field-label">Default Backend</label>
<select
className="m3-input"
value={speechSettings.backendPreference || "auto"}
onChange={(e) => updateSpeechSetting("backendPreference", e.target.value)}
>
<option value="auto">Auto - GPU if installed</option>
<option value="vulkan">Vulkan GPU</option>
<option value="metal">Metal GPU</option>
<option value="cpu">CPU</option>
</select>
<span className="settings-option-desc" style={{ marginTop: "4px", display: "block" }}>
Auto uses a GPU whisper.cpp backend when its binary exists, then falls back to CPU.
</span>
</div>
<div className="m3-text-field">
<label className="m3-text-field-label">Default Language</label>
<select
@@ -145,10 +145,12 @@ export default function SpeechTranscriber({
const language = speechSettings?.language || "auto";
const threads = speechSettings?.threads || 4;
const backendPreference = speechSettings?.backendPreference || "auto";
const translate = speechSettings?.translate === true;
const setLanguage = (val) => setSpeechSettings((prev) => ({ ...prev, language: val }));
const setThreads = (val) => setSpeechSettings((prev) => ({ ...prev, threads: val }));
const setBackendPreference = (val) => setSpeechSettings((prev) => ({ ...prev, backendPreference: val }));
const [audioBlob, setAudioBlob] = useState(null);
const [audioName, setAudioName] = useState("");
@@ -163,6 +165,9 @@ export default function SpeechTranscriber({
const abortRef = useRef(null);
const installedModels = useMemo(() => models.filter((model) => model.installed), [models]);
const isSelectedRuntimeLoaded = status.ready &&
status.settings?.model === selectedModel &&
(status.settings?.backendPreference || "auto") === backendPreference;
const isEnglishOnly = useMemo(() => {
if (!selectedModel) return false;
@@ -237,8 +242,8 @@ export default function SpeechTranscriber({
if (!selectedModel) return;
setIsLoadingModel(true);
try {
if (!status.ready || status.settings?.model !== selectedModel) {
await startSpeech(selectedModel, { language, threads });
if (!isSelectedRuntimeLoaded) {
await startSpeech(selectedModel, { language, threads, backendPreference });
}
await refresh();
} catch (err) {
@@ -300,10 +305,10 @@ export default function SpeechTranscriber({
const controller = new AbortController();
abortRef.current = controller;
try {
if (!status.ready || status.settings?.model !== selectedModel) {
if (!isSelectedRuntimeLoaded) {
setIsLoadingModel(true);
try {
await startSpeech(selectedModel, { language, threads });
await startSpeech(selectedModel, { language, threads, backendPreference });
await refresh();
} finally {
setIsLoadingModel(false);
@@ -313,6 +318,7 @@ export default function SpeechTranscriber({
model: selectedModel,
language,
threads,
backendPreference,
translate,
filename: audioName || "audio.wav",
signal: controller.signal,
@@ -386,6 +392,17 @@ export default function SpeechTranscriber({
</label>
<div className="speech-controls-row">
<label className="speech-label">
Backend
<select className="m3-input" value={backendPreference} onChange={(event) => setBackendPreference(event.target.value)}>
<option value="auto">Auto (GPU if installed)</option>
{(status.backends || []).map((backend) => (
<option key={backend.key} value={backend.key}>
{backend.label}{backend.installed ? " - installed" : " - missing"}
</option>
))}
</select>
</label>
<label className="speech-label">
Language
<select className="m3-input" value={language} onChange={(event) => setLanguage(event.target.value)}>
@@ -420,10 +437,10 @@ export default function SpeechTranscriber({
<button
className="m3-btn m3-btn-filled"
onClick={handleLoadSpeechModel}
disabled={!selectedModel || isLoadingModel || isTranscribing || (status.ready && status.settings?.model === selectedModel)}
disabled={!selectedModel || isLoadingModel || isTranscribing || isSelectedRuntimeLoaded}
>
{isLoadingModel ? <LoaderCircle className="progress-spinner" size={14} /> : <Play size={14} />}
<span>{status.ready && status.settings?.model === selectedModel ? "Loaded" : isLoadingModel ? "Loading" : "Load"}</span>
<span>{isSelectedRuntimeLoaded ? "Loaded" : isLoadingModel ? "Loading" : "Load"}</span>
</button>
<button className="m3-btn m3-btn-outlined" onClick={refresh}>
<Play size={14} />
+2
View File
@@ -593,6 +593,7 @@ export async function startSpeech(model, options = {}) {
model,
language: options.language || "auto",
threads: options.threads,
backendPreference: options.backendPreference || "auto",
}),
});
return await readJsonResponse(res, "The local server returned an invalid speech start response.");
@@ -609,6 +610,7 @@ export async function transcribeSpeech(fileOrBlob, options = {}) {
if (options.language) params.set("language", options.language);
if (options.filename) params.set("filename", options.filename);
if (options.threads) params.set("threads", String(options.threads));
if (options.backendPreference) params.set("backendPreference", options.backendPreference);
if (options.translate) params.set("translate", "true");
const res = await fetch(`/api/speech/transcribe?${params.toString()}`, {