implement modular backend setup scripts, new frontend components, and expanded server services
This commit is contained in:
1 parent
bd89699994
commit
ffec37fd0a
27 files changed
+309
-149
No files matched your search
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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()}`, {
|
||||
|
||||
Reference in new issue
Block a user