This commit is contained in:
Timothy Jaeryang Baek committed 2026-06-29 05:16:57 -05:00
1 parent 83ec36cd38
commit fa76764c3b
6 files changed
+149 -22

No files matched your search

@@ -167,6 +167,31 @@
</div>
</div>
<div class="mb-2.5 flex w-full justify-between">
<div class="self-center text-xs font-medium">
{$i18n.t('Web Search Confirmation')}
</div>
<div class="flex items-center relative">
<Tooltip content={$i18n.t('Require users to confirm before using Web Search.')}>
<Switch bind:state={webConfig.ENABLE_WEB_SEARCH_CONFIRMATION} />
</Tooltip>
</div>
</div>
{#if webConfig.ENABLE_WEB_SEARCH_CONFIRMATION}
<div class="mb-2.5">
<div class="self-center text-xs font-medium mb-2">
{$i18n.t('Web Search Confirmation Content')}
</div>
<Textarea
placeholder={$i18n.t(
'Your query will be sent to the configured web search provider.'
)}
bind:value={webConfig.WEB_SEARCH_CONFIRMATION_CONTENT}
/>
</div>
{/if}
<div class=" mb-2.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">
{$i18n.t('Web Search Engine')}
+110 -18
View File
@@ -106,6 +106,7 @@
import ChatControls from './ChatControls.svelte';
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
import DeleteConfirmDialog from '../common/ConfirmDialog.svelte';
import WebSearchConfirmDialog from '../common/ConfirmDialog.svelte';
import Placeholder from './Placeholder.svelte';
import FilesOverlay from './MessageInput/FilesOverlay.svelte';
import NotificationToast from '../NotificationToast.svelte';
@@ -139,6 +140,7 @@
let eventConfirmationInputPlaceholder = '';
let eventConfirmationInputValue = '';
let eventConfirmationInputType = '';
let eventConfirmationInputOptions: ({ label?: string; value: string } | string)[] = [];
let eventCallback = null;
let selectedModels = [''];
@@ -158,6 +160,49 @@
let imageGenerationEnabled = false;
let webSearchEnabled = false;
let codeInterpreterEnabled = false;
let webSearchActive = false;
let showWebSearchConfirm = false;
let pendingWebSearchPrompt: string | null = null;
let webSearchConfirmed = false;
$: {
const currentModels = atSelectedModel?.id ? [atSelectedModel.id] : selectedModels;
const allModelsSupportWebSearch =
currentModels.filter(
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.web_search ?? true
).length === currentModels.length;
webSearchActive = Boolean(
$config?.features?.enable_web_search &&
($user?.role === 'admin' || $user?.permissions?.features?.web_search) &&
(webSearchEnabled ||
(allModelsSupportWebSearch && ($settings?.webSearch ?? false) === 'always'))
);
}
const openWebSearchConfirm = () => {
window.setTimeout(() => {
showWebSearchConfirm = true;
}, 0);
};
const handleWebSearchToggle = (enabled: boolean) => {
if (enabled && $config?.features?.enable_web_search_confirmation && !webSearchConfirmed) {
webSearchEnabled = false;
pendingWebSearchPrompt = null;
openWebSearchConfirm();
}
};
const resetWebSearchConfirmation = () => {
webSearchConfirmed = false;
pendingWebSearchPrompt = null;
showWebSearchConfirm = false;
};
$: if (!webSearchActive) {
resetWebSearchConfirmation();
}
let showCommands = false;
@@ -671,6 +716,7 @@
eventConfirmationInput = false;
showEventConfirmation = true;
eventConfirmationInputOptions = [];
eventConfirmationTitle = data.title;
eventConfirmationMessage = data.message;
@@ -698,7 +744,8 @@
eventConfirmationMessage = data.message;
eventConfirmationInputPlaceholder = data.placeholder;
eventConfirmationInputValue = data?.value ?? '';
eventConfirmationInputType = data?.type ?? '';
eventConfirmationInputType = data?.input?.type ?? data?.type ?? '';
eventConfirmationInputOptions = data?.input?.options ?? data?.options ?? [];
} else if (type.startsWith('terminal:')) {
terminalEventHandler(type, data);
} else {
@@ -819,11 +866,25 @@
} catch {}
};
const handleSocketConnect = async () => {
if (!chatIdProp || $temporaryChatEnabled) {
return;
}
const currentMessage = history.currentId ? history.messages[history.currentId] : null;
if (currentMessage?.role !== 'assistant' || currentMessage.done) {
return;
}
await loadChat();
};
onMount(() => {
loading = true;
console.log('mounted');
window.addEventListener('message', onMessageHandler);
$socket?.on('events', chatEventHandler);
$socket?.on('connect', handleSocketConnect);
$audioQueue?.destroy();
@@ -940,6 +1001,7 @@
selectedFolderSubscribe();
window.removeEventListener('message', onMessageHandler);
$socket?.off('events', chatEventHandler);
$socket?.off('connect', handleSocketConnect);
dismissContextCompactionToast();
audioQueueInstance?.destroy();
audioQueue.set(null);
@@ -1203,6 +1265,7 @@
const initNewChat = async () => {
console.log('initNewChat');
resetWebSearchConfirmation();
// Mark the outgoing chat as read before resetting; in-place created chats
// keep chatIdProp undefined, so navigateHandler never marks them read.
@@ -2080,6 +2143,16 @@
return;
}
if (
$config?.features?.enable_web_search_confirmation &&
webSearchActive &&
!webSearchConfirmed
) {
pendingWebSearchPrompt = userPrompt ?? '';
openWebSearchConfirm();
return;
}
// Check if the assistant is still generating the main response
// (don't block on background tasks like title gen, follow-ups, tags)
const lastMessage = history.currentId ? history.messages[history.currentId] : null;
@@ -2278,24 +2351,9 @@
($user?.role === 'admin' || $user?.permissions?.features?.code_interpreter)
? codeInterpreterEnabled
: false,
web_search:
$config?.features?.enable_web_search &&
($user?.role === 'admin' || $user?.permissions?.features?.web_search)
? webSearchEnabled
: false
web_search: webSearchActive
};
const currentModels = atSelectedModel?.id ? [atSelectedModel.id] : selectedModels;
if (
currentModels.filter(
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.web_search ?? true
).length === currentModels.length
) {
if ($config?.features?.enable_web_search && ($settings?.webSearch ?? false) === 'always') {
features = { ...features, web_search: true };
}
}
if ($settings?.memory ?? $config?.features?.enable_memories ?? false) {
features = { ...features, memory: true };
}
@@ -2981,6 +3039,18 @@
let showDeleteConfirm = false;
const confirmWebSearch = async () => {
const userPrompt = pendingWebSearchPrompt;
pendingWebSearchPrompt = null;
webSearchConfirmed = true;
if (userPrompt !== null) {
await submitHandler(userPrompt);
} else {
webSearchEnabled = true;
}
};
const deleteChatHandler = async (id: string) => {
showDeleteConfirm = true;
};
@@ -3017,6 +3087,23 @@
<audio id="audioElement" style="display: none;"></audio>
<WebSearchConfirmDialog
bind:show={showWebSearchConfirm}
title={$i18n.t('Use Web Search?')}
message={($config?.features?.web_search_confirmation_content ?? '').trim() !== ''
? ($config?.features?.web_search_confirmation_content ?? '')
: $i18n.t('Your query will be sent to the configured web search provider.')}
confirmLabel={$i18n.t('Continue')}
cancelLabel={$i18n.t('Cancel')}
on:confirm={confirmWebSearch}
on:cancel={() => {
if (pendingWebSearchPrompt === null) {
webSearchEnabled = false;
}
pendingWebSearchPrompt = null;
}}
/>
<DeleteConfirmDialog
bind:show={showDeleteConfirm}
title={$i18n.t('Delete chat?')}
@@ -3037,8 +3124,11 @@
inputPlaceholder={eventConfirmationInputPlaceholder}
inputValue={eventConfirmationInputValue}
inputType={eventConfirmationInputType}
inputOptions={eventConfirmationInputOptions}
on:confirm={(e) => {
if (e.detail) {
if (eventConfirmationInput) {
eventCallback(e.detail);
} else if (e.detail) {
eventCallback(e.detail);
} else {
eventCallback(true);
@@ -3257,6 +3347,7 @@
saveDraft(data, $chatId);
}
}}
onWebSearchToggle={handleWebSearchToggle}
on:submit={async (e) => {
clearDraft($chatId);
if (e.detail || files.length > 0) {
@@ -3298,6 +3389,7 @@
{createMessagePair}
{onSelect}
{onUpload}
onWebSearchToggle={handleWebSearchToggle}
onChange={(data) => {
if (!$temporaryChatEnabled) {
saveDraft(data);
@@ -109,6 +109,7 @@
export let onUpload: Function = (e) => {};
export let onChange: Function = () => {};
export let onWebSearchToggle: Function = () => {};
export let createMessagePair: Function;
export let stopResponse: Function;
@@ -1763,6 +1764,7 @@
bind:webSearchEnabled
bind:imageGenerationEnabled
bind:codeInterpreterEnabled
{onWebSearchToggle}
closeOnOutsideClick={integrationsMenuCloseOnOutsideClick}
onShowValves={(e) => {
const { type, id } = e;
@@ -56,6 +56,7 @@
export let onShowValves: Function;
export let onClose: Function;
export let onWebSearchToggle: Function = () => {};
export let closeOnOutsideClick = true;
let show = false;
@@ -270,9 +271,12 @@
<button
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"
aria-pressed={webSearchEnabled}
aria-label={webSearchEnabled ? $i18n.t('Disable Web Search') : $i18n.t('Enable Web Search')}
aria-label={webSearchEnabled
? $i18n.t('Disable Web Search')
: $i18n.t('Enable Web Search')}
on:click={() => {
webSearchEnabled = !webSearchEnabled;
onWebSearchToggle(webSearchEnabled);
}}
>
<div class="flex-1 truncate">
@@ -303,7 +307,9 @@
<button
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"
aria-pressed={imageGenerationEnabled}
aria-label={imageGenerationEnabled ? $i18n.t('Disable Image Generation') : $i18n.t('Enable Image Generation')}
aria-label={imageGenerationEnabled
? $i18n.t('Disable Image Generation')
: $i18n.t('Enable Image Generation')}
on:click={() => {
imageGenerationEnabled = !imageGenerationEnabled;
}}
@@ -60,6 +60,7 @@
export let onUpload: Function = (e) => {};
export let onSelect = (e) => {};
export let onChange = (e) => {};
export let onWebSearchToggle: Function = () => {};
export let toolServers = [];
@@ -244,6 +245,7 @@
placeholder={$i18n.t('How can I help you today?')}
{onChange}
{onUpload}
{onWebSearchToggle}
on:submit={(e) => {
dispatch('submit', e.detail);
}}