refac
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
import UserProfileImage from './Account/UserProfileImage.svelte';
|
||||
import UserSettingField from './UserSettingField.svelte';
|
||||
import UserSettingRow from './UserSettingRow.svelte';
|
||||
import UserSettingSelect from './UserSettingSelect.svelte';
|
||||
import UserSettingSection from './UserSettingSection.svelte';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
@@ -171,10 +172,10 @@
|
||||
label={$i18n.t('Gender')}
|
||||
description={$i18n.t('Choose the gender value stored on your profile.')}
|
||||
>
|
||||
<select
|
||||
class={inputClass}
|
||||
<UserSettingSelect
|
||||
className="w-full"
|
||||
bind:value={_gender}
|
||||
aria-label={$i18n.t('Gender')}
|
||||
ariaLabel={$i18n.t('Gender')}
|
||||
on:change={(e) => {
|
||||
console.log(_gender);
|
||||
|
||||
@@ -190,7 +191,7 @@
|
||||
<option value="male">{$i18n.t('Male')}</option>
|
||||
<option value="female">{$i18n.t('Female')}</option>
|
||||
<option value="custom">{$i18n.t('Custom')}</option>
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
|
||||
{#if _gender === 'custom'}
|
||||
<input
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
import UserSettingField from './UserSettingField.svelte';
|
||||
import UserSettingRow from './UserSettingRow.svelte';
|
||||
import UserSettingSelect from './UserSettingSelect.svelte';
|
||||
import UserSettingSection from './UserSettingSection.svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -40,8 +41,6 @@
|
||||
let playbackRate = 1;
|
||||
const inputClass =
|
||||
'h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const selectClass =
|
||||
'h-7 w-fit min-w-28 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-right text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
|
||||
const actionButtonClass =
|
||||
'text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white';
|
||||
|
||||
@@ -193,15 +192,14 @@
|
||||
label={$i18n.t('Speech-to-Text Engine')}
|
||||
description={$i18n.t('Choose the engine used to transcribe voice input.')}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
<UserSettingSelect
|
||||
bind:value={STTEngine}
|
||||
aria-label={$i18n.t('Speech-to-Text Engine')}
|
||||
ariaLabel={$i18n.t('Speech-to-Text Engine')}
|
||||
placeholder={$i18n.t('Select an engine')}
|
||||
>
|
||||
<option value="">{$i18n.t('Default')}</option>
|
||||
<option value="web">{$i18n.t('Web API')}</option>
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
</UserSettingRow>
|
||||
|
||||
<UserSettingRow
|
||||
@@ -252,15 +250,14 @@
|
||||
label={$i18n.t('Text-to-Speech Engine')}
|
||||
description={$i18n.t('Choose the engine used to read assistant responses aloud.')}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
<UserSettingSelect
|
||||
bind:value={TTSEngine}
|
||||
aria-label={$i18n.t('Text-to-Speech Engine')}
|
||||
ariaLabel={$i18n.t('Text-to-Speech Engine')}
|
||||
placeholder={$i18n.t('Select an engine')}
|
||||
>
|
||||
<option value="">{$i18n.t('Default')}</option>
|
||||
<option value="browser-kokoro">{$i18n.t('Kokoro.js (Browser)')}</option>
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
</UserSettingRow>
|
||||
|
||||
{#if TTSEngine === 'browser-kokoro'}
|
||||
@@ -268,10 +265,9 @@
|
||||
label={$i18n.t('Kokoro.js Dtype')}
|
||||
description={$i18n.t('Select the local model precision used by Kokoro.js.')}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
<UserSettingSelect
|
||||
bind:value={TTSEngineConfig.dtype}
|
||||
aria-label={$i18n.t('Kokoro.js Dtype')}
|
||||
ariaLabel={$i18n.t('Kokoro.js Dtype')}
|
||||
placeholder={$i18n.t('Select dtype')}
|
||||
>
|
||||
<option value="" disabled selected>{$i18n.t('Select dtype')}</option>
|
||||
@@ -279,7 +275,7 @@
|
||||
<option value="fp16">fp16</option>
|
||||
<option value="q8">q8</option>
|
||||
<option value="q4">q4</option>
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
</UserSettingRow>
|
||||
{/if}
|
||||
|
||||
@@ -364,7 +360,7 @@
|
||||
label={$i18n.t('Set Voice')}
|
||||
description={$i18n.t('Choose the browser voice used for speech output.')}
|
||||
>
|
||||
<select class={inputClass} bind:value={voice} aria-label={$i18n.t('Voice')}>
|
||||
<UserSettingSelect className="w-full" bind:value={voice} ariaLabel={$i18n.t('Voice')}>
|
||||
<option value="" selected={voice !== ''}>{$i18n.t('Default')}</option>
|
||||
{#each voices.filter((v) => nonLocalVoices || v.localService === true) as _voice}
|
||||
<option
|
||||
@@ -373,7 +369,7 @@
|
||||
selected={voice === _voice.name}>{_voice.name}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
</UserSettingField>
|
||||
<UserSettingRow
|
||||
label={$i18n.t('Allow non-local voices')}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import UserSettingField from './UserSettingField.svelte';
|
||||
import UserSettingRow from './UserSettingRow.svelte';
|
||||
import UserSettingSection from './UserSettingSection.svelte';
|
||||
import UserSettingSelect from './UserSettingSelect.svelte';
|
||||
export let saveSettings: Function;
|
||||
export let getModels: Function;
|
||||
|
||||
@@ -29,8 +30,6 @@
|
||||
|
||||
const systemPromptTextareaClass =
|
||||
'w-full resize-y rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 py-1.5 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const selectClass =
|
||||
'h-7 w-fit min-w-28 rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-right text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500';
|
||||
const actionButtonClass =
|
||||
'text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white';
|
||||
|
||||
@@ -224,9 +223,9 @@
|
||||
label={$i18n.t('Theme')}
|
||||
description={$i18n.t('Choose the color theme used by the interface.')}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
<UserSettingSelect
|
||||
bind:value={selectedTheme}
|
||||
ariaLabel={$i18n.t('Theme')}
|
||||
placeholder={$i18n.t('Select a theme')}
|
||||
on:change={() => themeChangeHandler(selectedTheme)}
|
||||
>
|
||||
@@ -237,16 +236,17 @@
|
||||
{#if $config?.features?.enable_easter_eggs}
|
||||
<option value="her">🌷 Her</option>
|
||||
{/if}
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
</UserSettingRow>
|
||||
|
||||
<UserSettingRow
|
||||
label={$i18n.t('Language')}
|
||||
description={$i18n.t('Choose the language used for interface text.')}
|
||||
>
|
||||
<select
|
||||
class={selectClass}
|
||||
<UserSettingSelect
|
||||
bind:value={lang}
|
||||
ariaLabel={$i18n.t('Language')}
|
||||
className="w-48"
|
||||
placeholder={$i18n.t('Select a language')}
|
||||
on:change={(e) => {
|
||||
changeLanguage(lang);
|
||||
@@ -255,7 +255,7 @@
|
||||
{#each languages as language}
|
||||
<option value={language['code']}>{language['title']}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</UserSettingSelect>
|
||||
</UserSettingRow>
|
||||
{#if $i18n.language === 'en-US' && !($config?.license_metadata ?? false)}
|
||||
<div class="-mt-1 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
import UserSettingSection from './UserSettingSection.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
import Plus from '$lib/components/icons/Plus.svelte';
|
||||
import Search from '$lib/components/icons/Search.svelte';
|
||||
import Trash from '$lib/components/icons/Trash.svelte';
|
||||
import XMark from '$lib/components/icons/XMark.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -34,8 +36,6 @@
|
||||
let showClearConfirmDialog = false;
|
||||
let showDeleteConfirm = false;
|
||||
let query = '';
|
||||
const inputClass =
|
||||
'h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors placeholder:text-gray-300 focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:placeholder:text-gray-700 dark:focus:border-blue-500';
|
||||
const actionButtonClass =
|
||||
'shrink-0 text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white';
|
||||
|
||||
@@ -145,54 +145,13 @@
|
||||
|
||||
{#if enableMemory}
|
||||
<div>
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<div class="mb-1 flex items-center">
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{$i18n.t('Saved Memories')}
|
||||
{#if !loadingMemories}
|
||||
<span class="ml-1 text-gray-400 dark:text-gray-600">{memories.length}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Dropdown align="end">
|
||||
<Tooltip content={$i18n.t('Actions')}>
|
||||
<button
|
||||
class="flex h-7 items-center gap-1.5 rounded-lg bg-transparent px-1.5 text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"
|
||||
type="button"
|
||||
>
|
||||
<span>{$i18n.t('Actions')}</span>
|
||||
<ChevronDown className="size-3" strokeWidth="2.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu className="w-[170px] shadow-sm">
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-lg bg-transparent px-2 text-xs hover:text-gray-900 disabled:cursor-default disabled:opacity-30 dark:hover:text-gray-100"
|
||||
disabled={loadingMemories}
|
||||
type="button"
|
||||
on:click={() => {
|
||||
selectedMemory = null;
|
||||
showMemoryModal = true;
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3.5 shrink-0" strokeWidth="1.5" />
|
||||
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Add Memory')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-lg bg-transparent px-2 text-xs hover:text-gray-900 disabled:cursor-default disabled:opacity-30 dark:hover:text-gray-100"
|
||||
disabled={loadingMemories || memories.length === 0}
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showClearConfirmDialog = true;
|
||||
}}
|
||||
>
|
||||
<Trash className="size-3.5 shrink-0" strokeWidth="1.5" />
|
||||
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Clear memory')}</div>
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
{#if loadingMemories}
|
||||
@@ -200,28 +159,76 @@
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{:else}
|
||||
{#if memories.length > 0}
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<input
|
||||
class={inputClass}
|
||||
bind:value={query}
|
||||
placeholder={$i18n.t('Search Memories')}
|
||||
maxlength="500"
|
||||
/>
|
||||
<div class="mb-2 flex min-w-0 items-center justify-between gap-3">
|
||||
{#if memories.length > 0}
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<Search className="size-3.5 shrink-0 text-gray-400 dark:text-gray-600" />
|
||||
<input
|
||||
data-settings-search
|
||||
class="min-w-0 flex-1 bg-transparent py-0.5 text-xs text-gray-700 outline-hidden placeholder:text-gray-300 dark:text-gray-300 dark:placeholder:text-gray-700"
|
||||
bind:value={query}
|
||||
placeholder={$i18n.t('Search Memories')}
|
||||
maxlength="500"
|
||||
/>
|
||||
{#if query}
|
||||
<div class="shrink-0">
|
||||
<button
|
||||
class="rounded-lg p-0.5 text-gray-400 transition-colors hover:text-gray-700 dark:text-gray-600 dark:hover:text-gray-300"
|
||||
type="button"
|
||||
aria-label={$i18n.t('Clear search')}
|
||||
on:click={() => {
|
||||
query = '';
|
||||
}}
|
||||
>
|
||||
<XMark className="size-3" strokeWidth="2" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="min-w-0 flex-1"></div>
|
||||
{/if}
|
||||
|
||||
{#if query}
|
||||
<Dropdown align="end">
|
||||
<Tooltip content={$i18n.t('Actions')}>
|
||||
<button
|
||||
class="flex h-7 items-center gap-1.5 rounded-lg bg-transparent px-1.5 text-xs text-gray-500 transition-colors hover:text-gray-900 dark:text-gray-500 dark:hover:text-white"
|
||||
type="button"
|
||||
class={actionButtonClass}
|
||||
on:click={() => {
|
||||
query = '';
|
||||
}}
|
||||
>
|
||||
{$i18n.t('Clear')}
|
||||
<span>{$i18n.t('Actions')}</span>
|
||||
<ChevronDown className="size-3" strokeWidth="2.5" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</Tooltip>
|
||||
|
||||
<div slot="content">
|
||||
<DropdownMenu className="w-[170px] shadow-sm">
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-lg bg-transparent px-2 text-xs hover:text-gray-900 disabled:cursor-default disabled:opacity-30 dark:hover:text-gray-100"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
selectedMemory = null;
|
||||
showMemoryModal = true;
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3.5 shrink-0" strokeWidth="1.5" />
|
||||
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Add Memory')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="flex h-[1.6875rem] w-full cursor-pointer select-none items-center gap-2 rounded-lg bg-transparent px-2 text-xs hover:text-gray-900 disabled:cursor-default disabled:opacity-30 dark:hover:text-gray-100"
|
||||
disabled={memories.length === 0}
|
||||
type="button"
|
||||
on:click={() => {
|
||||
showClearConfirmDialog = true;
|
||||
}}
|
||||
>
|
||||
<Trash className="size-3.5 shrink-0" strokeWidth="1.5" />
|
||||
<div class="min-w-0 flex-1 truncate text-left">{$i18n.t('Clear memory')}</div>
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
{#if sortedMemories.length === 0}
|
||||
<div class="min-h-16 text-[0.6875rem] text-gray-400 dark:text-gray-600">
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let value = '';
|
||||
export let ariaLabel = '';
|
||||
export let placeholder = '';
|
||||
export let className = 'w-fit min-w-32';
|
||||
export let selectClassName = '';
|
||||
</script>
|
||||
|
||||
<div class="relative inline-flex {className}">
|
||||
<select
|
||||
bind:value
|
||||
aria-label={ariaLabel || undefined}
|
||||
title={placeholder || undefined}
|
||||
class="h-7 w-full truncate rounded-lg border border-gray-100/50 !bg-gray-50/40 ps-2.5 pe-9 text-left text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:!bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500 {selectClassName}"
|
||||
on:change
|
||||
>
|
||||
<slot />
|
||||
</select>
|
||||
</div>
|
||||
Reference in New Issue
Block a user