noise suppression added via nnnoiseless + vad + gain-normalizer + few frontend changes
This commit is contained in:
parent
bd8e2c989d
commit
88ecf21b2c
73 changed files with 1235 additions and 112 deletions
|
|
@ -1,8 +1,26 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { Router } from "@roxi/routify"
|
||||
import routes from "../.routify/routes.default.js"
|
||||
import { SvelteUIProvider } from "@svelteuidev/core"
|
||||
import Events from "./Events.svelte"
|
||||
|
||||
import {
|
||||
loadVoiceSetting,
|
||||
loadAppInfo,
|
||||
startStatsPolling,
|
||||
stopStatsPolling
|
||||
} from "@/stores"
|
||||
|
||||
onMount(() => {
|
||||
loadVoiceSetting()
|
||||
loadAppInfo()
|
||||
startStatsPolling(5000)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
stopStatsPolling()
|
||||
})
|
||||
</script>
|
||||
|
||||
<SvelteUIProvider themeObserver="dark" withNormalizeCSS withGlobalStyles>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,24 @@
|
|||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { capitalizeFirstLetter } from "@/functions"
|
||||
|
||||
import {
|
||||
Text,
|
||||
} from "@svelteuidev/core"
|
||||
|
||||
let jarvisStats = { running: false, ram_mb: 0, cpu_usage: 0 }
|
||||
|
||||
let microphoneLabel = ""
|
||||
let wakeWordEngine = ""
|
||||
let sttEngine = "Vosk"
|
||||
let ramUsage = "-"
|
||||
// let ramUsage = "-"
|
||||
|
||||
let interval: number | null = null
|
||||
let statsUpdateInterval: number | null = null
|
||||
|
||||
async function updateRamUsage() {
|
||||
async function updateStats() {
|
||||
try {
|
||||
const usage = await invoke<number>("get_current_ram_usage")
|
||||
ramUsage = usage.toFixed(2)
|
||||
jarvisStats = await invoke<{running: boolean, ram_mb: number, cpu_usage: number}>("get_jarvis_app_stats")
|
||||
//const usage = await invoke<number>("get_current_ram_usage")
|
||||
//ramUsage = usage.toFixed(2)
|
||||
} catch (err) {
|
||||
console.error("failed to get ram usage:", err)
|
||||
}
|
||||
|
|
@ -21,7 +28,8 @@
|
|||
|
||||
onMount(async () => {
|
||||
// start polling ram usage
|
||||
interval = setInterval(updateRamUsage, 1000) as unknown as number
|
||||
updateStats()
|
||||
statsUpdateInterval = setInterval(updateStats, 5000) as unknown as number
|
||||
|
||||
try {
|
||||
// load microphone info
|
||||
|
|
@ -37,8 +45,8 @@
|
|||
})
|
||||
|
||||
onDestroy(() => {
|
||||
if (interval) {
|
||||
clearInterval(interval)
|
||||
if (statsUpdateInterval) {
|
||||
clearInterval(statsUpdateInterval)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -64,7 +72,12 @@
|
|||
<div class="pulse"><div class="wave"></div></div>
|
||||
<div class="info">
|
||||
<span class="num">Ресурсы</span>
|
||||
<small>RAM {ramUsage}mb</small>
|
||||
{#if jarvisStats.running}
|
||||
<small>RAM: {jarvisStats.ram_mb} MB</small>
|
||||
<!--<Text>CPU: {jarvisStats.cpu_usage.toFixed(1)}%</Text>-->
|
||||
{:else}
|
||||
<Text color="gray">-</Text>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,17 +11,3 @@ export function showInExplorer(path: string): void {
|
|||
invoke("show_in_folder", { path })
|
||||
.catch(err => console.error("failed to open explorer:", err))
|
||||
}
|
||||
|
||||
// ### LISTENER FUNCTIONS
|
||||
// removed since gui now doesn't handle listening
|
||||
|
||||
export function startListening(): void {
|
||||
// disabled in GUI - listening is handled by the tray app
|
||||
console.log("[gui] listening not available in settings app")
|
||||
}
|
||||
|
||||
export function stopListening(callback?: () => void): void {
|
||||
// disabled in GUI - just call the callback if provided
|
||||
console.log("[gui] listening not available in settings app")
|
||||
if (callback) callback()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
import { Notification, Space } from "@svelteuidev/core"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
import { Notification, Space, Button } from "@svelteuidev/core"
|
||||
import { InfoCircled } from "radix-icons-svelte"
|
||||
|
||||
import SearchBar from "@/components/elements/SearchBar.svelte"
|
||||
|
|
@ -9,11 +10,13 @@
|
|||
import Stats from "@/components/elements/Stats.svelte"
|
||||
import Footer from "@/components/Footer.svelte"
|
||||
|
||||
import { isListening } from "@/stores"
|
||||
import { isJarvisRunning, updateJarvisStats } from "@/stores"
|
||||
|
||||
let listening = false
|
||||
isListening.subscribe(value => {
|
||||
listening = value
|
||||
let running = false
|
||||
let launching = false
|
||||
|
||||
isJarvisRunning.subscribe(value => {
|
||||
running = value
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
|
|
@ -23,19 +26,48 @@
|
|||
onDestroy(() => {
|
||||
document.body.classList.remove("assist-page")
|
||||
})
|
||||
|
||||
async function runAssistant() {
|
||||
launching = true
|
||||
try {
|
||||
await invoke("run_jarvis_app")
|
||||
// wait a bit then check if it's running
|
||||
setTimeout(() => {
|
||||
updateJarvisStats()
|
||||
launching = false
|
||||
}, 2000)
|
||||
} catch (err) {
|
||||
console.error("Failed to run jarvis-app:", err)
|
||||
launching = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<HDivider />
|
||||
|
||||
{#if !listening}
|
||||
{#if !running}
|
||||
<Notification
|
||||
title="Внимание!"
|
||||
icon={InfoCircled}
|
||||
color="cyan"
|
||||
withCloseButton={false}
|
||||
>
|
||||
В данный момент ассистент не прослушивает команды.<br />
|
||||
Пожалуйста, <a href="/settings">перейдите в настройки</a> и введите ключ Picovoice.
|
||||
В данный момент ассистент не запущен.<br />
|
||||
Но вы всё еще можете изменять его настройки.<br />
|
||||
<br />
|
||||
|
||||
<Button
|
||||
color="lime"
|
||||
radius="md"
|
||||
size="sm"
|
||||
uppercase
|
||||
ripple
|
||||
fullSize
|
||||
on:click={runAssistant}
|
||||
disabled={launching}
|
||||
>
|
||||
{launching ? "Запуск..." : "Запустить"}
|
||||
</Button>
|
||||
</Notification>
|
||||
{:else}
|
||||
<ArcReactor />
|
||||
|
|
@ -43,4 +75,4 @@
|
|||
|
||||
<HDivider noMargin />
|
||||
<Stats />
|
||||
<Footer />
|
||||
<Footer />
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
import { goto } from "@roxi/routify"
|
||||
import { setTimeout } from "worker-timers"
|
||||
|
||||
import { showInExplorer, stopListening, startListening } from "@/functions"
|
||||
import { showInExplorer } from "@/functions"
|
||||
import { appInfo, assistantVoice } from "@/stores"
|
||||
|
||||
import HDivider from "@/components/elements/HDivider.svelte"
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
Alert,
|
||||
Input,
|
||||
InputWrapper,
|
||||
NativeSelect
|
||||
NativeSelect,
|
||||
Switch
|
||||
} from "@svelteuidev/core"
|
||||
|
||||
import {
|
||||
|
|
@ -43,12 +44,15 @@
|
|||
let settingsSaved = false
|
||||
let saveButtonDisabled = false
|
||||
|
||||
// form values
|
||||
// form values (state vars)
|
||||
let voiceVal = ""
|
||||
let selectedMicrophone = ""
|
||||
let selectedWakeWordEngine = ""
|
||||
let selectedIntentRecognitionEngine = ""
|
||||
let selectedVoskModel = ""
|
||||
let selectedNoiseSuppression = ""
|
||||
let selectedVad = ""
|
||||
let gainNormalizerEnabled = false
|
||||
let apiKeyPicovoice = ""
|
||||
let apiKeyOpenai = ""
|
||||
|
||||
|
|
@ -76,6 +80,11 @@
|
|||
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
|
||||
invoke("db_write", { key: "selected_intent_recognition_engine", val: selectedIntentRecognitionEngine }),
|
||||
invoke("db_write", { key: "selected_vosk_model", val: selectedVoskModel }),
|
||||
|
||||
invoke("db_write", { key: "noise_suppression", val: selectedNoiseSuppression }),
|
||||
invoke("db_write", { key: "vad", val: selectedVad }),
|
||||
invoke("db_write", { key: "gain_normalizer", val: gainNormalizerEnabled.toString() }),
|
||||
|
||||
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
|
||||
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
|
||||
])
|
||||
|
|
@ -90,7 +99,7 @@
|
|||
}, 5000)
|
||||
|
||||
// restart listening with new settings
|
||||
stopListening(() => startListening())
|
||||
// stopListening(() => startListening())
|
||||
} catch (err) {
|
||||
console.error("failed to save settings:", err)
|
||||
}
|
||||
|
|
@ -105,10 +114,13 @@
|
|||
try {
|
||||
// load microphones
|
||||
const mics = await invoke<string[]>("pv_get_audio_devices")
|
||||
availableMicrophones = mics.map((name, idx) => ({
|
||||
label: name,
|
||||
value: String(idx)
|
||||
}))
|
||||
availableMicrophones = [
|
||||
{ label: "По умолчанию (Система)", value: "-1" }, // system default
|
||||
...mics.map((name, idx) => ({
|
||||
label: name,
|
||||
value: String(idx)
|
||||
}))
|
||||
]
|
||||
|
||||
// load vosk models
|
||||
const voskModels = await invoke<{ name: string; language: string; size: string }[]>("list_vosk_models")
|
||||
|
|
@ -118,11 +130,18 @@
|
|||
}))
|
||||
|
||||
// load settings from db
|
||||
const [mic, wakeWord, intentReco, voskModel, pico, openai] = await Promise.all([
|
||||
const [mic, wakeWord, intentReco, voskModel,
|
||||
noiseSuppression, vad, gainNormalizer,
|
||||
pico, openai] = await Promise.all([
|
||||
invoke<string>("db_read", { key: "selected_microphone" }),
|
||||
invoke<string>("db_read", { key: "selected_wake_word_engine" }),
|
||||
invoke<string>("db_read", { key: "selected_intent_recognition_engine" }),
|
||||
invoke<string>("db_read", { key: "selected_vosk_model" }),
|
||||
|
||||
invoke<string>("db_read", { key: "noise_suppression" }),
|
||||
invoke<string>("db_read", { key: "vad" }),
|
||||
invoke<string>("db_read", { key: "gain_normalizer" }),
|
||||
|
||||
invoke<string>("db_read", { key: "api_key__picovoice" }),
|
||||
invoke<string>("db_read", { key: "api_key__openai" })
|
||||
])
|
||||
|
|
@ -131,6 +150,9 @@
|
|||
selectedWakeWordEngine = wakeWord
|
||||
selectedIntentRecognitionEngine = intentReco
|
||||
selectedVoskModel = voskModel
|
||||
selectedNoiseSuppression = noiseSuppression
|
||||
selectedVad = vad
|
||||
gainNormalizerEnabled = gainNormalizer === "true"
|
||||
apiKeyPicovoice = pico
|
||||
apiKeyOpenai = openai
|
||||
} catch (err) {
|
||||
|
|
@ -179,7 +201,9 @@
|
|||
<Space h="sm" />
|
||||
<NativeSelect
|
||||
data={[
|
||||
{ label: "Jarvis ремейк (от Хауди)", value: "jarvis-remake" },
|
||||
{ label: "Jarvis New (ремастер)", value: "jarvis-remaster" },
|
||||
{ label: "Рик из «Рик и Морти»", value: "rick-morty" },
|
||||
{ label: "Jarvis (от Хауди)", value: "jarvis-howdy" },
|
||||
{ label: "Jarvis OG (из фильмов)", value: "jarvis-og" }
|
||||
]}
|
||||
label="Голос ассистента"
|
||||
|
|
@ -281,6 +305,46 @@
|
|||
|
||||
<Space h="xl" />
|
||||
|
||||
<NativeSelect
|
||||
data={[
|
||||
{ label: "Отключено", value: "None" },
|
||||
{ label: "Nnnoiseless", value: "Nnnoiseless" }
|
||||
]}
|
||||
label="Шумоподавление"
|
||||
description="Уменьшает фоновый шум. Может ухудшить распознавание в некоторых случаях."
|
||||
variant="filled"
|
||||
bind:value={selectedNoiseSuppression}
|
||||
/>
|
||||
|
||||
<Space h="md" />
|
||||
|
||||
<NativeSelect
|
||||
data={[
|
||||
{ label: "Отключено", value: "None" },
|
||||
{ label: "Energy (простой)", value: "Energy" },
|
||||
{ label: "Nnnoiseless (нейросеть)", value: "Nnnoiseless" }
|
||||
]}
|
||||
label="Определение голосой активности (VAD)"
|
||||
description="Пропускает тишину, экономит ресурсы CPU."
|
||||
variant="filled"
|
||||
bind:value={selectedVad}
|
||||
/>
|
||||
|
||||
<Space h="md" />
|
||||
|
||||
<InputWrapper label="Нормализация громкости">
|
||||
<Text size="sm" color="gray">
|
||||
Автоматически регулирует уровень громкости.
|
||||
</Text>
|
||||
<Space h="xs" />
|
||||
<Switch
|
||||
label={gainNormalizerEnabled ? "Включено" : "Выключено"}
|
||||
bind:checked={gainNormalizerEnabled}
|
||||
/>
|
||||
</InputWrapper>
|
||||
|
||||
<Space h="xl" />
|
||||
|
||||
<InputWrapper label="Ключ OpenAI">
|
||||
<Text size="sm" color="gray">
|
||||
В данный момент ChatGPT <u>не поддерживается</u>.
|
||||
|
|
|
|||
|
|
@ -1,26 +1,15 @@
|
|||
import { writable, get } from "svelte/store"
|
||||
import { writable } from "svelte/store"
|
||||
import { invoke } from "@tauri-apps/api/core"
|
||||
|
||||
// ### LISTENING STATE
|
||||
// note: defaults to false since GUI doesn't have listening capability
|
||||
export const isListening = writable(false)
|
||||
// ### RUNNING STATE
|
||||
export const isJarvisRunning = writable(false)
|
||||
export const jarvisRamUsage = writable(0)
|
||||
export const jarvisCpuUsage = writable(0)
|
||||
|
||||
// ### ASSISTANT VOICE
|
||||
export const assistantVoice = writable("")
|
||||
|
||||
// load voice setting from db
|
||||
async function loadVoiceSetting() {
|
||||
try {
|
||||
const voice = await invoke<string>("db_read", { key: "assistant_voice" })
|
||||
assistantVoice.set(voice)
|
||||
} catch (err) {
|
||||
console.error("failed to load voice setting:", err)
|
||||
}
|
||||
}
|
||||
loadVoiceSetting()
|
||||
|
||||
// ### APP INFO
|
||||
// these are loaded once on startup
|
||||
export const appInfo = writable({
|
||||
tgOfficialLink: "",
|
||||
feedbackLink: "",
|
||||
|
|
@ -28,7 +17,17 @@ export const appInfo = writable({
|
|||
logFilePath: ""
|
||||
})
|
||||
|
||||
async function loadAppInfo() {
|
||||
// ### INIT FUNCTIONS (call these from a component)
|
||||
export async function loadVoiceSetting() {
|
||||
try {
|
||||
const voice = await invoke<string>("db_read", { key: "assistant_voice" })
|
||||
assistantVoice.set(voice)
|
||||
} catch (err) {
|
||||
console.error("failed to load voice setting:", err)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadAppInfo() {
|
||||
try {
|
||||
const [tg, feedback, repo, logPath] = await Promise.all([
|
||||
invoke<string>("get_tg_official_link"),
|
||||
|
|
@ -47,4 +46,31 @@ async function loadAppInfo() {
|
|||
console.error("failed to load app info:", err)
|
||||
}
|
||||
}
|
||||
loadAppInfo()
|
||||
|
||||
export async function updateJarvisStats() {
|
||||
try {
|
||||
const stats = await invoke<{running: boolean, ram_mb: number, cpu_usage: number}>("get_jarvis_app_stats")
|
||||
isJarvisRunning.set(stats.running)
|
||||
jarvisRamUsage.set(stats.ram_mb)
|
||||
jarvisCpuUsage.set(stats.cpu_usage)
|
||||
} catch (err) {
|
||||
console.error("failed to get jarvis stats:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// polling manager
|
||||
let statsInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
export function startStatsPolling(intervalMs = 5000) {
|
||||
if (statsInterval) return // already running
|
||||
|
||||
updateJarvisStats()
|
||||
statsInterval = setInterval(updateJarvisStats, intervalMs)
|
||||
}
|
||||
|
||||
export function stopStatsPolling() {
|
||||
if (statsInterval) {
|
||||
clearInterval(statsInterval)
|
||||
statsInterval = null
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue