Tray options implementation + Voice system rewrite + build fixes
This commit is contained in:
parent
412acb7e2d
commit
47b7e7a65d
117 changed files with 1300 additions and 2334 deletions
|
|
@ -21,13 +21,17 @@
|
|||
|
||||
let processRunning = false
|
||||
let launching = false
|
||||
let wasRunning = false // track previous state
|
||||
|
||||
isJarvisRunning.subscribe((value) => {
|
||||
processRunning = value
|
||||
if (value) {
|
||||
enableIpc()
|
||||
} else {
|
||||
wasRunning = true
|
||||
} else if (wasRunning) {
|
||||
// only disable if it was running before
|
||||
disableIpc()
|
||||
wasRunning = false
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,30 @@
|
|||
|
||||
$: t = (key: string) => translate($translations, key)
|
||||
|
||||
interface VoiceMeta {
|
||||
id: string
|
||||
name: string
|
||||
author: string
|
||||
languages: string[]
|
||||
}
|
||||
|
||||
interface VoiceConfig {
|
||||
voice: VoiceMeta
|
||||
}
|
||||
|
||||
let availableVoices: VoiceMeta[] = []
|
||||
|
||||
async function selectVoice(voiceId: string) {
|
||||
voiceVal = voiceId
|
||||
|
||||
// play preview sound
|
||||
try {
|
||||
await invoke("preview_voice", { voiceId })
|
||||
} catch (err) {
|
||||
console.error("Failed to preview voice:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ### STATE
|
||||
interface MicrophoneOption {
|
||||
label: string
|
||||
|
|
@ -113,11 +137,20 @@
|
|||
|
||||
// ### INIT
|
||||
onMount(async () => {
|
||||
// load voices
|
||||
try {
|
||||
const voices = await invoke<VoiceConfig[]>("list_voices")
|
||||
availableVoices = voices.map(v => v.voice)
|
||||
} catch (err) {
|
||||
console.error("Failed to load voices:", err)
|
||||
availableVoices = []
|
||||
}
|
||||
|
||||
try {
|
||||
// load microphones
|
||||
const mics = await invoke<string[]>("pv_get_audio_devices")
|
||||
availableMicrophones = [
|
||||
{ label: "По умолчанию (Система)", value: "-1" }, // system default
|
||||
{ label: t('settings-mic-default'), value: "-1" }, // system default
|
||||
...mics.map((name, idx) => ({
|
||||
label: name,
|
||||
value: String(idx)
|
||||
|
|
@ -200,18 +233,42 @@
|
|||
<Tabs class="form" color="#8AC832" position="left">
|
||||
<Tabs.Tab label={t('settings-general')} icon={Gear}>
|
||||
<Space h="sm" />
|
||||
<NativeSelect
|
||||
data={[
|
||||
{ label: "Jarvis New (ремастер)", value: "jarvis-remaster" },
|
||||
{ label: "Рик из «Рик и Морти»", value: "rick-morty" },
|
||||
{ label: "Jarvis (от Хауди)", value: "jarvis-howdy" },
|
||||
{ label: "Jarvis OG (из фильмов)", value: "jarvis-og" }
|
||||
]}
|
||||
label={t('settings-voice')}
|
||||
description={t('settings-voice-desc')}
|
||||
variant="filled"
|
||||
bind:value={voiceVal}
|
||||
/>
|
||||
<div class="voice-select">
|
||||
<label>{t('settings-voice')}</label>
|
||||
<p class="description">{t('settings-voice-desc')}</p>
|
||||
|
||||
<div class="voice-options">
|
||||
{#each availableVoices as voice}
|
||||
<button
|
||||
type="button"
|
||||
class="voice-option"
|
||||
class:selected={voiceVal === voice.id}
|
||||
on:click={() => selectVoice(voice.id)}
|
||||
>
|
||||
<div class="voice-info">
|
||||
<span class="voice-name">{voice.name}</span>
|
||||
{#if voice.author}
|
||||
<span class="voice-author">by {voice.author}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="voice-languages">
|
||||
{#each voice.languages as lang}
|
||||
<img
|
||||
src="/media/flags/{lang.toUpperCase()}.png"
|
||||
alt={lang}
|
||||
width="20"
|
||||
title={lang}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
{#if availableVoices.length === 0}
|
||||
<p class="no-voices">{t('settings-no-voices')}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Tab>
|
||||
|
||||
<Tabs.Tab label={t('settings-devices')} icon={Mix}>
|
||||
|
|
@ -390,3 +447,112 @@
|
|||
|
||||
<HDivider />
|
||||
<Footer />
|
||||
|
||||
<style lang="scss">
|
||||
.voice-select {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: #fff;
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255,255,255,0.5);
|
||||
margin: 0 0 0.75rem;
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
||||
$voice-item-height: 70px;
|
||||
$voice-item-gap: 0.5rem;
|
||||
$voice-max-visible: 3;
|
||||
|
||||
.voice-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $voice-item-gap;
|
||||
max-height: $voice-item-height * $voice-max-visible;
|
||||
overflow-y: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 3px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.voice-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background: rgba(30, 40, 45, 0.8);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background: rgba(40, 55, 60, 0.9);
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: rgba(82, 254, 254, 0.1);
|
||||
border-color: rgba(82, 254, 254, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.voice-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.voice-name {
|
||||
font-size: 0.85rem;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.voice-author {
|
||||
font-size: 0.7rem;
|
||||
color: rgba(255,255,255,0.4);
|
||||
}
|
||||
|
||||
.voice-languages {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
|
||||
img {
|
||||
opacity: 0.8;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-voices {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255,255,255,0.4);
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue