frontend cleanup

This commit is contained in:
Priler 2026-01-04 22:50:34 +05:00
parent 091a41ac33
commit 0c28304840
38 changed files with 2873 additions and 1180 deletions

View file

@ -1,7 +1,6 @@
<!-- _layout.svelte -->
<script>
import { Container } from '@svelteuidev/core'
import Header from '@/components/Header.svelte'
<script lang="ts">
import { Container } from "@svelteuidev/core"
import Header from "@/components/Header.svelte"
</script>
<Container fluid id="wrapper">
@ -9,4 +8,4 @@
<main>
<slot></slot>
</main>
</Container>
</Container>

View file

@ -1,23 +1,39 @@
<script lang="ts">
import { Notification, Space } from "@svelteuidev/core"
import { InfoCircled } from "radix-icons-svelte"
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import { appInfo } from "@/stores"
import { Notification, Space } from '@svelteuidev/core';
import { InfoCircled } from 'radix-icons-svelte';
import { tg_official_link } from "@/stores";
let tgLink = ""
appInfo.subscribe(info => {
tgLink = info.tgOfficialLink
})
</script>
<Space h="xl" />
<Notification title='[404] Этот раздел еще находится в разработке!' icon={InfoCircled} color='blue' withCloseButton={false}>
Тут будет список команд + полноценный редактор команд.<br>
Следите за обновлениями в <a href="{tg_official_link}" target="_blank">нашем телеграм канале</a>!
<Notification
title="[404] Этот раздел еще находится в разработке!"
icon={InfoCircled}
color="blue"
withCloseButton={false}
>
Тут будет список команд + полноценный редактор команд.<br />
Следите за обновлениями в <a href={tgLink} target="_blank">нашем телеграм канале</a>!
</Notification>
<div style="text-align: center;margin-top: 25px;">
<img src="/media/images/tenor.gif" alt="bruh" width="320px">
<div class="placeholder-image">
<img src="/media/images/tenor.gif" alt="bruh" width="320px" />
</div>
<HDivider />
<Footer />
<Footer />
<style>
.placeholder-image {
text-align: center;
margin-top: 25px;
}
</style>

View file

