|
106 | 106 | import ChatControls from './ChatControls.svelte'; |
107 | 107 | import EventConfirmDialog from '../common/ConfirmDialog.svelte'; |
108 | 108 | import DeleteConfirmDialog from '../common/ConfirmDialog.svelte'; |
| 109 | + import WebSearchConfirmDialog from '../common/ConfirmDialog.svelte'; |
109 | 110 | import Placeholder from './Placeholder.svelte'; |
110 | 111 | import FilesOverlay from './MessageInput/FilesOverlay.svelte'; |
111 | 112 | import NotificationToast from '../NotificationToast.svelte'; |
|
139 | 140 | let eventConfirmationInputPlaceholder = ''; |
140 | 141 | let eventConfirmationInputValue = ''; |
141 | 142 | let eventConfirmationInputType = ''; |
| 143 | + let eventConfirmationInputOptions: ({ label?: string; value: string } | string)[] = []; |
142 | 144 | let eventCallback = null; |
143 | 145 |
|
144 | 146 | let selectedModels = ['']; |
|
158 | 160 | let imageGenerationEnabled = false; |
159 | 161 | let webSearchEnabled = false; |
160 | 162 | 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 | + } |
161 | 206 |
|
162 | 207 | let showCommands = false; |
163 | 208 |
|
|
671 | 716 |
|
672 | 717 | eventConfirmationInput = false; |
673 | 718 | showEventConfirmation = true; |
| 719 | + eventConfirmationInputOptions = []; |
674 | 720 |
|
675 | 721 | eventConfirmationTitle = data.title; |
676 | 722 | eventConfirmationMessage = data.message; |
|
698 | 744 | eventConfirmationMessage = data.message; |
699 | 745 | eventConfirmationInputPlaceholder = data.placeholder; |
700 | 746 | eventConfirmationInputValue = data?.value ?? ''; |
701 | | - eventConfirmationInputType = data?.type ?? ''; |
| 747 | + eventConfirmationInputType = data?.input?.type ?? data?.type ?? ''; |
| 748 | + eventConfirmationInputOptions = data?.input?.options ?? data?.options ?? []; |
702 | 749 | } else if (type.startsWith('terminal:')) { |
703 | 750 | terminalEventHandler(type, data); |
704 | 751 | } else { |
|
819 | 866 | } catch {} |
820 | 867 | }; |
821 | 868 |
|
| 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 | +
|
822 | 882 | onMount(() => { |
823 | 883 | loading = true; |
824 | 884 | console.log('mounted'); |
825 | 885 | window.addEventListener('message', onMessageHandler); |
826 | 886 | $socket?.on('events', chatEventHandler); |
| 887 | + $socket?.on('connect', handleSocketConnect); |
827 | 888 |
|
828 | 889 | $audioQueue?.destroy(); |
829 | 890 |
|
|
940 | 1001 | selectedFolderSubscribe(); |
941 | 1002 | window.removeEventListener('message', onMessageHandler); |
942 | 1003 | $socket?.off('events', chatEventHandler); |
| 1004 | + $socket?.off('connect', handleSocketConnect); |
943 | 1005 | dismissContextCompactionToast(); |
944 | 1006 | audioQueueInstance?.destroy(); |
945 | 1007 | audioQueue.set(null); |
|
1203 | 1265 |
|
1204 | 1266 | const initNewChat = async () => { |
1205 | 1267 | console.log('initNewChat'); |
| 1268 | + resetWebSearchConfirmation(); |
1206 | 1269 |
|
1207 | 1270 | // Mark the outgoing chat as read before resetting; in-place created chats |
1208 | 1271 | // keep chatIdProp undefined, so navigateHandler never marks them read. |
|
2080 | 2143 | return; |
2081 | 2144 | } |
2082 | 2145 |
|
| 2146 | + if ( |
| 2147 | + $config?.features?.enable_web_search_confirmation && |
| 2148 | + webSearchActive && |
| 2149 | + !webSearchConfirmed |
| 2150 | + ) { |
| 2151 | + pendingWebSearchPrompt = userPrompt ?? ''; |
| 2152 | + openWebSearchConfirm(); |
| 2153 | + return; |
| 2154 | + } |
| 2155 | +
|
2083 | 2156 | // Check if the assistant is still generating the main response |
2084 | 2157 | // (don't block on background tasks like title gen, follow-ups, tags) |
2085 | 2158 | const lastMessage = history.currentId ? history.messages[history.currentId] : null; |
|
2278 | 2351 | ($user?.role === 'admin' || $user?.permissions?.features?.code_interpreter) |
2279 | 2352 | ? codeInterpreterEnabled |
2280 | 2353 | : 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 |
2286 | 2355 | }; |
2287 | 2356 |
|
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 | | -
|
2299 | 2357 | if ($settings?.memory ?? $config?.features?.enable_memories ?? false) { |
2300 | 2358 | features = { ...features, memory: true }; |
2301 | 2359 | } |
|
2981 | 3039 |
|
2982 | 3040 | let showDeleteConfirm = false; |
2983 | 3041 |
|
| 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 | +
|
2984 | 3054 | const deleteChatHandler = async (id: string) => { |
2985 | 3055 | showDeleteConfirm = true; |
2986 | 3056 | }; |
|
3017 | 3087 |
|
3018 | 3088 | <audio id="audioElement" style="display: none;"></audio> |
3019 | 3089 |
|
| 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 | + |
3020 | 3107 | <DeleteConfirmDialog |
3021 | 3108 | bind:show={showDeleteConfirm} |
3022 | 3109 | title={$i18n.t('Delete chat?')} |
|
3037 | 3124 | inputPlaceholder={eventConfirmationInputPlaceholder} |
3038 | 3125 | inputValue={eventConfirmationInputValue} |
3039 | 3126 | inputType={eventConfirmationInputType} |
| 3127 | + inputOptions={eventConfirmationInputOptions} |
3040 | 3128 | on:confirm={(e) => { |
3041 | | - if (e.detail) { |
| 3129 | + if (eventConfirmationInput) { |
| 3130 | + eventCallback(e.detail); |
| 3131 | + } else if (e.detail) { |
3042 | 3132 | eventCallback(e.detail); |
3043 | 3133 | } else { |
3044 | 3134 | eventCallback(true); |
|
3257 | 3347 | saveDraft(data, $chatId); |
3258 | 3348 | } |
3259 | 3349 | }} |
| 3350 | + onWebSearchToggle={handleWebSearchToggle} |
3260 | 3351 | on:submit={async (e) => { |
3261 | 3352 | clearDraft($chatId); |
3262 | 3353 | if (e.detail || files.length > 0) { |
|
3298 | 3389 | {createMessagePair} |
3299 | 3390 | {onSelect} |
3300 | 3391 | {onUpload} |
| 3392 | + onWebSearchToggle={handleWebSearchToggle} |
3301 | 3393 | onChange={(data) => { |
3302 | 3394 | if (!$temporaryChatEnabled) { |
3303 | 3395 | saveDraft(data); |
|
0 commit comments