Feature chore bug 007 #21
@@ -752,14 +752,18 @@
|
|||||||
</section>
|
</section>
|
||||||
{:else if section === "hardware"}
|
{:else if section === "hardware"}
|
||||||
<section>
|
<section>
|
||||||
<h3>Hardware</h3>
|
<h3>{t("settings.hardware.title")}</h3>
|
||||||
{#if settings.hardware}
|
{#if settings.hardware}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
Active backend<code>{settings.hardware.active}</code>
|
{t("settings.hardware.active_backend")}<code>{settings.hardware.active}</code>
|
||||||
<span class="muted">· model {settings.hardware.modelSize}</span>
|
<span class="muted"
|
||||||
|
>{t("settings.hardware.model_meta", {
|
||||||
|
size: settings.hardware.modelSize,
|
||||||
|
})}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<label
|
<label
|
||||||
>Preferred backend
|
>{t("settings.hardware.preferred_backend")}
|
||||||
<select
|
<select
|
||||||
value={settings.settings.preferred_backend}
|
value={settings.settings.preferred_backend}
|
||||||
onchange={(e) =>
|
onchange={(e) =>
|
||||||
@@ -767,40 +771,34 @@
|
|||||||
(e.target as HTMLSelectElement).value as BackendId | "auto",
|
(e.target as HTMLSelectElement).value as BackendId | "auto",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<option value="auto">Auto (best available)</option>
|
<option value="auto">{t("settings.hardware.auto_backend")}</option>
|
||||||
{#each settings.hardware.backends as b (b.id)}
|
{#each settings.hardware.backends as b (b.id)}
|
||||||
<option value={b.id} disabled={!b.available}>
|
<option value={b.id} disabled={!b.available}>
|
||||||
{b.name} ({b.id}){b.available ? "" : " — not available"}
|
{b.name} ({b.id}){b.available ? "" : ` — ${t("settings.hardware.not_available")}`}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<p class="muted">
|
<p class="muted">{t("settings.hardware.fallback_hint")}</p>
|
||||||
Falls back automatically (NPU → NVIDIA → AMD → Intel → CPU) if the chosen backend fails
|
|
||||||
to load.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h4>Audio Devices</h4>
|
<h4>{t("settings.hardware.audio_devices")}</h4>
|
||||||
<label
|
<label
|
||||||
>Recording device
|
>{t("settings.hardware.recording_device")}
|
||||||
<select
|
<select
|
||||||
value={settings.settings.audio_output_device ?? ""}
|
value={settings.settings.audio_output_device ?? ""}
|
||||||
onchange={(e) =>
|
onchange={(e) =>
|
||||||
settings.setAudioOutputDevice((e.target as HTMLSelectElement).value || null)}
|
settings.setAudioOutputDevice((e.target as HTMLSelectElement).value || null)}
|
||||||
>
|
>
|
||||||
<option value="">Default system audio</option>
|
<option value="">{t("settings.hardware.default_system_audio")}</option>
|
||||||
{#each settings.audioDevices as d (d.id)}
|
{#each settings.audioDevices as d (d.id)}
|
||||||
<option value={d.id}>{d.name}</option>
|
<option value={d.id}>{d.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<p class="muted">
|
<p class="muted">{t("settings.hardware.recording_device_hint")}</p>
|
||||||
WhispAssist records whatever this device plays (loopback) — the other side of the call.
|
|
||||||
Pick a specific output if you don't want it following Windows' system default.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<label
|
<label
|
||||||
>Microphone
|
>{t("settings.hardware.microphone")}
|
||||||
<select
|
<select
|
||||||
value={!settings.settings.microphone_enabled
|
value={!settings.settings.microphone_enabled
|
||||||
? "off"
|
? "off"
|
||||||
@@ -811,18 +809,14 @@
|
|||||||
else settings.setMicrophone(true, v || null);
|
else settings.setMicrophone(true, v || null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="off">Off — don't capture my microphone</option>
|
<option value="off">{t("settings.hardware.mic_off")}</option>
|
||||||
<option value="">Default microphone</option>
|
<option value="">{t("settings.hardware.default_mic")}</option>
|
||||||
{#each settings.inputDevices as d (d.id)}
|
{#each settings.inputDevices as d (d.id)}
|
||||||
<option value={d.id}>{d.name}</option>
|
<option value={d.id}>{d.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<p class="muted">
|
<p class="muted">{t("settings.hardware.mic_hint")}</p>
|
||||||
Adds your own voice to the live transcript so both sides of the meeting are captured.
|
|
||||||
Stays on your device — nothing is uploaded. Choose “Off” to transcribe only the system
|
|
||||||
audio above.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{#if settings.hardware.npu?.present}
|
{#if settings.hardware.npu?.present}
|
||||||
{@const npu = settings.hardware.npu}
|
{@const npu = settings.hardware.npu}
|
||||||
@@ -830,24 +824,27 @@
|
|||||||
<div class="npu-package">
|
<div class="npu-package">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Cpu size={14} aria-hidden="true" />
|
<Cpu size={14} aria-hidden="true" />
|
||||||
<strong>NPU accelerator detected</strong>
|
<strong>{t("settings.hardware.npu_detected")}</strong>
|
||||||
{#if ready}
|
{#if ready}
|
||||||
<span class="badge ok"><Check size={12} aria-hidden="true" /> Ready</span>
|
<span class="badge ok"
|
||||||
|
><Check size={12} aria-hidden="true" /> {t("settings.hardware.ready")}</span
|
||||||
|
>
|
||||||
{:else if npuBusy}
|
{:else if npuBusy}
|
||||||
<span class="badge busy">Downloading…</span>
|
<span class="badge busy">{t("settings.hardware.downloading")}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="badge">Package needed</span>
|
<span class="badge">{t("settings.hardware.package_needed")}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if !ready}
|
{#if !ready}
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
Your NPU needs a one-time acceleration package (Whisper ONNX model{npu.runtimeReady
|
{t("settings.hardware.npu_hint", {
|
||||||
? ""
|
extra: npu.runtimeReady ? "" : t("settings.hardware.plus_openvino"),
|
||||||
: " + OpenVINO runtime"}). It downloads in the background; you can also start it
|
})}
|
||||||
now.
|
|
||||||
</p>
|
</p>
|
||||||
<button onclick={downloadNpuPackage} disabled={npuBusy}>
|
<button onclick={downloadNpuPackage} disabled={npuBusy}>
|
||||||
{npuBusy ? "Downloading…" : "Download NPU package"}
|
{npuBusy
|
||||||
|
? t("settings.hardware.downloading")
|
||||||
|
: t("settings.hardware.download_npu")}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if npuMsg}<p class="muted npu-status">{npuMsg}</p>{/if}
|
{#if npuMsg}<p class="muted npu-status">{npuMsg}</p>{/if}
|
||||||
@@ -861,29 +858,34 @@
|
|||||||
<div class="npu-package">
|
<div class="npu-package">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<Cpu size={14} aria-hidden="true" />
|
<Cpu size={14} aria-hidden="true" />
|
||||||
<strong>GPU acceleration (DirectML)</strong>
|
<strong>{t("settings.hardware.dml_title")}</strong>
|
||||||
{#if dmlReady}
|
{#if dmlReady}
|
||||||
<span class="badge ok"><Check size={12} aria-hidden="true" /> Ready</span>
|
<span class="badge ok"
|
||||||
|
><Check size={12} aria-hidden="true" /> {t("settings.hardware.ready")}</span
|
||||||
|
>
|
||||||
{:else if dmlBusy}
|
{:else if dmlBusy}
|
||||||
<span class="badge busy">Downloading…</span>
|
<span class="badge busy">{t("settings.hardware.downloading")}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="badge">Package needed</span>
|
<span class="badge">{t("settings.hardware.package_needed")}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if !dmlReady}
|
{#if !dmlReady}
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
Your GPU can be accelerated without Vulkan via DirectML — a one-time package
|
{t("settings.hardware.dml_hint", {
|
||||||
(Whisper ONNX model{dml.runtimeReady ? "" : " + DirectML runtime"}).
|
extra: dml.runtimeReady ? "" : t("settings.hardware.plus_directml"),
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
<button onclick={downloadDirectmlPackage} disabled={dmlBusy}>
|
<button onclick={downloadDirectmlPackage} disabled={dmlBusy}>
|
||||||
{dmlBusy ? "Downloading…" : "Download DirectML package"}
|
{dmlBusy
|
||||||
|
? t("settings.hardware.downloading")
|
||||||
|
: t("settings.hardware.download_dml")}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{#if dmlMsg}<p class="muted npu-status">{dmlMsg}</p>{/if}
|
{#if dmlMsg}<p class="muted npu-status">{dmlMsg}</p>{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p class="muted">Hardware detection unavailable.</p>
|
<p class="muted">{t("settings.hardware.detection_unavailable")}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<label class="row">
|
<label class="row">
|
||||||
@@ -893,11 +895,12 @@
|
|||||||
onchange={(e) => settings.setLowOverhead((e.target as HTMLInputElement).checked)}
|
onchange={(e) => settings.setLowOverhead((e.target as HTMLInputElement).checked)}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
>Low overhead preset <em>(CPU + smallest model, for battery/background use)</em></span
|
>{t("settings.hardware.low_overhead")}
|
||||||
|
<em>({t("settings.hardware.low_overhead_note")})</em></span
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<h4>Models</h4>
|
<h4>{t("settings.hardware.models")}</h4>
|
||||||
<ul class="models">
|
<ul class="models">
|
||||||
{#each settings.models as m (m.id)}
|
{#each settings.models as m (m.id)}
|
||||||
{@const downloading = m.id in settings.downloadProgress}
|
{@const downloading = m.id in settings.downloadProgress}
|
||||||
@@ -908,16 +911,19 @@
|
|||||||
<span class="meta">
|
<span class="meta">
|
||||||
{fmtBytes(m.size_mb)}
|
{fmtBytes(m.size_mb)}
|
||||||
{#if m.active}
|
{#if m.active}
|
||||||
<span class="badge ok"><Check size={11} aria-hidden="true" /> Active</span>
|
<span class="badge ok"
|
||||||
|
><Check size={11} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.active_badge")}</span
|
||||||
|
>
|
||||||
{:else if m.installed}
|
{:else if m.installed}
|
||||||
<span class="badge">Installed</span>
|
<span class="badge">{t("settings.hardware.installed")}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if downloading}
|
{#if downloading}
|
||||||
<div
|
<div
|
||||||
class="progress"
|
class="progress"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
aria-label="Downloading {m.label}"
|
aria-label={t("settings.hardware.downloading_model", { name: m.label })}
|
||||||
aria-valuenow={pct ?? undefined}
|
aria-valuenow={pct ?? undefined}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuemax={100}
|
aria-valuemax={100}
|
||||||
@@ -929,7 +935,7 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="muted progress-label"
|
<span class="muted progress-label"
|
||||||
>Downloading… {pct !== null ? `${pct}%` : ""}</span
|
>{t("settings.hardware.downloading")} {pct !== null ? `${pct}%` : ""}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -942,19 +948,24 @@
|
|||||||
disabled={m.active}
|
disabled={m.active}
|
||||||
onclick={() => settings.setActiveModel(m.id)}
|
onclick={() => settings.setActiveModel(m.id)}
|
||||||
>
|
>
|
||||||
<Check size={13} aria-hidden="true" /> Use
|
<Check size={13} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.use")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="icon-btn danger"
|
class="icon-btn danger"
|
||||||
disabled={m.active}
|
disabled={m.active}
|
||||||
title={m.active ? "Can't remove the active model" : "Remove"}
|
title={m.active
|
||||||
|
? t("settings.hardware.cant_remove_active")
|
||||||
|
: t("settings.hardware.remove")}
|
||||||
onclick={() => settings.removeModel(m.id)}
|
onclick={() => settings.removeModel(m.id)}
|
||||||
>
|
>
|
||||||
<Trash2 size={13} aria-hidden="true" /> Remove
|
<Trash2 size={13} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.remove")}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="icon-btn" onclick={() => settings.downloadModel(m.id)}>
|
<button class="icon-btn" onclick={() => settings.downloadModel(m.id)}>
|
||||||
<Download size={13} aria-hidden="true" /> Download
|
<Download size={13} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.download")}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -962,12 +973,8 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h4>Speaker diarization</h4>
|
<h4>{t("settings.hardware.diarization")}</h4>
|
||||||
<p class="muted">
|
<p class="muted">{t("settings.hardware.diarization_hint")}</p>
|
||||||
Install both models to have finished recordings separated by speaker (Speaker 1, Speaker
|
|
||||||
2, …) instead of one running transcript. Runs fully offline, once per meeting after it
|
|
||||||
stops. Without them, every line is attributed to a single speaker.
|
|
||||||
</p>
|
|
||||||
<ul class="models">
|
<ul class="models">
|
||||||
{#each settings.diarizationModels as m (m.id)}
|
{#each settings.diarizationModels as m (m.id)}
|
||||||
{@const downloading = m.id in settings.downloadProgress}
|
{@const downloading = m.id in settings.downloadProgress}
|
||||||
@@ -978,14 +985,17 @@
|
|||||||
<span class="meta">
|
<span class="meta">
|
||||||
{fmtBytes(m.size_mb)}
|
{fmtBytes(m.size_mb)}
|
||||||
{#if m.installed}
|
{#if m.installed}
|
||||||
<span class="badge ok"><Check size={11} aria-hidden="true" /> Installed</span>
|
<span class="badge ok"
|
||||||
|
><Check size={11} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.installed")}</span
|
||||||
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if downloading}
|
{#if downloading}
|
||||||
<div
|
<div
|
||||||
class="progress"
|
class="progress"
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
aria-label="Downloading {m.label}"
|
aria-label={t("settings.hardware.downloading_model", { name: m.label })}
|
||||||
aria-valuenow={pct ?? undefined}
|
aria-valuenow={pct ?? undefined}
|
||||||
aria-valuemin={0}
|
aria-valuemin={0}
|
||||||
aria-valuemax={100}
|
aria-valuemax={100}
|
||||||
@@ -997,7 +1007,7 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="muted progress-label"
|
<span class="muted progress-label"
|
||||||
>Downloading… {pct !== null ? `${pct}%` : ""}</span
|
>{t("settings.hardware.downloading")} {pct !== null ? `${pct}%` : ""}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -1007,14 +1017,16 @@
|
|||||||
{:else if m.installed}
|
{:else if m.installed}
|
||||||
<button
|
<button
|
||||||
class="icon-btn danger"
|
class="icon-btn danger"
|
||||||
title="Remove"
|
title={t("settings.hardware.remove")}
|
||||||
onclick={() => settings.removeDiarizationModel(m.id)}
|
onclick={() => settings.removeDiarizationModel(m.id)}
|
||||||
>
|
>
|
||||||
<Trash2 size={13} aria-hidden="true" /> Remove
|
<Trash2 size={13} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.remove")}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="icon-btn" onclick={() => settings.downloadDiarizationModel(m.id)}>
|
<button class="icon-btn" onclick={() => settings.downloadDiarizationModel(m.id)}>
|
||||||
<Download size={13} aria-hidden="true" /> Download
|
<Download size={13} aria-hidden="true" />
|
||||||
|
{t("settings.hardware.download")}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -1042,14 +1054,16 @@
|
|||||||
<p class="muted">{t("settings.transcription.applies_hint")}</p>
|
<p class="muted">{t("settings.transcription.applies_hint")}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<label
|
<label
|
||||||
>Language
|
>{t("settings.transcription.label")}
|
||||||
<select disabled aria-describedby="language-disabled-hint">
|
<select disabled aria-describedby="language-disabled-hint">
|
||||||
<option>English only</option>
|
<option>{t("settings.transcription.english_only")}</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<p class="muted" id="language-disabled-hint">
|
<p class="muted" id="language-disabled-hint">
|
||||||
{activeModel ? `"${activeModel.label}" is English-only.` : "No model selected."} Switch to
|
{activeModel
|
||||||
a multilingual model above to choose a language.
|
? t("settings.transcription.model_english_only", { model: activeModel.label })
|
||||||
|
: t("settings.transcription.no_model")}
|
||||||
|
{t("settings.transcription.switch_multilingual")}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in new issue
Block a user