Skip to content

Commit fa76764

Browse files
committed
refac
1 parent 83ec36c commit fa76764

6 files changed

Lines changed: 149 additions & 22 deletions

File tree

backend/open_webui/routers/configs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ async def set_tool_servers_config(
238238

239239
if auth_type in ('oauth_2.1', 'oauth_2.1_static'):
240240
# Remove existing OAuth clients for tool servers
241-
server_id = connection.get('info', {}).get('id')
241+
server_id = (connection.get('info') or {}).get('id')
242242
client_key = f'{server_type}:{server_id}'
243243

244244
try:
@@ -255,7 +255,7 @@ async def set_tool_servers_config(
255255
for connection in connections:
256256
server_type = connection.get('type', 'openapi')
257257
if server_type == 'mcp':
258-
server_id = connection.get('info', {}).get('id')
258+
server_id = (connection.get('info') or {}).get('id')
259259
auth_type = connection.get('auth_type', 'none')
260260

261261
if auth_type in ('oauth_2.1', 'oauth_2.1_static') and server_id:

src/lib/components/admin/Settings/WebSearch.svelte

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,31 @@
167167
</div>
168168
</div>
169169

170+
<div class="mb-2.5 flex w-full justify-between">
171+
<div class="self-center text-xs font-medium">
172+
{$i18n.t('Web Search Confirmation')}
173+
</div>
174+
<div class="flex items-center relative">
175+
<Tooltip content={$i18n.t('Require users to confirm before using Web Search.')}>
176+
<Switch bind:state={webConfig.ENABLE_WEB_SEARCH_CONFIRMATION} />
177+
</Tooltip>
178+
</div>
179+
</div>
180+
181+
{#if webConfig.ENABLE_WEB_SEARCH_CONFIRMATION}
182+
<div class="mb-2.5">
183+
<div class="self-center text-xs font-medium mb-2">
184+
{$i18n.t('Web Search Confirmation Content')}
185+
</div>
186+
<Textarea
187+
placeholder={$i18n.t(
188+
'Your query will be sent to the configured web search provider.'
189+
)}
190+
bind:value={webConfig.WEB_SEARCH_CONFIRMATION_CONTENT}
191+
/>
192+
</div>
193+
{/if}
194+
170195
<div class=" mb-2.5 flex w-full justify-between">
171196
<div class=" self-center text-xs font-medium">
172197
{$i18n.t('Web Search Engine')}

src/lib/components/chat/Chat.svelte

Lines changed: 110 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
import ChatControls from './ChatControls.svelte';
107107
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
108108
import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
109+
import WebSearchConfirmDialog from '../common/ConfirmDialog.svelte';
109110
import Placeholder from './Placeholder.svelte';
110111
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
111112
import NotificationToast from '../NotificationToast.svelte';
@@ -139,6 +140,7 @@
139140
let eventConfirmationInputPlaceholder = '';
140141
let eventConfirmationInputValue = '';
141142
let eventConfirmationInputType = '';
143+
let eventConfirmationInputOptions: ({ label?: string; value: string } | string)[] = [];
142144
let eventCallback = null;
143145
144146
let selectedModels = [''];
@@ -158,6 +160,49 @@
158160
let imageGenerationEnabled = false;
159161
let webSearchEnabled = false;
160162
let codeInterpreterEnabled = false;
163+
let webSearchActive = false;
164+
let showWebSearchConfirm = false;
165+
let pendingWebSearchPrompt: string | null = null;
166+
let webSearchConfirmed = false;
167+
168+
$: {
169+
const currentModels = atSelectedModel?.id ? [atSelectedModel.id] : selectedModels;
170+
const allModelsSupportWebSearch =
171+
currentModels.filter(
172+
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.web_search ?? true
173+
).length === currentModels.length;
174+
175+
webSearchActive = Boolean(
176+
$config?.features?.enable_web_search &&
177+
($user?.role === 'admin' || $user?.permissions?.features?.web_search) &&
178+
(webSearchEnabled ||
179+
(allModelsSupportWebSearch && ($settings?.webSearch ?? false) === 'always'))
180+
);
181+
}
182+
183+
const openWebSearchConfirm = () => {
184+
window.setTimeout(() => {
185+
showWebSearchConfirm = true;
186+
}, 0);
187+
};
188+
189+
const handleWebSearchToggle = (enabled: boolean) => {
190+
if (enabled && $config?.features?.enable_web_search_confirmation && !webSearchConfirmed) {
191+
webSearchEnabled = false;
192+
pendingWebSearchPrompt = null;
193+
openWebSearchConfirm();
194+
}
195+
};
196+
197+
const resetWebSearchConfirmation = () => {
198+
webSearchConfirmed = false;
199+
pendingWebSearchPrompt = null;
200+
showWebSearchConfirm = false;
201+
};
202+
203+
$: if (!webSearchActive) {
204+
resetWebSearchConfirmation();
205+
}
161206
162207
let showCommands = false;
163208
@@ -671,6 +716,7 @@
671716
672717
eventConfirmationInput = false;
673718
showEventConfirmation = true;
719+
eventConfirmationInputOptions = [];
674720
675721
eventConfirmationTitle = data.title;
676722
eventConfirmationMessage = data.message;
@@ -698,7 +744,8 @@
698744
eventConfirmationMessage = data.message;
699745
eventConfirmationInputPlaceholder = data.placeholder;
700746
eventConfirmationInputValue = data?.value ?? '';
701-
eventConfirmationInputType = data?.type ?? '';
747+
eventConfirmationInputType = data?.input?.type ?? data?.type ?? '';
748+
eventConfirmationInputOptions = data?.input?.options ?? data?.options ?? [];
702749
} else if (type.startsWith('terminal:')) {
703750
terminalEventHandler(type, data);
704751
} else {
@@ -819,11 +866,25 @@
819866
} catch {}
820867
};
821868
869+
const handleSocketConnect = async () => {
870+
if (!chatIdProp || $temporaryChatEnabled) {
871+
return;
872+
}
873+
874+
const currentMessage = history.currentId ? history.messages[history.currentId] : null;
875+
if (currentMessage?.role !== 'assistant' || currentMessage.done) {
876+
return;
877+
}
878+
879+
await loadChat();
880+
};
881+
822882
onMount(() => {
823883
loading = true;
824884
console.log('mounted');
825885
window.addEventListener('message', onMessageHandler);
826886
$socket?.on('events', chatEventHandler);
887+
$socket?.on('connect', handleSocketConnect);
827888
828889
$audioQueue?.destroy();
829890
@@ -940,6 +1001,7 @@
9401001
selectedFolderSubscribe();
9411002
window.removeEventListener('message', onMessageHandler);
9421003
$socket?.off('events', chatEventHandler);
1004+
$socket?.off('connect', handleSocketConnect);
9431005
dismissContextCompactionToast();
9441006
audioQueueInstance?.destroy();
9451007
audioQueue.set(null);
@@ -1203,6 +1265,7 @@
12031265
12041266
const initNewChat = async () => {
12051267
console.log('initNewChat');
1268+
resetWebSearchConfirmation();
12061269
12071270
// Mark the outgoing chat as read before resetting; in-place created chats
12081271
// keep chatIdProp undefined, so navigateHandler never marks them read.
@@ -2080,6 +2143,16 @@
20802143
return;
20812144
}
20822145
2146+
if (
2147+
$config?.features?.enable_web_search_confirmation &&
2148+
webSearchActive &&
2149+
!webSearchConfirmed
2150+
) {
2151+
pendingWebSearchPrompt = userPrompt ?? '';
2152+
openWebSearchConfirm();
2153+
return;
2154+
}
2155+
20832156
// Check if the assistant is still generating the main response
20842157
// (don't block on background tasks like title gen, follow-ups, tags)
20852158
const lastMessage = history.currentId ? history.messages[history.currentId] : null;
@@ -2278,24 +2351,9 @@
22782351
($user?.role === 'admin' || $user?.permissions?.features?.code_interpreter)
22792352
? codeInterpreterEnabled
22802353
: false,
2281-
web_search:
2282-
$config?.features?.enable_web_search &&
2283-
($user?.role === 'admin' || $user?.permissions?.features?.web_search)
2284-
? webSearchEnabled
2285-
: false
2354+
web_search: webSearchActive
22862355
};
22872356
2288-
const currentModels = atSelectedModel?.id ? [atSelectedModel.id] : selectedModels;
2289-
if (
2290-
currentModels.filter(
2291-
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.web_search ?? true
2292-
).length === currentModels.length
2293-
) {
2294-
if ($config?.features?.enable_web_search && ($settings?.webSearch ?? false) === 'always') {
2295-
features = { ...features, web_search: true };
2296-
}
2297-
}
2298-
22992357
if ($settings?.memory ?? $config?.features?.enable_memories ?? false) {
23002358
features = { ...features, memory: true };
23012359
}
@@ -2981,6 +3039,18 @@
29813039
29823040
let showDeleteConfirm = false;
29833041
3042+
const confirmWebSearch = async () => {
3043+
const userPrompt = pendingWebSearchPrompt;
3044+
pendingWebSearchPrompt = null;
3045+
webSearchConfirmed = true;
3046+
3047+
if (userPrompt !== null) {
3048+
await submitHandler(userPrompt);
3049+
} else {
3050+
webSearchEnabled = true;
3051+
}
3052+
};
3053+
29843054
const deleteChatHandler = async (id: string) => {
29853055
showDeleteConfirm = true;
29863056
};
@@ -3017,6 +3087,23 @@
30173087

30183088
<audio id="audioElement" style="display: none;"></audio>
30193089

3090+
<WebSearchConfirmDialog
3091+
bind:show={showWebSearchConfirm}
3092+
title={$i18n.t('Use Web Search?')}
3093+
message={($config?.features?.web_search_confirmation_content ?? '').trim() !== ''
3094+
? ($config?.features?.web_search_confirmation_content ?? '')
3095+
: $i18n.t('Your query will be sent to the configured web search provider.')}
3096+
confirmLabel={$i18n.t('Continue')}
3097+
cancelLabel={$i18n.t('Cancel')}
3098+
on:confirm={confirmWebSearch}
3099+
on:cancel={() => {
3100+
if (pendingWebSearchPrompt === null) {
3101+
webSearchEnabled = false;
3102+
}
3103+
pendingWebSearchPrompt = null;
3104+
}}
3105+
/>
3106+
30203107
<DeleteConfirmDialog
30213108
bind:show={showDeleteConfirm}
30223109
title={$i18n.t('Delete chat?')}
@@ -3037,8 +3124,11 @@
30373124
inputPlaceholder={eventConfirmationInputPlaceholder}
30383125
inputValue={eventConfirmationInputValue}
30393126
inputType={eventConfirmationInputType}
3127+
inputOptions={eventConfirmationInputOptions}
30403128
on:confirm={(e) => {
3041-
if (e.detail) {
3129+
if (eventConfirmationInput) {
3130+
eventCallback(e.detail);
3131+
} else if (e.detail) {
30423132
eventCallback(e.detail);
30433133
} else {
30443134
eventCallback(true);
@@ -3257,6 +3347,7 @@
32573347
saveDraft(data, $chatId);
32583348
}
32593349
}}
3350+
onWebSearchToggle={handleWebSearchToggle}
32603351
on:submit={async (e) => {
32613352
clearDraft($chatId);
32623353
if (e.detail || files.length > 0) {
@@ -3298,6 +3389,7 @@
32983389
{createMessagePair}
32993390
{onSelect}
33003391
{onUpload}
3392+
onWebSearchToggle={handleWebSearchToggle}
33013393
onChange={(data) => {
33023394
if (!$temporaryChatEnabled) {
33033395
saveDraft(data);

src/lib/components/chat/MessageInput.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
110110
export let onUpload: Function = (e) => {};
111111
export let onChange: Function = () => {};
112+
export let onWebSearchToggle: Function = () => {};
112113
113114
export let createMessagePair: Function;
114115
export let stopResponse: Function;
@@ -1763,6 +1764,7 @@
17631764
bind:webSearchEnabled
17641765
bind:imageGenerationEnabled
17651766
bind:codeInterpreterEnabled
1767+
{onWebSearchToggle}
17661768
closeOnOutsideClick={integrationsMenuCloseOnOutsideClick}
17671769
onShowValves={(e) => {
17681770
const { type, id } = e;

src/lib/components/chat/MessageInput/IntegrationsMenu.svelte

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
5757
export let onShowValves: Function;
5858
export let onClose: Function;
59+
export let onWebSearchToggle: Function = () => {};
5960
export let closeOnOutsideClick = true;
6061
6162
let show = false;
@@ -270,9 +271,12 @@
270271
<button
271272
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
272273
aria-pressed={webSearchEnabled}
273-
aria-label={webSearchEnabled ? $i18n.t('Disable Web Search') : $i18n.t('Enable Web Search')}
274+
aria-label={webSearchEnabled
275+
? $i18n.t('Disable Web Search')
276+
: $i18n.t('Enable Web Search')}
274277
on:click={() => {
275278
webSearchEnabled = !webSearchEnabled;
279+
onWebSearchToggle(webSearchEnabled);
276280
}}
277281
>
278282
<div class="flex-1 truncate">
@@ -303,7 +307,9 @@
303307
<button
304308
class="flex w-full justify-between gap-2 items-center px-3 py-1.5 text-sm cursor-pointer rounded-xl hover:bg-gray-50 dark:hover:bg-gray-800/50"
305309
aria-pressed={imageGenerationEnabled}
306-
aria-label={imageGenerationEnabled ? $i18n.t('Disable Image Generation') : $i18n.t('Enable Image Generation')}
310+
aria-label={imageGenerationEnabled
311+
? $i18n.t('Disable Image Generation')
312+
: $i18n.t('Enable Image Generation')}
307313
on:click={() => {
308314
imageGenerationEnabled = !imageGenerationEnabled;
309315
}}

src/lib/components/chat/Placeholder.svelte

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
export let onUpload: Function = (e) => {};
6161
export let onSelect = (e) => {};
6262
export let onChange = (e) => {};
63+
export let onWebSearchToggle: Function = () => {};
6364
6465
export let toolServers = [];
6566
@@ -244,6 +245,7 @@
244245
placeholder={$i18n.t('How can I help you today?')}
245246
{onChange}
246247
{onUpload}
248+
{onWebSearchToggle}
247249
on:submit={(e) => {
248250
dispatch('submit', e.detail);
249251
}}

0 commit comments

Comments
 (0)