mirror of
https://github.com/open-webui/open-webui
synced 2024-11-24 21:13:59 +00:00
feat: background image
This commit is contained in:
parent
a28ad06bf0
commit
5fa355e1ae
@ -273,6 +273,7 @@
|
||||
id: m.id,
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
info: m.info ? m.info : undefined,
|
||||
timestamp: m.timestamp
|
||||
})),
|
||||
chat_id: $chatId
|
||||
@ -1322,6 +1323,19 @@
|
||||
? 'md:max-w-[calc(100%-260px)]'
|
||||
: ''} w-full max-w-full flex flex-col"
|
||||
>
|
||||
{#if $settings?.backgroundImageUrl ?? null}
|
||||
<div
|
||||
class="absolute {$showSidebar
|
||||
? 'md:max-w-[calc(100%-260px)] md:translate-x-[260px]'
|
||||
: ''} top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
|
||||
style="background-image: url({$settings.backgroundImageUrl}) "
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full bg-gradient-to-t from-white to-white/90 dark:from-gray-900 dark:to-[#171717]/90 z-0 -translate-y-[78px]"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<Navbar
|
||||
{title}
|
||||
bind:selectedModels
|
||||
@ -1333,7 +1347,9 @@
|
||||
|
||||
{#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
|
||||
<div
|
||||
class="absolute top-[4.25rem] w-full {$showSidebar ? 'md:max-w-[calc(100%-260px)]' : ''}"
|
||||
class="absolute top-[4.25rem] w-full {$showSidebar
|
||||
? 'md:max-w-[calc(100%-260px)]'
|
||||
: ''} z-0"
|
||||
>
|
||||
<div class=" flex flex-col gap-1 w-full">
|
||||
{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
|
||||
@ -1358,9 +1374,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col flex-auto">
|
||||
<div class="flex flex-col flex-auto z-10">
|
||||
<div
|
||||
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full"
|
||||
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0 max-w-full z-10"
|
||||
id="messages-container"
|
||||
bind:this={messagesContainerElement}
|
||||
on:scroll={(e) => {
|
||||
|
@ -13,6 +13,10 @@
|
||||
|
||||
export let saveSettings: Function;
|
||||
|
||||
let backgroundImageUrl = null;
|
||||
let inputFiles = null;
|
||||
let filesInputElement;
|
||||
|
||||
// Addons
|
||||
let titleAutoGenerate = true;
|
||||
let responseAutoCopy = false;
|
||||
@ -132,6 +136,8 @@
|
||||
userLocation = $settings.userLocation ?? false;
|
||||
|
||||
defaultModelId = ($settings?.models ?? ['']).at(0);
|
||||
|
||||
backgroundImageUrl = $settings.backgroundImageUrl ?? null;
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -142,13 +148,63 @@
|
||||
dispatch('save');
|
||||
}}
|
||||
>
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem]">
|
||||
<input
|
||||
bind:this={filesInputElement}
|
||||
bind:files={inputFiles}
|
||||
type="file"
|
||||
hidden
|
||||
accept="image/*"
|
||||
on:change={() => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
let originalImageUrl = `${event.target.result}`;
|
||||
|
||||
backgroundImageUrl = originalImageUrl;
|
||||
saveSettings({ backgroundImageUrl });
|
||||
};
|
||||
|
||||
if (
|
||||
inputFiles &&
|
||||
inputFiles.length > 0 &&
|
||||
['image/gif', 'image/webp', 'image/jpeg', 'image/png'].includes(inputFiles[0]['type'])
|
||||
) {
|
||||
reader.readAsDataURL(inputFiles[0]);
|
||||
} else {
|
||||
console.log(`Unsupported File Type '${inputFiles[0]['type']}'.`);
|
||||
inputFiles = null;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-[25rem] scrollbar-hidden">
|
||||
<div class=" space-y-1 mb-3">
|
||||
<div class="mb-2">
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<div class=" text-sm font-medium">{$i18n.t('Default Model')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 mr-2">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
bind:value={defaultModelId}
|
||||
placeholder="Select a model"
|
||||
>
|
||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
||||
{#each $models.filter((model) => model.id) as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<hr class=" dark:border-gray-850" />
|
||||
|
||||
<div>
|
||||
<div class=" mb-1 text-sm font-medium">{$i18n.t('WebUI Add-ons')}</div>
|
||||
<div class=" mb-1.5 text-sm font-medium">{$i18n.t('UI')}</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Chat Bubble UI')}</div>
|
||||
<div class=" self-center text-xs">{$i18n.t('Chat Bubble UI')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
@ -166,112 +222,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Widescreen Mode')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
togglewidescreenMode();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if widescreenMode === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Title Auto-Generation')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleTitleAutoGenerate();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if titleAutoGenerate === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Response AutoCopy to Clipboard')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleResponseAutoCopy();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if responseAutoCopy === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Allow User Location')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleUserLocation();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if userLocation === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Display Emoji in Call')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleEmojiInCall();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if showEmojiInCall === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !$settings.chatBubble}
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Display the username instead of You in the Chat')}
|
||||
</div>
|
||||
|
||||
@ -294,7 +248,45 @@
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
<div class=" self-center text-xs">{$i18n.t('Widescreen Mode')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
togglewidescreenMode();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if widescreenMode === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">{$i18n.t('Chat direction')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={toggleChangeChatDirection}
|
||||
type="button"
|
||||
>
|
||||
{#if chatDirection === 'LTR'}
|
||||
<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Fluidly stream large external response chunks')}
|
||||
</div>
|
||||
|
||||
@ -313,46 +305,118 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">{$i18n.t('Chat direction')}</div>
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Chat Background Image')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={toggleChangeChatDirection}
|
||||
type="button"
|
||||
>
|
||||
{#if chatDirection === 'LTR'}
|
||||
<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-850" />
|
||||
|
||||
<div class=" space-y-1 mb-3">
|
||||
<div class="mb-2">
|
||||
<div class="flex justify-between items-center text-xs">
|
||||
<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
if (backgroundImageUrl !== null) {
|
||||
backgroundImageUrl = null;
|
||||
saveSettings({ backgroundImageUrl });
|
||||
} else {
|
||||
filesInputElement.click();
|
||||
}
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if backgroundImageUrl !== null}
|
||||
<span class="ml-2 self-center">{$i18n.t('Reset')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Upload')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 mr-2">
|
||||
<select
|
||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
||||
bind:value={defaultModelId}
|
||||
placeholder="Select a model"
|
||||
>
|
||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
||||
{#each $models.filter((model) => model.id) as model}
|
||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<div class=" my-1.5 text-sm font-medium">{$i18n.t('Chat')}</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">{$i18n.t('Title Auto-Generation')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleTitleAutoGenerate();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if titleAutoGenerate === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">
|
||||
{$i18n.t('Response AutoCopy to Clipboard')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleResponseAutoCopy();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if responseAutoCopy === 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>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">{$i18n.t('Allow User Location')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleUserLocation();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if userLocation === 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=" my-1.5 text-sm font-medium">{$i18n.t('Voice')}</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs">{$i18n.t('Display Emoji in Call')}</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleEmojiInCall();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if showEmojiInCall === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
0
src/lib/utils/characters/index.ts
Normal file
0
src/lib/utils/characters/index.ts
Normal file
@ -1,6 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Chat from '$lib/components/chat/Chat.svelte';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
import Chat from '$lib/components/chat/Chat.svelte';
|
||||
import Help from '$lib/components/layout/Help.svelte';
|
||||
</script>
|
||||
|
||||
<Help />
|
||||
<Chat chatIdProp={$page.params.id} />
|
||||
|
Loading…
Reference in New Issue
Block a user