Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
4ff17acc
Commit
4ff17acc
authored
Jun 16, 2024
by
Jun Siang Cheah
Browse files
Merge remote-tracking branch 'upstream/dev' into feat/oauth
parents
f49d814d
9928114c
Changes
168
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
836 additions
and
638 deletions
+836
-638
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
...b/components/chat/Settings/Advanced/AdvancedParams.svelte
+219
-121
src/lib/components/chat/Settings/Audio.svelte
src/lib/components/chat/Settings/Audio.svelte
+38
-201
src/lib/components/chat/Settings/Chats.svelte
src/lib/components/chat/Settings/Chats.svelte
+2
-2
src/lib/components/chat/Settings/General.svelte
src/lib/components/chat/Settings/General.svelte
+36
-34
src/lib/components/chat/Settings/Interface.svelte
src/lib/components/chat/Settings/Interface.svelte
+32
-181
src/lib/components/chat/Settings/Models.svelte
src/lib/components/chat/Settings/Models.svelte
+2
-2
src/lib/components/chat/Settings/Personalization.svelte
src/lib/components/chat/Settings/Personalization.svelte
+7
-4
src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte
...nents/chat/Settings/Personalization/AddMemoryModal.svelte
+5
-4
src/lib/components/chat/Settings/Personalization/EditMemoryModal.svelte
...ents/chat/Settings/Personalization/EditMemoryModal.svelte
+136
-0
src/lib/components/chat/Settings/Personalization/ManageModal.svelte
...mponents/chat/Settings/Personalization/ManageModal.svelte
+48
-5
src/lib/components/chat/SettingsModal.svelte
src/lib/components/chat/SettingsModal.svelte
+10
-79
src/lib/components/common/CodeEditor.svelte
src/lib/components/common/CodeEditor.svelte
+135
-0
src/lib/components/common/ConfirmDialog.svelte
src/lib/components/common/ConfirmDialog.svelte
+109
-0
src/lib/components/common/Selector.svelte
src/lib/components/common/Selector.svelte
+1
-1
src/lib/components/common/Tooltip.svelte
src/lib/components/common/Tooltip.svelte
+2
-0
src/lib/components/documents/Settings/QueryParams.svelte
src/lib/components/documents/Settings/QueryParams.svelte
+1
-1
src/lib/components/documents/Settings/WebParams.svelte
src/lib/components/documents/Settings/WebParams.svelte
+3
-3
src/lib/components/icons/Headphone.svelte
src/lib/components/icons/Headphone.svelte
+20
-0
src/lib/components/icons/MagnifyingGlass.svelte
src/lib/components/icons/MagnifyingGlass.svelte
+19
-0
src/lib/components/icons/WrenchSolid.svelte
src/lib/components/icons/WrenchSolid.svelte
+11
-0
No files found.
src/lib/components/chat/Settings/Advanced/AdvancedParams.svelte
View file @
4ff17acc
...
...
@@ -5,21 +5,25 @@
const i18n = getContext('i18n');
export let admin = false;
export let params = {
// Advanced
seed:
0
,
seed:
null
,
stop: null,
temperature: '',
frequency_penalty: '',
repeat_last_n: '',
mirostat: '',
mirostat_eta: '',
mirostat_tau: '',
top_k: '',
top_p: '',
tfs_z: '',
num_ctx: '',
max_tokens: '',
temperature: null,
frequency_penalty: null,
repeat_last_n: null,
mirostat: null,
mirostat_eta: null,
mirostat_tau: null,
top_k: null,
top_p: null,
tfs_z: null,
num_ctx: null,
num_batch: null,
num_keep: null,
max_tokens: null,
use_mmap: null,
use_mlock: null,
num_thread: null,
...
...
@@ -112,10 +116,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.temperature = (params?.temperature ??
''
) ===
''
? 0.8 :
''
;
params.temperature = (params?.temperature ??
null
) ===
null
? 0.8 :
null
;
}}
>
{#if (params?.temperature ??
''
) ===
''
}
{#if (params?.temperature ??
null
) ===
null
}
<span class="ml-2 self-center"> {$i18n.t('Default')} </span>
{:else}
<span class="ml-2 self-center"> {$i18n.t('Custom')} </span>
...
...
@@ -123,7 +127,7 @@
</button>
</div>
{#if (params?.temperature ??
''
) !==
''
}
{#if (params?.temperature ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -143,7 +147,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="
0.05
"
step="
any
"
/>
</div>
</div>
...
...
@@ -158,10 +162,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.mirostat = (params?.mirostat ??
''
) ===
''
? 0 :
''
;
params.mirostat = (params?.mirostat ??
null
) ===
null
? 0 :
null
;
}}
>
{#if (params?.mirostat ??
''
) ===
''
}
{#if (params?.mirostat ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -169,7 +173,7 @@
</button>
</div>
{#if (params?.mirostat ??
''
) !==
''
}
{#if (params?.mirostat ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -204,10 +208,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.mirostat_eta = (params?.mirostat_eta ??
''
) ===
''
? 0.1 :
''
;
params.mirostat_eta = (params?.mirostat_eta ??
null
) ===
null
? 0.1 :
null
;
}}
>
{#if (params?.mirostat_eta ??
''
) ===
''
}
{#if (params?.mirostat_eta ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -215,7 +219,7 @@
</button>
</div>
{#if (params?.mirostat_eta ??
''
) !==
''
}
{#if (params?.mirostat_eta ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -235,7 +239,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="
0.05
"
step="
any
"
/>
</div>
</div>
...
...
@@ -250,10 +254,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.mirostat_tau = (params?.mirostat_tau ??
''
) ===
''
? 5.0 :
''
;
params.mirostat_tau = (params?.mirostat_tau ??
null
) ===
null
? 5.0 :
null
;
}}
>
{#if (params?.mirostat_tau ??
''
) ===
''
}
{#if (params?.mirostat_tau ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -261,7 +265,7 @@
</button>
</div>
{#if (params?.mirostat_tau ??
''
) !==
''
}
{#if (params?.mirostat_tau ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -281,7 +285,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="10"
step="
0.5
"
step="
any
"
/>
</div>
</div>
...
...
@@ -296,10 +300,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.top_k = (params?.top_k ??
''
) ===
''
? 40 :
''
;
params.top_k = (params?.top_k ??
null
) ===
null
? 40 :
null
;
}}
>
{#if (params?.top_k ??
''
) ===
''
}
{#if (params?.top_k ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -307,7 +311,7 @@
</button>
</div>
{#if (params?.top_k ??
''
) !==
''
}
{#if (params?.top_k ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -327,7 +331,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="100"
step="
0.5
"
step="
any
"
/>
</div>
</div>
...
...
@@ -342,10 +346,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.top_p = (params?.top_p ??
''
) ===
''
? 0.9 :
''
;
params.top_p = (params?.top_p ??
null
) ===
null
? 0.9 :
null
;
}}
>
{#if (params?.top_p ??
''
) ===
''
}
{#if (params?.top_p ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -353,7 +357,7 @@
</button>
</div>
{#if (params?.top_p ??
''
) !==
''
}
{#if (params?.top_p ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -373,7 +377,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="
0.05
"
step="
any
"
/>
</div>
</div>
...
...
@@ -388,10 +392,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.frequency_penalty = (params?.frequency_penalty ??
''
) ===
''
? 1.1 :
''
;
params.frequency_penalty = (params?.frequency_penalty ??
null
) ===
null
? 1.1 :
null
;
}}
>
{#if (params?.frequency_penalty ??
''
) ===
''
}
{#if (params?.frequency_penalty ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -399,7 +403,7 @@
</button>
</div>
{#if (params?.frequency_penalty ??
''
) !==
''
}
{#if (params?.frequency_penalty ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -419,7 +423,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="2"
step="
0.05
"
step="
any
"
/>
</div>
</div>
...
...
@@ -434,10 +438,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.repeat_last_n = (params?.repeat_last_n ??
''
) ===
''
? 64 :
''
;
params.repeat_last_n = (params?.repeat_last_n ??
null
) ===
null
? 64 :
null
;
}}
>
{#if (params?.repeat_last_n ??
''
) ===
''
}
{#if (params?.repeat_last_n ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -445,7 +449,7 @@
</button>
</div>
{#if (params?.repeat_last_n ??
''
) !==
''
}
{#if (params?.repeat_last_n ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -480,10 +484,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.tfs_z = (params?.tfs_z ??
''
) ===
''
? 1 :
''
;
params.tfs_z = (params?.tfs_z ??
null
) ===
null
? 1 :
null
;
}}
>
{#if (params?.tfs_z ??
''
) ===
''
}
{#if (params?.tfs_z ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -491,7 +495,7 @@
</button>
</div>
{#if (params?.tfs_z ??
''
) !==
''
}
{#if (params?.tfs_z ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -511,7 +515,7 @@
class=" bg-transparent text-center w-14"
min="0"
max="2"
step="
0.05
"
step="
any
"
/>
</div>
</div>
...
...
@@ -526,10 +530,10 @@
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_ctx = (params?.num_ctx ??
''
) ===
''
? 2048 :
''
;
params.num_ctx = (params?.num_ctx ??
null
) ===
null
? 2048 :
null
;
}}
>
{#if (params?.num_ctx ??
''
) ===
''
}
{#if (params?.num_ctx ??
null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -537,7 +541,7 @@
</button>
</div>
{#if (params?.num_ctx ??
''
) !==
''
}
{#if (params?.num_ctx ??
null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
...
...
@@ -556,7 +560,7 @@
type="number"
class=" bg-transparent text-center w-14"
min="-1"
step="1
0
"
step="1"
/>
</div>
</div>
...
...
@@ -565,16 +569,16 @@
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('
Max Tokens (num_predict
)')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('
Batch Size (num_batch
)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.
max_tokens
= (params?.
max_tokens ?? ''
) ===
''
? 12
8
:
''
;
params.
num_batch
= (params?.
num_batch ?? null
) ===
null
?
5
12 :
null
;
}}
>
{#if (params?.
max_tokens ?? ''
) ===
''
}
{#if (params?.
num_batch ?? null
) ===
null
}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -582,27 +586,26 @@
</button>
</div>
{#if (params?.
max_tokens ?? ''
) !==
''
}
{#if (params?.
num_batch ?? null
) !==
null
}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="
-
2"
max="
16000
"
step="
1
"
bind:value={params.
max_tokens
}
min="2
56
"
max="
8192
"
step="
256
"
bind:value={params.
num_batch
}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.
max_tokens
}
bind:value={params.
num_batch
}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
max="16000"
step="1"
min="256"
step="256"
/>
</div>
</div>
...
...
@@ -611,56 +614,18 @@
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mmap (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mmap = (params?.use_mmap ?? null) === null ? true : null;
}}
>
{#if (params?.use_mmap ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mlock (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
}}
>
{#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('num_thread (Ollama)')}</div>
<div class=" self-center text-xs font-medium">
{$i18n.t('Tokens To Keep On Context Refresh (num_keep)')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_
thread
= (params?.num_
thread
?? null) === null ? 2 : null;
params.num_
keep
= (params?.num_
keep
?? null) === null ? 2
4
: null;
}}
>
{#if (params?.num_
thread
?? null) === null}
{#if (params?.num_
keep
?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -668,26 +633,25 @@
</button>
</div>
{#if (params?.num_
thread
?? null) !== null}
{#if (params?.num_
keep
?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="1"
max="
256
"
min="
-
1"
max="
10240000
"
step="1"
bind:value={params.num_
thread
}
bind:value={params.num_
keep
}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_
thread
}
bind:value={params.num_
keep
}
type="number"
class=" bg-transparent text-center w-14"
min="1"
max="256"
min="-1"
step="1"
/>
</div>
...
...
@@ -697,16 +661,16 @@
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('
Template
')}</div>
<div class=" self-center text-xs font-medium">{$i18n.t('
Max Tokens (num_predict)
')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.
template
= (params?.
template
?? null) === null ?
''
: null;
params.
max_tokens
= (params?.
max_tokens
?? null) === null ?
128
: null;
}}
>
{#if (params?.
template
?? null) === null}
{#if (params?.
max_tokens
?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
...
...
@@ -714,17 +678,151 @@
</button>
</div>
{#if (params?.
template
?? null) !== null}
{#if (params?.
max_tokens
?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
placeholder="Write your model template content here"
rows="4"
bind:value={params.template}
<input
id="steps-range"
type="range"
min="-2"
max="16000"
step="1"
bind:value={params.max_tokens}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.max_tokens}
type="number"
class=" bg-transparent text-center w-14"
min="-2"
max="16000"
step="1"
/>
</div>
</div>
{/if}
</div>
{#if admin}
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mmap (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mmap = (params?.use_mmap ?? null) === null ? true : null;
}}
>
{#if (params?.use_mmap ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('use_mlock (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.use_mlock = (params?.use_mlock ?? null) === null ? true : null;
}}
>
{#if (params?.use_mlock ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{/if}
</button>
</div>
</div>
<div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('num_thread (Ollama)')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.num_thread = (params?.num_thread ?? null) === null ? 2 : null;
}}
>
{#if (params?.num_thread ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.num_thread ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="1"
max="256"
step="1"
bind:value={params.num_thread}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div class="">
<input
bind:value={params.num_thread}
type="number"
class=" bg-transparent text-center w-14"
min="1"
max="256"
step="1"
/>
</div>
</div>
{/if}
</div>
<!-- <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Template')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
params.template = (params?.template ?? null) === null ? '' : null;
}}
>
{#if (params?.template ?? null) === null}
<span class="ml-2 self-center">{$i18n.t('Default')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Custom')}</span>
{/if}
</button>
</div>
{#if (params?.template ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<textarea
class="px-3 py-1.5 text-sm w-full bg-transparent border dark:border-gray-600 outline-none rounded-lg -mb-1"
placeholder="Write your model template content here"
rows="4"
bind:value={params.template}
/>
</div>
</div>
{/if}
</div> -->
{/if}
</div>
src/lib/components/chat/Settings/Audio.svelte
View file @
4ff17acc
<script lang="ts">
import
{
getAudioConfig
,
updateAudioConfig
}
from
'$lib/apis/audio'
;
import
{
user
,
settings
}
from
'$lib/stores'
;
import { user, settings, config } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import Switch from '$lib/components/common/Switch.svelte';
...
...
@@ -11,26 +10,15 @@
export let saveSettings: Function;
// Audio
let
OpenAIUrl
=
''
;
let
OpenAIKey
=
''
;
let
OpenAISpeaker
=
''
;
let
STTEngines
=
[
''
,
'openai'
];
let
STTEngine
=
''
;
let conversationMode = false;
let speechAutoSend = false;
let responseAutoPlayback = false;
let nonLocalVoices = false;
let
TTSEngines
=
[
''
,
'openai'
];
let
TTSEngine
=
''
;
let STTEngine = '';
let voices = [];
let
speaker
=
''
;
let
models
=
[];
let
model
=
''
;
let voice = '';
const getOpenAIVoices = () => {
voices = [
...
...
@@ -43,10 +31,6 @@
];
};
const
getOpenAIVoicesModel
=
()
=>
{
models
=
[{
name
:
'tts-1'
},
{
name
:
'tts-1-hd'
}];
};
const getWebAPIVoices = () => {
const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
...
...
@@ -58,21 +42,6 @@
}, 100);
};
const
toggleConversationMode
=
async
()
=>
{
conversationMode
=
!conversationMode;
if
(
conversationMode
)
{
responseAutoPlayback
=
true
;
speechAutoSend
=
true
;
}
saveSettings
({
conversationMode
:
conversationMode
,
responseAutoPlayback
:
responseAutoPlayback
,
speechAutoSend
:
speechAutoSend
});
};
const toggleResponseAutoPlayback = async () => {
responseAutoPlayback = !responseAutoPlayback;
saveSettings({ responseAutoPlayback: responseAutoPlayback });
...
...
@@ -83,76 +52,35 @@
saveSettings({ speechAutoSend: speechAutoSend });
};
const
updateConfigHandler
=
async
()
=>
{
if
(
TTSEngine
===
'openai'
)
{
const
res
=
await
updateAudioConfig
(
localStorage
.
token
,
{
url
:
OpenAIUrl
,
key
:
OpenAIKey
,
model
:
model
,
speaker
:
OpenAISpeaker
});
if
(
res
)
{
OpenAIUrl
=
res
.
OPENAI_API_BASE_URL
;
OpenAIKey
=
res
.
OPENAI_API_KEY
;
model
=
res
.
OPENAI_API_MODEL
;
OpenAISpeaker
=
res
.
OPENAI_API_VOICE
;
}
}
};
onMount(async () => {
conversationMode = $settings.conversationMode ?? false;
speechAutoSend = $settings.speechAutoSend ?? false;
responseAutoPlayback = $settings.responseAutoPlayback ?? false;
STTEngine
=
$
settings
?.
audio
?.
STTEngine
??
''
;
TTSEngine
=
$
settings
?.
audio
?.
TTSEngine
??
''
;
nonLocalVoices
=
$
settings
.
audio
?.
nonLocalVoices
??
false
;
speaker
=
$
settings
?.
audio
?.
speaker
??
''
;
model
=
$
settings
?.
audio
?.
model
??
''
;
STTEngine = $settings?.audio?.stt?.engine ?? '';
voice = $settings?.audio?.tts?.voice ?? $config.audio.tts.voice ?? '';
nonLocalVoices = $settings.audio?.tts?.nonLocalVoices ?? false;
if
(
TTSE
ngine
===
'openai'
)
{
if (
$config.audio.tts.e
ngine === 'openai') {
getOpenAIVoices();
getOpenAIVoicesModel
();
} else {
getWebAPIVoices();
}
if
($
user
.
role
===
'admin'
)
{
const
res
=
await
getAudioConfig
(
localStorage
.
token
);
if
(
res
)
{
OpenAIUrl
=
res
.
OPENAI_API_BASE_URL
;
OpenAIKey
=
res
.
OPENAI_API_KEY
;
model
=
res
.
OPENAI_API_MODEL
;
OpenAISpeaker
=
res
.
OPENAI_API_VOICE
;
if
(
TTSEngine
===
'openai'
)
{
speaker
=
OpenAISpeaker
;
}
}
}
});
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
if
($
user
.
role
===
'admin'
)
{
await
updateConfigHandler
();
}
saveSettings({
audio: {
STTEngine
:
STTEngine
!== '' ? STTEngine : undefined,
TTSEngine
:
TTSEngine
!== '' ? TTSEngine : undefined,
speaker
:
(
TTSEngine
===
'openai'
?
OpenAISpeaker
:
speaker
)
!== ''
?
TTSEngine
===
'openai'
?
OpenAISpeaker
:
speaker
:
undefined
,
model
:
model
!== '' ? model : undefined,
nonLocalVoices
:
nonLocalVoices
stt: {
engine: STTEngine !== '' ? STTEngine : undefined
},
tts: {
voice: voice !== '' ? voice : undefined,
nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined
}
}
});
dispatch('save');
...
...
@@ -162,53 +90,25 @@
<div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('STT Settings')}</div>
<
div
class
=
" py-0.5 flex w-full justify-between"
>
<
div
class
=
" self-center text-xs font-medium"
>{$
i18n
.
t
(
'Speech-to-Text Engine'
)}</
div
>
<
div
class
=
"flex items-center relative"
>
<
select
class
=
"dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind
:
value
={
STTEngine
}
placeholder
=
"Select a mode"
on
:
change
={(
e
)
=>
{
if
(
e
.
target
.
value
!== '') {
navigator
.
mediaDevices
.
getUserMedia
({
audio
:
true
}).
catch
(
function
(
err
)
{
toast
.
error
(
$
i18n
.
t
(`
Permission
denied
when
accessing
microphone
:
{{
error
}}`,
{
error
:
err
})
);
STTEngine
=
''
;
});
}
}}
>
<
option
value
=
""
>{$
i18n
.
t
(
'Default (Web API)'
)}</
option
>
<
option
value
=
"whisper-local"
>{$
i18n
.
t
(
'Whisper (Local)'
)}</
option
>
</
select
>
{#if $config.audio.stt.engine !== 'web'}
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Speech-to-Text Engine')}</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={STTEngine}
placeholder="Select an engine"
>
<option value="">{$i18n.t('Default')}</option>
<option value="web">{$i18n.t('Web API')}</option>
</select>
</div>
</div>
</
div
>
<
div
class
=
" py-0.5 flex w-full justify-between"
>
<
div
class
=
" self-center text-xs font-medium"
>{$
i18n
.
t
(
'Conversation Mode'
)}</
div
>
<
button
class
=
"p-1 px-3 text-xs flex rounded transition"
on
:
click
={()
=>
{
toggleConversationMode
();
}}
type
=
"button"
>
{#
if
conversationMode
===
true
}
<
span
class
=
"ml-2 self-center"
>{$
i18n
.
t
(
'On'
)}</
span
>
{:
else
}
<
span
class
=
"ml-2 self-center"
>{$
i18n
.
t
(
'Off'
)}</
span
>
{/
if
}
</
button
>
</
div
>
{/if}
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$
i18n
.
t
(
'Auto-
s
end
input after 3 sec.
'
)}
{$i18n.t('
Instant
Auto-
S
end
After Voice Transcription
')}
</div>
<button
...
...
@@ -230,50 +130,6 @@
<div>
<div class=" mb-1 text-sm font-medium">{$i18n.t('TTS Settings')}</div>
<
div
class
=
" py-0.5 flex w-full justify-between"
>
<
div
class
=
" self-center text-xs font-medium"
>{$
i18n
.
t
(
'Text-to-Speech Engine'
)}</
div
>
<
div
class
=
"flex items-center relative"
>
<
select
class
=
" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind
:
value
={
TTSEngine
}
placeholder
=
"Select a mode"
on
:
change
={(
e
)
=>
{
if
(
e
.
target
.
value
===
'openai'
)
{
getOpenAIVoices
();
OpenAISpeaker
=
'alloy'
;
model
=
'tts-1'
;
}
else
{
getWebAPIVoices
();
speaker
=
''
;
}
}}
>
<
option
value
=
""
>{$
i18n
.
t
(
'Default (Web API)'
)}</
option
>
<
option
value
=
"openai"
>{$
i18n
.
t
(
'Open AI'
)}</
option
>
</
select
>
</
div
>
</
div
>
{#
if
$
user
.
role
===
'admin'
}
{#
if
TTSEngine
===
'openai'
}
<
div
class
=
"mt-1 flex gap-2 mb-1"
>
<
input
class
=
"w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder
={$
i18n
.
t
(
'API Base URL'
)}
bind
:
value
={
OpenAIUrl
}
required
/>
<
input
class
=
"w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder
={$
i18n
.
t
(
'API Key'
)}
bind
:
value
={
OpenAIKey
}
required
/>
</
div
>
{/
if
}
{/
if
}
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Auto-playback response')}</div>
...
...
@@ -293,23 +149,23 @@
</div>
</div>
<
hr
class
=
" dark:border-gray-
70
0"
/>
<hr class=" dark:border-gray-
85
0" />
{#
if
TTSE
ngine
===
''
}
{#if
$config.audio.tts.e
ngine === ''}
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full">
<div class="flex-1">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind
:
value
={
speaker
}
bind:value={
voice
}
>
<
option
value
=
""
selected
={
speaker
!== ''}>{$i18n.t('Default')}</option>
{#
each
voices
.
filter
((
v
)
=>
nonLocalVoices
||
v
.
localService
===
true
)
as
voice
}
<option value="" selected={
voice
!== ''}>{$i18n.t('Default')}</option>
{#each voices.filter((v) => nonLocalVoices || v.localService === true) as
_
voice}
<option
value
={
voice
.
name
}
value={
_
voice.name}
class="bg-gray-100 dark:bg-gray-700"
selected
={
speaker
===
voice
.
name
}>{
voice
.
name
}</
option
selected={
voice
===
_
voice.name}>{
_
voice.name}</option
>
{/each}
</select>
...
...
@@ -325,7 +181,7 @@
</div>
</div>
</div>
{:
else
if
TTSE
ngine
===
'openai'
}
{:else if
$config.audio.tts.e
ngine === 'openai'}
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Set Voice')}</div>
<div class="flex w-full">
...
...
@@ -333,7 +189,7 @@
<input
list="voice-list"
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind
:
value
={
OpenAISpeaker
}
bind:value={
voice
}
placeholder="Select a voice"
/>
...
...
@@ -345,25 +201,6 @@
</div>
</div>
</div>
<
div
>
<
div
class
=
" mb-2.5 text-sm font-medium"
>{$
i18n
.
t
(
'Set Model'
)}</
div
>
<
div
class
=
"flex w-full"
>
<
div
class
=
"flex-1"
>
<
input
list
=
"model-list"
class
=
"w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind
:
value
={
model
}
placeholder
=
"Select a model"
/>
<
datalist
id
=
"model-list"
>
{#
each
models
as
model
}
<
option
value
={
model
.
name
}
/>
{/
each
}
</
datalist
>
</
div
>
</
div
>
</
div
>
{/if}
</div>
...
...
src/lib/components/chat/Settings/Chats.svelte
View file @
4ff17acc
...
...
@@ -161,7 +161,7 @@
</div>
</div>
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div class="flex flex-col">
<input
...
...
@@ -218,7 +218,7 @@
</button>
</div>
<hr class=" dark:border-gray-
70
0" />
<hr class=" dark:border-gray-
85
0" />
<div class="flex flex-col">
{#if showArchiveConfirm}
...
...
src/lib/components/chat/Settings/General.svelte
View file @
4ff17acc
...
...
@@ -4,7 +4,7 @@
import { getLanguages } from '$lib/i18n';
const dispatch = createEventDispatcher();
import { models, settings, theme } from '$lib/stores';
import { models, settings, theme
, user
} from '$lib/stores';
const i18n = getContext('i18n');
...
...
@@ -43,19 +43,21 @@
let params = {
// Advanced
seed:
0
,
temperature:
''
,
frequency_penalty:
''
,
repeat_last_n:
''
,
mirostat:
''
,
mirostat_eta:
''
,
mirostat_tau:
''
,
top_k:
''
,
top_p:
''
,
seed:
null
,
temperature:
null
,
frequency_penalty:
null
,
repeat_last_n:
null
,
mirostat:
null
,
mirostat_eta:
null
,
mirostat_tau:
null
,
top_k:
null
,
top_p:
null
,
stop: null,
tfs_z: '',
num_ctx: '',
max_tokens: ''
tfs_z: null,
num_ctx: null,
num_batch: null,
num_keep: null,
max_tokens: null
};
const toggleRequestFormat = async () => {
...
...
@@ -79,12 +81,6 @@
requestFormat = $settings.requestFormat ?? '';
keepAlive = $settings.keepAlive ?? null;
params.seed = $settings.seed ?? 0;
params.temperature = $settings.temperature ?? '';
params.frequency_penalty = $settings.frequency_penalty ?? '';
params.top_k = $settings.top_k ?? '';
params.top_p = $settings.top_p ?? '';
params.num_ctx = $settings.num_ctx ?? '';
params = { ...params, ...$settings.params };
params.stop = $settings?.params?.stop ? ($settings?.params?.stop ?? []).join(',') : null;
});
...
...
@@ -146,6 +142,7 @@
<option value="dark">🌑 {$i18n.t('Dark')}</option>
<option value="oled-dark">🌃 {$i18n.t('OLED Dark')}</option>
<option value="light">☀️ {$i18n.t('Light')}</option>
<option value="her">🌷 Her</option>
<!-- <option value="rose-pine dark">🪻 {$i18n.t('Rosé Pine')}</option>
<option value="rose-pine-dawn light">🌷 {$i18n.t('Rosé Pine Dawn')}</option> -->
</select>
...
...
@@ -203,7 +200,7 @@
</div>
</div>
<hr class=" dark:border-gray-
70
0 my-3" />
<hr class=" dark:border-gray-
85
0 my-3" />
<div>
<div class=" my-2.5 text-sm font-medium">{$i18n.t('System Prompt')}</div>
...
...
@@ -227,8 +224,8 @@
</div>
{#if showAdvanced}
<AdvancedParams bind:params />
<hr class=" dark:border-gray-
70
0" />
<AdvancedParams
admin={$user?.role === 'admin'}
bind:params />
<hr class=" dark:border-gray-
85
0" />
<div class=" py-1 w-full justify-between">
<div class="flex w-full justify-between">
...
...
@@ -300,20 +297,25 @@
saveSettings({
system: system !== '' ? system : undefined,
params: {
seed: (params.seed !==
0
? params.seed : undefined) ?? undefined,
seed: (params.seed !==
null
? params.seed : undefined) ?? undefined,
stop: params.stop ? params.stop.split(',').filter((e) => e) : undefined,
temperature: params.temperature !==
''
? params.temperature : undefined,
temperature: params.temperature !==
null
? params.temperature : undefined,
frequency_penalty:
params.frequency_penalty !== '' ? params.frequency_penalty : undefined,
repeat_last_n: params.repeat_last_n !== '' ? params.repeat_last_n : undefined,
mirostat: params.mirostat !== '' ? params.mirostat : undefined,
mirostat_eta: params.mirostat_eta !== '' ? params.mirostat_eta : undefined,
mirostat_tau: params.mirostat_tau !== '' ? params.mirostat_tau : undefined,
top_k: params.top_k !== '' ? params.top_k : undefined,
top_p: params.top_p !== '' ? params.top_p : undefined,
tfs_z: params.tfs_z !== '' ? params.tfs_z : undefined,
num_ctx: params.num_ctx !== '' ? params.num_ctx : undefined,
max_tokens: params.max_tokens !== '' ? params.max_tokens : undefined
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_last_n: params.repeat_last_n !== null ? params.repeat_last_n : undefined,
mirostat: params.mirostat !== null ? params.mirostat : undefined,
mirostat_eta: params.mirostat_eta !== null ? params.mirostat_eta : undefined,
mirostat_tau: params.mirostat_tau !== null ? params.mirostat_tau : undefined,
top_k: params.top_k !== null ? params.top_k : undefined,
top_p: params.top_p !== null ? params.top_p : undefined,
tfs_z: params.tfs_z !== null ? params.tfs_z : undefined,
num_ctx: params.num_ctx !== null ? params.num_ctx : undefined,
num_batch: params.num_batch !== null ? params.num_batch : undefined,
num_keep: params.num_keep !== null ? params.num_keep : undefined,
max_tokens: params.max_tokens !== null ? params.max_tokens : undefined,
use_mmap: params.use_mmap !== null ? params.use_mmap : undefined,
use_mlock: params.use_mlock !== null ? params.use_mlock : undefined,
num_thread: params.num_thread !== null ? params.num_thread : undefined
},
keepAlive: keepAlive ? (isNaN(keepAlive) ? keepAlive : parseInt(keepAlive)) : undefined
});
...
...
src/lib/components/chat/Settings/Interface.svelte
View file @
4ff17acc
...
...
@@ -14,19 +14,18 @@
// Addons
let titleAutoGenerate = true;
let responseAutoCopy = false;
let titleAutoGenerateModel = '';
let titleAutoGenerateModelExternal = '';
let widescreenMode = false;
let titleGenerationPrompt = '';
let splitLargeChunks = false;
// Interface
let defaultModelId = '';
let promptSuggestions = [];
let showUsername = false;
let chatBubble = true;
let chatDirection: 'LTR' | 'RTL' = 'LTR';
let showEmojiInCall = false;
const toggleSplitLargeChunks = async () => {
splitLargeChunks = !splitLargeChunks;
saveSettings({ splitLargeChunks: splitLargeChunks });
...
...
@@ -47,6 +46,11 @@
saveSettings({ showUsername: showUsername });
};
const toggleEmojiInCall = async () => {
showEmojiInCall = !showEmojiInCall;
saveSettings({ showEmojiInCall: showEmojiInCall });
};
const toggleTitleAutoGenerate = async () => {
titleAutoGenerate = !titleAutoGenerate;
saveSettings({
...
...
@@ -85,36 +89,19 @@
};
const updateInterfaceHandler = async () => {
if ($user.role === 'admin') {
promptSuggestions = await setDefaultPromptSuggestions(localStorage.token, promptSuggestions);
await config.set(await getBackendConfig());
}
saveSettings({
title: {
...$settings.title,
model: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
modelExternal:
titleAutoGenerateModelExternal !== '' ? titleAutoGenerateModelExternal : undefined,
prompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
},
models: [defaultModelId]
});
};
onMount(async () => {
if ($user.role === 'admin') {
promptSuggestions = $config?.default_prompt_suggestions;
}
titleAutoGenerate = $settings?.title?.auto ?? true;
titleAutoGenerateModel = $settings?.title?.model ?? '';
titleAutoGenerateModelExternal = $settings?.title?.modelExternal ?? '';
titleGenerationPrompt =
$settings?.title?.prompt ??
`Create a concise, 3-5 word phrase as a header for the following query, strictly adhering to the 3-5 word limit and avoiding the use of the word 'title': {{prompt}}`;
responseAutoCopy = $settings.responseAutoCopy ?? false;
showUsername = $settings.showUsername ?? false;
showEmojiInCall = $settings.showEmojiInCall ?? false;
chatBubble = $settings.chatBubble ?? true;
widescreenMode = $settings.widescreenMode ?? false;
splitLargeChunks = $settings.splitLargeChunks ?? false;
...
...
@@ -217,6 +204,26 @@
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Display Emoji in Call')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
toggleEmojiInCall();
}}
type="button"
>
{#if showEmojiInCall === true}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if}
</button>
</div>
</div>
{#if !$settings.chatBubble}
<div>
<div class=" py-0.5 flex w-full justify-between">
...
...
@@ -304,162 +311,6 @@
</select>
</div>
</div>
<hr class=" dark:border-gray-850" />
<div>
<div class=" mb-2.5 text-sm font-medium flex">
<div class=" mr-1">{$i18n.t('Set Task Model')}</div>
<Tooltip
content={$i18n.t(
'A task model is used when performing tasks such as generating titles for chats and web search queries'
)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-5 h-5"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
/>
</svg>
</Tooltip>
</div>
<div class="flex w-full gap-2 pr-2">
<div class="flex-1">
<div class=" text-xs mb-1">Local Models</div>
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModel}
placeholder={$i18n.t('Select a model')}
>
<option value="" selected>{$i18n.t('Current Model')}</option>
{#each $models.filter((m) => m.owned_by === 'ollama') as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
{model.name}
</option>
{/each}
</select>
</div>
<div class="flex-1">
<div class=" text-xs mb-1">External Models</div>
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={titleAutoGenerateModelExternal}
placeholder={$i18n.t('Select a model')}
>
<option value="" selected>{$i18n.t('Current Model')}</option>
{#each $models as model}
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">
{model.name}
</option>
{/each}
</select>
</div>
</div>
<div class="mt-3 mr-2">
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('Title Generation Prompt')}</div>
<textarea
bind:value={titleGenerationPrompt}
class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="3"
/>
</div>
</div>
{#if $user.role === 'admin'}
<hr class=" dark:border-gray-700" />
<div class=" space-y-3 pr-1.5">
<div class="flex w-full justify-between mb-2">
<div class=" self-center text-sm font-semibold">
{$i18n.t('Default Prompt Suggestions')}
</div>
<button
class="p-1 px-3 text-xs flex rounded transition"
type="button"
on:click={() => {
if (promptSuggestions.length === 0 || promptSuggestions.at(-1).content !== '') {
promptSuggestions = [...promptSuggestions, { content: '', title: ['', ''] }];
}
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M10.75 4.75a.75.75 0 00-1.5 0v4.5h-4.5a.75.75 0 000 1.5h4.5v4.5a.75.75 0 001.5 0v-4.5h4.5a.75.75 0 000-1.5h-4.5v-4.5z"
/>
</svg>
</button>
</div>
<div class="flex flex-col space-y-1">
{#each promptSuggestions as prompt, promptIdx}
<div class=" flex border dark:border-gray-600 rounded-lg">
<div class="flex flex-col flex-1">
<div class="flex border-b dark:border-gray-600 w-full">
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder={$i18n.t('Title (e.g. Tell me a fun fact)')}
bind:value={prompt.title[0]}
/>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder={$i18n.t('Subtitle (e.g. about the Roman Empire)')}
bind:value={prompt.title[1]}
/>
</div>
<input
class="px-3 py-1.5 text-xs w-full bg-transparent outline-none border-r dark:border-gray-600"
placeholder={$i18n.t('Prompt (e.g. Tell me a fun fact about the Roman Empire)')}
bind:value={prompt.content}
/>
</div>
<button
class="px-2"
type="button"
on:click={() => {
promptSuggestions.splice(promptIdx, 1);
promptSuggestions = promptSuggestions;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
{/each}
</div>
{#if promptSuggestions.length > 0}
<div class="text-xs text-left w-full mt-2">
{$i18n.t('Adjusting these settings will apply changes universally to all users.')}
</div>
{/if}
</div>
{/if}
</div>
<div class="flex justify-end text-sm font-medium">
...
...
src/lib/components/chat/Settings/Models.svelte
View file @
4ff17acc
...
...
@@ -541,7 +541,7 @@
]);
} else {
ollamaEnabled = false;
toast.error('Ollama API is disabled');
toast.error(
$i18n.t(
'Ollama API is disabled')
)
;
}
});
</script>
...
...
@@ -1063,7 +1063,7 @@
</div>
{/if}
{:else if ollamaEnabled === false}
<div>Ollama API is disabled</div>
<div>
{$i18n.t('
Ollama API is disabled
')}
</div>
{:else}
<div class="flex h-full justify-center">
<div class="my-auto">
...
...
src/lib/components/chat/Settings/Personalization.svelte
View file @
4ff17acc
...
...
@@ -35,7 +35,9 @@
<div>
<div class="flex items-center justify-between mb-1">
<Tooltip
content="This is an experimental feature, it may not function as expected and is subject to change at any time."
content={$i18n.t(
'This is an experimental feature, it may not function as expected and is subject to change at any time.'
)}
>
<div class="text-sm font-medium">
{$i18n.t('Memory')}
...
...
@@ -57,8 +59,9 @@
<div class="text-xs text-gray-600 dark:text-gray-400">
<div>
You can personalize your interactions with LLMs by adding memories through the 'Manage'
button below, making them more helpful and tailored to you.
{$i18n.t(
"You can personalize your interactions with LLMs by adding memories through the 'Manage' button below, making them more helpful and tailored to you."
)}
</div>
<!-- <div class="mt-3">
...
...
@@ -79,7 +82,7 @@
showManageModal = true;
}}
>
Manage
{$i18n.t('
Manage
')}
</button>
</div>
</div>
...
...
src/lib/components/chat/Settings/Personalization/AddMemoryModal.svelte
View file @
4ff17acc
...
...
@@ -2,13 +2,12 @@
import { createEventDispatcher, getContext } from 'svelte';
import Modal from '$lib/components/common/Modal.svelte';
import { addNewMemory } from '$lib/apis/memories';
import { addNewMemory
, updateMemoryById
} from '$lib/apis/memories';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
export let show;
const i18n = getContext('i18n');
let loading = false;
...
...
@@ -38,7 +37,9 @@
<Modal bind:show size="sm">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
<div class=" text-lg font-medium self-center">{$i18n.t('Add Memory')}</div>
<div class=" text-lg font-medium self-center">
{$i18n.t('Add Memory')}
</div>
<button
class="self-center"
on:click={() => {
...
...
@@ -75,7 +76,7 @@
/>
<div class="text-xs text-gray-500">
ⓘ Refer to yourself as "User" (e.g., "User is learning Spanish")
ⓘ
{$i18n.t('
Refer to yourself as "User" (e.g., "User is learning Spanish")
')}
</div>
</div>
...
...
src/lib/components/chat/Settings/Personalization/EditMemoryModal.svelte
0 → 100644
View file @
4ff17acc
<script>
import { createEventDispatcher, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import { updateMemoryById } from '$lib/apis/memories';
import Modal from '$lib/components/common/Modal.svelte';
const dispatch = createEventDispatcher();
export let show;
export let memory = {};
const i18n = getContext('i18n');
let loading = false;
let content = '';
$: if (show) {
setContent();
}
const setContent = () => {
content = memory.content;
};
const submitHandler = async () => {
loading = true;
const res = await updateMemoryById(localStorage.token, memory.id, content).catch((error) => {
toast.error(error);
return null;
});
if (res) {
console.log(res);
toast.success('Memory updated successfully');
dispatch('save');
show = false;
}
loading = false;
};
</script>
<Modal bind:show size="sm">
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-2">
<div class=" text-lg font-medium self-center">
{$i18n.t('Edit Memory')}
</div>
<button
class="self-center"
on:click={() => {
show = false;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5"
>
<path
d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
/>
</svg>
</button>
</div>
<div class="flex flex-col md:flex-row w-full px-5 pb-4 md:space-x-4 dark:text-gray-200">
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
<form
class="flex flex-col w-full"
on:submit|preventDefault={() => {
submitHandler();
}}
>
<div class="">
<textarea
bind:value={content}
class=" bg-transparent w-full text-sm resize-none rounded-xl p-3 outline outline-1 outline-gray-100 dark:outline-gray-800"
rows="3"
placeholder={$i18n.t('Enter a detail about yourself for your LLMs to recall')}
/>
<div class="text-xs text-gray-500">
ⓘ {$i18n.t('Refer to yourself as "User" (e.g., "User is learning Spanish")')}
</div>
</div>
<div class="flex justify-end pt-1 text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-3xl flex flex-row space-x-1 items-center {loading
? ' cursor-not-allowed'
: ''}"
type="submit"
disabled={loading}
>
{$i18n.t('Update')}
{#if loading}
<div class="ml-2 self-center">
<svg
class=" w-4 h-4"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
><style>
.spinner_ajPY {
transform-origin: center;
animation: spinner_AtaB 0.75s infinite linear;
}
@keyframes spinner_AtaB {
100% {
transform: rotate(360deg);
}
}
</style><path
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
opacity=".25"
/><path
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
class="spinner_ajPY"
/></svg
>
</div>
{/if}
</button>
</div>
</form>
</div>
</div>
</div>
</Modal>
src/lib/components/chat/Settings/Personalization/ManageModal.svelte
View file @
4ff17acc
...
...
@@ -10,18 +10,24 @@
import { deleteMemoriesByUserId, deleteMemoryById, getMemories } from '$lib/apis/memories';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import { error } from '@sveltejs/kit';
import EditMemoryModal from './EditMemoryModal.svelte';
const i18n = getContext('i18n');
export let show = false;
let memories = [];
let loading = true;
let showAddMemoryModal = false;
let showEditMemoryModal = false;
$: if (show) {
let selectedMemory = null;
$: if (show && memories.length === 0 && loading) {
(async () => {
memories = await getMemories(localStorage.token);
loading = false;
})();
}
</script>
...
...
@@ -62,7 +68,9 @@
>
<tr>
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
<th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created At')} </th>
<th scope="col" class="px-3 py-2 hidden md:flex">
{$i18n.t('Last Modified')}
</th>
<th scope="col" class="px-3 py-2 text-right" />
</tr>
</thead>
...
...
@@ -76,11 +84,38 @@
</td>
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
<div class="my-auto whitespace-nowrap">
{dayjs(memory.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
{dayjs(memory.updated_at * 1000).format(
$i18n.t('MMMM DD, YYYY hh:mm:ss A')
)}
</div>
</td>
<td class="px-3 py-1">
<div class="flex justify-end w-full">
<Tooltip content="Edit">
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
on:click={() => {
selectedMemory = memory;
showEditMemoryModal = true;
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-4 h-4 s-FoVA_WMOgxUD"
><path
stroke-linecap="round"
stroke-linejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
class="s-FoVA_WMOgxUD"
/></svg
>
</button>
</Tooltip>
<Tooltip content="Delete">
<button
class="self-center w-fit text-sm px-2 py-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-xl"
...
...
@@ -136,7 +171,7 @@
class=" px-3.5 py-1.5 font-medium hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-gray-300 dark:outline-gray-800 rounded-3xl"
on:click={() => {
showAddMemoryModal = true;
}}>Add
m
emory</button
}}>
{$i18n.t('
Add
M
emory
')}
</button
>
<button
class=" px-3.5 py-1.5 font-medium text-red-500 hover:bg-black/5 dark:hover:bg-white/5 outline outline-1 outline-red-300 dark:outline-red-800 rounded-3xl"
...
...
@@ -150,7 +185,7 @@
toast.success('Memory cleared successfully');
memories = [];
}
}}>Clear memory</button
}}>
{$i18n.t('
Clear memory
')}
</button
>
</div>
</div>
...
...
@@ -163,3 +198,11 @@
memories = await getMemories(localStorage.token);
}}
/>
<EditMemoryModal
bind:show={showEditMemoryModal}
memory={selectedMemory}
on:save={async () => {
memories = await getMemories(localStorage.token);
}}
/>
src/lib/components/chat/SettingsModal.svelte
View file @
4ff17acc
...
...
@@ -8,16 +8,14 @@
import Modal from '../common/Modal.svelte';
import Account from './Settings/Account.svelte';
import About from './Settings/About.svelte';
import Models from './Settings/Models.svelte';
import General from './Settings/General.svelte';
import Interface from './Settings/Interface.svelte';
import Audio from './Settings/Audio.svelte';
import Chats from './Settings/Chats.svelte';
import Connections from './Settings/Connections.svelte';
import Images from './Settings/Images.svelte';
import User from '../icons/User.svelte';
import Personalization from './Settings/Personalization.svelte';
import { updateUserSettings } from '$lib/apis/users';
import { goto } from '$app/navigation';
const i18n = getContext('i18n');
...
...
@@ -90,55 +88,32 @@
<div class=" self-center">{$i18n.t('General')}</div>
</button>
{#if $user?.role === 'admin'}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'connections'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'connections';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M1 9.5A3.5 3.5 0 0 0 4.5 13H12a3 3 0 0 0 .917-5.857 2.503 2.503 0 0 0-3.198-3.019 3.5 3.5 0 0 0-6.628 2.171A3.5 3.5 0 0 0 1 9.5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Connections')}</div>
</button>
{#if $user.role === 'admin'}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'
models
'
'
admin
'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'models';
on:click={async () => {
await goto('/admin/settings');
show = false;
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 2
0
2
0
"
viewBox="0 0 2
4
2
4
"
fill="currentColor"
class="
w-4 h
-4"
class="
size
-4"
>
<path
fill-rule="evenodd"
d="M
10 1c3.866 0 7 1.79 7 4s-3.134 4-7 4-7-1.79-7-4 3.134-4 7-4zm5.694 8.13c.464-.
264.
91-.583 1.306-.952V10c0 2.21-3.134 4-7 4s-7-1.79-7-4V8.178c.396.37.842.688 1.306.953C5.838 10.006 7.854 10.5 10 10.5s4.162-.494 5.694-1.37zM3 13.179V15c0 2.21 3.134 4 7 4s7-1.79 7-4v-1.822c-.396.37-.842.688-1.306.953-1.532.875-3.548 1.369-5.694 1.369s-4.162-.494-5.694-1.37A7.009 7.009 0 013 13.179z
"
d="M
4.5 3.75a3 3 0 0 0-3 3v10.5a3 3 0 0 0 3 3h15a3 3 0 0 0 3-3V6.75a3 3 0 0 0-3-3h-15Zm4.125 3a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Zm-3.873 8.703a4.1
26
4.
126 0 0 1 7.746 0 .75.75 0 0 1-.351.92 7.47 7.47 0 0 1-3.522.877 7.47 7.47 0 0 1-3.522-.877.75.75 0 0 1-.351-.92ZM15 8.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15ZM14.25 12a.75.75 0 0 1 .75-.75h3.75a.75.75 0 0 1 0 1.5H15a.75.75 0 0 1-.75-.75Zm.75 2.25a.75.75 0 0 0 0 1.5h3.75a.75.75 0 0 0 0-1.5H15Z
"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('
Model
s')}</div>
<div class=" self-center">{$i18n.t('
Admin Setting
s')}</div>
</button>
{/if}
...
...
@@ -210,34 +185,6 @@
<div class=" self-center">{$i18n.t('Audio')}</div>
</button>
{#if $user.role === 'admin'}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'images'
? 'bg-gray-200 dark:bg-gray-700'
: ' hover:bg-gray-300 dark:hover:bg-gray-800'}"
on:click={() => {
selectedTab = 'images';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V4Zm10.5 5.707a.5.5 0 0 0-.146-.353l-1-1a.5.5 0 0 0-.708 0L9.354 9.646a.5.5 0 0 1-.708 0L6.354 7.354a.5.5 0 0 0-.708 0l-2 2a.5.5 0 0 0-.146.353V12a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5V9.707ZM12 5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Images')}</div>
</button>
{/if}
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'chats'
...
...
@@ -325,15 +272,6 @@
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'models'}
<Models {getModels} />
{:else if selectedTab === 'connections'}
<Connections
{getModels}
on:save={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'interface'}
<Interface
{saveSettings}
...
...
@@ -355,13 +293,6 @@
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'images'}
<Images
{saveSettings}
on:save={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'chats'}
<Chats {saveSettings} />
{:else if selectedTab === 'account'}
...
...
src/lib/components/common/CodeEditor.svelte
0 → 100644
View file @
4ff17acc
<script lang="ts">
import { basicSetup, EditorView } from 'codemirror';
import { keymap, placeholder } from '@codemirror/view';
import { Compartment, EditorState } from '@codemirror/state';
import { acceptCompletion } from '@codemirror/autocomplete';
import { indentWithTab } from '@codemirror/commands';
import { indentUnit } from '@codemirror/language';
import { python } from '@codemirror/lang-python';
import { oneDark } from '@codemirror/theme-one-dark';
import { onMount, createEventDispatcher } from 'svelte';
import { formatPythonCode } from '$lib/apis/utils';
import { toast } from 'svelte-sonner';
const dispatch = createEventDispatcher();
export let boilerplate = '';
export let value = '';
let codeEditor;
let isDarkMode = false;
let editorTheme = new Compartment();
export const formatPythonCodeHandler = async () => {
if (codeEditor) {
const res = await formatPythonCode(value).catch((error) => {
toast.error(error);
return null;
});
if (res && res.code) {
const formattedCode = res.code;
codeEditor.dispatch({
changes: [{ from: 0, to: codeEditor.state.doc.length, insert: formattedCode }]
});
toast.success('Code formatted successfully');
return true;
}
return false;
}
return false;
};
let extensions = [
basicSetup,
keymap.of([{ key: 'Tab', run: acceptCompletion }, indentWithTab]),
python(),
indentUnit.of(' '),
placeholder('Enter your code here...'),
EditorView.updateListener.of((e) => {
if (e.docChanged) {
value = e.state.doc.toString();
}
}),
editorTheme.of([])
];
onMount(() => {
console.log(value);
if (value === '') {
value = boilerplate;
}
// Check if html class has dark mode
isDarkMode = document.documentElement.classList.contains('dark');
// python code editor, highlight python code
codeEditor = new EditorView({
state: EditorState.create({
doc: value,
extensions: extensions
}),
parent: document.getElementById('code-textarea')
});
if (isDarkMode) {
codeEditor.dispatch({
effects: editorTheme.reconfigure(oneDark)
});
}
// listen to html class changes this should fire only when dark mode is toggled
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
const _isDarkMode = document.documentElement.classList.contains('dark');
if (_isDarkMode !== isDarkMode) {
isDarkMode = _isDarkMode;
if (_isDarkMode) {
codeEditor.dispatch({
effects: editorTheme.reconfigure(oneDark)
});
} else {
codeEditor.dispatch({
effects: editorTheme.reconfigure()
});
}
}
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
const keydownHandler = async (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
dispatch('save');
}
// Format code when Ctrl + Shift + F is pressed
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'f') {
e.preventDefault();
await formatPythonCodeHandler();
}
};
document.addEventListener('keydown', keydownHandler);
return () => {
observer.disconnect();
document.removeEventListener('keydown', keydownHandler);
};
});
</script>
<div id="code-textarea" class="h-full w-full" />
src/lib/components/common/ConfirmDialog.svelte
0 → 100644
View file @
4ff17acc
<script lang="ts">
import { onMount, createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import { flyAndScale } from '$lib/utils/transitions';
const dispatch = createEventDispatcher();
export let title = 'Confirm your action';
export let message = 'This action cannot be undone. Do you wish to continue?';
export let cancelLabel = 'Cancel';
export let confirmLabel = 'Confirm';
export let show = false;
let modalElement = null;
let mounted = false;
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
console.log('Escape');
show = false;
}
};
onMount(() => {
mounted = true;
});
$: if (mounted) {
if (show) {
window.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
} else {
window.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'unset';
}
}
</script>
{#if show}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={modalElement}
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
in:fade={{ duration: 10 }}
on:mousedown={() => {
show = false;
}}
>
<div
class=" m-auto rounded-2xl max-w-full w-[32rem] mx-2 bg-gray-50 dark:bg-gray-950 shadow-3xl border border-gray-850"
in:flyAndScale
on:mousedown={(e) => {
e.stopPropagation();
}}
>
<div class="px-[1.75rem] py-6">
<div class=" text-lg font-semibold dark:text-gray-200 mb-2.5">{title}</div>
<slot>
<div class=" text-sm text-gray-500">
{message}
</div>
</slot>
<div class="mt-6 flex justify-between gap-1.5">
<button
class="bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white font-medium w-full py-2.5 rounded-lg transition"
on:click={() => {
show = false;
}}
type="button"
>
{cancelLabel}
</button>
<button
class="bg-gray-900 hover:bg-gray-850 text-gray-100 dark:bg-gray-100 dark:hover:bg-white dark:text-gray-800 font-medium w-full py-2.5 rounded-lg transition"
on:click={() => {
show = false;
dispatch('confirm');
}}
type="button"
>
{confirmLabel}
</button>
</div>
</div>
</div>
</div>
{/if}
<style>
.modal-content {
animation: scaleUp 0.1s ease-out forwards;
}
@keyframes scaleUp {
from {
transform: scale(0.985);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
</style>
src/lib/components/common/Selector.svelte
View file @
4ff17acc
...
...
@@ -48,7 +48,7 @@
<ChevronDown className="absolute end-2 top-1/2 -translate-y-[45%] size-3.5" strokeWidth="2.5" />
</Select.Trigger>
<Select.Content
class="w-full rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-
70
0/50 outline-none"
class="w-full rounded-lg bg-white dark:bg-gray-900 dark:text-white shadow-lg border border-gray-300/30 dark:border-gray-
85
0/50 outline-none"
transition={flyAndScale}
sideOffset={4}
>
...
...
src/lib/components/common/Tooltip.svelte
View file @
4ff17acc
<script lang="ts">
import { onDestroy } from 'svelte';
import { marked } from 'marked';
import tippy from 'tippy.js';
export let placement = 'top';
...
...
src/lib/components/documents/Settings/QueryParams.svelte
View file @
4ff17acc
...
...
@@ -95,7 +95,7 @@
)}
</div>
<hr class=" dark:border-gray-
70
0 my-3" />
<hr class=" dark:border-gray-
85
0 my-3" />
{/if}
<div>
...
...
src/lib/components/documents/Settings/WebParams.svelte
View file @
4ff17acc
...
...
@@ -11,7 +11,7 @@
export let saveHandler: Function;
let webConfig = null;
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper'];
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper'
, 'serply'
];
let youtubeLanguage = 'en';
let youtubeTranslation = null;
...
...
@@ -68,10 +68,10 @@
<select
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={webConfig.search.engine}
placeholder=
"
Select a engine
"
placeholder=
{$i18n.t('
Select a engine
')}
required
>
<option disabled selected value="">Select a engine</option>
<option disabled selected value="">
{$i18n.t('
Select a engine
')}
</option>
{#each webSearchEngines as engine}
<option value={engine}>{engine}</option>
{/each}
...
...
src/lib/components/icons/Headphone.svelte
0 → 100644
View file @
4ff17acc
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '0';
</script>
<svg
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
fill-rule="evenodd"
d="M12 5a7 7 0 0 0-7 7v1.17c.313-.11.65-.17 1-.17h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H6a3 3 0 0 1-3-3v-6a9 9 0 0 1 18 0v6a3 3 0 0 1-3 3h-2a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h2c.35 0 .687.06 1 .17V12a7 7 0 0 0-7-7Z"
clip-rule="evenodd"
/>
</svg>
src/lib/components/icons/MagnifyingGlass.svelte
0 → 100644
View file @
4ff17acc
<script lang="ts">
export let className = 'size-4';
export let strokeWidth = '2';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z"
/>
</svg>
src/lib/components/icons/WrenchSolid.svelte
0 → 100644
View file @
4ff17acc
<script lang="ts">
export let className = 'size-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M12 6.75a5.25 5.25 0 0 1 6.775-5.025.75.75 0 0 1 .313 1.248l-3.32 3.319c.063.475.276.934.641 1.299.365.365.824.578 1.3.64l3.318-3.319a.75.75 0 0 1 1.248.313 5.25 5.25 0 0 1-5.472 6.756c-1.018-.086-1.87.1-2.309.634L7.344 21.3A3.298 3.298 0 1 1 2.7 16.657l8.684-7.151c.533-.44.72-1.291.634-2.309A5.342 5.342 0 0 1 12 6.75ZM4.117 19.125a.75.75 0 0 1 .75-.75h.008a.75.75 0 0 1 .75.75v.008a.75.75 0 0 1-.75.75h-.008a.75.75 0 0 1-.75-.75v-.008Z"
clip-rule="evenodd"
/>
</svg>
Prev
1
2
3
4
5
6
7
8
9
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment