Commit 6847c2fc authored by Aryan Kothari's avatar Aryan Kothari
Browse files

Merge branch 'origin/dev' into sidebar-pagination [skip ci]

parents 06a64219 774defd1
...@@ -131,3 +131,59 @@ export const synthesizeOpenAISpeech = async ( ...@@ -131,3 +131,59 @@ export const synthesizeOpenAISpeech = async (
return res; return res;
}; };
export const getModels = async (token: string = '') => {
let error = null;
const res = await fetch(`${AUDIO_API_BASE_URL}/models`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getVoices = async (token: string = '') => {
let error = null;
const res = await fetch(`${AUDIO_API_BASE_URL}/voices`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
};
<script lang="ts"> <script lang="ts">
import { getAudioConfig, updateAudioConfig } from '$lib/apis/audio';
import { user, settings, config } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import Switch from '$lib/components/common/Switch.svelte'; import { createEventDispatcher, onMount, getContext } from 'svelte';
const dispatch = createEventDispatcher();
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
import {
getAudioConfig,
updateAudioConfig,
getModels as _getModels,
getVoices as _getVoices
} from '$lib/apis/audio';
import { user, settings, config } from '$lib/stores';
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte'; import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -30,30 +36,41 @@ ...@@ -30,30 +36,41 @@
let models = []; let models = [];
let nonLocalVoices = false; let nonLocalVoices = false;
const getOpenAIVoices = () => { const getModels = async () => {
voices = [ if (TTS_ENGINE === '') {
{ name: 'alloy' }, models = [];
{ name: 'echo' }, } else {
{ name: 'fable' }, const res = await _getModels(localStorage.token).catch((e) => {
{ name: 'onyx' }, toast.error(e);
{ name: 'nova' }, });
{ name: 'shimmer' }
];
};
const getOpenAIModels = () => { if (res) {
models = [{ name: 'tts-1' }, { name: 'tts-1-hd' }]; console.log(res);
models = res.models;
}
}
}; };
const getWebAPIVoices = () => { const getVoices = async () => {
const getVoicesLoop = setInterval(async () => { if (TTS_ENGINE === '') {
voices = await speechSynthesis.getVoices(); const getVoicesLoop = setInterval(async () => {
voices = await speechSynthesis.getVoices();
// do your loop
if (voices.length > 0) {
clearInterval(getVoicesLoop);
}
}, 100);
} else {
const res = await _getVoices(localStorage.token).catch((e) => {
toast.error(e);
});
// do your loop if (res) {
if (voices.length > 0) { console.log(res);
clearInterval(getVoicesLoop); voices = res.voices;
} }
}, 100); }
}; };
const updateConfigHandler = async () => { const updateConfigHandler = async () => {
...@@ -101,12 +118,8 @@ ...@@ -101,12 +118,8 @@
STT_MODEL = res.stt.MODEL; STT_MODEL = res.stt.MODEL;
} }
if (TTS_ENGINE === 'openai') { await getVoices();
getOpenAIVoices(); await getModels();
getOpenAIModels();
} else {
getWebAPIVoices();
}
}); });
</script> </script>
...@@ -185,13 +198,15 @@ ...@@ -185,13 +198,15 @@
class=" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right" class=" dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={TTS_ENGINE} bind:value={TTS_ENGINE}
placeholder="Select a mode" placeholder="Select a mode"
on:change={(e) => { on:change={async (e) => {
await updateConfigHandler();
await getVoices();
await getModels();
if (e.target.value === 'openai') { if (e.target.value === 'openai') {
getOpenAIVoices();
TTS_VOICE = 'alloy'; TTS_VOICE = 'alloy';
TTS_MODEL = 'tts-1'; TTS_MODEL = 'tts-1';
} else { } else {
getWebAPIVoices();
TTS_VOICE = ''; TTS_VOICE = '';
TTS_MODEL = ''; TTS_MODEL = '';
} }
...@@ -268,7 +283,7 @@ ...@@ -268,7 +283,7 @@
<datalist id="voice-list"> <datalist id="voice-list">
{#each voices as voice} {#each voices as voice}
<option value={voice.name} /> <option value={voice.id}>{voice.name}</option>
{/each} {/each}
</datalist> </datalist>
</div> </div>
...@@ -279,15 +294,15 @@ ...@@ -279,15 +294,15 @@
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1">
<input <input
list="model-list" list="tts-model-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_MODEL} bind:value={TTS_MODEL}
placeholder="Select a model" placeholder="Select a model"
/> />
<datalist id="model-list"> <datalist id="tts-model-list">
{#each models as model} {#each models as model}
<option value={model.name} /> <option value={model.id} />
{/each} {/each}
</datalist> </datalist>
</div> </div>
...@@ -309,7 +324,7 @@ ...@@ -309,7 +324,7 @@
<datalist id="voice-list"> <datalist id="voice-list">
{#each voices as voice} {#each voices as voice}
<option value={voice.name} /> <option value={voice.id}>{voice.name}</option>
{/each} {/each}
</datalist> </datalist>
</div> </div>
...@@ -320,15 +335,15 @@ ...@@ -320,15 +335,15 @@
<div class="flex w-full"> <div class="flex w-full">
<div class="flex-1"> <div class="flex-1">
<input <input
list="model-list" list="tts-model-list"
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={TTS_MODEL} bind:value={TTS_MODEL}
placeholder="Select a model" placeholder="Select a model"
/> />
<datalist id="model-list"> <datalist id="tts-model-list">
{#each models as model} {#each models as model}
<option value={model.name} /> <option value={model.id} />
{/each} {/each}
</datalist> </datalist>
</div> </div>
......
...@@ -111,7 +111,6 @@ ...@@ -111,7 +111,6 @@
}; };
let params = {}; let params = {};
let valves = {};
$: if (history.currentId !== null) { $: if (history.currentId !== null) {
let _messages = []; let _messages = [];
...@@ -285,6 +284,10 @@ ...@@ -285,6 +284,10 @@
if ($page.url.searchParams.get('q')) { if ($page.url.searchParams.get('q')) {
prompt = $page.url.searchParams.get('q') ?? ''; prompt = $page.url.searchParams.get('q') ?? '';
selectedToolIds = ($page.url.searchParams.get('tool_ids') ?? '')
.split(',')
.map((id) => id.trim())
.filter((id) => id);
if (prompt) { if (prompt) {
await tick(); await tick();
...@@ -821,7 +824,6 @@ ...@@ -821,7 +824,6 @@
keep_alive: $settings.keepAlive ?? undefined, keep_alive: $settings.keepAlive ?? undefined,
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined, tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
files: files.length > 0 ? files : undefined, files: files.length > 0 ? files : undefined,
...(Object.keys(valves).length ? { valves } : {}),
session_id: $socket?.id, session_id: $socket?.id,
chat_id: $chatId, chat_id: $chatId,
id: responseMessageId id: responseMessageId
...@@ -1123,7 +1125,6 @@ ...@@ -1123,7 +1125,6 @@
max_tokens: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined, max_tokens: params?.max_tokens ?? $settings?.params?.max_tokens ?? undefined,
tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined, tool_ids: selectedToolIds.length > 0 ? selectedToolIds : undefined,
files: files.length > 0 ? files : undefined, files: files.length > 0 ? files : undefined,
...(Object.keys(valves).length ? { valves } : {}),
session_id: $socket?.id, session_id: $socket?.id,
chat_id: $chatId, chat_id: $chatId,
id: responseMessageId id: responseMessageId
...@@ -1654,7 +1655,6 @@ ...@@ -1654,7 +1655,6 @@
bind:show={showControls} bind:show={showControls}
bind:chatFiles bind:chatFiles
bind:params bind:params
bind:valves
/> />
</div> </div>
{/if} {/if}
...@@ -9,9 +9,7 @@ ...@@ -9,9 +9,7 @@
export let models = []; export let models = [];
export let chatId = null; export let chatId = null;
export let chatFiles = []; export let chatFiles = [];
export let valves = {};
export let params = {}; export let params = {};
let largeScreen = false; let largeScreen = false;
...@@ -50,7 +48,6 @@ ...@@ -50,7 +48,6 @@
}} }}
{models} {models}
bind:chatFiles bind:chatFiles
bind:valves
bind:params bind:params
/> />
</div> </div>
...@@ -66,7 +63,6 @@ ...@@ -66,7 +63,6 @@
}} }}
{models} {models}
bind:chatFiles bind:chatFiles
bind:valves
bind:params bind:params
/> />
</div> </div>
......
...@@ -5,14 +5,13 @@ ...@@ -5,14 +5,13 @@
import XMark from '$lib/components/icons/XMark.svelte'; import XMark from '$lib/components/icons/XMark.svelte';
import AdvancedParams from '../Settings/Advanced/AdvancedParams.svelte'; import AdvancedParams from '../Settings/Advanced/AdvancedParams.svelte';
import Valves from '$lib/components/common/Valves.svelte'; import Valves from '$lib/components/chat/Controls/Valves.svelte';
import FileItem from '$lib/components/common/FileItem.svelte'; import FileItem from '$lib/components/common/FileItem.svelte';
import Collapsible from '$lib/components/common/Collapsible.svelte'; import Collapsible from '$lib/components/common/Collapsible.svelte';
export let models = []; export let models = [];
export let chatFiles = []; export let chatFiles = [];
export let valves = {};
export let params = {}; export let params = {};
</script> </script>
...@@ -39,6 +38,7 @@ ...@@ -39,6 +38,7 @@
url={`${file?.url}`} url={`${file?.url}`}
name={file.name} name={file.name}
type={file.type} type={file.type}
size={file?.size}
dismissible={true} dismissible={true}
on:dismiss={() => { on:dismiss={() => {
// Remove the file from the chatFiles array // Remove the file from the chatFiles array
...@@ -54,17 +54,13 @@ ...@@ -54,17 +54,13 @@
<hr class="my-2 border-gray-100 dark:border-gray-800" /> <hr class="my-2 border-gray-100 dark:border-gray-800" />
{/if} {/if}
{#if models.length === 1 && models[0]?.pipe?.valves_spec} <Collapsible title={$i18n.t('Valves')}>
<div> <div class="text-sm mt-1.5" slot="content">
<div class=" font-medium">{$i18n.t('Valves')}</div> <Valves />
<div>
<Valves valvesSpec={models[0]?.pipe?.valves_spec} bind:valves />
</div>
</div> </div>
</Collapsible>
<hr class="my-2 border-gray-100 dark:border-gray-800" /> <hr class="my-2 border-gray-100 dark:border-gray-800" />
{/if}
<Collapsible title={$i18n.t('System Prompt')} open={true}> <Collapsible title={$i18n.t('System Prompt')} open={true}>
<div class=" mt-1.5" slot="content"> <div class=" mt-1.5" slot="content">
......
...@@ -15,18 +15,14 @@ ...@@ -15,18 +15,14 @@
updateUserValvesById as updateFunctionUserValvesById updateUserValvesById as updateFunctionUserValvesById
} from '$lib/apis/functions'; } from '$lib/apis/functions';
import ManageModal from './Personalization/ManageModal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte'; import Tooltip from '$lib/components/common/Tooltip.svelte';
import Spinner from '$lib/components/common/Spinner.svelte'; import Spinner from '$lib/components/common/Spinner.svelte';
import Switch from '$lib/components/common/Switch.svelte';
import Valves from '$lib/components/common/Valves.svelte'; import Valves from '$lib/components/common/Valves.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const i18n = getContext('i18n'); const i18n = getContext('i18n');
export let saveSettings: Function;
let tab = 'tools'; let tab = 'tools';
let selectedId = ''; let selectedId = '';
...@@ -35,6 +31,19 @@ ...@@ -35,6 +31,19 @@
let valvesSpec = null; let valvesSpec = null;
let valves = {}; let valves = {};
let debounceTimer;
const debounceSubmitHandler = async () => {
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// Set a new timer
debounceTimer = setTimeout(() => {
submitHandler();
}, 500); // 0.5 second debounce
};
const getUserValves = async () => { const getUserValves = async () => {
loading = true; loading = true;
if (tab === 'tools') { if (tab === 'tools') {
...@@ -112,53 +121,45 @@ ...@@ -112,53 +121,45 @@
dispatch('save'); dispatch('save');
}} }}
> >
<div class="flex flex-col pr-1.5 overflow-y-scroll max-h-[25rem]"> <div class="flex flex-col">
<div> <div class="space-y-1">
<div class="flex items-center justify-between mb-2"> <div class="flex gap-2">
<Tooltip content=""> <div class="flex-1">
<div class="text-sm font-medium">
{$i18n.t('Manage Valves')}
</div>
</Tooltip>
<div class=" self-end">
<select <select
class=" dark:bg-gray-900 w-fit pr-8 rounded text-xs bg-transparent outline-none text-right" class=" w-full rounded text-xs py-2 px-1 bg-transparent outline-none"
bind:value={tab} bind:value={tab}
placeholder="Select" placeholder="Select"
> >
<option value="tools">{$i18n.t('Tools')}</option> <option value="tools" class="bg-gray-100 dark:bg-gray-800">{$i18n.t('Tools')}</option>
<option value="functions">{$i18n.t('Functions')}</option> <option value="functions" class="bg-gray-100 dark:bg-gray-800"
>{$i18n.t('Functions')}</option
>
</select> </select>
</div> </div>
</div>
</div>
<div class="space-y-1">
<div class="flex gap-2">
<div class="flex-1"> <div class="flex-1">
<select <select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" class="w-full rounded py-2 px-1 text-xs bg-transparent outline-none"
bind:value={selectedId} bind:value={selectedId}
on:change={async () => { on:change={async () => {
await tick(); await tick();
}} }}
> >
{#if tab === 'tools'} {#if tab === 'tools'}
<option value="" selected disabled class="bg-gray-100 dark:bg-gray-700" <option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
>{$i18n.t('Select a tool')}</option >{$i18n.t('Select a tool')}</option
> >
{#each $tools as tool, toolIdx} {#each $tools as tool, toolIdx}
<option value={tool.id} class="bg-gray-100 dark:bg-gray-700">{tool.name}</option> <option value={tool.id} class="bg-gray-100 dark:bg-gray-800">{tool.name}</option>
{/each} {/each}
{:else if tab === 'functions'} {:else if tab === 'functions'}
<option value="" selected disabled class="bg-gray-100 dark:bg-gray-700" <option value="" selected disabled class="bg-gray-100 dark:bg-gray-800"
>{$i18n.t('Select a function')}</option >{$i18n.t('Select a function')}</option
> >
{#each $functions as func, funcIdx} {#each $functions as func, funcIdx}
<option value={func.id} class="bg-gray-100 dark:bg-700">{func.name}</option> <option value={func.id} class="bg-gray-100 dark:bg-gray-800">{func.name}</option>
{/each} {/each}
{/if} {/if}
</select> </select>
...@@ -167,24 +168,21 @@ ...@@ -167,24 +168,21 @@
</div> </div>
{#if selectedId} {#if selectedId}
<hr class="dark:border-gray-800 my-3 w-full" /> <hr class="dark:border-gray-800 my-1 w-full" />
<div> <div class="my-2 text-xs">
{#if !loading} {#if !loading}
<Valves {valvesSpec} bind:valves /> <Valves
{valvesSpec}
bind:valves
on:change={() => {
debounceSubmitHandler();
}}
/>
{:else} {:else}
<Spinner className="size-5" /> <Spinner className="size-5" />
{/if} {/if}
</div> </div>
{/if} {/if}
</div> </div>
<div class="flex justify-end text-sm font-medium">
<button
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit"
>
{$i18n.t('Save')}
</button>
</div>
</form> </form>
...@@ -98,6 +98,7 @@ ...@@ -98,6 +98,7 @@
const uploadFileHandler = async (file) => { const uploadFileHandler = async (file) => {
console.log(file); console.log(file);
// Check if the file is an audio file and transcribe/convert it to text file // Check if the file is an audio file and transcribe/convert it to text file
if (['audio/mpeg', 'audio/wav'].includes(file['type'])) { if (['audio/mpeg', 'audio/wav'].includes(file['type'])) {
const res = await transcribeAudio(localStorage.token, file).catch((error) => { const res = await transcribeAudio(localStorage.token, file).catch((error) => {
...@@ -112,40 +113,49 @@ ...@@ -112,40 +113,49 @@
} }
} }
// Upload the file to the server const fileItem = {
const uploadedFile = await uploadFile(localStorage.token, file).catch((error) => { type: 'file',
toast.error(error); file: '',
return null; id: null,
}); url: '',
name: file.name,
if (uploadedFile) { collection_name: '',
const fileItem = { status: '',
type: 'file', size: file.size,
file: uploadedFile, error: ''
id: uploadedFile.id, };
url: `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`, files = [...files, fileItem];
name: file.name,
collection_name: '', try {
status: 'uploaded', const uploadedFile = await uploadFile(localStorage.token, file);
error: ''
}; if (uploadedFile) {
files = [...files, fileItem]; fileItem.status = 'uploaded';
fileItem.file = uploadedFile;
// TODO: Check if tools & functions have files support to skip this step to delegate file processing fileItem.id = uploadedFile.id;
// Default Upload to VectorDB fileItem.url = `${WEBUI_API_BASE_URL}/files/${uploadedFile.id}`;
if (
SUPPORTED_FILE_TYPE.includes(file['type']) || // TODO: Check if tools & functions have files support to skip this step to delegate file processing
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1)) // Default Upload to VectorDB
) { if (
processFileItem(fileItem); SUPPORTED_FILE_TYPE.includes(file['type']) ||
SUPPORTED_FILE_EXTENSIONS.includes(file.name.split('.').at(-1))
) {
processFileItem(fileItem);
} else {
toast.error(
$i18n.t(`Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.`, {
file_type: file['type']
})
);
processFileItem(fileItem);
}
} else { } else {
toast.error( files = files.filter((item) => item.status !== null);
$i18n.t(`Unknown file type '{{file_type}}'. Proceeding with the file upload anyway.`, {
file_type: file['type']
})
);
processFileItem(fileItem);
} }
} catch (e) {
toast.error(e);
files = files.filter((item) => item.status !== null);
} }
}; };
...@@ -162,7 +172,6 @@ ...@@ -162,7 +172,6 @@
// Remove the failed doc from the files array // Remove the failed doc from the files array
// files = files.filter((f) => f.id !== fileItem.id); // files = files.filter((f) => f.id !== fileItem.id);
toast.error(e); toast.error(e);
fileItem.status = 'processed'; fileItem.status = 'processed';
files = files; files = files;
} }
...@@ -545,6 +554,7 @@ ...@@ -545,6 +554,7 @@
<FileItem <FileItem
name={file.name} name={file.name}
type={file.type} type={file.type}
size={file?.size}
status={file.status} status={file.status}
dismissible={true} dismissible={true}
on:dismiss={() => { on:dismiss={() => {
......
...@@ -253,7 +253,9 @@ ...@@ -253,7 +253,9 @@
for (const [idx, sentence] of sentences.entries()) { for (const [idx, sentence] of sentences.entries()) {
const res = await synthesizeOpenAISpeech( const res = await synthesizeOpenAISpeech(
localStorage.token, localStorage.token,
$settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice, $settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice
? $settings?.audio?.tts?.voice ?? $config?.audio?.tts?.voice
: $config?.audio?.tts?.voice,
sentence sentence
).catch((error) => { ).catch((error) => {
toast.error(error); toast.error(error);
......
...@@ -104,6 +104,7 @@ ...@@ -104,6 +104,7 @@
url={file.url} url={file.url}
name={file.name} name={file.name}
type={file.type} type={file.type}
size={file?.size}
colorClassName="bg-white dark:bg-gray-850 " colorClassName="bg-white dark:bg-gray-850 "
/> />
{/if} {/if}
......
<script lang="ts"> <script lang="ts">
import { DropdownMenu } from 'bits-ui'; import { DropdownMenu } from 'bits-ui';
import { marked } from 'marked'; import { marked } from 'marked';
import Fuse from 'fuse.js';
import { flyAndScale } from '$lib/utils/transitions'; import { flyAndScale } from '$lib/utils/transitions';
import { createEventDispatcher, onMount, getContext, tick } from 'svelte'; import { createEventDispatcher, onMount, getContext, tick } from 'svelte';
...@@ -45,17 +46,29 @@ ...@@ -45,17 +46,29 @@
let selectedModelIdx = 0; let selectedModelIdx = 0;
$: filteredItems = items.filter( const fuse = new Fuse(
(item) => items
(searchValue .filter((item) => !item.model?.info?.meta?.hidden)
? item.value.toLowerCase().includes(searchValue.toLowerCase()) || .map((item) => {
item.label.toLowerCase().includes(searchValue.toLowerCase()) || const _item = {
(item.model?.info?.meta?.tags ?? []).some((tag) => ...item,
tag.name.toLowerCase().includes(searchValue.toLowerCase()) modelName: item.model?.name,
) tags: item.model?.info?.meta?.tags?.map((tag) => tag.name).join(' '),
: true) && !(item.model?.info?.meta?.hidden ?? false) desc: item.model?.info?.meta?.description
};
return _item;
}),
{
keys: ['value', 'label', 'tags', 'desc', 'modelName']
}
); );
$: filteredItems = searchValue
? fuse.search(searchValue).map((e) => {
return e.item;
})
: items.filter((item) => !item.model?.info?.meta?.hidden);
const pullModelHandler = async () => { const pullModelHandler = async () => {
const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, ''); const sanitizedModelTag = searchValue.trim().replace(/^ollama\s+(run|pull)\s+/, '');
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
mirostat_tau: null, mirostat_tau: null,
top_k: null, top_k: null,
top_p: null, top_p: null,
min_p: null,
tfs_z: null, tfs_z: null,
num_ctx: null, num_ctx: null,
num_batch: null, num_batch: null,
...@@ -385,6 +386,52 @@ ...@@ -385,6 +386,52 @@
{/if} {/if}
</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('Min P')}</div>
<button
class="p-1 px-3 text-xs flex rounded transition flex-shrink-0 outline-none"
type="button"
on:click={() => {
params.min_p = (params?.min_p ?? null) === null ? 0.0 : null;
}}
>
{#if (params?.min_p ?? 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?.min_p ?? null) !== null}
<div class="flex mt-0.5 space-x-2">
<div class=" flex-1">
<input
id="steps-range"
type="range"
min="0"
max="1"
step="0.05"
bind:value={params.min_p}
class="w-full h-2 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
/>
</div>
<div>
<input
bind:value={params.min_p}
type="number"
class=" bg-transparent text-center w-14"
min="0"
max="1"
step="any"
/>
</div>
</div>
{/if}
</div>
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Frequency Penalty')}</div> <div class=" self-center text-xs font-medium">{$i18n.t('Frequency Penalty')}</div>
......
<script lang="ts"> <script lang="ts">
import { user, settings, config } from '$lib/stores';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { createEventDispatcher, onMount, getContext } from 'svelte';
import { user, settings, config } from '$lib/stores';
import { getVoices as _getVoices } from '$lib/apis/audio';
import Switch from '$lib/components/common/Switch.svelte'; import Switch from '$lib/components/common/Switch.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
...@@ -20,26 +23,26 @@ ...@@ -20,26 +23,26 @@
let voices = []; let voices = [];
let voice = ''; let voice = '';
const getOpenAIVoices = () => { const getVoices = async () => {
voices = [ if ($config.audio.tts.engine === '') {
{ name: 'alloy' }, const getVoicesLoop = setInterval(async () => {
{ name: 'echo' }, voices = await speechSynthesis.getVoices();
{ name: 'fable' },
{ name: 'onyx' },
{ name: 'nova' },
{ name: 'shimmer' }
];
};
const getWebAPIVoices = () => { // do your loop
const getVoicesLoop = setInterval(async () => { if (voices.length > 0) {
voices = await speechSynthesis.getVoices(); clearInterval(getVoicesLoop);
}
}, 100);
} else {
const res = await _getVoices(localStorage.token).catch((e) => {
toast.error(e);
});
// do your loop if (res) {
if (voices.length > 0) { console.log(res);
clearInterval(getVoicesLoop); voices = res.voices;
} }
}, 100); }
}; };
const toggleResponseAutoPlayback = async () => { const toggleResponseAutoPlayback = async () => {
...@@ -58,14 +61,16 @@ ...@@ -58,14 +61,16 @@
responseAutoPlayback = $settings.responseAutoPlayback ?? false; responseAutoPlayback = $settings.responseAutoPlayback ?? false;
STTEngine = $settings?.audio?.stt?.engine ?? ''; STTEngine = $settings?.audio?.stt?.engine ?? '';
voice = $settings?.audio?.tts?.voice ?? $config.audio.tts.voice ?? '';
nonLocalVoices = $settings.audio?.tts?.nonLocalVoices ?? false;
if ($config.audio.tts.engine === 'openai') { if ($settings?.audio?.tts?.defaultVoice === $config.audio.tts.voice) {
getOpenAIVoices(); voice = $settings?.audio?.tts?.voice ?? $config.audio.tts.voice ?? '';
} else { } else {
getWebAPIVoices(); voice = $config.audio.tts.voice ?? '';
} }
nonLocalVoices = $settings.audio?.tts?.nonLocalVoices ?? false;
await getVoices();
}); });
</script> </script>
...@@ -79,6 +84,7 @@ ...@@ -79,6 +84,7 @@
}, },
tts: { tts: {
voice: voice !== '' ? voice : undefined, voice: voice !== '' ? voice : undefined,
defaultVoice: $config?.audio?.tts?.voice ?? '',
nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined nonLocalVoices: $config.audio.tts.engine === '' ? nonLocalVoices : undefined
} }
} }
...@@ -195,7 +201,7 @@ ...@@ -195,7 +201,7 @@
<datalist id="voice-list"> <datalist id="voice-list">
{#each voices as voice} {#each voices as voice}
<option value={voice.name} /> <option value={voice.id}>{voice.name}</option>
{/each} {/each}
</datalist> </datalist>
</div> </div>
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
import Chats from './Settings/Chats.svelte'; import Chats from './Settings/Chats.svelte';
import User from '../icons/User.svelte'; import User from '../icons/User.svelte';
import Personalization from './Settings/Personalization.svelte'; import Personalization from './Settings/Personalization.svelte';
import Valves from './Settings/Valves.svelte';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -188,30 +187,6 @@ ...@@ -188,30 +187,6 @@
<div class=" self-center">{$i18n.t('Audio')}</div> <div class=" self-center">{$i18n.t('Audio')}</div>
</button> </button>
<button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'valves'
? 'bg-gray-200 dark:bg-gray-800'
: ' hover:bg-gray-100 dark:hover:bg-gray-850'}"
on:click={() => {
selectedTab = 'valves';
}}
>
<div class=" self-center mr-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="size-4"
>
<path
d="M18.75 12.75h1.5a.75.75 0 0 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM12 6a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 6ZM12 18a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 12 18ZM3.75 6.75h1.5a.75.75 0 1 0 0-1.5h-1.5a.75.75 0 0 0 0 1.5ZM5.25 18.75h-1.5a.75.75 0 0 1 0-1.5h1.5a.75.75 0 0 1 0 1.5ZM3 12a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 0 1.5h-7.5A.75.75 0 0 1 3 12ZM9 3.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5ZM12.75 12a2.25 2.25 0 1 1 4.5 0 2.25 2.25 0 0 1-4.5 0ZM9 15.75a2.25 2.25 0 1 0 0 4.5 2.25 2.25 0 0 0 0-4.5Z"
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Valves')}</div>
</button>
<button <button
class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab === class="px-2.5 py-2.5 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'chats' 'chats'
...@@ -349,13 +324,6 @@ ...@@ -349,13 +324,6 @@
toast.success($i18n.t('Settings saved successfully!')); toast.success($i18n.t('Settings saved successfully!'));
}} }}
/> />
{:else if selectedTab === 'valves'}
<Valves
{saveSettings}
on:save={() => {
toast.success($i18n.t('Settings saved successfully!'));
}}
/>
{:else if selectedTab === 'chats'} {:else if selectedTab === 'chats'}
<Chats {saveSettings} /> <Chats {saveSettings} />
{:else if selectedTab === 'account'} {:else if selectedTab === 'account'}
......
...@@ -15,6 +15,21 @@ ...@@ -15,6 +15,21 @@
export let name: string; export let name: string;
export let type: string; export let type: string;
export let size: number;
function formatSize(size) {
if (size == null) return 'Unknown size';
if (typeof size !== 'number' || size < 0) return 'Invalid size';
if (size === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
</script> </script>
<div class="relative group"> <div class="relative group">
...@@ -93,11 +108,11 @@ ...@@ -93,11 +108,11 @@
</div> </div>
<div class="flex flex-col justify-center -space-y-0.5 pl-1.5 pr-4 w-full"> <div class="flex flex-col justify-center -space-y-0.5 pl-1.5 pr-4 w-full">
<div class=" dark:text-gray-100 text-sm font-medium line-clamp-1"> <div class=" dark:text-gray-100 text-sm font-medium line-clamp-1 mb-1">
{name} {name}
</div> </div>
<div class=" text-gray-500 text-xs"> <div class=" flex justify-between text-gray-500 text-xs">
{#if type === 'file'} {#if type === 'file'}
{$i18n.t('File')} {$i18n.t('File')}
{:else if type === 'doc'} {:else if type === 'doc'}
...@@ -107,6 +122,9 @@ ...@@ -107,6 +122,9 @@
{:else} {:else}
<span class=" capitalize">{type}</span> <span class=" capitalize">{type}</span>
{/if} {/if}
{#if size}
<span class="capitalize">{formatSize(size)}</span>
{/if}
</div> </div>
</div> </div>
</button> </button>
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
let mounted = false; let mounted = false;
let previewElement = null;
const downloadImage = (url, filename) => { const downloadImage = (url, filename) => {
fetch(url) fetch(url)
.then((response) => response.blob()) .then((response) => response.blob())
...@@ -34,14 +36,14 @@ ...@@ -34,14 +36,14 @@
mounted = true; mounted = true;
}); });
$: if (mounted) { $: if (show && previewElement) {
if (show) { document.body.appendChild(previewElement);
window.addEventListener('keydown', handleKeyDown); window.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} else { } else if (previewElement) {
window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'unset'; document.body.removeChild(previewElement);
} document.body.style.overflow = 'unset';
} }
</script> </script>
...@@ -49,7 +51,8 @@ ...@@ -49,7 +51,8 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions --> <!-- svelte-ignore a11y-no-static-element-interactions -->
<div <div
class="fixed top-0 right-0 left-0 bottom-0 bg-black text-white w-full min-h-screen h-screen flex justify-center z-50 overflow-hidden overscroll-contain" bind:this={previewElement}
class="modal fixed top-0 right-0 left-0 bottom-0 bg-black text-white w-full min-h-screen h-screen flex justify-center z-[9999] overflow-hidden overscroll-contain"
> >
<div class=" absolute left-0 w-full flex justify-between"> <div class=" absolute left-0 w-full flex justify-between">
<div> <div>
......
...@@ -13,13 +13,13 @@ ...@@ -13,13 +13,13 @@
<div class={outerClassName}> <div class={outerClassName}>
<input <input
class={inputClassName} class={`${inputClassName} ${show ? '' : 'password'}`}
{placeholder} {placeholder}
bind:value bind:value
required={required && !readOnly} required={required && !readOnly}
disabled={readOnly} disabled={readOnly}
autocomplete="off" autocomplete="off"
{...{ type: show ? 'text' : 'password' }} type="text"
/> />
<button <button
class={showButtonClassName} class={showButtonClassName}
......
<script> <script>
import { onMount, getContext } from 'svelte'; import { onMount, getContext, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n'); const i18n = getContext('i18n');
import Switch from './Switch.svelte'; import Switch from './Switch.svelte';
...@@ -8,7 +9,7 @@ ...@@ -8,7 +9,7 @@
export let valves = {}; export let valves = {};
</script> </script>
{#if valvesSpec} {#if valvesSpec && Object.keys(valvesSpec?.properties ?? {}).length}
{#each Object.keys(valvesSpec.properties) as property, idx} {#each Object.keys(valvesSpec.properties) as property, idx}
<div class=" py-0.5 w-full justify-between"> <div class=" py-0.5 w-full justify-between">
<div class="flex w-full justify-between"> <div class="flex w-full justify-between">
...@@ -28,6 +29,8 @@ ...@@ -28,6 +29,8 @@
(valves[property] ?? null) === null (valves[property] ?? null) === null
? valvesSpec.properties[property]?.default ?? '' ? valvesSpec.properties[property]?.default ?? ''
: null; : null;
dispatch('change');
}} }}
> >
{#if (valves[property] ?? null) === null} {#if (valves[property] ?? null) === null}
...@@ -52,6 +55,9 @@ ...@@ -52,6 +55,9 @@
<select <select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none border border-gray-100 dark:border-gray-800" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none border border-gray-100 dark:border-gray-800"
bind:value={valves[property]} bind:value={valves[property]}
on:change={() => {
dispatch('change');
}}
> >
{#each valvesSpec.properties[property].enum as option} {#each valvesSpec.properties[property].enum as option}
<option value={option} selected={option === valves[property]}> <option value={option} selected={option === valves[property]}>
...@@ -66,7 +72,12 @@ ...@@ -66,7 +72,12 @@
</div> </div>
<div class=" pr-2"> <div class=" pr-2">
<Switch bind:state={valves[property]} /> <Switch
bind:state={valves[property]}
on:change={() => {
dispatch('change');
}}
/>
</div> </div>
</div> </div>
{:else} {:else}
...@@ -77,6 +88,9 @@ ...@@ -77,6 +88,9 @@
bind:value={valves[property]} bind:value={valves[property]}
autocomplete="off" autocomplete="off"
required required
on:change={() => {
dispatch('change');
}}
/> />
{/if} {/if}
</div> </div>
...@@ -91,5 +105,5 @@ ...@@ -91,5 +105,5 @@
</div> </div>
{/each} {/each}
{:else} {:else}
<div class="text-sm">No valves</div> <div class="text-xs">No valves</div>
{/if} {/if}
...@@ -364,7 +364,6 @@ ...@@ -364,7 +364,6 @@
"Manage Models": "إدارة النماذج", "Manage Models": "إدارة النماذج",
"Manage Ollama Models": "Ollama إدارة موديلات ", "Manage Ollama Models": "Ollama إدارة موديلات ",
"Manage Pipelines": "إدارة خطوط الأنابيب", "Manage Pipelines": "إدارة خطوط الأنابيب",
"Manage Valves": "",
"March": "مارس", "March": "مارس",
"Max Tokens (num_predict)": "ماكس توكنز (num_predict)", "Max Tokens (num_predict)": "ماكس توكنز (num_predict)",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "يمكن تنزيل 3 نماذج كحد أقصى في وقت واحد. الرجاء معاودة المحاولة في وقت لاحق.",
...@@ -376,6 +375,7 @@ ...@@ -376,6 +375,7 @@
"Memory deleted successfully": "", "Memory deleted successfully": "",
"Memory updated successfully": "", "Memory updated successfully": "",
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "لن تتم مشاركة الرسائل التي ترسلها بعد إنشاء الرابط الخاص بك. سيتمكن المستخدمون الذين لديهم عنوان URL من عرض الدردشة المشتركة",
"Min P": "",
"Minimum Score": "الحد الأدنى من النقاط", "Minimum Score": "الحد الأدنى من النقاط",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta", "Mirostat Eta": "Mirostat Eta",
......
...@@ -364,7 +364,6 @@ ...@@ -364,7 +364,6 @@
"Manage Models": "Управление на Моделите", "Manage Models": "Управление на Моделите",
"Manage Ollama Models": "Управление на Ollama Моделите", "Manage Ollama Models": "Управление на Ollama Моделите",
"Manage Pipelines": "Управление на тръбопроводи", "Manage Pipelines": "Управление на тръбопроводи",
"Manage Valves": "",
"March": "Март", "March": "Март",
"Max Tokens (num_predict)": "Макс токени (num_predict)", "Max Tokens (num_predict)": "Макс токени (num_predict)",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "Максимум 3 модели могат да бъдат сваляни едновременно. Моля, опитайте отново по-късно.",
...@@ -376,6 +375,7 @@ ...@@ -376,6 +375,7 @@
"Memory deleted successfully": "", "Memory deleted successfully": "",
"Memory updated successfully": "", "Memory updated successfully": "",
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Съобщенията, които изпращате след създаването на връзката, няма да бъдат споделяни. Потребителите с URL адреса ще могат да видят споделения чат.", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "Съобщенията, които изпращате след създаването на връзката, няма да бъдат споделяни. Потребителите с URL адреса ще могат да видят споделения чат.",
"Min P": "",
"Minimum Score": "Минимална оценка", "Minimum Score": "Минимална оценка",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta", "Mirostat Eta": "Mirostat Eta",
......
...@@ -364,7 +364,6 @@ ...@@ -364,7 +364,6 @@
"Manage Models": "মডেলসমূহ ব্যবস্থাপনা করুন", "Manage Models": "মডেলসমূহ ব্যবস্থাপনা করুন",
"Manage Ollama Models": "Ollama মডেলসূহ ব্যবস্থাপনা করুন", "Manage Ollama Models": "Ollama মডেলসূহ ব্যবস্থাপনা করুন",
"Manage Pipelines": "পাইপলাইন পরিচালনা করুন", "Manage Pipelines": "পাইপলাইন পরিচালনা করুন",
"Manage Valves": "",
"March": "মার্চ", "March": "মার্চ",
"Max Tokens (num_predict)": "সর্বোচ্চ টোকেন (num_predict)", "Max Tokens (num_predict)": "সর্বোচ্চ টোকেন (num_predict)",
"Maximum of 3 models can be downloaded simultaneously. Please try again later.": "একসঙ্গে সর্বোচ্চ তিনটি মডেল ডাউনলোড করা যায়। দয়া করে পরে আবার চেষ্টা করুন।", "Maximum of 3 models can be downloaded simultaneously. Please try again later.": "একসঙ্গে সর্বোচ্চ তিনটি মডেল ডাউনলোড করা যায়। দয়া করে পরে আবার চেষ্টা করুন।",
...@@ -376,6 +375,7 @@ ...@@ -376,6 +375,7 @@
"Memory deleted successfully": "", "Memory deleted successfully": "",
"Memory updated successfully": "", "Memory updated successfully": "",
"Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "আপনার লিঙ্ক তৈরি করার পরে আপনার পাঠানো বার্তাগুলি শেয়ার করা হবে না। ইউআরএল ব্যবহারকারীরা শেয়ার করা চ্যাট দেখতে পারবেন।", "Messages you send after creating your link won't be shared. Users with the URL will be able to view the shared chat.": "আপনার লিঙ্ক তৈরি করার পরে আপনার পাঠানো বার্তাগুলি শেয়ার করা হবে না। ইউআরএল ব্যবহারকারীরা শেয়ার করা চ্যাট দেখতে পারবেন।",
"Min P": "",
"Minimum Score": "Minimum Score", "Minimum Score": "Minimum Score",
"Mirostat": "Mirostat", "Mirostat": "Mirostat",
"Mirostat Eta": "Mirostat Eta", "Mirostat Eta": "Mirostat Eta",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment