Skip to content

Commit c98e1ce

Browse files
Merge remote-tracking branch 'upstream/dev' into dev
2 parents 1741141 + 6cd35b1 commit c98e1ce

8 files changed

Lines changed: 106 additions & 118 deletions

File tree

backend/open_webui/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3450,6 +3450,12 @@ class BannerModel(BaseModel):
34503450
os.getenv("IMAGE_GENERATION_MODEL", ""),
34513451
)
34523452

3453+
# Regex pattern for models that support IMAGE_SIZE = "auto".
3454+
IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN = os.getenv("IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN", "^gpt-image")
3455+
3456+
# Regex pattern for models that return URLs instead of base64 data.
3457+
IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN = os.getenv("IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN", "^gpt-image")
3458+
34533459
IMAGE_SIZE = PersistentConfig(
34543460
"IMAGE_SIZE", "image_generation.size", os.getenv("IMAGE_SIZE", "512x512")
34553461
)

backend/open_webui/routers/images.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile
1515
from fastapi.responses import FileResponse
1616

17-
from open_webui.config import CACHE_DIR
17+
from open_webui.config import CACHE_DIR, IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN
1818
from open_webui.constants import ERROR_MESSAGES
1919
from open_webui.retrieval.web.utils import validate_url
2020
from open_webui.env import ENABLE_FORWARD_USER_INFO_HEADERS
@@ -201,12 +201,12 @@ async def update_config(
201201
set_image_model(request, form_data.IMAGE_GENERATION_MODEL)
202202
if (
203203
form_data.IMAGE_SIZE == "auto"
204-
and not form_data.IMAGE_GENERATION_MODEL.startswith("gpt-image")
204+
and not re.match(IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN, form_data.IMAGE_GENERATION_MODEL)
205205
):
206206
raise HTTPException(
207207
status_code=400,
208208
detail=ERROR_MESSAGES.INCORRECT_FORMAT(
209-
" (auto is only allowed with gpt-image models)."
209+
f" (auto is only allowed with models matching {IMAGE_AUTO_SIZE_MODELS_REGEX_PATTERN})."
210210
),
211211
)
212212

@@ -610,9 +610,7 @@ async def image_generations(
610610
),
611611
**(
612612
{}
613-
if request.app.state.config.IMAGE_GENERATION_MODEL.startswith(
614-
"gpt-image"
615-
)
613+
if re.match(IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, request.app.state.config.IMAGE_GENERATION_MODEL)
616614
else {"response_format": "b64_json"}
617615
),
618616
**(
@@ -949,7 +947,7 @@ def get_image_file_item(base64_string, param_name="image"):
949947
**({"size": size} if size else {}),
950948
**(
951949
{}
952-
if request.app.state.config.IMAGE_EDIT_MODEL.startswith("gpt-image")
950+
if re.match(IMAGE_URL_RESPONSE_MODELS_REGEX_PATTERN, request.app.state.config.IMAGE_EDIT_MODEL)
953951
else {"response_format": "b64_json"}
954952
),
955953
}

src/lib/components/chat/Messages/RateComment.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@
258258
class="flex cursor-pointer items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-850 w-full px-3 py-2 rounded-xl transition"
259259
>
260260
<div class="self-center">
261-
<div class="text-sm font-medium">{$i18n.t('Leave a review for {{modelName}}', { modelName: message.model })}</div>
261+
<div class="text-sm font-medium">{$i18n.t('Leave a public review for {{modelName}}', { modelName: message.model })}</div>
262262
<div class="text-xs text-gray-500">{$i18n.t('Help the community discover great models')}</div>
263263
</div>
264264
<ChevronRight className="size-4" />

src/lib/components/chat/ModelSelector/ModelItemMenu.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@
9696
</DropdownMenu.Item>
9797

9898
{#if $config?.features.enable_community_sharing}
99+
<hr class="border-gray-50 dark:border-gray-850/30 my-1" />
100+
99101
<DropdownMenu.Item
100102
type="button"
101103
class="flex rounded-xl py-1.5 px-3 w-full hover:bg-gray-50 dark:hover:bg-gray-800 transition items-center gap-2"
Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
<script lang="ts">
2-
import TagInput from './Tags/TagInput.svelte';
32
import TagList from './Tags/TagList.svelte';
43
import { getContext, createEventDispatcher } from 'svelte';
54
const dispatch = createEventDispatcher();
@@ -8,21 +7,37 @@
87
98
export let tags = [];
109
export let suggestionTags = [];
10+
11+
let inputValue = '';
12+
13+
const addTag = () => {
14+
const value = inputValue.trim();
15+
if (value !== '') {
16+
dispatch('add', value);
17+
inputValue = '';
18+
}
19+
};
1120
</script>
1221

13-
<ul class="flex flex-row flex-wrap gap-[0.3rem] line-clamp-1">
22+
<div class="flex flex-wrap items-center gap-1 w-full">
1423
<TagList
1524
{tags}
1625
on:delete={(e) => {
1726
dispatch('delete', e.detail);
1827
}}
1928
/>
2029

21-
<TagInput
22-
label={tags.length == 0 ? $i18n.t('Add Tags') : ''}
23-
{suggestionTags}
24-
on:add={(e) => {
25-
dispatch('add', e.detail);
30+
<input
31+
bind:value={inputValue}
32+
class="flex-1 min-w-24 {tags.length > 0
33+
? 'px-0.5'
34+
: ''} text-xs bg-transparent outline-hidden placeholder:text-gray-400 dark:placeholder:text-gray-500"
35+
placeholder={$i18n.t('Add a tag...')}
36+
on:keydown={(event) => {
37+
if (event.key === 'Enter' || event.key === ' ') {
38+
event.preventDefault();
39+
addTag();
40+
}
2641
}}
2742
/>
28-
</ul>
43+
</div>
Lines changed: 47 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,72 @@
11
<script lang="ts">
22
import { createEventDispatcher, getContext } from 'svelte';
3-
import { tags } from '$lib/stores';
4-
import { toast } from 'svelte-sonner';
53
const dispatch = createEventDispatcher();
64
75
const i18n = getContext('i18n');
86
9-
export let label = '';
107
export let suggestionTags = [];
118
12-
let showTagInput = false;
139
let tagName = '';
10+
let showInput = false;
11+
let inputElement: HTMLInputElement;
1412
1513
const addTagHandler = async () => {
1614
tagName = tagName.trim();
1715
if (tagName !== '') {
1816
dispatch('add', tagName);
1917
tagName = '';
20-
showTagInput = false;
21-
} else {
22-
toast.error($i18n.t(`Invalid Tag`));
2318
}
2419
};
25-
</script>
2620
27-
<div class="px-0.5 flex {showTagInput ? 'flex-row-reverse' : ''}">
28-
{#if showTagInput}
29-
<div class="flex items-center">
30-
<input
31-
bind:value={tagName}
32-
class=" px-2 cursor-pointer self-center text-xs h-fit bg-transparent outline-hidden line-clamp-1 w-[6.5rem]"
33-
placeholder={$i18n.t('Add a tag')}
34-
aria-label={$i18n.t('Add a tag')}
35-
list="tagOptions"
36-
on:keydown={(event) => {
37-
if (event.key === 'Enter') {
38-
addTagHandler();
39-
}
40-
}}
41-
/>
42-
{#if suggestionTags.length > 0}
43-
<datalist id="tagOptions">
44-
{#each suggestionTags as tag}
45-
<option value={tag.name} />
46-
{/each}
47-
</datalist>
48-
{/if}
21+
const openInput = () => {
22+
showInput = true;
23+
setTimeout(() => inputElement?.focus(), 0);
24+
};
4925
50-
<button type="button" aria-label={$i18n.t('Save Tag')} on:click={addTagHandler}>
51-
<svg
52-
xmlns="http://www.w3.org/2000/svg"
53-
viewBox="0 0 16 16"
54-
fill="currentColor"
55-
stroke-width="2"
56-
aria-hidden="true"
57-
class="w-3 h-3"
58-
>
59-
<path
60-
fill-rule="evenodd"
61-
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"
62-
clip-rule="evenodd"
63-
/>
64-
</svg>
65-
</button>
66-
</div>
67-
{/if}
26+
const closeInput = () => {
27+
if (tagName.trim() === '') {
28+
showInput = false;
29+
}
30+
};
31+
</script>
6832

33+
{#if showInput}
34+
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gray-200/80 dark:bg-gray-700">
35+
<span class="text-gray-500 dark:text-blue-400">+</span>
36+
<input
37+
bind:this={inputElement}
38+
bind:value={tagName}
39+
class="w-20 text-sm bg-transparent outline-hidden text-gray-700 dark:text-blue-400 placeholder:text-gray-400 dark:placeholder:text-blue-400/50"
40+
placeholder={$i18n.t('Add tag')}
41+
aria-label={$i18n.t('Add a tag')}
42+
list="tagOptions"
43+
on:keydown={(event) => {
44+
if (event.key === 'Enter' || event.key === ' ') {
45+
event.preventDefault();
46+
addTagHandler();
47+
} else if (event.key === 'Escape') {
48+
tagName = '';
49+
showInput = false;
50+
}
51+
}}
52+
on:blur={closeInput}
53+
/>
54+
</div>
55+
{:else}
6956
<button
70-
class=" cursor-pointer self-center p-0.5 flex h-fit items-center rounded-full transition border dark:border-gray-600 border-dashed"
7157
type="button"
72-
aria-label={$i18n.t('Add Tag')}
73-
on:click={() => {
74-
showTagInput = !showTagInput;
75-
}}
58+
class="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-gray-200/80 dark:bg-gray-700 text-gray-500 dark:text-blue-400 text-sm font-medium hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
59+
on:click={openInput}
7660
>
77-
<div class=" m-auto self-center">
78-
<svg
79-
xmlns="http://www.w3.org/2000/svg"
80-
viewBox="0 0 16 16"
81-
aria-hidden="true"
82-
fill="currentColor"
83-
class="size-2.5 {showTagInput ? 'rotate-45' : ''} transition-all transform"
84-
>
85-
<path
86-
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
87-
/>
88-
</svg>
89-
</div>
61+
<span>+</span>
62+
<span>{$i18n.t('Add Tag')}</span>
9063
</button>
64+
{/if}
9165

92-
{#if label && !showTagInput}
93-
<span class="text-xs pl-2 self-center">{label}</span>
94-
{/if}
95-
</div>
66+
{#if suggestionTags.length > 0}
67+
<datalist id="tagOptions">
68+
{#each suggestionTags as tag}
69+
<option value={tag.name} />
70+
{/each}
71+
</datalist>
72+
{/if}

src/lib/components/common/Tags/TagItem.svelte

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,21 @@
33
44
const i18n = getContext('i18n');
55
6-
import Tooltip from '../Tooltip.svelte';
76
import XMark from '$lib/components/icons/XMark.svelte';
87
98
export let tag;
109
export let onDelete = () => {};
1110
</script>
1211

1312
{#if tag}
14-
<Tooltip content={tag.name}>
15-
<button
16-
aria-label={$i18n.t('Remove this tag from list')}
17-
class="relative group/tags px-1.5 py-[0.5px] gap-0.5 flex justify-between h-fit max-h-fit w-fit items-center rounded-lg bg-gray-500/20 text-gray-700 dark:text-gray-200 transition cursor-pointer"
18-
on:click={() => {
19-
onDelete();
20-
}}
21-
>
22-
<div class=" text-[0.7rem] font-medium self-center line-clamp-1 w-fit">
23-
{tag.name}
24-
</div>
25-
26-
<div class="hidden group-hover/tags:block transition">
27-
<div class="rounded-full pl-[1px] backdrop-blur-sm h-full flex self-center cursor-pointer">
28-
<XMark className="size-3" strokeWidth="2.5" />
29-
</div>
30-
</div>
31-
</button>
32-
</Tooltip>
13+
<button
14+
type="button"
15+
class="flex items-center gap-1 px-1.5 py-[1px] rounded-full bg-gray-100/50 dark:bg-gray-800/50 border border-gray-100 dark:border-gray-800 text-gray-600 dark:text-gray-300 text-xs font-medium hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
16+
on:click={() => {
17+
onDelete();
18+
}}
19+
>
20+
<span class="line-clamp-1">{tag.name}</span>
21+
<XMark className="size-3" strokeWidth="2.5" />
22+
</button>
3323
{/if}

src/lib/components/workspace/Models/ModelEditor.svelte

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -724,19 +724,19 @@
724724
{/if}
725725
</div>
726726

727-
<div class="my-2">
727+
<div class="my-4">
728728
<Knowledge bind:selectedItems={knowledge} />
729729
</div>
730730

731-
<div class="my-2">
731+
<div class="my-4">
732732
<ToolsSelector bind:selectedToolIds={toolIds} tools={$tools ?? []} />
733733
</div>
734734

735735
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0 || ($functions ?? []).filter((func) => func.type === 'action').length > 0}
736-
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
736+
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-4" />
737737

738738
{#if ($functions ?? []).filter((func) => func.type === 'filter').length > 0}
739-
<div class="my-2">
739+
<div class="my-4">
740740
<FiltersSelector
741741
bind:selectedFilterIds={filterIds}
742742
filters={($functions ?? []).filter((func) => func.type === 'filter')}
@@ -751,7 +751,7 @@
751751
)}
752752

753753
{#if toggleableFilters.length > 0}
754-
<div class="my-2">
754+
<div class="my-4">
755755
<DefaultFiltersSelector
756756
bind:selectedFilterIds={defaultFilterIds}
757757
filters={toggleableFilters}
@@ -761,7 +761,7 @@
761761
{/if}
762762

763763
{#if ($functions ?? []).filter((func) => func.type === 'action').length > 0}
764-
<div class="my-2">
764+
<div class="my-4">
765765
<ActionsSelector
766766
bind:selectedActionIds={actionIds}
767767
actions={($functions ?? []).filter((func) => func.type === 'action')}
@@ -770,9 +770,9 @@
770770
{/if}
771771
{/if}
772772

773-
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
773+
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-4" />
774774

775-
<div class="my-2">
775+
<div class="my-4">
776776
<Capabilities bind:capabilities />
777777
</div>
778778

@@ -785,19 +785,19 @@
785785
.map(([key, value]) => key)}
786786

787787
{#if availableFeatures.length > 0}
788-
<div class="my-2">
788+
<div class="my-4">
789789
<DefaultFeatures {availableFeatures} bind:featureIds={defaultFeatureIds} />
790790
</div>
791791
{/if}
792792
{/if}
793793

794794
{#if capabilities.builtin_tools}
795-
<div class="my-2">
795+
<div class="my-4">
796796
<BuiltinTools bind:builtinTools />
797797
</div>
798798
{/if}
799799

800-
<div class="my-2">
800+
<div class="my-4">
801801
<div class="flex w-full justify-between mb-1">
802802
<div class="self-center text-xs font-medium text-gray-500">
803803
{$i18n.t('TTS Voice')}
@@ -811,7 +811,7 @@
811811
/>
812812
</div>
813813

814-
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-2" />
814+
<hr class=" border-gray-100/30 dark:border-gray-850/30 my-4" />
815815

816816
<div class="my-2 flex justify-end">
817817
<button

0 commit comments

Comments
 (0)