@ -1,71 +1,46 @@
<script lang="ts">
import { onMount, onDestroy } from "svelte"
import { Notification, Space } from "@svelteuidev/core"
import { InfoCircled } from "radix-icons-svelte"
import SearchBar from "@/components/elements/SearchBar.svelte"
import ArcReactor from "@/components/elements/ArcReactor.svelte"
import HDivider from "@/components/elements/HDivider.svelte"
import Stats from "@/components/elements/Stats.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Space } from '@svelteuidev/core'
import { InfoCircled } from 'radix-icons-svelte'
import { onMount, onDestroy } from 'svelte'
import { isListening } from "@/stores"
onMount(async () => {
document.body.classList.add('assist-page');
});
let listening = false
isListening.subscribe(value => {
listening = value
})
onDestroy(async () => {
document.body.classList.remove('assist-page');
});
onMount(() => {
document.body.classList.add("assist-page")
})
import { is_listening } from "@/stores"
let is_listening__val: boolean;
is_listening.subscribe(value => {
is_listening__val = value;
});
onDestroy(() => {
document.body.classList.remove("assist-page")
})
</script>
<HDivider />
{#if !is_listening__val}
<Notification title='Внимание!' icon={InfoCircled} color='cyan' withCloseButton={false}>
В данный момент ассистент не прослушивает команды.<br />
Пожалуйста, <a href="/settings">перейдите в настройки</a> и введите ключ Picovoice.
</Notification>
<!-- <SearchBar /> -->
{#if !listening}
<Notification
title="Внимание!"
icon={InfoCircled}
color="cyan"
withCloseButton={false}
>
В данный момент ассистент не прослушивает команды.<br />
Пожалуйста, <a href="/settings">перейдите в настройки</a> и введите ключ Picovoice.
</Notification>
{:else}
<!-- <SearchBar /> -->
<ArcReactor />
<ArcReactor />
{/if}
<HDivider no_margin />
<HDivider noMargin />
<Stats />
<Footer />
<!--
<Title order={1}>This is h1 title</Title>
<Title order={1} variant='gradient' gradient={{from: 'blue', to: 'red', deg: 45}}>This is h1 title with a twist</Title>
<Menu>
<Button slot="control" variant="gradient" gradient={{ from: 'blue', to: 'teal', deg: 50 }} radius="md" size="md">Toggle Menu</Button>
<Menu.Label>Application</Menu.Label>
<Menu.Item icon={Gear}>Settings</Menu.Item>
<Menu.Item icon={ChatBubble}>Messages</Menu.Item>
<Menu.Item icon={Camera}>Gallery</Menu.Item>
<Menu.Item icon={MagnifyingGlass}>
<svelte:fragment slot='rightSection'>
<Text size="xs" color="dimmed">⌘K</Text>
</svelte:fragment>
Search
</Menu.Item>
<Divider />
<Menu.Label>Danger zone</Menu.Label>
<Menu.Item icon={Width}>Transfer my data</Menu.Item>
<Menu.Item color="red" icon={Trash}>Delete my account</Menu.Item>
</Menu>
<Checkbox bind:checked={checked} label="I agree to sell my privacy" />
{checked}
{#if checked}
YEP!
{/if} -->

View file

@ -1,188 +1,280 @@
<script lang="ts">
// IMPORTS
import { invoke } from "@tauri-apps/api/core"
import { goto } from '@roxi/routify'
import { onMount } from 'svelte'
import { startListening, stopListening, showInExplorer } from "@/functions";
// import { setTimeout } from 'worker-timers';
import { onMount } from "svelte"
import { invoke } from "@tauri-apps/api/core"
import { goto } from "@roxi/routify"
import { setTimeout } from "worker-timers"
import { feedback_link, log_file_path } from "@/stores";
import { showInExplorer, stopListening, startListening } from "@/functions"
import { appInfo, assistantVoice } from "@/stores"
// COMPONENTS & STUFF
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import HDivider from "@/components/elements/HDivider.svelte"
import Footer from "@/components/Footer.svelte"
import { Notification, Button, Text, Tabs, Space, Alert, Input, InputWrapper, NativeSelect } from '@svelteuidev/core';
import { Check, Mix, Cube, Code, Gear, QuestionMarkCircled, CrossCircled } from 'radix-icons-svelte';
import {
Notification,
Button,
Text,
Tabs,
Space,
Alert,
Input,
InputWrapper,
NativeSelect
} from "@svelteuidev/core"
// VARIABLES
import {
Check,
Mix,
Cube,
Code,
Gear,
QuestionMarkCircled,
CrossCircled
} from "radix-icons-svelte"
let available_microphones: { label: string; value: number }[] = [];
let settings_saved = false;
let save_button_disabled = false;
// ### STATE
interface MicrophoneOption {
label: string
value: string
}
let assistant_voice_val = ""; // shared
let selected_microphone = "";
let availableMicrophones: MicrophoneOption[] = []
let settingsSaved = false
let saveButtonDisabled = false
let selected_wake_word_engine = "";
let api_key__picovoice = "";
let api_key__openai = "";
// form values
let voiceVal = ""
let selectedMicrophone = ""
let selectedWakeWordEngine = ""
let apiKeyPicovoice = ""
let apiKeyOpenai = ""
// SHARED VALUES
import { assistant_voice } from "@/stores"
assistant_voice.subscribe(value => {
assistant_voice_val = value;
});
// subscribe to stores
assistantVoice.subscribe(value => {
voiceVal = value
})
// FUNCTIONS
async function save_settings() {
save_button_disabled = true; // disable save button for a while
settings_saved = false; // hide alert
let feedbackLink = ""
let logFilePath = ""
appInfo.subscribe(info => {
feedbackLink = info.feedbackLink
logFilePath = info.logFilePath
})
await invoke("db_write", {key: "assistant_voice", val: assistant_voice_val});
await invoke("db_write", {key: "selected_microphone", val: selected_microphone});
// ### FUNCTIONS
async function saveSettings() {
saveButtonDisabled = true
settingsSaved = false
await invoke("db_write", {key: "selected_wake_word_engine", val: selected_wake_word_engine});
await invoke("db_write", {key: "api_key__picovoice", val: api_key__picovoice});
await invoke("db_write", {key: "api_key__openai", val: api_key__openai});
try {
await Promise.all([
invoke("db_write", { key: "assistant_voice", val: voiceVal }),
invoke("db_write", { key: "selected_microphone", val: selectedMicrophone }),
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
])
// update shared
assistant_voice.set(assistant_voice_val);
// update shared store
assistantVoice.set(voiceVal)
settingsSaved = true
settings_saved = true; // show alert
setTimeout(() => {
settings_saved = false; // hide alert again after N seconds
}, 5000);
// hide alert after 5 seconds
setTimeout(() => {
settingsSaved = false
}, 5000)
setTimeout(() => {
save_button_disabled = false; // enable save button again
}, 1000);
// restart listening with new settings
stopListening(() => startListening())
} catch (err) {
console.error("failed to save settings:", err)
}
// restart listening everytime new settings is saved
stopListening(() => {
startListening();
});
}
setTimeout(() => {
saveButtonDisabled = false
}, 1000)
}
// CODE
onMount(async () => {
// preload some vars
let _available_microphones: Array<Number> = await invoke("pv_get_audio_devices");
Object.entries(_available_microphones).forEach(entry => {
const [k, v] = entry;
// ### INIT
onMount(async () => {
try {
// load microphones
const mics = await invoke<string[]>("pv_get_audio_devices")
availableMicrophones = mics.map((name, idx) => ({
label: name,
value: String(idx)
}))
available_microphones.push({
label: String(v),
value: Number(k)
});
});
available_microphones = available_microphones; // update component options
// load values from db
// assistant_voice.set(await invoke("db_read", {key: "assistant_voice"}));
selected_microphone = await invoke("db_read", {key: "selected_microphone"});
selected_wake_word_engine = await invoke("db_read", {key: "selected_wake_word_engine"});
api_key__picovoice = await invoke("db_read", {key: "api_key__picovoice"});
api_key__openai = await invoke("db_read", {key: "api_key__openai"});
});
// load settings from db
const [mic, wakeWord, 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: "api_key__picovoice" }),
invoke<string>("db_read", { key: "api_key__openai" })
])
selectedMicrophone = mic
selectedWakeWordEngine = wakeWord
apiKeyPicovoice = pico
apiKeyOpenai = openai
} catch (err) {
console.error("failed to load settings:", err)
}
})
</script>
<Space h="xl" />
<Notification title='БЕТА версия!' icon={QuestionMarkCircled} color='blue' withCloseButton={false}>
Часть функций может работать некорректно.<br />
Сообщайте обо всех найденных багах в <a href="{feedback_link}" target="_blank">наш телеграм бот</a>.
<Space h="sm" />
<Button color="gray" radius="md" size="xs" uppercase on:click={() => {showInExplorer(log_file_path)}}>Открыть папку с логами</Button>
<Notification
title="БЕТА версия!"
icon={QuestionMarkCircled}
color="blue"
withCloseButton={false}
>
Часть функций может работать некорректно.<br />
Сообщайте обо всех найденных багах в <a href={feedbackLink} target="_blank">наш телеграм бот</a>.
<Space h="sm" />
<Button
color="gray"
radius="md"
size="xs"
uppercase
on:click={() => showInExplorer(logFilePath)}
>
Открыть папку с логами
</Button>
</Notification>
<Space h="xl" />
{#if settings_saved }
<Notification title='Настройки сохранены!' icon={Check} color='teal' on:close="{() => {settings_saved = false}}"></Notification>
<Space h="xl" />
{#if settingsSaved}
<Notification
title="Настройки сохранены!"
icon={Check}
color="teal"
on:close={() => { settingsSaved = false }}
/>
<Space h="xl" />
{/if}
<Tabs class="form" color='#8AC832' position="left">
<Tabs.Tab label='Общее' icon={Gear}>
<Space h="sm" />
<Tabs class="form" color="#8AC832" position="left">
<!-- general tab -->
<Tabs.Tab label="Общее" icon={Gear}>
<Space h="sm" />
<NativeSelect
data={[
{ label: "Jarvis ремейк (от Хауди)", value: "jarvis-remake" },
{ label: "Jarvis OG (из фильмов)", value: "jarvis-og" }
]}
label="Голос ассистента"
description="Не все команды работают со всеми звуковыми пакетами."
variant="filled"
bind:value={voiceVal}
/>
</Tabs.Tab>
<NativeSelect data={[
{ label: 'Jarvis ремейк (от Хауди)', value: 'jarvis-remake' },
{ label: 'Jarvis OG (из фильмов)', value: 'jarvis-og' }
]}
label="Голос ассистента"
description="Не все команды работают со всеми звуковыми пакетами."
variant="filled"
bind:value={assistant_voice_val}
/>
</Tabs.Tab>
<Tabs.Tab label='Устройства' icon={Mix}>
<Space h="sm" />
<!-- devices tab -->
<Tabs.Tab label="Устройства" icon={Mix}>
<Space h="sm" />
<NativeSelect
data={availableMicrophones}
label="Выберите микрофон"
description="Его будет слушать ассистент."
variant="filled"
bind:value={selectedMicrophone}
/>
</Tabs.Tab>
<NativeSelect data={available_microphones}
label="Выберите микрофон"
description="Его будет слушать ассистент."
variant="filled"
bind:value={selected_microphone}
/>
</Tabs.Tab>
<Tabs.Tab label='Нейросети' icon={Cube}>
<Space h="sm" />
<!-- neural networks tab -->
<Tabs.Tab label="Нейросети" icon={Cube}>
<Space h="sm" />
<NativeSelect
data={[
{ label: "Rustpotter", value: "Rustpotter" },
{ label: "Vosk (медленный)", value: "Vosk" },
{ label: "Picovoice Porcupine (требует API ключ)", value: "Picovoice" }
]}
label="Распознавание активационной фразы (Wake Word)"
description="Выберите, какая нейросеть будет отвечать за распознавание активационной фразы."
variant="filled"
bind:value={selectedWakeWordEngine}
/>
<NativeSelect data={[
{ label: 'Rustpotter', value: 'Rustpotter' },
{ label: 'Vosk (медленный)', value: 'Vosk' },
{ label: 'Picovoice Porcupine (требует API ключ)', value: 'Picovoice' }
]}
label="Распознавание активационной фразы (Wake Word)"
description="Выберите, какая нейросеть будет отвечать за распознавание активационной фразы."
variant="filled"
bind:value={selected_wake_word_engine}
/>
{#if selectedWakeWordEngine === "picovoice"}
<Space h="sm" />
<Alert title="Внимание!" color="#868E96" variant="outline">
<Notification
title="Эта нейросеть работает не у всех!"
icon={CrossCircled}
color="orange"
withCloseButton={false}
>
Мы ждем официального патча от разработчиков.
</Notification>
<Space h="sm" />
<Text size="sm" color="gray">
Введите сюда свой ключ Picovoice.<br />
Он выдается бесплатно при регистрации в
<a href="https://console.picovoice.ai/" target="_blank">Picovoice Console</a>.
</Text>
<Space h="sm" />
<Input
icon={Code}
placeholder="Ключ Picovoice"
variant="filled"
autocomplete="off"
bind:value={apiKeyPicovoice}
/>
</Alert>
{/if}
{#if selected_wake_word_engine == "picovoice"}
<Space h="sm" />
<Alert title="Внимание!" color="#868E96" variant="outline">
<Space h="xl" />
<Notification title='Эта нейросеть работает не у всех!' icon={CrossCircled} color='orange' withCloseButton={false}>
Мы ждем официального патча от разработчиков.
</Notification>
<Space h="sm" />
<Text size='sm' color="gray">
Введите сюда свой ключ Picovoice.<br />
Он выдается бесплатно при регистрации в <a href='https://console.picovoice.ai/' target="_blank">Picovoice Console</a>.<br>
</Text>
<Space h="sm" />
<Input icon={Code} placeholder='Ключ Picovoice' variant='filled' autocomplete="off" bind:value={api_key__picovoice}/>
</Alert>
{/if}
<Space h="xl" />
<InputWrapper label="Ключ OpenAI">
<!-- <Text size='sm' color="gray">Введите сюда свой ключ OpenAI, он требуется для работы ChatGPT.<br />Получить его можно <a href="https://chat.openai.com/auth/login" target="_blank">на официальном сайте OpenAI</a>.</Text> -->
<Text size='sm' color="gray">В данный момент ChatGPT <u>не поддерживается</u>. Он будет добавлен в ближайших обновлениях.</Text>
<Space h="sm" />
<Input icon={Code} placeholder='Ключ OpenAI' variant='filled' autocomplete="off" bind:value={api_key__openai} disabled/>
</InputWrapper>
</Tabs.Tab>
<InputWrapper label="Ключ OpenAI">
<Text size="sm" color="gray">
В данный момент ChatGPT <u>не поддерживается</u>.
Он будет добавлен в ближайших обновлениях.
</Text>
<Space h="sm" />
<Input
icon={Code}
placeholder="Ключ OpenAI"
variant="filled"
autocomplete="off"
bind:value={apiKeyOpenai}
disabled
/>
</InputWrapper>
</Tabs.Tab>
</Tabs>
<Space h="xl" />
<Button color="lime" radius="md" size="sm" uppercase ripple fullSize on:click={save_settings} disabled={save_button_disabled}>
Сохранить
<Button
color="lime"
radius="md"
size="sm"
uppercase
ripple
fullSize
on:click={saveSettings}
disabled={saveButtonDisabled}
>
Сохранить
</Button>
<Space h="sm" />
<Button color="gray" radius="md" size="sm" uppercase fullSize on:click={() => {$goto('/')}}>
Назад
<Button
color="gray"
radius="md"
size="sm"
uppercase
fullSize
on:click={() => $goto("/")}
>
Назад
</Button>
<HDivider />
<Footer />
<Footer />