Unverified Commit 72354e06 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #2476 from open-webui/dev

0.2.0
parents 36e2a5e6 207e2503
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { models, settings, user } from '$lib/stores'; import { models, settings, user } from '$lib/stores';
import { getModels as _getModels } from '$lib/utils'; import { getModels as _getModels } from '$lib/apis';
import Modal from '../common/Modal.svelte'; import Modal from '../common/Modal.svelte';
import Account from './Settings/Account.svelte'; import Account from './Settings/Account.svelte';
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
import Images from './Settings/Images.svelte'; import Images from './Settings/Images.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 { updateUserSettings } from '$lib/apis/users';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -26,7 +27,7 @@ ...@@ -26,7 +27,7 @@
console.log(updated); console.log(updated);
await settings.set({ ...$settings, ...updated }); await settings.set({ ...$settings, ...updated });
await models.set(await getModels()); await models.set(await getModels());
localStorage.setItem('settings', JSON.stringify($settings)); await updateUserSettings(localStorage.token, { ui: $settings });
}; };
const getModels = async () => { const getModels = async () => {
......
<script lang="ts"> <script lang="ts">
import { getContext, onMount } from 'svelte'; import { getContext, onMount } from 'svelte';
import { models, config } from '$lib/stores';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats'; import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
import { modelfiles } from '$lib/stores';
import { copyToClipboard } from '$lib/utils'; import { copyToClipboard } from '$lib/utils';
import Modal from '../common/Modal.svelte'; import Modal from '../common/Modal.svelte';
...@@ -43,9 +43,7 @@ ...@@ -43,9 +43,7 @@
tab.postMessage( tab.postMessage(
JSON.stringify({ JSON.stringify({
chat: _chat, chat: _chat,
modelfiles: $modelfiles.filter((modelfile) => models: $models.filter((m) => _chat.models.includes(m.id))
_chat.models.includes(modelfile.tagName)
)
}), }),
'*' '*'
); );
...@@ -136,16 +134,18 @@ ...@@ -136,16 +134,18 @@
<div class="flex justify-end"> <div class="flex justify-end">
<div class="flex flex-col items-end space-x-1 mt-1.5"> <div class="flex flex-col items-end space-x-1 mt-1.5">
<div class="flex gap-1"> <div class="flex gap-1">
<button {#if $config?.features.enable_community_sharing}
class=" self-center px-3.5 py-2 rounded-xl text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white" <button
type="button" class=" self-center px-3.5 py-2 rounded-xl text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-white"
on:click={() => { type="button"
shareChat(); on:click={() => {
show = false; shareChat();
}} show = false;
> }}
{$i18n.t('Share to OpenWebUI Community')} >
</button> {$i18n.t('Share to OpenWebUI Community')}
</button>
{/if}
<button <button
class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white" class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
......
<script lang="ts">
import type { Banner } from '$lib/types';
import { onMount, createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
const dispatch = createEventDispatcher();
export let banner: Banner = {
id: '',
type: 'info',
title: '',
content: '',
url: '',
dismissable: true,
timestamp: Math.floor(Date.now() / 1000)
};
export let dismissed = false;
let mounted = false;
const classNames: Record<string, string> = {
info: 'bg-blue-500/20 text-blue-700 dark:text-blue-200 ',
success: 'bg-green-500/20 text-green-700 dark:text-green-200',
warning: 'bg-yellow-500/20 text-yellow-700 dark:text-yellow-200',
error: 'bg-red-500/20 text-red-700 dark:text-red-200'
};
const dismiss = (id) => {
dismissed = true;
dispatch('dismiss', id);
};
onMount(() => {
mounted = true;
});
</script>
{#if !dismissed}
{#if mounted}
<div
class=" top-0 left-0 right-0 p-2 mx-4 px-3 flex justify-center items-center relative rounded-xl border border-gray-50 dark:border-gray-850 text-gray-800 dark:text-gary-100 bg-white dark:bg-gray-900 backdrop-blur-xl z-40"
transition:fade={{ delay: 100, duration: 300 }}
>
<div class=" flex flex-col md:flex-row md:items-center flex-1 text-sm w-fit gap-1.5">
<div class="flex justify-between self-start">
<div
class=" text-xs font-black {classNames[banner.type] ??
classNames['info']} w-fit px-2 rounded uppercase line-clamp-1 mr-0.5"
>
{banner.type}
</div>
{#if banner.url}
<div class="flex md:hidden group w-fit md:items-center">
<a
class="text-gray-700 dark:text-white text-xs font-bold underline"
href="/assets/files/whitepaper.pdf"
target="_blank">Learn More</a
>
<div
class=" ml-1 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-white"
>
<!-- -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
{/if}
</div>
<div class="flex-1 text-xs text-gray-700 dark:text-white">
{banner.content}
</div>
</div>
{#if banner.url}
<div class="hidden md:flex group w-fit md:items-center">
<a
class="text-gray-700 dark:text-white text-xs font-bold underline"
href="/"
target="_blank">Learn More</a
>
<div class=" ml-1 text-gray-400 group-hover:text-gray-600 dark:group-hover:text-white">
<!-- -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="size-4"
>
<path
fill-rule="evenodd"
d="M4.22 11.78a.75.75 0 0 1 0-1.06L9.44 5.5H5.75a.75.75 0 0 1 0-1.5h5.5a.75.75 0 0 1 .75.75v5.5a.75.75 0 0 1-1.5 0V6.56l-5.22 5.22a.75.75 0 0 1-1.06 0Z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
{/if}
<div class="flex self-start">
{#if banner.dismissible}
<button
on:click={() => {
dismiss(banner.id);
}}
class=" -mt-[3px] ml-1.5 mr-1 text-gray-400 dark:hover:text-white h-1">&times;</button
>
{/if}
</div>
</div>
{/if}
{/if}
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
dispatch('change', _state); dispatch('change', _state);
} }
}} }}
type="button"
> >
<div class="top-0 left-0 absolute w-full flex justify-center"> <div class="top-0 left-0 absolute w-full flex justify-center">
{#if _state === 'checked'} {#if _state === 'checked'}
......
...@@ -10,9 +10,11 @@ ...@@ -10,9 +10,11 @@
<DropdownMenu.Root <DropdownMenu.Root
bind:open={show} bind:open={show}
closeFocus={false}
onOpenChange={(state) => { onOpenChange={(state) => {
dispatch('change', state); dispatch('change', state);
}} }}
typeahead={false}
> >
<DropdownMenu.Trigger> <DropdownMenu.Trigger>
<slot /> <slot />
......
...@@ -19,5 +19,5 @@ ...@@ -19,5 +19,5 @@
showImagePreview = true; showImagePreview = true;
}} }}
> >
<img src={_src} {alt} class=" max-h-96 rounded-lg" draggable="false" /> <img src={_src} {alt} class=" max-h-96 rounded-lg" draggable="false" data-cy="image" />
</button> </button>
<script lang="ts"> <script lang="ts">
export let className: string = ''; export let className: string = 'size-5';
</script> </script>
<div class="flex justify-center text-center {className}"> <div class="flex justify-center text-center">
<svg class="size-5" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" <svg class={className} viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"
><style> ><style>
.spinner_ajPY { .spinner_ajPY {
transform-origin: center; transform-origin: center;
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
export let addTag: Function; export let addTag: Function;
</script> </script>
<div class="flex flex-row flex-wrap gap-0.5 line-clamp-1"> <div class="flex flex-row flex-wrap gap-1 line-clamp-1">
<TagList <TagList
{tags} {tags}
on:delete={(e) => { on:delete={(e) => {
......
...@@ -22,26 +22,12 @@ ...@@ -22,26 +22,12 @@
}; };
</script> </script>
<div class="flex space-x-1 pl-1.5"> <div class="flex {showTagInput ? 'flex-row-reverse' : ''}">
{#if showTagInput} {#if showTagInput}
<div class="flex items-center"> <div class="flex items-center">
<button type="button" on:click={addTagHandler}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3 h-3"
>
<path
fill-rule="evenodd"
d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
clip-rule="evenodd"
/>
</svg>
</button>
<input <input
bind:value={tagName} bind:value={tagName}
class=" pl-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[5.5rem]" class=" px-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-none line-clamp-1 w-[5.5rem]"
placeholder={$i18n.t('Add a tag')} placeholder={$i18n.t('Add a tag')}
list="tagOptions" list="tagOptions"
on:keydown={(event) => { on:keydown={(event) => {
...@@ -55,11 +41,27 @@ ...@@ -55,11 +41,27 @@
<option value={tag.name} /> <option value={tag.name} />
{/each} {/each}
</datalist> </datalist>
<button type="button" on:click={addTagHandler}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
stroke-width="2"
class="w-3 h-3"
>
<path
fill-rule="evenodd"
d="M12.416 3.376a.75.75 0 0 1 .208 1.04l-5 7.5a.75.75 0 0 1-1.154.114l-3-3a.75.75 0 0 1 1.06-1.06l2.353 2.353 4.493-6.74a.75.75 0 0 1 1.04-.207Z"
clip-rule="evenodd"
/>
</svg>
</button>
</div> </div>
{/if} {/if}
<button <button
class=" cursor-pointer self-center p-0.5 space-x-1 flex h-fit items-center dark:hover:bg-gray-700 rounded-full transition border dark:border-gray-600 border-dashed" class=" cursor-pointer self-center p-0.5 flex h-fit items-center dark:hover:bg-gray-700 rounded-full transition border dark:border-gray-600 border-dashed"
type="button" type="button"
on:click={() => { on:click={() => {
showTagInput = !showTagInput; showTagInput = !showTagInput;
...@@ -80,6 +82,6 @@ ...@@ -80,6 +82,6 @@
</button> </button>
{#if label && !showTagInput} {#if label && !showTagInput}
<span class="text-xs pl-1.5 self-center">{label}</span> <span class="text-xs pl-2 self-center">{label}</span>
{/if} {/if}
</div> </div>
...@@ -7,22 +7,23 @@ ...@@ -7,22 +7,23 @@
{#each tags as tag} {#each tags as tag}
<div <div
class="px-2 py-0.5 space-x-1 flex h-fit items-center rounded-full transition border dark:border-gray-800 dark:text-white" class="px-2 py-[0.5px] gap-0.5 flex justify-between h-fit items-center rounded-full transition border dark:border-gray-800 dark:text-white"
> >
<div class=" text-[0.7rem] font-medium self-center line-clamp-1"> <div class=" text-[0.7rem] font-medium self-center line-clamp-1">
{tag.name} {tag.name}
</div> </div>
<button <button
class=" m-auto self-center cursor-pointer" class="h-full flex self-center cursor-pointer"
on:click={() => { on:click={() => {
dispatch('delete', tag.name); dispatch('delete', tag.name);
}} }}
type="button"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="currentColor" fill="currentColor"
class="w-3 h-3" class="size-3 m-auto self-center translate-y-[0.3px] translate-x-[3px]"
> >
<path <path
d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z" d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z"
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
export let placement = 'top'; export let placement = 'top';
export let content = `I'm a tooltip!`; export let content = `I'm a tooltip!`;
export let touch = true; export let touch = true;
export let className = 'flex';
let tooltipElement; let tooltipElement;
let tooltipInstance; let tooltipInstance;
...@@ -29,6 +30,6 @@ ...@@ -29,6 +30,6 @@
}); });
</script> </script>
<div bind:this={tooltipElement} aria-label={content} class="flex"> <div bind:this={tooltipElement} aria-label={content} class={className}>
<slot /> <slot />
</div> </div>
<script lang="ts"> <script lang="ts">
import { getRAGConfig, updateRAGConfig } from '$lib/apis/rag'; import { getRAGConfig, updateRAGConfig } from '$lib/apis/rag';
import Switch from '$lib/components/common/Switch.svelte';
import { documents, models } from '$lib/stores'; import { documents, models } from '$lib/stores';
import { onMount, getContext } from 'svelte'; import { onMount, getContext } from 'svelte';
...@@ -9,14 +10,15 @@ ...@@ -9,14 +10,15 @@
export let saveHandler: Function; export let saveHandler: Function;
let webLoaderSSLVerification = true; let webConfig = null;
let webSearchEngines = ['searxng', 'google_pse', 'brave', 'serpstack', 'serper'];
let youtubeLanguage = 'en'; let youtubeLanguage = 'en';
let youtubeTranslation = null; let youtubeTranslation = null;
const submitHandler = async () => { const submitHandler = async () => {
const res = await updateRAGConfig(localStorage.token, { const res = await updateRAGConfig(localStorage.token, {
web_loader_ssl_verification: webLoaderSSLVerification, web: webConfig,
youtube: { youtube: {
language: youtubeLanguage.split(',').map((lang) => lang.trim()), language: youtubeLanguage.split(',').map((lang) => lang.trim()),
translation: youtubeTranslation translation: youtubeTranslation
...@@ -28,7 +30,8 @@ ...@@ -28,7 +30,8 @@
const res = await getRAGConfig(localStorage.token); const res = await getRAGConfig(localStorage.token);
if (res) { if (res) {
webLoaderSSLVerification = res.web_loader_ssl_verification; webConfig = res.web;
youtubeLanguage = res.youtube.language.join(','); youtubeLanguage = res.youtube.language.join(',');
youtubeTranslation = res.youtube.translation; youtubeTranslation = res.youtube.translation;
} }
...@@ -37,59 +40,239 @@ ...@@ -37,59 +40,239 @@
<form <form
class="flex flex-col h-full justify-between space-y-3 text-sm" class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={() => { on:submit|preventDefault={async () => {
submitHandler(); await submitHandler();
saveHandler(); saveHandler();
}} }}
> >
<div class=" space-y-3 pr-1.5 overflow-y-scroll h-full max-h-[22rem]"> <div class=" space-y-3 pr-1.5 overflow-y-scroll h-full max-h-[22rem]">
<div> {#if webConfig}
<div class=" mb-1 text-sm font-medium">
{$i18n.t('Web Loader Settings')}
</div>
<div> <div>
<div class=" mb-1 text-sm font-medium">
{$i18n.t('Web Search')}
</div>
<div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Enable Web Search')}
</div>
<Switch bind:state={webConfig.search.enabled} />
</div>
</div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium"> <div class=" self-center text-xs font-medium">{$i18n.t('Web Search Engine')}</div>
{$i18n.t('Bypass SSL verification for Websites')} <div class="flex items-center relative">
<select
class="dark:bg-gray-900 w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={webConfig.search.engine}
placeholder="Select a engine"
required
>
<option disabled selected value="">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 === 'searxng'}
<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 dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter Searxng Query URL')}
bind:value={webConfig.search.searxng_query_url}
autocomplete="off"
/>
</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="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter Google PSE API Key')}
bind:value={webConfig.search.google_pse_api_key}
autocomplete="off"
/>
</div>
</div>
</div>
<div class="mt-1.5">
<div class=" self-center text-xs font-medium mb-1">
{$i18n.t('Google PSE Engine Id')}
</div>
<button <div class="flex w-full">
class="p-1 px-3 text-xs flex rounded transition" <div class="flex-1">
on:click={() => { <input
webLoaderSSLVerification = !webLoaderSSLVerification; class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
submitHandler(); type="text"
}} placeholder={$i18n.t('Enter Google PSE Engine Id')}
type="button" bind:value={webConfig.search.google_pse_engine_id}
> autocomplete="off"
{#if webLoaderSSLVerification === true} />
<span class="ml-2 self-center">{$i18n.t('On')}</span> </div>
{:else} </div>
<span class="ml-2 self-center">{$i18n.t('Off')}</span> </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="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter Brave Search API Key')}
bind:value={webConfig.search.brave_search_api_key}
autocomplete="off"
/>
</div>
</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="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter Serpstack API Key')}
bind:value={webConfig.search.serpstack_api_key}
autocomplete="off"
/>
</div>
</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="flex w-full">
<div class="flex-1">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter Serper API Key')}
bind:value={webConfig.search.serper_api_key}
autocomplete="off"
/>
</div>
</div>
</div>
{/if} {/if}
</button> </div>
</div> {/if}
</div>
{#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')}
</div>
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Search Result Count')}
bind:value={webConfig.search.result_count}
required
/>
</div>
<div class=" mt-2 mb-1 text-sm font-medium"> <div class="w-full">
{$i18n.t('Youtube Loader Settings')} <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 dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Concurrent Requests')}
bind:value={webConfig.search.concurrent_requests}
required
/>
</div>
</div>
{/if}
</div> </div>
<hr class=" dark:border-gray-850 my-2" />
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" mb-1 text-sm font-medium">
<div class=" w-20 text-xs font-medium self-center">{$i18n.t('Language')}</div> {$i18n.t('Web Loader Settings')}
<div class=" flex-1 self-center"> </div>
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none" <div>
type="text" <div class=" py-0.5 flex w-full justify-between">
placeholder={$i18n.t('Enter language codes')} <div class=" self-center text-xs font-medium">
bind:value={youtubeLanguage} {$i18n.t('Bypass SSL verification for Websites')}
autocomplete="off" </div>
/>
<button
class="p-1 px-3 text-xs flex rounded transition"
on:click={() => {
webConfig.ssl_verification = !webConfig.ssl_verification;
submitHandler();
}}
type="button"
>
{#if webConfig.ssl_verification === true}
<span class="ml-2 self-center">{$i18n.t('On')}</span>
{:else}
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
{/if}
</button>
</div>
</div>
<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">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
type="text"
placeholder={$i18n.t('Enter language codes')}
bind:value={youtubeLanguage}
autocomplete="off"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> {/if}
</div> </div>
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
......
<script> <script>
import { getContext } from 'svelte'; import { getContext, tick } from 'svelte';
import Modal from '../common/Modal.svelte'; import Modal from '../common/Modal.svelte';
import General from './Settings/General.svelte'; import General from './Settings/General.svelte';
import ChunkParams from './Settings/ChunkParams.svelte'; import ChunkParams from './Settings/ChunkParams.svelte';
import QueryParams from './Settings/QueryParams.svelte'; import QueryParams from './Settings/QueryParams.svelte';
import WebParams from './Settings/WebParams.svelte'; import WebParams from './Settings/WebParams.svelte';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { config } from '$lib/stores';
import { getBackendConfig } from '$lib/apis';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -171,8 +173,11 @@ ...@@ -171,8 +173,11 @@
/> />
{:else if selectedTab === 'web'} {:else if selectedTab === 'web'}
<WebParams <WebParams
saveHandler={() => { saveHandler={async () => {
toast.success($i18n.t('Settings saved successfully!')); toast.success($i18n.t('Settings saved successfully!'));
await tick();
await config.set(await getBackendConfig());
}} }}
/> />
{/if} {/if}
......
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5" />
</svg>
<script lang="ts">
export let className = 'size-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm6.905 9.97a.75.75 0 0 0-1.06 0l-3 3a.75.75 0 1 0 1.06 1.06l1.72-1.72V18a.75.75 0 0 0 1.5 0v-4.19l1.72 1.72a.75.75 0 1 0 1.06-1.06l-3-3Z"
clip-rule="evenodd"
/>
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg>
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
/>
</svg>
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width={strokeWidth}
stroke="currentColor"
class={className}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
/>
</svg>
<script lang="ts">
export let className = 'size-4';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class={className}>
<path
d="M21.721 12.752a9.711 9.711 0 0 0-.945-5.003 12.754 12.754 0 0 1-4.339 2.708 18.991 18.991 0 0 1-.214 4.772 17.165 17.165 0 0 0 5.498-2.477ZM14.634 15.55a17.324 17.324 0 0 0 .332-4.647c-.952.227-1.945.347-2.966.347-1.021 0-2.014-.12-2.966-.347a17.515 17.515 0 0 0 .332 4.647 17.385 17.385 0 0 0 5.268 0ZM9.772 17.119a18.963 18.963 0 0 0 4.456 0A17.182 17.182 0 0 1 12 21.724a17.18 17.18 0 0 1-2.228-4.605ZM7.777 15.23a18.87 18.87 0 0 1-.214-4.774 12.753 12.753 0 0 1-4.34-2.708 9.711 9.711 0 0 0-.944 5.004 17.165 17.165 0 0 0 5.498 2.477ZM21.356 14.752a9.765 9.765 0 0 1-7.478 6.817 18.64 18.64 0 0 0 1.988-4.718 18.627 18.627 0 0 0 5.49-2.098ZM2.644 14.752c1.682.971 3.53 1.688 5.49 2.099a18.64 18.64 0 0 0 1.988 4.718 9.765 9.765 0 0 1-7.478-6.816ZM13.878 2.43a9.755 9.755 0 0 1 6.116 3.986 11.267 11.267 0 0 1-3.746 2.504 18.63 18.63 0 0 0-2.37-6.49ZM12 2.276a17.152 17.152 0 0 1 2.805 7.121c-.897.23-1.837.353-2.805.353-.968 0-1.908-.122-2.805-.353A17.151 17.151 0 0 1 12 2.276ZM10.122 2.43a18.629 18.629 0 0 0-2.37 6.49 11.266 11.266 0 0 1-3.746-2.504 9.754 9.754 0 0 1 6.116-3.985Z"
/>
</svg>
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
WEBUI_NAME, WEBUI_NAME,
chatId, chatId,
mobile, mobile,
modelfiles,
settings, settings,
showArchivedChats, showArchivedChats,
showSettings, showSettings,
......
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
getChatListByTagName, getChatListByTagName,
updateChatById, updateChatById,
getAllChatTags, getAllChatTags,
archiveChatById archiveChatById,
cloneChatById
} from '$lib/apis/chats'; } from '$lib/apis/chats';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { fade, slide } from 'svelte/transition'; import { fade, slide } from 'svelte/transition';
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
import ArchiveBox from '../icons/ArchiveBox.svelte'; import ArchiveBox from '../icons/ArchiveBox.svelte';
import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte'; import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte';
import UserMenu from './Sidebar/UserMenu.svelte'; import UserMenu from './Sidebar/UserMenu.svelte';
import { updateUserSettings } from '$lib/apis/users';
const BREAKPOINT = 768; const BREAKPOINT = 768;
...@@ -181,9 +183,21 @@ ...@@ -181,9 +183,21 @@
} }
}; };
const cloneChatHandler = async (id) => {
const res = await cloneChatById(localStorage.token, id).catch((error) => {
toast.error(error);
return null;
});
if (res) {
goto(`/c/${res.id}`);
await chats.set(await getChatList(localStorage.token));
}
};
const saveSettings = async (updated) => { const saveSettings = async (updated) => {
await settings.set({ ...$settings, ...updated }); await settings.set({ ...$settings, ...updated });
localStorage.setItem('settings', JSON.stringify($settings)); await updateUserSettings(localStorage.token, { ui: $settings });
location.href = '/'; location.href = '/';
}; };
...@@ -600,6 +614,9 @@ ...@@ -600,6 +614,9 @@
<div class="flex self-center space-x-1 z-10"> <div class="flex self-center space-x-1 z-10">
<ChatMenu <ChatMenu
chatId={chat.id} chatId={chat.id}
cloneChatHandler={() => {
cloneChatHandler(chat.id);
}}
shareHandler={() => { shareHandler={() => {
shareChatId = selectedChatId; shareChatId = selectedChatId;
showShareChatModal = true; showShareChatModal = true;
......
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