Skip to content

Commit a60f489

Browse files
committed
feat(chat): add toggle to enable online search and its settings
- Implemented migration for old chat history records to include online search settings. - Created OnlineSearchSwitch component for toggling online search. - Updated ThinkingModeSwitch component for improved styling and functionality. - Integrated OnlineSearchSwitch into the Chat component toolbar. - Enhanced Chat class to manage online search settings in user configuration. - Added checks in tool calls to disable online search if not enabled in settings. - Updated localization files to include translations for online search tool. - Modified user configuration to support reactive default values for online search settings.
1 parent e8793b7 commit a60f489

File tree

30 files changed

+377
-231
lines changed

30 files changed

+377
-231
lines changed

assets/icons/online-search.svg

Lines changed: 16 additions & 0 deletions
Loading

components/ModelSelector.vue

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
{{ option.label }}
4545
</span>
4646
</div>
47+
<div v-else-if="modelListUpdating">
48+
<Loading :size="12" />
49+
</div>
4750
<div v-else>
4851
⚠️ No model
4952
</div>
@@ -52,7 +55,13 @@
5255
<div
5356
class="cursor-pointer text-[13px] text-[#5B5B5B] font-medium px-1 leading-5 flex flex-row gap-1 items-center justify-center"
5457
>
55-
<span class="text-ellipsis overflow-hidden whitespace-nowrap">
58+
<div v-if="modelListUpdating">
59+
<Loading :size="12" />
60+
</div>
61+
<span
62+
v-else
63+
class="text-ellipsis overflow-hidden whitespace-nowrap"
64+
>
5665
{{ option?.label || t('settings.models.no_model') }}
5766
</span>
5867
<IconExpand class="shrink-0" />
@@ -131,6 +140,7 @@ import { getUserConfig } from '@/utils/user-config'
131140
import { classNames } from '@/utils/vue/utils'
132141
133142
import ExhaustiveError from './ExhaustiveError.vue'
143+
import Loading from './Loading.vue'
134144
import Selector from './Selector.vue'
135145
import Button from './ui/Button.vue'
136146
import Divider from './ui/Divider.vue'
@@ -150,8 +160,8 @@ const props = withDefaults(defineProps<{
150160
})
151161
152162
const { t } = useI18n()
153-
const { modelList: composedModelList } = toRefs(useLLMBackendStatusStore())
154-
const { updateModelList, updateOllamaModelList, updateLMStudioModelList } = useLLMBackendStatusStore()
163+
const { modelList: composedModelList, modelListUpdating } = toRefs(useLLMBackendStatusStore())
164+
const { updateModelList } = useLLMBackendStatusStore()
155165
156166
only(['sidepanel'], () => {
157167
const removeListener = registerSidepanelRpcEvent('updateModelList', async () => await updateModelList())
@@ -240,7 +250,8 @@ const onClick = () => {
240250
}
241251
}
242252
243-
watch(modelList, (modelList) => {
253+
watch([modelList, modelListUpdating], ([modelList, updating]) => {
254+
if (updating) return
244255
if (modelList.length === 0) {
245256
commonModel.value = undefined
246257
translationModel.value = undefined
@@ -262,8 +273,7 @@ watch([endpointType, selectedModel], async () => {
262273
updateModelList()
263274
})
264275
265-
watch(ollamaBaseUrl, async () => updateOllamaModelList())
266-
watch(lmStudioBaseUrl, async () => updateLMStudioModelList())
276+
watch([ollamaBaseUrl, lmStudioBaseUrl], async () => updateModelList())
267277
268278
onMounted(async () => {
269279
updateModelList()

components/Selector.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<div
77
v-if="isGhostBtn"
88
ref="selectorRef"
9-
:class="['flex justify-between items-center cursor-pointer text-[13px] font-medium py-0 h-8', containerClass]"
9+
:class="classNames('flex justify-between items-center cursor-pointer text-[13px] font-medium py-0 h-8', containerClass)"
1010
:disabled="disabled"
1111
@click="toggleDropdown"
1212
>
@@ -132,6 +132,7 @@ import { Component, computed, FunctionalComponent, Ref, ref, watch, watchEffect
132132
133133
import { useInjectContext } from '@/composables/useInjectContext'
134134
import { useZIndex } from '@/composables/useZIndex'
135+
import { classNames } from '@/utils/vue/utils'
135136
136137
import ScrollContainer from './ScrollContainer.vue'
137138
import Button from './ui/Button.vue'

entrypoints/background/database/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface ChatHistoryRecord {
1010
history: string // JSON serialized HistoryItemV1[]
1111
contextUpdateInfo?: string // JSON serialized contextUpdateInfo from ChatHistoryV1
1212
reasoningEnabled?: boolean // reasoning setting for this chat
13+
onlineSearchEnabled?: boolean // online search setting for this chat, default is true
1314
createdAt: number
1415
updatedAt: number
1516
}

entrypoints/background/services/chat-history-service.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,18 @@ export class BackgroundChatHistoryService {
6464
}
6565
}
6666

67+
migrateFromOldHistoryRecord(record: ChatHistoryRecord): ChatHistoryV1 {
68+
return {
69+
id: record.id,
70+
title: record.title,
71+
lastInteractedAt: record.lastInteractedAt,
72+
contextUpdateInfo: record.contextUpdateInfo ? JSON.parse(record.contextUpdateInfo) : undefined,
73+
reasoningEnabled: record.reasoningEnabled,
74+
onlineSearchEnabled: record.onlineSearchEnabled ?? true, // default to true if undefined for backward compatibility
75+
history: JSON.parse(record.history) as HistoryItemV1[],
76+
}
77+
}
78+
6779
/**
6880
* Get chat history by ID
6981
*/
@@ -75,14 +87,7 @@ export class BackgroundChatHistoryService {
7587
const record = await db.get(CHAT_OBJECT_STORES.CHAT_HISTORY, chatId)
7688
if (!record) return null
7789

78-
return {
79-
id: record.id,
80-
title: record.title,
81-
lastInteractedAt: record.lastInteractedAt,
82-
contextUpdateInfo: record.contextUpdateInfo ? JSON.parse(record.contextUpdateInfo) : undefined,
83-
reasoningEnabled: record.reasoningEnabled,
84-
history: JSON.parse(record.history) as HistoryItemV1[],
85-
}
90+
return this.migrateFromOldHistoryRecord(record)
8691
}
8792
catch (error) {
8893
log.error('Failed to get chat history:', error)
@@ -108,6 +113,7 @@ export class BackgroundChatHistoryService {
108113
history: JSON.stringify(chatHistory.history),
109114
contextUpdateInfo: chatHistory.contextUpdateInfo ? JSON.stringify(chatHistory.contextUpdateInfo) : undefined,
110115
reasoningEnabled: chatHistory.reasoningEnabled,
116+
onlineSearchEnabled: chatHistory.onlineSearchEnabled,
111117
createdAt: now, // Will be overwritten if record exists
112118
updatedAt: now,
113119
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<template>
2+
<div>
3+
<div
4+
class="flex items-center gap-1 px-1 py-1 min-h-6 rounded-sm cursor-pointer"
5+
:class="[
6+
isOnlineSearchEnabled ? 'bg-[#DEFFEB] text-[#5B5B5B]' : 'text-[#AEB5BD]',
7+
]"
8+
@click="toggleOnlineSearch"
9+
>
10+
<IconOnlineSearch
11+
class="w-4 h-4"
12+
/>
13+
<span class="text-xs font-medium select-none">
14+
{{ t('chat.tools.online_search.title') }}
15+
</span>
16+
</div>
17+
</div>
18+
</template>
19+
20+
<script setup lang="ts">
21+
import { computed } from 'vue'
22+
23+
import IconOnlineSearch from '@/assets/icons/online-search.svg'
24+
import { useI18n } from '@/utils/i18n'
25+
import { getUserConfig } from '@/utils/user-config'
26+
27+
import { Chat } from '../../utils/chat'
28+
29+
const { t } = useI18n()
30+
31+
const userConfig = await getUserConfig()
32+
const chat = await Chat.getInstance()
33+
34+
// Use chat-specific reasoning setting with fallback to global setting
35+
const isOnlineSearchEnabled = computed({
36+
get() {
37+
return chat.historyManager.chatHistory.value.onlineSearchEnabled
38+
},
39+
set(value: boolean) {
40+
// Update both chat-specific setting and global setting
41+
chat.historyManager.chatHistory.value.onlineSearchEnabled = value
42+
userConfig.chat.onlineSearch.enable.set(value)
43+
},
44+
})
45+
46+
const toggleOnlineSearch = () => {
47+
isOnlineSearchEnabled.value = !isOnlineSearchEnabled.value
48+
}
49+
</script>

entrypoints/sidepanel/components/Chat/ThinkingModeSwitch.vue

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,27 @@
11
<template>
2-
<div class="flex items-center gap-1">
3-
<IconThinking
4-
class="w-4 h-4 text-[#5B5B5B]"
5-
:class="{ 'opacity-50': !isModelSupportsThinking }"
6-
/>
7-
<span
8-
class="text-xs text-[#5B5B5B] font-medium select-none"
9-
:class="{ 'opacity-50': !isModelSupportsThinking }"
10-
>
11-
{{ t('chat.thinking_mode.label') }}
12-
</span>
13-
<button
14-
:disabled="!isModelSupportsThinking || !isThinkingToggleable"
2+
<div>
3+
<div
4+
class="flex items-center gap-1 px-1 py-1 min-h-6 rounded-sm"
155
:class="[
16-
'relative inline-flex h-4 w-7 items-center rounded-full transition-colors',
176
{
18-
'bg-[#24B960]': isThinkingEnabled && isModelSupportsThinking,
19-
'bg-gray-300': !isThinkingEnabled || !isModelSupportsThinking,
207
'cursor-not-allowed opacity-50': !isModelSupportsThinking || !isThinkingToggleable,
21-
'cursor-pointer': isModelSupportsThinking && isThinkingToggleable
22-
}
8+
'cursor-pointer': isModelSupportsThinking && isThinkingToggleable,
9+
},
10+
isThinkingEnabled ? 'bg-[#DEFFEB] text-[#5B5B5B]' : 'text-[#AEB5BD]',
2311
]"
2412
@click="toggleThinking"
2513
>
26-
<span
27-
:class="[
28-
'inline-block h-3 w-3 transform rounded-full bg-white transition-transform',
29-
{
30-
'translate-x-3.5': isThinkingEnabled && isModelSupportsThinking,
31-
'translate-x-0.5': !isThinkingEnabled || !isModelSupportsThinking
32-
}
33-
]"
14+
<IconThinking
15+
class="w-4 h-4"
16+
:class="{ 'opacity-50': !isModelSupportsThinking }"
3417
/>
35-
</button>
18+
<span
19+
class="text-xs font-medium select-none"
20+
:class="{ 'opacity-50': !isModelSupportsThinking }"
21+
>
22+
{{ t('chat.thinking_mode.label') }}
23+
</span>
24+
</div>
3625
</div>
3726
</template>
3827

@@ -147,18 +136,4 @@ watch([endpointType, currentModel], async () => {
147136
onMounted(async () => {
148137
await updateModelList()
149138
})
150-
151-
// Debug logging for development, temporarily keep
152-
// watch([currentModel, endpointType, isModelSupportsThinking, isThinkingToggleable, isThinkingEnabled],
153-
// ([model, endpoint, supportsThinking, toggleable, enabled]) => {
154-
// logger.debug('ThinkingModeSwitch state:', {
155-
// currentModel: model,
156-
// endpointType: endpoint,
157-
// isModelSupportsThinking: supportsThinking,
158-
// isThinkingToggleable: toggleable,
159-
// isThinkingEnabled: enabled,
160-
// })
161-
// },
162-
// { immediate: true },
163-
// )
164139
</script>

0 commit comments

Comments
 (0)