Merge remote-tracking branch 'origin' into logit_bias

This commit is contained in:
dannyl1u
2025-02-27 23:48:22 -08:00
181 changed files with 10428 additions and 5218 deletions
@@ -131,7 +131,9 @@
</div>
</div>
<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5">
<div
class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5"
>
{#if (feedbacks ?? []).length === 0}
<div class="text-center text-xs text-gray-500 dark:text-gray-400 py-1">
{$i18n.t('No feedbacks found')}
@@ -300,7 +300,9 @@
</div>
</div>
<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5">
<div
class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5"
>
{#if loadingLeaderboard}
<div class=" absolute top-0 bottom-0 left-0 right-0 flex">
<div class="m-auto">
@@ -1,8 +1,7 @@
<script>
import { getContext, createEventDispatcher, onMount, tick } from 'svelte';
import { getContext, onMount, tick } from 'svelte';
import { goto } from '$app/navigation';
const dispatch = createEventDispatcher();
const i18n = getContext('i18n');
import CodeEditor from '$lib/components/common/CodeEditor.svelte';
@@ -15,6 +14,8 @@
let loading = false;
let showConfirm = false;
export let onSave = () => {};
export let edit = false;
export let clone = false;
@@ -256,7 +257,7 @@ class Pipe:
const saveHandler = async () => {
loading = true;
dispatch('save', {
onSave({
id,
name,
meta,
@@ -371,10 +372,10 @@ class Pipe:
value={content}
lang="python"
{boilerplate}
on:change={(e) => {
_content = e.detail.value;
onChange={(e) => {
_content = e;
}}
on:save={async () => {
onSave={async () => {
if (formElement) {
formElement.requestSubmit();
}
+6 -6
View File
@@ -19,7 +19,7 @@
import ChartBar from '../icons/ChartBar.svelte';
import DocumentChartBar from '../icons/DocumentChartBar.svelte';
import Evaluations from './Settings/Evaluations.svelte';
import CodeInterpreter from './Settings/CodeInterpreter.svelte';
import CodeExecution from './Settings/CodeExecution.svelte';
const i18n = getContext('i18n');
@@ -191,11 +191,11 @@
<button
class="px-0.5 py-1 min-w-fit rounded-lg flex-1 md:flex-none flex text-right transition {selectedTab ===
'code-interpreter'
'code-execution'
? ''
: ' text-gray-300 dark:text-gray-600 hover:text-gray-700 dark:hover:text-white'}"
on:click={() => {
selectedTab = 'code-interpreter';
selectedTab = 'code-execution';
}}
>
<div class=" self-center mr-2">
@@ -212,7 +212,7 @@
/>
</svg>
</div>
<div class=" self-center">{$i18n.t('Code Interpreter')}</div>
<div class=" self-center">{$i18n.t('Code Execution')}</div>
</button>
<button
@@ -391,8 +391,8 @@
await config.set(await getBackendConfig());
}}
/>
{:else if selectedTab === 'code-interpreter'}
<CodeInterpreter
{:else if selectedTab === 'code-execution'}
<CodeExecution
saveHandler={async () => {
toast.success($i18n.t('Settings saved successfully!'));
@@ -0,0 +1,317 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onMount, getContext } from 'svelte';
import { getCodeExecutionConfig, setCodeExecutionConfig } from '$lib/apis/configs';
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Textarea from '$lib/components/common/Textarea.svelte';
import Switch from '$lib/components/common/Switch.svelte';
const i18n = getContext('i18n');
export let saveHandler: Function;
let config = null;
let engines = ['pyodide', 'jupyter'];
const submitHandler = async () => {
const res = await setCodeExecutionConfig(localStorage.token, config);
};
onMount(async () => {
const res = await getCodeExecutionConfig(localStorage.token);
if (res) {
config = res;
}
});
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
await submitHandler();
saveHandler();
}}
>
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
{#if config}
<div>
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class="mb-2.5">
<div class="flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Code Execution Engine')}</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={config.CODE_EXECUTION_ENGINE}
placeholder={$i18n.t('Select a engine')}
required
>
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
{#each engines as engine}
<option value={engine}>{engine}</option>
{/each}
</select>
</div>
</div>
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
<div class="text-gray-500 text-xs">
{$i18n.t(
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
)}
</div>
{/if}
</div>
{#if config.CODE_EXECUTION_ENGINE === 'jupyter'}
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
<div class="text-xs font-medium">
{$i18n.t('Jupyter URL')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Enter Jupyter URL')}
bind:value={config.CODE_EXECUTION_JUPYTER_URL}
autocomplete="off"
/>
</div>
</div>
</div>
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
<div class=" flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{#if config.CODE_EXECUTION_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_EXECUTION_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_EXECUTION_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
</div>
</div>
{/if}
</div>
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Code Execution Timeout')}
</div>
<div class="">
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
<input
class="dark:bg-gray-900 w-fit rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
type="number"
bind:value={config.CODE_EXECUTION_JUPYTER_TIMEOUT}
placeholder={$i18n.t('e.g. 60')}
autocomplete="off"
/>
</Tooltip>
</div>
</div>
{/if}
</div>
<div class="mb-3.5">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Code Interpreter')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class="mb-2.5">
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Enable Code Interpreter')}
</div>
<Switch bind:state={config.ENABLE_CODE_INTERPRETER} />
</div>
</div>
{#if config.ENABLE_CODE_INTERPRETER}
<div class="mb-2.5">
<div class=" flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Code Interpreter Engine')}
</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={config.CODE_INTERPRETER_ENGINE}
placeholder={$i18n.t('Select a engine')}
required
>
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
{#each engines as engine}
<option value={engine}>{engine}</option>
{/each}
</select>
</div>
</div>
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
<div class="text-gray-500 text-xs">
{$i18n.t(
'Warning: Jupyter execution enables arbitrary code execution, posing severe security risks—proceed with extreme caution.'
)}
</div>
{/if}
</div>
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
<div class="text-xs font-medium">
{$i18n.t('Jupyter URL')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Enter Jupyter URL')}
bind:value={config.CODE_INTERPRETER_JUPYTER_URL}
autocomplete="off"
/>
</div>
</div>
</div>
<div class="mb-2.5 flex flex-col gap-1.5 w-full">
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
</div>
</div>
{/if}
</div>
<div class="flex gap-2 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Code Execution Timeout')}
</div>
<div class="">
<Tooltip content={$i18n.t('Enter timeout in seconds')}>
<input
class="dark:bg-gray-900 w-fit rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
type="number"
bind:value={config.CODE_INTERPRETER_JUPYTER_TIMEOUT}
placeholder={$i18n.t('e.g. 60')}
autocomplete="off"
/>
</Tooltip>
</div>
</div>
{/if}
<hr class="border-gray-100 dark:border-gray-850 my-2" />
<div>
<div class="py-0.5 w-full">
<div class=" mb-2.5 text-xs font-medium">
{$i18n.t('Code Interpreter Prompt Template')}
</div>
<Tooltip
content={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt'
)}
placement="top-start"
>
<Textarea
bind:value={config.CODE_INTERPRETER_PROMPT_TEMPLATE}
placeholder={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt'
)}
/>
</Tooltip>
</div>
</div>
{/if}
</div>
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
type="submit"
>
{$i18n.t('Save')}
</button>
</div>
</form>
@@ -1,166 +0,0 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onMount, getContext } from 'svelte';
import { getCodeInterpreterConfig, setCodeInterpreterConfig } from '$lib/apis/configs';
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
import Textarea from '$lib/components/common/Textarea.svelte';
import Switch from '$lib/components/common/Switch.svelte';
const i18n = getContext('i18n');
export let saveHandler: Function;
let config = null;
let engines = ['pyodide', 'jupyter'];
const submitHandler = async () => {
const res = await setCodeInterpreterConfig(localStorage.token, config);
};
onMount(async () => {
const res = await getCodeInterpreterConfig(localStorage.token);
if (res) {
config = res;
}
});
</script>
<form
class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => {
await submitHandler();
saveHandler();
}}
>
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
{#if config}
<div>
<div class=" mb-1 text-sm font-medium">
{$i18n.t('Code Interpreter')}
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Enable Code Interpreter')}
</div>
<Switch bind:state={config.ENABLE_CODE_INTERPRETER} />
</div>
</div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Code Interpreter Engine')}</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={config.CODE_INTERPRETER_ENGINE}
placeholder={$i18n.t('Select a engine')}
required
>
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
{#each engines as engine}
<option value={engine}>{engine}</option>
{/each}
</select>
</div>
</div>
{#if config.CODE_INTERPRETER_ENGINE === 'jupyter'}
<div class="mt-1 flex flex-col gap-1.5 mb-1 w-full">
<div class="text-xs font-medium">
{$i18n.t('Jupyter URL')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full text-sm py-0.5 placeholder:text-gray-300 dark:placeholder:text-gray-700 bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Enter Jupyter URL')}
bind:value={config.CODE_INTERPRETER_JUPYTER_URL}
autocomplete="off"
/>
</div>
</div>
</div>
<div class="mt-1 flex gap-2 mb-1 w-full items-center justify-between">
<div class="text-xs font-medium">
{$i18n.t('Jupyter Auth')}
</div>
<div>
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-left"
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH}
placeholder={$i18n.t('Select an auth method')}
>
<option selected value="">{$i18n.t('None')}</option>
<option value="token">{$i18n.t('Token')}</option>
<option value="password">{$i18n.t('Password')}</option>
</select>
</div>
</div>
{#if config.CODE_INTERPRETER_JUPYTER_AUTH}
<div class="flex w-full gap-2">
<div class="flex-1">
{#if config.CODE_INTERPRETER_JUPYTER_AUTH === 'password'}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Password')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_PASSWORD}
autocomplete="off"
/>
{:else}
<SensitiveInput
type="text"
placeholder={$i18n.t('Enter Jupyter Token')}
bind:value={config.CODE_INTERPRETER_JUPYTER_AUTH_TOKEN}
autocomplete="off"
/>
{/if}
</div>
</div>
{/if}
{/if}
</div>
<hr class="border-gray-100 dark:border-gray-850 my-2" />
<div>
<div class="py-0.5 w-full">
<div class=" mb-2.5 text-xs font-medium">
{$i18n.t('Code Interpreter Prompt Template')}
</div>
<Tooltip
content={$i18n.t('Leave empty to use the default prompt, or enter a custom prompt')}
placement="top-start"
>
<Textarea
bind:value={config.CODE_INTERPRETER_PROMPT_TEMPLATE}
placeholder={$i18n.t(
'Leave empty to use the default prompt, or enter a custom prompt'
)}
/>
</Tooltip>
</div>
</div>
{/if}
</div>
<div class="flex justify-end pt-3 text-sm font-medium">
<button
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
type="submit"
>
{$i18n.t('Save')}
</button>
</div>
</form>
@@ -274,6 +274,7 @@
newConfig[newIdx] = OPENAI_API_CONFIGS[newIdx < idx ? newIdx : newIdx + 1];
});
OPENAI_API_CONFIGS = newConfig;
updateOpenAIHandler();
}}
/>
{/each}
File diff suppressed because it is too large Load Diff
@@ -103,10 +103,12 @@
<div class="overflow-y-scroll scrollbar-hidden h-full">
{#if evaluationConfig !== null}
<div class="">
<div class="text-sm font-medium mb-2">{$i18n.t('General Settings')}</div>
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div class=" mb-2">
<div class="flex justify-between items-center text-xs">
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class="mb-2.5 flex w-full justify-between">
<div class=" text-xs font-medium">{$i18n.t('Arena Models')}</div>
<Tooltip content={$i18n.t(`Message rating should be enabled to use this feature`)}>
@@ -116,46 +118,50 @@
</div>
{#if evaluationConfig.ENABLE_EVALUATION_ARENA_MODELS}
<hr class=" border-gray-50 dark:border-gray-700/10 my-2" />
<div class="flex justify-between items-center mb-2">
<div class="text-sm font-medium">{$i18n.t('Manage Arena Models')}</div>
<div>
<Tooltip content={$i18n.t('Add Arena Model')}>
<button
class="p-1"
type="button"
on:click={() => {
showAddModel = true;
}}
>
<Plus />
</button>
</Tooltip>
</div>
</div>
<div class="flex flex-col gap-2">
{#if (evaluationConfig?.EVALUATION_ARENA_MODELS ?? []).length > 0}
{#each evaluationConfig.EVALUATION_ARENA_MODELS as model, index}
<Model
{model}
on:edit={(e) => {
editModelHandler(e.detail, index);
}}
on:delete={(e) => {
deleteModelHandler(index);
}}
/>
{/each}
{:else}
<div class=" text-center text-xs text-gray-500">
{$i18n.t(
`Using the default arena model with all models. Click the plus button to add custom models.`
)}
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium flex justify-between items-center">
<div>
{$i18n.t('Manage')}
</div>
{/if}
<div>
<Tooltip content={$i18n.t('Add Arena Model')}>
<button
class="p-1"
type="button"
on:click={() => {
showAddModel = true;
}}
>
<Plus />
</button>
</Tooltip>
</div>
</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class="flex flex-col gap-2">
{#if (evaluationConfig?.EVALUATION_ARENA_MODELS ?? []).length > 0}
{#each evaluationConfig.EVALUATION_ARENA_MODELS as model, index}
<Model
{model}
on:edit={(e) => {
editModelHandler(e.detail, index);
}}
on:delete={(e) => {
deleteModelHandler(index);
}}
/>
{/each}
{:else}
<div class=" text-center text-xs text-gray-500">
{$i18n.t(
`Using the default arena model with all models. Click the plus button to add custom models.`
)}
</div>
{/if}
</div>
</div>
{/if}
</div>
@@ -231,11 +231,11 @@
</a>
</div>
<button
<!-- <button
class="flex-shrink-0 text-xs px-3 py-1.5 bg-gray-50 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-800 transition rounded-lg font-medium"
>
{$i18n.t('Activate')}
</button>
</button> -->
</div>
</div>
</div>
@@ -261,6 +261,9 @@
} else if (config.engine === 'openai' && config.openai.OPENAI_API_KEY === '') {
toast.error($i18n.t('OpenAI API Key is required.'));
config.enabled = false;
} else if (config.engine === 'gemini' && config.gemini.GEMINI_API_KEY === '') {
toast.error($i18n.t('Gemini API Key is required.'));
config.enabled = false;
}
}
@@ -294,6 +297,7 @@
<option value="openai">{$i18n.t('Default (Open AI)')}</option>
<option value="comfyui">{$i18n.t('ComfyUI')}</option>
<option value="automatic1111">{$i18n.t('Automatic1111')}</option>
<option value="gemini">{$i18n.t('Gemini')}</option>
</select>
</div>
</div>
@@ -605,6 +609,24 @@
/>
</div>
</div>
{:else if config?.engine === 'gemini'}
<div>
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('Gemini API Config')}</div>
<div class="flex gap-2 mb-1">
<input
class="flex-1 w-full text-sm bg-transparent outline-none"
placeholder={$i18n.t('API Base URL')}
bind:value={config.gemini.GEMINI_API_BASE_URL}
required
/>
<SensitiveInput
placeholder={$i18n.t('API Key')}
bind:value={config.gemini.GEMINI_API_KEY}
/>
</div>
</div>
{/if}
</div>
@@ -51,7 +51,7 @@
onMount(async () => {
taskConfig = await getTaskConfig(localStorage.token);
promptSuggestions = $config?.default_prompt_suggestions;
promptSuggestions = $config?.default_prompt_suggestions ?? [];
banners = await getBanners(localStorage.token);
});
@@ -16,6 +16,8 @@
import Spinner from '$lib/components/common/Spinner.svelte';
import Minus from '$lib/components/icons/Minus.svelte';
import Plus from '$lib/components/icons/Plus.svelte';
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
export let show = false;
export let initHandler = () => {};
@@ -26,6 +28,9 @@
let defaultModelIds = [];
let modelIds = [];
let sortKey = '';
let sortOrder = '';
let loading = false;
let showResetModal = false;
@@ -71,6 +76,9 @@
// Add remaining IDs not in MODEL_ORDER_LIST, sorted alphabetically
...allModelIds.filter((id) => !orderedSet.has(id)).sort((a, b) => a.localeCompare(b))
];
sortKey = '';
sortOrder = '';
};
const submitHandler = async () => {
loading = true;
@@ -145,9 +153,45 @@
>
<div>
<div class="flex flex-col w-full">
<div class="mb-1 flex justify-between">
<button
class="mb-1 flex gap-2"
type="button"
on:click={() => {
sortKey = 'model';
if (sortOrder === 'asc') {
sortOrder = 'desc';
} else {
sortOrder = 'asc';
}
modelIds = modelIds
.filter((id) => id !== '')
.sort((a, b) => {
const nameA = $models.find((model) => model.id === a)?.name || a;
const nameB = $models.find((model) => model.id === b)?.name || b;
return sortOrder === 'desc'
? nameA.localeCompare(nameB)
: nameB.localeCompare(nameA);
});
}}
>
<div class="text-xs text-gray-500">{$i18n.t('Reorder Models')}</div>
</div>
{#if sortKey === 'model'}
<span class="font-normal self-center">
{#if sortOrder === 'asc'}
<ChevronUp className="size-3" />
{:else}
<ChevronDown className="size-3" />
{/if}
</span>
{:else}
<span class="invisible">
<ChevronUp className="size-3" />
</span>
{/if}
</button>
<ModelList bind:modelIds />
</div>
@@ -21,14 +21,24 @@
modelIds = modelList;
};
onMount(() => {
sortable = Sortable.create(modelListElement, {
animation: 150,
onUpdate: async (event) => {
positionChangeHandler();
}
});
});
$: if (modelIds) {
init();
}
const init = () => {
if (sortable) {
sortable.destroy();
}
if (modelListElement) {
sortable = Sortable.create(modelListElement, {
animation: 150,
onUpdate: async (event) => {
positionChangeHandler();
}
});
}
};
</script>
{#if modelIds.length > 0}
+325 -278
View File
@@ -6,6 +6,7 @@
import { onMount, getContext } from 'svelte';
import { toast } from 'svelte-sonner';
import SensitiveInput from '$lib/components/common/SensitiveInput.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const i18n = getContext('i18n');
@@ -84,370 +85,416 @@
>
<div class=" space-y-3 overflow-y-scroll scrollbar-hidden h-full">
{#if webConfig}
<div>
<div class=" mb-1 text-sm font-medium">
{$i18n.t('Web Search')}
</div>
<div class="">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('General')}</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Enable Web Search')}
{$i18n.t('Web Search')}
</div>
<div class="flex items-center relative">
<Switch bind:state={webConfig.search.enabled} />
</div>
<Switch bind:state={webConfig.search.enabled} />
</div>
</div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={webConfig.search.engine}
placeholder={$i18n.t('Select a engine')}
required
>
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
{#each webSearchEngines as engine}
<option value={engine}>{engine}</option>
{/each}
</select>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Web Search Engine')}
</div>
<div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 p-1 text-xs bg-transparent outline-hidden text-right"
bind:value={webConfig.search.engine}
placeholder={$i18n.t('Select a engine')}
required
>
<option disabled selected value="">{$i18n.t('Select a engine')}</option>
{#each webSearchEngines as engine}
<option value={engine}>{engine}</option>
{/each}
</select>
</div>
</div>
</div>
{#if webConfig.search.engine !== ''}
<div class="mt-1.5">
{#if webConfig.search.engine !== ''}
{#if webConfig.search.engine === 'searxng'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Searxng Query URL')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Searxng Query URL')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter Searxng Query URL')}
bind:value={webConfig.search.searxng_query_url}
autocomplete="off"
/>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter Searxng Query URL')}
bind:value={webConfig.search.searxng_query_url}
autocomplete="off"
/>
</div>
</div>
</div>
</div>
{:else if webConfig.search.engine === 'google_pse'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Google PSE API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Google PSE API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Google PSE API Key')}
bind:value={webConfig.search.google_pse_api_key}
/>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Google PSE Engine Id')}
<SensitiveInput
placeholder={$i18n.t('Enter Google PSE API Key')}
bind:value={webConfig.search.google_pse_api_key}
/>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Google PSE Engine Id')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter Google PSE Engine Id')}
bind:value={webConfig.search.google_pse_engine_id}
autocomplete="off"
/>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter Google PSE Engine Id')}
bind:value={webConfig.search.google_pse_engine_id}
autocomplete="off"
/>
</div>
</div>
</div>
</div>
{:else if webConfig.search.engine === 'brave'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Brave Search API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Brave Search API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Brave Search API Key')}
bind:value={webConfig.search.brave_search_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Brave Search API Key')}
bind:value={webConfig.search.brave_search_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'kagi'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Kagi Search API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Kagi Search API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Kagi Search API Key')}
bind:value={webConfig.search.kagi_search_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Kagi Search API Key')}
bind:value={webConfig.search.kagi_search_api_key}
/>
</div>
.
</div>
{:else if webConfig.search.engine === 'mojeek'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Mojeek Search API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Mojeek Search API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Mojeek Search API Key')}
bind:value={webConfig.search.mojeek_search_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Mojeek Search API Key')}
bind:value={webConfig.search.mojeek_search_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'bocha'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Bocha Search API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Bocha Search API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Bocha Search API Key')}
bind:value={webConfig.search.bocha_search_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Bocha Search API Key')}
bind:value={webConfig.search.bocha_search_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'serpstack'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serpstack API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serpstack API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Serpstack API Key')}
bind:value={webConfig.search.serpstack_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Serpstack API Key')}
bind:value={webConfig.search.serpstack_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'serper'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serper API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serper API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Serper API Key')}
bind:value={webConfig.search.serper_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Serper API Key')}
bind:value={webConfig.search.serper_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'serply'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serply API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Serply API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Serply API Key')}
bind:value={webConfig.search.serply_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Serply API Key')}
bind:value={webConfig.search.serply_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'searchapi'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SearchApi API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SearchApi API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter SearchApi API Key')}
bind:value={webConfig.search.searchapi_api_key}
/>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SearchApi Engine')}
<SensitiveInput
placeholder={$i18n.t('Enter SearchApi API Key')}
bind:value={webConfig.search.searchapi_api_key}
/>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SearchApi Engine')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter SearchApi Engine')}
bind:value={webConfig.search.searchapi_engine}
autocomplete="off"
/>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter SearchApi Engine')}
bind:value={webConfig.search.searchapi_engine}
autocomplete="off"
/>
</div>
</div>
</div>
</div>
{:else if webConfig.search.engine === 'serpapi'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SerpApi API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SerpApi API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter SerpApi API Key')}
bind:value={webConfig.search.serpapi_api_key}
/>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SerpApi Engine')}
<SensitiveInput
placeholder={$i18n.t('Enter SerpApi API Key')}
bind:value={webConfig.search.serpapi_api_key}
/>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('SerpApi Engine')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter SerpApi Engine')}
bind:value={webConfig.search.serpapi_engine}
autocomplete="off"
/>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter SerpApi Engine')}
bind:value={webConfig.search.serpapi_engine}
autocomplete="off"
/>
</div>
</div>
</div>
</div>
{:else if webConfig.search.engine === 'tavily'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Tavily API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Tavily API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={webConfig.search.tavily_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Tavily API Key')}
bind:value={webConfig.search.tavily_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'jina'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Jina API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Jina API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Jina API Key')}
bind:value={webConfig.search.jina_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Jina API Key')}
bind:value={webConfig.search.jina_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'exa'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Exa API Key')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Exa API Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Exa API Key')}
bind:value={webConfig.search.exa_api_key}
/>
<SensitiveInput
placeholder={$i18n.t('Enter Exa API Key')}
bind:value={webConfig.search.exa_api_key}
/>
</div>
</div>
{:else if webConfig.search.engine === 'bing'}
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Bing Search V7 Endpoint')}
</div>
<div class="mb-2.5 flex w-full flex-col">
<div>
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Bing Search V7 Endpoint')}
</div>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
bind:value={webConfig.search.bing_search_v7_endpoint}
autocomplete="off"
/>
<div class="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
type="text"
placeholder={$i18n.t('Enter Bing Search V7 Endpoint')}
bind:value={webConfig.search.bing_search_v7_endpoint}
autocomplete="off"
/>
</div>
</div>
</div>
</div>
<div class="mt-2">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Bing Search V7 Subscription Key')}
<div class="mt-2">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Bing Search V7 Subscription Key')}
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
bind:value={webConfig.search.bing_search_v7_subscription_key}
/>
</div>
<SensitiveInput
placeholder={$i18n.t('Enter Bing Search V7 Subscription Key')}
bind:value={webConfig.search.bing_search_v7_subscription_key}
/>
</div>
{/if}
</div>
{/if}
{/if}
{#if webConfig.search.enabled}
<div class="mt-2 flex gap-2 mb-1">
<div class="w-full">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Search Result Count')}
{#if webConfig.search.enabled}
<div class="mb-2.5 flex w-full flex-col">
<div class="flex gap-2">
<div class="w-full">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Search Result Count')}
</div>
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Search Result Count')}
bind:value={webConfig.search.result_count}
required
/>
</div>
<div class="w-full">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Concurrent Requests')}
</div>
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Concurrent Requests')}
bind:value={webConfig.search.concurrent_requests}
required
/>
</div>
</div>
</div>
<div class="mb-2.5 flex w-full flex-col">
<div class=" text-xs font-medium mb-1">
{$i18n.t('Domain Filter List')}
</div>
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Search Result Count')}
bind:value={webConfig.search.result_count}
required
placeholder={$i18n.t(
'Enter domains separated by commas (e.g., example.com,site.org)'
)}
bind:value={webConfig.search.domain_filter_list}
/>
</div>
{/if}
<div class="w-full">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Concurrent Requests')}
</div>
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t('Concurrent Requests')}
bind:value={webConfig.search.concurrent_requests}
required
/>
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
<Tooltip content={$i18n.t('Full Context Mode')} placement="top-start">
{$i18n.t('Bypass Embedding and Retrieval')}
</Tooltip>
</div>
<div class="flex items-center relative">
<Tooltip
content={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL
? 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
>
<Switch bind:state={webConfig.BYPASS_WEB_SEARCH_EMBEDDING_AND_RETRIEVAL} />
</Tooltip>
</div>
</div>
<div class="mt-2">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Domain Filter List')}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Trust Proxy Environment')}
</div>
<div class="flex items-center relative">
<Tooltip
content={webConfig.search.trust_env
? 'Use proxy designated by http_proxy and https_proxy environment variables to fetch page contents'
: 'Use no proxy to fetch page contents.'}
>
<Switch bind:state={webConfig.search.trust_env} />
</Tooltip>
</div>
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
placeholder={$i18n.t(
'Enter domains separated by commas (e.g., example.com,site.org)'
)}
bind:value={webConfig.search.domain_filter_list}
/>
</div>
{/if}
</div>
<hr class="border-gray-100 dark:border-gray-850 my-2" />
<div>
<div class=" mb-1 text-sm font-medium">
{$i18n.t('Web Loader Settings')}
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class="mb-3">
<div class=" mb-2.5 text-base font-medium">{$i18n.t('Loader')}</div>
<hr class=" border-gray-100 dark:border-gray-850 my-2" />
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Bypass SSL verification for Websites')}
</div>
<button
class="p-1 px-3 text-xs flex rounded-sm transition"
on:click={() => {
webConfig.web_loader_ssl_verification = !webConfig.web_loader_ssl_verification;
submitHandler();
}}
type="button"
>
{#if webConfig.web_loader_ssl_verification === false}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if}
</button>
<div class="flex items-center relative">
<Switch bind:state={webConfig.ENABLE_RAG_WEB_LOADER_SSL_VERIFICATION} />
</div>
</div>
</div>
<div class=" mt-2 mb-1 text-sm font-medium">
{$i18n.t('Youtube Loader Settings')}
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div>
<div class=" flex-1 self-center">
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Youtube Language')}
</div>
<div class="flex items-center relative">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Enter language codes')}
bind:value={youtubeLanguage}
@@ -455,14 +502,14 @@
/>
</div>
</div>
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Proxy URL')}</div>
<div class=" flex-1 self-center">
<div class=" mb-2.5 flex flex-col w-full justify-between">
<div class=" mb-1 text-xs font-medium">
{$i18n.t('Youtube Proxy URL')}
</div>
<div class="flex items-center relative">
<input
class="w-full rounded-lg py-2 px-4 text-sm bg-gray-50 dark:text-gray-300 dark:bg-gray-850 outline-hidden"
class="flex-1 w-full rounded-lg text-sm bg-transparent outline-hidden"
type="text"
placeholder={$i18n.t('Enter proxy URL (e.g. https://user:password@host:port)')}
bind:value={youtubeProxyUrl}
@@ -85,8 +85,9 @@
return true;
} else {
let name = user.name.toLowerCase();
let email = user.email.toLowerCase();
const query = search.toLowerCase();
return name.includes(query);
return name.includes(query) || email.includes(query);
}
})
.sort((a, b) => {
@@ -171,7 +172,9 @@
</div>
</div>
<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5">
<div
class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-sm pt-0.5"
>
<table
class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto max-w-full rounded-sm"
>
+3 -17
View File
@@ -103,7 +103,9 @@
return;
}
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
if (
['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/avif'].includes(file['type'])
) {
let reader = new FileReader();
reader.onload = async (event) => {
@@ -155,22 +157,6 @@
}
files = [...files, fileItem];
// Check if the file is an audio file and transcribe/convert it to text file
if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
console.log(res);
const blob = new Blob([res.text], { type: 'text/plain' });
file = blobToFile(blob, `${file.name}.txt`);
fileItem.name = file.name;
fileItem.size = file.size;
}
}
try {
// During the file upload, file content is automatically extracted.
+21 -4
View File
@@ -187,15 +187,20 @@
setToolIds();
}
$: if (atSelectedModel || selectedModels) {
setToolIds();
}
const setToolIds = async () => {
if (!$tools) {
tools.set(await getTools(localStorage.token));
}
if (selectedModels.length !== 1) {
if (selectedModels.length !== 1 && !atSelectedModel) {
return;
}
const model = $models.find((m) => m.id === selectedModels[0]);
const model = atSelectedModel ?? $models.find((m) => m.id === selectedModels[0]);
if (model) {
selectedToolIds = (model?.info?.meta?.toolIds ?? []).filter((id) =>
$tools.find((t) => t.id === id)
@@ -836,6 +841,7 @@
content: m.content,
info: m.info ? m.info : undefined,
timestamp: m.timestamp,
...(m.usage ? { usage: m.usage } : {}),
...(m.sources ? { sources: m.sources } : {})
})),
model_item: $models.find((m) => m.id === modelId),
@@ -1273,7 +1279,9 @@
const chatInputElement = document.getElementById('chat-input');
if (chatInputElement) {
await tick();
chatInputElement.style.height = '';
chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 320) + 'px';
}
const _files = JSON.parse(JSON.stringify(files));
@@ -1488,7 +1496,10 @@
params?.system ?? $settings?.system ?? '',
$user.name,
$settings?.userLocation
? await getAndUpdateUserLocation(localStorage.token)
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
console.error(err);
return undefined;
})
: undefined
)}${
(responseMessage?.userContext ?? null)
@@ -1573,7 +1584,12 @@
variables: {
...getPromptVariables(
$user.name,
$settings?.userLocation ? await getAndUpdateUserLocation(localStorage.token) : undefined
$settings?.userLocation
? await getAndUpdateUserLocation(localStorage.token).catch((err) => {
console.error(err);
return undefined;
})
: undefined
)
},
model_item: $models.find((m) => m.id === model.id),
@@ -1965,6 +1981,7 @@
bind:autoScroll
bind:prompt
{selectedModels}
{atSelectedModel}
{sendPrompt}
{showMessage}
{submitMessage}
@@ -16,6 +16,7 @@
export let modelIds = [];
export let models = [];
export let atSelectedModel;
export let submitPrompt;
@@ -126,7 +127,8 @@
<div class=" w-full font-primary" in:fade={{ duration: 200, delay: 300 }}>
<Suggestions
className="grid grid-cols-2"
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
suggestionPrompts={atSelectedModel?.info?.meta?.suggestion_prompts ??
models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
$config?.default_prompt_suggestions ??
[]}
on:select={(e) => {
+40 -27
View File
@@ -2,6 +2,7 @@
import { toast } from 'svelte-sonner';
import { v4 as uuidv4 } from 'uuid';
import { createPicker, getAuthToken } from '$lib/utils/google-drive-picker';
import { pickAndDownloadFile } from '$lib/utils/onedrive-file-picker';
import { onMount, tick, getContext, createEventDispatcher, onDestroy } from 'svelte';
const dispatch = createEventDispatcher();
@@ -173,22 +174,6 @@
}
files = [...files, fileItem];
// Check if the file is an audio file and transcribe/convert it to text file
if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
console.log(res);
const blob = new Blob([res.text], { type: 'text/plain' });
file = blobToFile(blob, `${file.name}.txt`);
fileItem.name = file.name;
fileItem.size = file.size;
}
}
try {
// During the file upload, file content is automatically extracted.
@@ -249,7 +234,9 @@
return;
}
if (['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(file['type'])) {
if (
['image/gif', 'image/webp', 'image/jpeg', 'image/png', 'image/avif'].includes(file['type'])
) {
if (visionCapableModels.length === 0) {
toast.error($i18n.t('Selected model(s) do not support image inputs'));
return;
@@ -428,7 +415,7 @@
</div>
{/if}
{#if webSearchEnabled || ($settings?.webSearch ?? false) === 'always'}
{#if webSearchEnabled || ($config?.features?.enable_web_search && ($settings?.webSearch ?? false)) === 'always'}
<div class="flex items-center justify-between w-full">
<div class="flex items-center gap-2.5 text-sm dark:text-gray-500">
<div class="pl-1">
@@ -825,7 +812,11 @@
}
// Submit the prompt when Enter key is pressed
if (prompt !== '' && e.keyCode === 13 && !e.shiftKey) {
if (
(prompt !== '' || files.length > 0) &&
e.keyCode === 13 &&
!e.shiftKey
) {
dispatch('submit', prompt);
}
}
@@ -904,7 +895,11 @@
}
// Submit the prompt when Enter key is pressed
if (prompt !== '' && e.key === 'Enter' && !e.shiftKey) {
if (
(prompt !== '' || files.length > 0) &&
e.key === 'Enter' &&
!e.shiftKey
) {
dispatch('submit', prompt);
}
}
@@ -1106,6 +1101,21 @@
);
}
}}
uploadOneDriveHandler={async () => {
try {
const fileData = await pickAndDownloadFile();
if (fileData) {
const file = new File([fileData.blob], fileData.name, {
type: fileData.blob.type || 'application/octet-stream'
});
await uploadFileHandler(file);
} else {
console.log('No file was selected from OneDrive');
}
} catch (error) {
console.error('OneDrive Error:', error);
}
}}
onClose={async () => {
await tick();
@@ -1283,14 +1293,17 @@
stream = null;
if (!$TTSWorker) {
await TTSWorker.set(
new KokoroWorker({
dtype: $settings.audio?.tts?.engineConfig?.dtype ?? 'fp32'
})
);
if ($settings.audio?.tts?.engine === 'browser-kokoro') {
// If the user has not initialized the TTS worker, initialize it
if (!$TTSWorker) {
await TTSWorker.set(
new KokoroWorker({
dtype: $settings.audio?.tts?.engineConfig?.dtype ?? 'fp32'
})
);
await $TTSWorker.init();
await $TTSWorker.init();
}
}
showCallOverlay.set(true);
@@ -74,7 +74,13 @@
}
if (command.content.includes('{{USER_LOCATION}}')) {
const location = await getUserPosition();
let location;
try {
location = await getUserPosition();
} catch (error) {
toast.error($i18n.t('Location access not allowed'));
location = 'LOCATION_UNKNOWN';
}
text = text.replaceAll('{{USER_LOCATION}}', String(location));
}
@@ -5,6 +5,7 @@
import { config, user, tools as _tools, mobile } from '$lib/stores';
import { createPicker } from '$lib/utils/google-drive-picker';
import { getTools } from '$lib/apis/tools';
import Dropdown from '$lib/components/common/Dropdown.svelte';
@@ -24,6 +25,7 @@
export let inputFilesHandler: Function;
export let uploadGoogleDriveHandler: Function;
export let uploadOneDriveHandler: Function;
export let selectedToolIds: string[] = [];
@@ -225,6 +227,97 @@
<div class="line-clamp-1">{$i18n.t('Google Drive')}</div>
</DropdownMenu.Item>
{/if}
{#if $config?.features?.enable_onedrive_integration}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
on:click={() => {
uploadOneDriveHandler();
}}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" class="w-5 h-5" fill="none">
<mask
id="mask0_87_7796"
style="mask-type:alpha"
maskUnits="userSpaceOnUse"
x="0"
y="6"
width="32"
height="20"
>
<path
d="M7.82979 26C3.50549 26 0 22.5675 0 18.3333C0 14.1921 3.35322 10.8179 7.54613 10.6716C9.27535 7.87166 12.4144 6 16 6C20.6308 6 24.5169 9.12183 25.5829 13.3335C29.1316 13.3603 32 16.1855 32 19.6667C32 23.0527 29 26 25.8723 25.9914L7.82979 26Z"
fill="#C4C4C4"
/>
</mask>
<g mask="url(#mask0_87_7796)">
<path
d="M7.83017 26.0001C5.37824 26.0001 3.18957 24.8966 1.75391 23.1691L18.0429 16.3335L30.7089 23.4647C29.5926 24.9211 27.9066 26.0001 26.0004 25.9915C23.1254 26.0001 12.0629 26.0001 7.83017 26.0001Z"
fill="url(#paint0_linear_87_7796)"
/>
<path
d="M25.5785 13.3149L18.043 16.3334L30.709 23.4647C31.5199 22.4065 32.0004 21.0916 32.0004 19.6669C32.0004 16.1857 29.1321 13.3605 25.5833 13.3337C25.5817 13.3274 25.5801 13.3212 25.5785 13.3149Z"
fill="url(#paint1_linear_87_7796)"
/>
<path
d="M7.06445 10.7028L18.0423 16.3333L25.5779 13.3148C24.5051 9.11261 20.6237 6 15.9997 6C12.4141 6 9.27508 7.87166 7.54586 10.6716C7.3841 10.6773 7.22358 10.6877 7.06445 10.7028Z"
fill="url(#paint2_linear_87_7796)"
/>
<path
d="M1.7535 23.1687L18.0425 16.3331L7.06471 10.7026C3.09947 11.0792 0 14.3517 0 18.3331C0 20.1665 0.657197 21.8495 1.7535 23.1687Z"
fill="url(#paint3_linear_87_7796)"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_87_7796"
x1="4.42591"
y1="24.6668"
x2="27.2309"
y2="23.2764"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#2086B8" />
<stop offset="1" stop-color="#46D3F6" />
</linearGradient>
<linearGradient
id="paint1_linear_87_7796"
x1="23.8302"
y1="19.6668"
x2="30.2108"
y2="15.2082"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#1694DB" />
<stop offset="1" stop-color="#62C3FE" />
</linearGradient>
<linearGradient
id="paint2_linear_87_7796"
x1="8.51037"
y1="7.33333"
x2="23.3335"
y2="15.9348"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#0D3D78" />
<stop offset="1" stop-color="#063B83" />
</linearGradient>
<linearGradient
id="paint3_linear_87_7796"
x1="-0.340429"
y1="19.9998"
x2="14.5634"
y2="14.4649"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#16589B" />
<stop offset="1" stop-color="#1464B7" />
</linearGradient>
</defs>
</svg>
<div class="line-clamp-1">{$i18n.t('OneDrive')}</div>
</DropdownMenu.Item>
{/if}
</DropdownMenu.Content>
</div>
</Dropdown>
+21 -9
View File
@@ -1,6 +1,14 @@
<script lang="ts">
import { v4 as uuidv4 } from 'uuid';
import { chats, config, settings, user as _user, mobile, currentChatPage } from '$lib/stores';
import {
chats,
config,
settings,
user as _user,
mobile,
currentChatPage,
temporaryChatEnabled
} from '$lib/stores';
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
@@ -24,6 +32,7 @@
export let prompt;
export let history = {};
export let selectedModels;
export let atSelectedModel;
let messages = [];
@@ -85,15 +94,17 @@
};
const updateChat = async () => {
history = history;
await tick();
await updateChatById(localStorage.token, chatId, {
history: history,
messages: messages
});
if (!$temporaryChatEnabled) {
history = history;
await tick();
await updateChatById(localStorage.token, chatId, {
history: history,
messages: messages
});
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
currentChatPage.set(1);
await chats.set(await getChatList(localStorage.token, $currentChatPage));
}
};
const showPreviousMessage = async (message) => {
@@ -339,6 +350,7 @@
{#if Object.keys(history?.messages ?? {}).length == 0}
<ChatPlaceholder
modelIds={selectedModels}
{atSelectedModel}
submitPrompt={async (p) => {
let text = p;
@@ -7,6 +7,7 @@
const i18n = getContext('i18n');
export let id = '';
export let sources = [];
let citations = [];
@@ -42,6 +43,7 @@
}
$: {
console.log('sources', sources);
citations = sources.reduce((acc, source) => {
if (Object.keys(source).length === 0) {
return acc;
@@ -52,7 +54,7 @@
const distance = source.distances?.[index];
// Within the same citation there could be multiple documents
const id = metadata?.source ?? 'N/A';
const id = metadata?.source ?? source?.source?.id ?? 'N/A';
let _source = source?.source;
if (metadata?.name) {
@@ -100,7 +102,7 @@
<div class="flex text-xs font-medium flex-wrap">
{#each citations as citation, idx}
<button
id={`source-${idx}`}
id={`source-${id}-${idx}`}
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-white dark:bg-gray-900 rounded-xl max-w-96"
on:click={() => {
showCitationModal = true;
@@ -179,7 +181,7 @@
<div class="flex text-xs font-medium flex-wrap">
{#each citations as citation, idx}
<button
id={`source-${idx}`}
id={`source-${id}-${idx}`}
class="no-toggle outline-hidden flex dark:text-gray-300 p-1 bg-gray-50 hover:bg-gray-100 dark:bg-gray-900 dark:hover:bg-gray-850 transition rounded-xl max-w-96"
on:click={() => {
showCitationModal = true;
+131 -25
View File
@@ -1,18 +1,9 @@
<script lang="ts">
import hljs from 'highlight.js';
import { loadPyodide } from 'pyodide';
import mermaid from 'mermaid';
import { v4 as uuidv4 } from 'uuid';
import {
getContext,
getAllContexts,
onMount,
tick,
createEventDispatcher,
onDestroy
} from 'svelte';
import { getContext, onMount, tick, onDestroy } from 'svelte';
import { copyToClipboard } from '$lib/utils';
import 'highlight.js/styles/github-dark.min.css';
@@ -20,12 +11,17 @@
import PyodideWorker from '$lib/workers/pyodide.worker?worker';
import CodeEditor from '$lib/components/common/CodeEditor.svelte';
import SvgPanZoom from '$lib/components/common/SVGPanZoom.svelte';
import { config } from '$lib/stores';
import { executeCode } from '$lib/apis/utils';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
export let id = '';
export let onSave = (e) => {};
export let onCode = (e) => {};
export let save = false;
export let run = true;
@@ -68,7 +64,7 @@
saved = true;
code = _code;
dispatch('save', code);
onSave(code);
setTimeout(() => {
saved = false;
@@ -120,16 +116,87 @@
};
const executePython = async (code) => {
executePythonAsWorker(code);
};
const executePythonAsWorker = async (code) => {
result = null;
stdout = null;
stderr = null;
executing = true;
if ($config?.code?.engine === 'jupyter') {
const output = await executeCode(localStorage.token, code).catch((error) => {
toast.error(`${error}`);
return null;
});
if (output) {
if (output['stdout']) {
stdout = output['stdout'];
const stdoutLines = stdout.split('\n');
for (const [idx, line] of stdoutLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (stdout.startsWith(`${line}\n`)) {
stdout = stdout.replace(`${line}\n`, ``);
} else if (stdout.startsWith(`${line}`)) {
stdout = stdout.replace(`${line}`, ``);
}
}
}
}
if (output['result']) {
result = output['result'];
const resultLines = result.split('\n');
for (const [idx, line] of resultLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (result.startsWith(`${line}\n`)) {
result = result.replace(`${line}\n`, ``);
} else if (result.startsWith(`${line}`)) {
result = result.replace(`${line}`, ``);
}
}
}
}
output['stderr'] && (stderr = output['stderr']);
}
executing = false;
} else {
executePythonAsWorker(code);
}
};
const executePythonAsWorker = async (code) => {
let packages = [
code.includes('requests') ? 'requests' : null,
code.includes('bs4') ? 'beautifulsoup4' : null,
@@ -189,7 +256,40 @@
];
}
stdout = stdout.replace(`${line}\n`, ``);
if (stdout.startsWith(`${line}\n`)) {
stdout = stdout.replace(`${line}\n`, ``);
} else if (stdout.startsWith(`${line}`)) {
stdout = stdout.replace(`${line}`, ``);
}
}
}
}
if (data['result']) {
result = data['result'];
const resultLines = result.split('\n');
for (const [idx, line] of resultLines.entries()) {
if (line.startsWith('data:image/png;base64')) {
if (files) {
files.push({
type: 'image/png',
data: line
});
} else {
files = [
{
type: 'image/png',
data: line
}
];
}
if (result.startsWith(`${line}\n`)) {
result = result.replace(`${line}\n`, ``);
} else if (result.startsWith(`${line}`)) {
result = result.replace(`${line}`, ``);
}
}
}
}
@@ -237,7 +337,7 @@
render();
}
$: dispatch('code', { lang, code });
$: onCode({ lang, code });
$: if (attributes) {
onAttributesUpdate();
@@ -273,7 +373,7 @@
console.log('codeblock', lang, code);
if (lang) {
dispatch('code', { lang, code });
onCode({ lang, code });
}
if (document.documentElement.classList.contains('dark')) {
mermaid.initialize({
@@ -361,11 +461,11 @@
value={code}
{id}
{lang}
on:save={() => {
onSave={() => {
saveCode();
}}
on:change={(e) => {
_code = e.detail.value;
onChange={(value) => {
_code = value;
}}
/>
</div>
@@ -375,7 +475,7 @@
class="bg-gray-50 dark:bg-[#202123] dark:text-white max-w-full overflow-x-auto scrollbar-hidden"
/>
{#if executing || stdout || stderr || result}
{#if executing || stdout || stderr || result || files}
<div
class="bg-gray-50 dark:bg-[#202123] dark:text-white rounded-b-lg! py-4 px-4 flex flex-col gap-2"
>
@@ -388,7 +488,13 @@
{#if stdout || stderr}
<div class=" ">
<div class=" text-gray-500 text-xs mb-1">STDOUT/STDERR</div>
<div class="text-sm">{stdout || stderr}</div>
<div
class="text-sm {stdout?.split('\n')?.length > 100
? `max-h-96`
: ''} overflow-y-auto"
>
{stdout || stderr}
</div>
</div>
{/if}
{#if result || files}
@@ -401,7 +507,7 @@
<div class="flex flex-col gap-2">
{#each files as file}
{#if file.type.startsWith('image')}
<img src={file.data} alt="Output" />
<img src={file.data} alt="Output" class=" w-full max-w-[36rem]" />
{/if}
{/each}
</div>
@@ -120,6 +120,11 @@
sourceIds={(sources ?? []).reduce((acc, s) => {
let ids = [];
s.document.forEach((document, index) => {
if (model?.info?.meta?.capabilities?.citations == false) {
ids.push('N/A');
return ids;
}
const metadata = s.metadata?.[index];
const id = metadata?.source ?? 'N/A';
@@ -29,7 +29,7 @@
{:else if token.text.includes(`<iframe src="${WEBUI_BASE_URL}/api/v1/files/`)}
{@html `${token.text}`}
{:else if token.text.includes(`<source_id`)}
<Source {token} onClick={onSourceClick} />
<Source {id} {token} onClick={onSourceClick} />
{:else}
{token.text}
{/if}
@@ -76,7 +76,7 @@
{#if token.type === 'hr'}
<hr class=" border-gray-100 dark:border-gray-850" />
{:else if token.type === 'heading'}
<svelte:element this={headerComponent(token.depth)}>
<svelte:element this={headerComponent(token.depth)} dir="auto">
<MarkdownInlineTokens id={`${id}-${tokenIdx}-h`} tokens={token.tokens} {onSourceClick} />
</svelte:element>
{:else if token.type === 'code'}
@@ -88,14 +88,14 @@
code={token?.text ?? ''}
{attributes}
{save}
on:code={(e) => {
dispatch('code', e.detail);
onCode={(value) => {
dispatch('code', value);
}}
on:save={(e) => {
onSave={(e) => {
dispatch('update', {
raw: token.raw,
oldContent: token.text,
newContent: e.detail
newContent: value
});
}}
/>
@@ -169,14 +169,14 @@
</div>
</div>
{:else if token.type === 'blockquote'}
<blockquote>
<blockquote dir="auto">
<svelte:self id={`${id}-${tokenIdx}`} tokens={token.tokens} {onTaskClick} {onSourceClick} />
</blockquote>
{:else if token.type === 'list'}
{#if token.ordered}
<ol start={token.start || 1}>
{#each token.items as item, itemIdx}
<li>
<li dir="auto" class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@@ -208,7 +208,7 @@
{:else}
<ul>
{#each token.items as item, itemIdx}
<li>
<li dir="auto" class="text-start">
{#if item?.task}
<input
class=" translate-y-[1px] -translate-x-1"
@@ -239,7 +239,12 @@
</ul>
{/if}
{:else if token.type === 'details'}
<Collapsible title={token.summary} attributes={token?.attributes} className="w-full space-y-1">
<Collapsible
title={token.summary}
attributes={token?.attributes}
className="w-full space-y-1"
dir="auto"
>
<div class=" mb-1.5" slot="content">
<svelte:self
id={`${id}-${tokenIdx}-d`}
@@ -268,7 +273,7 @@
onload="this.style.height=(this.contentWindow.document.body.scrollHeight+20)+'px';"
></iframe>
{:else if token.type === 'paragraph'}
<p>
<p dir="auto">
<MarkdownInlineTokens
id={`${id}-${tokenIdx}-p`}
tokens={token.tokens ?? []}
@@ -277,7 +282,7 @@
</p>
{:else if token.type === 'text'}
{#if top}
<p>
<p dir="auto">
{#if token.tokens}
<MarkdownInlineTokens id={`${id}-${tokenIdx}-t`} tokens={token.tokens} {onSourceClick} />
{:else}
@@ -1,8 +1,9 @@
<script lang="ts">
export let id;
export let token;
export let onClick: Function = () => {};
let attributes: Record<string, string> = {};
let attributes: Record<string, string | undefined> = {};
function extractAttributes(input: string): Record<string, string> {
const regex = /(\w+)="([^"]*)"/g;
@@ -35,13 +36,15 @@
$: attributes = extractAttributes(token.text);
</script>
<button
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
on:click={() => {
onClick(attributes.data);
}}
>
<span class="line-clamp-1">
{formattedTitle(attributes.title)}
</span>
</button>
{#if attributes.title !== 'N/A'}
<button
class="text-xs font-medium w-fit translate-y-[2px] px-2 py-0.5 dark:bg-white/5 dark:text-white/60 dark:hover:text-white bg-gray-50 text-black/60 hover:text-black transition rounded-lg"
on:click={() => {
onClick(id, attributes.data);
}}
>
<span class="line-clamp-1">
{attributes.title ? formattedTitle(attributes.title) : ''}
</span>
</button>
{/if}
@@ -37,6 +37,9 @@
import Spinner from '$lib/components/common/Spinner.svelte';
import WebSearchResults from './ResponseMessage/WebSearchResults.svelte';
import Sparkles from '$lib/components/icons/Sparkles.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import Error from './Error.svelte';
import Citations from './Citations.svelte';
import CodeExecutions from './CodeExecutions.svelte';
@@ -126,6 +129,9 @@
export let isLastMessage = true;
export let readOnly = false;
let buttonsContainerElement: HTMLDivElement;
let showDeleteConfirm = false;
let model = null;
$: model = $models.find((m) => m.id === message.model);
@@ -513,9 +519,29 @@
// console.log('ResponseMessage mounted');
await tick();
if (buttonsContainerElement) {
console.log(buttonsContainerElement);
buttonsContainerElement.addEventListener('wheel', function (event) {
// console.log(event.deltaY);
event.preventDefault();
if (event.deltaY !== 0) {
// Adjust horizontal scroll position based on vertical scroll
buttonsContainerElement.scrollLeft += event.deltaY;
}
});
}
});
</script>
<DeleteConfirmDialog
bind:show={showDeleteConfirm}
title={$i18n.t('Delete message?')}
on:confirm={() => {
deleteMessageHandler();
}}
/>
{#key message.id}
<div
class=" flex w-full message-{message.id}"
@@ -719,9 +745,9 @@
onTaskClick={async (e) => {
console.log(e);
}}
onSourceClick={async (e) => {
console.log(e);
let sourceButton = document.getElementById(`source-${e}`);
onSourceClick={async (id, idx) => {
console.log(id, idx);
let sourceButton = document.getElementById(`source-${message.id}-${idx}`);
const sourcesCollapsible = document.getElementById(`collapsible-sources`);
if (sourceButton) {
@@ -740,7 +766,7 @@
});
// Try clicking the source button again
sourceButton = document.getElementById(`source-${e}`);
sourceButton = document.getElementById(`source-${message.id}-${idx}`);
sourceButton && sourceButton.click();
}
}}
@@ -777,7 +803,7 @@
{/if}
{#if (message?.sources || message?.citations) && (model?.info?.meta?.capabilities?.citations ?? true)}
<Citations sources={message?.sources ?? message?.citations} />
<Citations id={message?.id} sources={message?.sources ?? message?.citations} />
{/if}
{#if message.code_executions}
@@ -789,10 +815,11 @@
</div>
{#if !edit}
{#if message.done || siblings.length > 1}
<div
class=" flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500 mt-0.5"
>
<div
bind:this={buttonsContainerElement}
class="flex justify-start overflow-x-auto buttons text-gray-600 dark:text-gray-500 mt-0.5"
>
{#if message.done || siblings.length > 1}
{#if siblings.length > 1}
<div class="flex self-center min-w-fit" dir="ltr">
<button
@@ -1247,7 +1274,7 @@
? 'visible'
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
on:click={() => {
deleteMessageHandler();
showDeleteConfirm = true;
}}
>
<svg
@@ -1300,8 +1327,8 @@
{/if}
{/if}
{/if}
</div>
{/if}
{/if}
</div>
{#if message.done && showRateComment}
<RateComment
@@ -5,22 +5,32 @@
<div class="w-full mt-2 mb-2">
<div class="animate-pulse flex w-full">
<div class="{size === 'md' ? 'space-y-2' : 'space-y-1.5'} w-full">
<div class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm mr-14" />
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm mr-14"
/>
<div class="grid grid-cols-3 gap-4">
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
/>
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
/>
</div>
<div class="grid grid-cols-4 gap-4">
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-1"
/>
<div
class="{size === 'md' ? 'h-2' : 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
class="{size === 'md'
? 'h-2'
: 'h-1.5'} bg-gray-200 dark:bg-gray-600 rounded-sm col-span-2"
/>
<div
class="{size === 'md'
@@ -13,6 +13,8 @@
import FileItem from '$lib/components/common/FileItem.svelte';
import Markdown from './Markdown.svelte';
import Image from '$lib/components/common/Image.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const i18n = getContext('i18n');
@@ -34,6 +36,8 @@
export let isFirstMessage: boolean;
export let readOnly: boolean;
let showDeleteConfirm = false;
let edit = false;
let editedContent = '';
let messageEditTextAreaElement: HTMLTextAreaElement;
@@ -85,6 +89,14 @@
});
</script>
<DeleteConfirmDialog
bind:show={showDeleteConfirm}
title={$i18n.t('Delete message?')}
on:confirm={() => {
deleteMessageHandler();
}}
/>
<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
{#if !($settings?.chatBubble ?? true)}
<div class={`shrink-0 ${($settings?.chatDirection ?? 'LTR') === 'LTR' ? 'mr-3' : 'ml-3'}`}>
@@ -340,7 +352,7 @@
<button
class="invisible group-hover:visible p-1 rounded-sm dark:hover:text-white hover:text-black transition"
on:click={() => {
deleteMessageHandler();
showDeleteConfirm = true;
}}
>
<svg
+2 -1
View File
@@ -213,7 +213,8 @@
<div class="mx-auto max-w-2xl font-primary" in:fade={{ duration: 200, delay: 200 }}>
<div class="mx-5">
<Suggestions
suggestionPrompts={models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
suggestionPrompts={atSelectedModel?.info?.meta?.suggestion_prompts ??
models[selectedModelIdx]?.info?.meta?.suggestion_prompts ??
$config?.default_prompt_suggestions ??
[]}
inputValue={prompt}
@@ -214,7 +214,7 @@
<button
class=" text-xs text-center text-gray-800 dark:text-gray-400 rounded-full px-4 py-0.5 bg-gray-100 dark:bg-gray-850"
on:click={async () => {
const url = await getGravatarUrl($user.email);
const url = await getGravatarUrl(localStorage.token, $user.email);
profileImageUrl = url;
}}>{$i18n.t('Use Gravatar')}</button
@@ -786,7 +786,6 @@
{/if}
</div>
<div class=" py-0.5 w-full justify-between">
<Tooltip
content={$i18n.t(
@@ -353,8 +353,10 @@
params.logit_bias !== null ? params.logit_bias : undefined,
frequency_penalty:
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
presence_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_penalty: params.frequency_penalty !== null ? params.frequency_penalty : undefined,
presence_penalty:
params.frequency_penalty !== null ? params.frequency_penalty : undefined,
repeat_penalty:
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,
@@ -12,6 +12,7 @@
import { error } from '@sveltejs/kit';
import EditMemoryModal from './EditMemoryModal.svelte';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
const i18n = getContext('i18n');
dayjs.extend(localizedFormat);
@@ -26,6 +27,21 @@
let selectedMemory = null;
let showClearConfirmDialog = false;
let onClearConfirmed = async () => {
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res && memories.length > 0) {
toast.success($i18n.t('Memory cleared successfully'));
memories = [];
}
showClearConfirmDialog = false;
};
$: if (show && memories.length === 0 && loading) {
(async () => {
memories = await getMemories(localStorage.token);
@@ -175,15 +191,11 @@
>
<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"
on:click={async () => {
const res = await deleteMemoriesByUserId(localStorage.token).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
toast.success($i18n.t('Memory cleared successfully'));
memories = [];
on:click={() => {
if (memories.length > 0) {
showClearConfirmDialog = true;
} else {
toast.error($i18n.t('No memories to clear'));
}
}}>{$i18n.t('Clear memory')}</button
>
@@ -192,6 +204,16 @@
</div>
</Modal>
<ConfirmDialog
title={$i18n.t('Clear Memory')}
message={$i18n.t('Are you sure you want to clear all memories? This action cannot be undone.')}
show={showClearConfirmDialog}
on:confirm={onClearConfirmed}
on:cancel={() => {
showClearConfirmDialog = false;
}}
/>
<AddMemoryModal
bind:show={showAddMemoryModal}
on:save={async () => {
+40 -2
View File
@@ -122,6 +122,30 @@
</div>
</div>
</div>
<div class="w-full flex justify-between items-center">
<div class=" text-sm">{$i18n.t('Generate prompt pair')}</div>
<div class="flex space-x-1 text-xs">
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
Ctrl/⌘
</div>
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
Shift
</div>
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
Enter
</div>
</div>
</div>
</div>
<div class="flex flex-col space-y-3 w-full self-start">
@@ -219,7 +243,7 @@
<div class="flex flex-col space-y-3 w-full self-start">
<div class="w-full flex justify-between items-center">
<div class=" text-sm">
{$i18n.t('Attach file')}
{$i18n.t('Attach file from knowledge')}
</div>
<div class="flex space-x-1 text-xs">
@@ -247,7 +271,7 @@
<div class="w-full flex justify-between items-center">
<div class=" text-sm">
{$i18n.t('Select model')}
{$i18n.t('Talk to model')}
</div>
<div class="flex space-x-1 text-xs">
@@ -258,6 +282,20 @@
</div>
</div>
</div>
<div class="w-full flex justify-between items-center">
<div class=" text-sm">
{$i18n.t('Accept autocomplete generation / Jump to prompt variable')}
</div>
<div class="flex space-x-1 text-xs">
<div
class=" h-fit py-1 px-2 flex items-center justify-center rounded-sm border border-black/10 capitalize text-gray-600 dark:border-white/10 dark:text-gray-300"
>
TAB
</div>
</div>
</div>
</div>
</div>
</div>
+13 -4
View File
@@ -21,6 +21,10 @@
export let boilerplate = '';
export let value = '';
export let onSave = () => {};
export let onChange = () => {};
let _value = '';
$: if (value) {
@@ -43,6 +47,10 @@
let codeEditor;
export const focus = () => {
codeEditor.focus();
};
let isDarkMode = false;
let editorTheme = new Compartment();
let editorLanguage = new Compartment();
@@ -63,7 +71,7 @@
export const formatPythonCodeHandler = async () => {
if (codeEditor) {
const res = await formatPythonCode(_value).catch((error) => {
const res = await formatPythonCode(localStorage.token, _value).catch((error) => {
toast.error(`${error}`);
return null;
});
@@ -75,7 +83,7 @@
});
_value = formattedCode;
dispatch('change', { value: _value });
onChange(_value);
await tick();
toast.success($i18n.t('Code formatted successfully'));
@@ -94,7 +102,7 @@
EditorView.updateListener.of((e) => {
if (e.docChanged) {
_value = e.state.doc.toString();
dispatch('change', { value: _value });
onChange(_value);
}
}),
editorTheme.of([]),
@@ -170,7 +178,8 @@
const keydownHandler = async (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
dispatch('save');
onSave();
}
// Format code when Ctrl + Shift + F is pressed
+26 -7
View File
@@ -1,6 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte';
import { formatFileSize, getLineCount } from '$lib/utils';
import { WEBUI_API_BASE_URL } from '$lib/constants';
const i18n = getContext('i18n');
@@ -12,14 +13,15 @@
export let item;
export let show = false;
export let edit = false;
let enableFullContent = false;
$: isPDF =
item?.meta?.content_type === 'application/pdf' ||
(item?.name && item?.name.toLowerCase().endsWith('.pdf'));
onMount(() => {
console.log(item);
if (item?.context === 'full') {
enableFullContent = true;
}
@@ -33,9 +35,16 @@
<div>
<div class=" font-medium text-lg dark:text-gray-100">
<a
href={item.url ? (item.type === 'file' ? `${item.url}/content` : `${item.url}`) : '#'}
target="_blank"
href="#"
class="hover:underline line-clamp-1"
on:click|preventDefault={() => {
if (!isPDF && item.url) {
window.open(
item.type === 'file' ? `${item.url}/content` : `${item.url}`,
'_blank'
);
}
}}
>
{item?.name ?? 'File'}
</a>
@@ -78,7 +87,7 @@
<div>
<Tooltip
content={enableFullContent
? 'Inject the entire document as context for comprehensive processing, this is recommended for complex queries.'
? 'Inject the entire content as context for comprehensive processing, this is recommended for complex queries.'
: 'Default to segmented retrieval for focused and relevant content extraction, this is recommended for most cases.'}
>
<div class="flex items-center gap-1.5 text-xs">
@@ -101,8 +110,18 @@
</div>
</div>
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{item?.file?.data?.content ?? 'No content'}
<div class="max-h-[75vh] overflow-auto">
{#if isPDF}
<iframe
title={item?.name}
src={`${WEBUI_API_BASE_URL}/files/${item.id}/content`}
class="w-full h-[70vh] border-0 rounded-lg mt-4"
/>
{:else}
<div class="max-h-96 overflow-scroll scrollbar-hidden text-xs whitespace-pre-wrap">
{item?.file?.data?.content ?? 'No content'}
</div>
{/if}
</div>
</div>
</Modal>
+1 -2
View File
@@ -6,7 +6,6 @@
export let show = true;
export let size = 'md';
export let containerClassName = 'p-3';
export let className = 'bg-gray-50 dark:bg-gray-900 rounded-2xl';
@@ -74,7 +73,7 @@
}}
>
<div
class=" m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
class="m-auto max-w-full {sizeToWidth(size)} {size !== 'full'
? 'mx-2'
: ''} shadow-3xl min-h-fit scrollbar-hidden {className}"
in:flyAndScale
+53 -32
View File
@@ -1,16 +1,22 @@
<script lang="ts">
import fileSaver from 'file-saver';
const { saveAs } = fileSaver;
import { toast } from 'svelte-sonner';
import panzoom, { type PanZoom } from 'panzoom';
import DOMPurify from 'dompurify';
import { onMount, getContext } from 'svelte';
const i18n = getContext('i18n');
import panzoom, { type PanZoom } from 'panzoom';
import DOMPurify from 'dompurify';
import DocumentDuplicate from '../icons/DocumentDuplicate.svelte';
import { copyToClipboard } from '$lib/utils';
import { toast } from 'svelte-sonner';
import DocumentDuplicate from '../icons/DocumentDuplicate.svelte';
import Tooltip from './Tooltip.svelte';
import Clipboard from '../icons/Clipboard.svelte';
import Reset from '../icons/Reset.svelte';
import ArrowDownTray from '../icons/ArrowDownTray.svelte';
export let className = '';
export let svg = '';
@@ -29,12 +35,16 @@
zoomSpeed: 0.065
});
}
function resetPanZoomViewport() {
console.log('Reset View');
const resetPanZoomViewport = () => {
instance.moveTo(0, 0);
instance.zoomAbs(0, 0, 1);
console.log(instance.getTransform());
}
};
const downloadAsSVG = () => {
const svgBlob = new Blob([svg], { type: 'image/svg+xml' });
saveAs(svgBlob, `diagram.svg`);
};
</script>
<div bind:this={sceneParentElement} class="relative {className}">
@@ -44,30 +54,41 @@
{#if content}
<div class=" absolute top-1 right-1">
<Tooltip content={$i18n.t('Copy to clipboard')}>
<button
class="p-1.5 rounded-lg border border-gray-100 dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition"
on:click={() => {
copyToClipboard(content);
toast.success($i18n.t('Copied to clipboard'));
}}
>
<Clipboard className=" size-4" strokeWidth="1.5" />
</button>
</Tooltip>
</div>
<div class=" absolute top-1 right-10">
<Tooltip content={$i18n.t('Reset view')}>
<button
class="p-1.5 rounded-lg border border-gray-100 dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition"
on:click={() => {
resetPanZoomViewport();
toast.success($i18n.t('Reset view'));
}}
>
<Reset className=" size-4" />
</button>
</Tooltip>
<div class="flex gap-1">
<Tooltip content={$i18n.t('Download as SVG')}>
<button
class="p-1.5 rounded-lg border border-gray-100 dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition"
on:click={() => {
downloadAsSVG();
}}
>
<ArrowDownTray className=" size-4" />
</button>
</Tooltip>
<Tooltip content={$i18n.t('Reset view')}>
<button
class="p-1.5 rounded-lg border border-gray-100 dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition"
on:click={() => {
resetPanZoomViewport();
}}
>
<Reset className=" size-4" />
</button>
</Tooltip>
<Tooltip content={$i18n.t('Copy to clipboard')}>
<button
class="p-1.5 rounded-lg border border-gray-100 dark:border-none dark:bg-gray-850 hover:bg-gray-50 dark:hover:bg-gray-800 transition"
on:click={() => {
copyToClipboard(content);
toast.success($i18n.t('Copied to clipboard'));
}}
>
<Clipboard className=" size-4" strokeWidth="1.5" />
</button>
</Tooltip>
</div>
</div>
{/if}
</div>
+12 -2
View File
@@ -1,9 +1,19 @@
<script lang="ts">
export let className = 'size-4';
export let strokeWidth = '2';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor" class={className}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"
stroke-linecap="round"
stroke-linejoin="round"
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
/>
</svg>
+1 -1
View File
@@ -60,7 +60,7 @@
const downloadPdf = async () => {
const history = chat.chat.history;
const messages = createMessagesList(history, history.currentId);
const blob = await downloadChatAsPDF(chat.chat.title, messages);
const blob = await downloadChatAsPDF(localStorage.token, chat.chat.title, messages);
// Create a URL for the blob
const url = window.URL.createObjectURL(blob);
@@ -274,7 +274,7 @@
draggable="false"
>
<div class=" flex self-center flex-1 w-full">
<div class=" text-left self-center overflow-hidden w-full h-[20px]">
<div dir="auto" class="text-left self-center overflow-hidden w-full h-[20px]">
{title}
</div>
</div>
@@ -83,7 +83,7 @@
const history = chat.chat.history;
const messages = createMessagesList(history, history.currentId);
const blob = await downloadChatAsPDF(chat.chat.title, messages);
const blob = await downloadChatAsPDF(localStorage.token, chat.chat.title, messages);
// Create a URL for the blob
const url = window.URL.createObjectURL(blob);
@@ -177,9 +177,7 @@
dispatch('input');
}}
>
<div
class="dark:text-gray-300 text-gray-700 font-medium line-clamp-1 shrink-0"
>
<div class="dark:text-gray-300 text-gray-700 font-medium line-clamp-1 shrink-0">
{tag.name}
</div>
@@ -133,20 +133,6 @@
knowledge.files = [...(knowledge.files ?? []), fileItem];
// Check if the file is an audio file and transcribe/convert it to text file
if (['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/x-m4a'].includes(file['type'])) {
const res = await transcribeAudio(localStorage.token, file).catch((error) => {
toast.error(`${error}`);
return null;
});
if (res) {
console.log(res);
const blob = new Blob([res.text], { type: 'text/plain' });
file = blobToFile(blob, `${file.name}.txt`);
}
}
try {
const uploadedFile = await uploadFile(localStorage.token, file).catch((e) => {
toast.error(`${e}`);
@@ -180,7 +180,6 @@
}
if (model) {
console.log(model);
name = model.name;
await tick();
@@ -2,6 +2,7 @@
import { DropdownMenu } from 'bits-ui';
import { flyAndScale } from '$lib/utils/transitions';
import { getContext } from 'svelte';
import { config } from '$lib/stores';
import Dropdown from '$lib/components/common/Dropdown.svelte';
import GarbageBin from '$lib/components/icons/GarbageBin.svelte';
@@ -44,15 +45,17 @@
align="start"
transition={flyAndScale}
>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
shareHandler();
}}
>
<Share />
<div class="flex items-center">{$i18n.t('Share')}</div>
</DropdownMenu.Item>
{#if $config.features.enable_community_sharing}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
shareHandler();
}}
>
<Share />
<div class="flex items-center">{$i18n.t('Share')}</div>
</DropdownMenu.Item>
{/if}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
@@ -12,6 +12,7 @@
import ArchiveBox from '$lib/components/icons/ArchiveBox.svelte';
import DocumentDuplicate from '$lib/components/icons/DocumentDuplicate.svelte';
import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
import { config } from '$lib/stores';
const i18n = getContext('i18n');
@@ -69,15 +70,17 @@
<div class="flex items-center">{$i18n.t('Edit')}</div>
</DropdownMenu.Item>
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
shareHandler();
}}
>
<Share />
<div class="flex items-center">{$i18n.t('Share')}</div>
</DropdownMenu.Item>
{#if $config.features.enable_community_sharing}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
on:click={() => {
shareHandler();
}}
>
<Share />
<div class="flex items-center">{$i18n.t('Share')}</div>
</DropdownMenu.Item>
{/if}
<DropdownMenu.Item
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-md"
@@ -1,5 +1,5 @@
<script>
import { getContext, createEventDispatcher, onMount, tick } from 'svelte';
import { getContext, onMount, tick } from 'svelte';
const i18n = getContext('i18n');
@@ -12,8 +12,6 @@
import LockClosed from '$lib/components/icons/LockClosed.svelte';
import AccessControlModal from '../common/AccessControlModal.svelte';
const dispatch = createEventDispatcher();
let formElement = null;
let loading = false;
@@ -23,6 +21,8 @@
export let edit = false;
export let clone = false;
export let onSave = () => {};
export let id = '';
export let name = '';
export let meta = {
@@ -150,7 +150,7 @@ class Tools:
const saveHandler = async () => {
loading = true;
dispatch('save', {
onSave({
id,
name,
meta,
@@ -284,10 +284,10 @@ class Tools:
value={content}
{boilerplate}
lang="python"
on:change={(e) => {
_content = e.detail.value;
onChange={(e) => {
_content = e;
}}
on:save={() => {
onSave={() => {
if (formElement) {
formElement.requestSubmit();
}