mirror of
https://github.com/open-webui/open-webui
synced 2024-11-28 23:13:30 +00:00
feat: background image
This commit is contained in:
parent
a28ad06bf0
commit
5fa355e1ae
@ -273,6 +273,7 @@
|
|||||||
id: m.id,
|
id: m.id,
|
||||||
role: m.role,
|
role: m.role,
|
||||||
content: m.content,
|
content: m.content,
|
||||||
|
info: m.info ? m.info : undefined,
|
||||||
timestamp: m.timestamp
|
timestamp: m.timestamp
|
||||||
})),
|
})),
|
||||||
chat_id: $chatId
|
chat_id: $chatId
|
||||||
@ -1322,6 +1323,19 @@
|
|||||||
? 'md:max-w-[calc(100%-260px)]'
|
? 'md:max-w-[calc(100%-260px)]'
|
||||||
: ''} w-full max-w-full flex flex-col"
|
: ''} 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
|
<Navbar
|
||||||
{title}
|
{title}
|
||||||
bind:selectedModels
|
bind:selectedModels
|
||||||
@ -1333,7 +1347,9 @@
|
|||||||
|
|
||||||
{#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
|
{#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
|
||||||
<div
|
<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">
|
<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}
|
{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
|
||||||
@ -1358,9 +1374,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="flex flex-col flex-auto">
|
<div class="flex flex-col flex-auto z-10">
|
||||||
<div
|
<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"
|
id="messages-container"
|
||||||
bind:this={messagesContainerElement}
|
bind:this={messagesContainerElement}
|
||||||
on:scroll={(e) => {
|
on:scroll={(e) => {
|
||||||
|
@ -13,6 +13,10 @@
|
|||||||
|
|
||||||
export let saveSettings: Function;
|
export let saveSettings: Function;
|
||||||
|
|
||||||
|
let backgroundImageUrl = null;
|
||||||
|
let inputFiles = null;
|
||||||
|
let filesInputElement;
|
||||||
|
|
||||||
// Addons
|
// Addons
|
||||||
let titleAutoGenerate = true;
|
let titleAutoGenerate = true;
|
||||||
let responseAutoCopy = false;
|
let responseAutoCopy = false;
|
||||||
@ -132,6 +136,8 @@
|
|||||||
userLocation = $settings.userLocation ?? false;
|
userLocation = $settings.userLocation ?? false;
|
||||||
|
|
||||||
defaultModelId = ($settings?.models ?? ['']).at(0);
|
defaultModelId = ($settings?.models ?? ['']).at(0);
|
||||||
|
|
||||||
|
backgroundImageUrl = $settings.backgroundImageUrl ?? null;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -142,13 +148,63 @@
|
|||||||
dispatch('save');
|
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>
|
||||||
<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>
|
||||||
<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">{$i18n.t('Chat Bubble UI')}</div>
|
<div class=" self-center text-xs">{$i18n.t('Chat Bubble UI')}</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="p-1 px-3 text-xs flex rounded transition"
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
@ -166,112 +222,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</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}
|
{#if !$settings.chatBubble}
|
||||||
<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">
|
||||||
{$i18n.t('Display the username instead of You in the Chat')}
|
{$i18n.t('Display the username instead of You in the Chat')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -294,7 +248,45 @@
|
|||||||
|
|
||||||
<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">{$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')}
|
{$i18n.t('Fluidly stream large external response chunks')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -313,46 +305,118 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<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">{$i18n.t('Chat direction')}</div>
|
<div class=" self-center text-xs">
|
||||||
|
{$i18n.t('Chat Background Image')}
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="p-1 px-3 text-xs flex rounded transition"
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
on:click={toggleChangeChatDirection}
|
on:click={() => {
|
||||||
|
if (backgroundImageUrl !== null) {
|
||||||
|
backgroundImageUrl = null;
|
||||||
|
saveSettings({ backgroundImageUrl });
|
||||||
|
} else {
|
||||||
|
filesInputElement.click();
|
||||||
|
}
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{#if chatDirection === 'LTR'}
|
{#if backgroundImageUrl !== null}
|
||||||
<span class="ml-2 self-center">{$i18n.t('LTR')}</span>
|
<span class="ml-2 self-center">{$i18n.t('Reset')}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="ml-2 self-center">{$i18n.t('RTL')}</span>
|
<span class="ml-2 self-center">{$i18n.t('Upload')}</span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class=" dark:border-gray-850" />
|
<div class=" my-1.5 text-sm font-medium">{$i18n.t('Chat')}</div>
|
||||||
|
|
||||||
<div class=" space-y-1 mb-3">
|
<div>
|
||||||
<div class="mb-2">
|
<div class=" py-0.5 flex w-full justify-between">
|
||||||
<div class="flex justify-between items-center text-xs">
|
<div class=" self-center text-xs">{$i18n.t('Title Auto-Generation')}</div>
|
||||||
<div class=" text-xs font-medium">{$i18n.t('Default Model')}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-1 mr-2">
|
<button
|
||||||
<select
|
class="p-1 px-3 text-xs flex rounded transition"
|
||||||
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
|
on:click={() => {
|
||||||
bind:value={defaultModelId}
|
toggleTitleAutoGenerate();
|
||||||
placeholder="Select a model"
|
}}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
{#if titleAutoGenerate === true}
|
||||||
{#each $models.filter((model) => model.id) as model}
|
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||||
<option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
|
{:else}
|
||||||
{/each}
|
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||||
</select>
|
{/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>
|
</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">
|
<script lang="ts">
|
||||||
import Chat from '$lib/components/chat/Chat.svelte';
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import Chat from '$lib/components/chat/Chat.svelte';
|
||||||
|
import Help from '$lib/components/layout/Help.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<Help />
|
||||||
<Chat chatIdProp={$page.params.id} />
|
<Chat chatIdProp={$page.params.id} />
|
||||||
|
Loading…
Reference in New Issue
Block a user