Skip to content

Commit 952a464

Browse files
authored
feat: add openrouter web search (#207)
* feat: add openrouter websearch * feat: add web search in sub menu * fix: menu padding
1 parent 73a5bd0 commit 952a464

File tree

8 files changed

+308
-196
lines changed

8 files changed

+308
-196
lines changed

app/api/chat/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ type ChatRequest = {
2828
model: string
2929
isAuthenticated: boolean
3030
systemPrompt: string
31-
agentId?: string
31+
agentId: string | null
32+
enableSearch: boolean
3233
}
3334

3435
export async function POST(req: Request) {
@@ -41,6 +42,7 @@ export async function POST(req: Request) {
4142
isAuthenticated,
4243
systemPrompt,
4344
agentId,
45+
enableSearch,
4446
} = (await req.json()) as ChatRequest
4547

4648
if (!messages || !chatId || !userId) {
@@ -114,7 +116,7 @@ export async function POST(req: Request) {
114116
}
115117

116118
const result = streamText({
117-
model: modelConfig.apiSdk(apiKey),
119+
model: modelConfig.apiSdk(apiKey, { enableSearch }),
118120
system: effectiveSystemPrompt,
119121
messages: cleanedMessages,
120122
tools: toolsToUse as ToolSet,

app/components/chat-input/chat-input.tsx

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@ import { Button } from "@/components/ui/button"
1212
import { useAgent } from "@/lib/agent-store/provider"
1313
import { getModelInfo } from "@/lib/models"
1414
import { ArrowUp, Stop, Warning } from "@phosphor-icons/react"
15-
import React, { useCallback, useEffect } from "react"
15+
import React, { useCallback, useEffect, useMemo } from "react"
1616
import { PromptSystem } from "../suggestions/prompt-system"
1717
import { AgentCommand } from "./agent-command"
1818
import { ButtonFileUpload } from "./button-file-upload"
1919
import { ButtonSearch } from "./button-search"
2020
import { FileList } from "./file-list"
2121
import { SelectedAgent } from "./selected-agent"
22-
import { useSearchAgent } from "./use-search-agent"
2322

2423
type ChatInputProps = {
2524
value: string
@@ -37,7 +36,8 @@ type ChatInputProps = {
3736
isUserAuthenticated: boolean
3837
stop: () => void
3938
status?: "submitted" | "streaming" | "ready" | "error"
40-
onSearchToggle?: (enabled: boolean, agentId: string | null) => void
39+
setEnableSearch?: (enabled: boolean) => void
40+
enableSearch?: boolean
4141
}
4242

4343
export function ChatInput({
@@ -55,10 +55,10 @@ export function ChatInput({
5555
isUserAuthenticated,
5656
stop,
5757
status,
58-
onSearchToggle,
58+
setEnableSearch,
59+
enableSearch,
5960
}: ChatInputProps) {
6061
const { currentAgent, curatedAgents, userAgents } = useAgent()
61-
const { isSearchEnabled, toggleSearch } = useSearchAgent()
6262

6363
const agentCommand = useAgentCommand({
6464
value,
@@ -69,18 +69,9 @@ export function ChatInput({
6969

7070
const selectModelConfig = getModelInfo(selectedModel)
7171
const hasToolSupport = Boolean(selectModelConfig?.tools)
72+
const hasSearchSupport = Boolean(selectModelConfig?.webSearch)
7273
const isOnlyWhitespace = (text: string) => !/[^\s]/.test(text)
7374

74-
// Handle search toggle
75-
const handleSearchToggle = useCallback(
76-
(enabled: boolean) => {
77-
toggleSearch(enabled)
78-
const agentId = enabled ? "search" : null
79-
onSearchToggle?.(enabled, agentId)
80-
},
81-
[toggleSearch, onSearchToggle]
82-
)
83-
8475
const handleSend = useCallback(() => {
8576
if (isSubmitting) {
8677
return
@@ -168,6 +159,12 @@ export function ChatInput({
168159
return () => el.removeEventListener("paste", handlePaste)
169160
}, [agentCommand.textareaRef, handlePaste])
170161

162+
useMemo(() => {
163+
if (!hasSearchSupport && enableSearch) {
164+
setEnableSearch?.(false)
165+
}
166+
}, [hasSearchSupport, enableSearch, setEnableSearch])
167+
171168
return (
172169
<div className="relative flex w-full flex-col gap-4">
173170
{hasSuggestions && (
@@ -222,11 +219,13 @@ export function ChatInput({
222219
isUserAuthenticated={isUserAuthenticated}
223220
className="rounded-full"
224221
/>
225-
{/* <ButtonSearch
226-
isSelected={isSearchEnabled}
227-
onToggle={handleSearchToggle}
228-
isAuthenticated={isUserAuthenticated}
229-
/> */}
222+
{hasSearchSupport ? (
223+
<ButtonSearch
224+
isSelected={enableSearch}
225+
onToggle={setEnableSearch}
226+
isAuthenticated={isUserAuthenticated}
227+
/>
228+
) : null}
230229
{currentAgent && !hasToolSupport && (
231230
<div className="flex items-center gap-1">
232231
<Warning className="size-4" />

app/components/chat/chat.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export function Chat() {
7070
const [isSubmitting, setIsSubmitting] = useState(false)
7171
const { preferences } = useUserPreferences()
7272
const [hasDialogAuth, setHasDialogAuth] = useState(false)
73-
const [searchAgentId, setSearchAgentId] = useState<string | null>(null)
73+
const [enableSearch, setEnableSearch] = useState(false)
7474

7575
const {
7676
files,
@@ -155,7 +155,7 @@ export function Chat() {
155155
input,
156156
selectedModel,
157157
systemPrompt,
158-
selectedAgentId: searchAgentId || currentAgent?.id || null,
158+
selectedAgentId: currentAgent?.id || null,
159159
createNewChat,
160160
setHasDialogAuth,
161161
})
@@ -232,15 +232,15 @@ export function Chat() {
232232
}
233233
}
234234

235-
const effectiveAgentId = searchAgentId || currentAgent?.id
236235
const options = {
237236
body: {
238237
chatId: currentChatId,
239238
userId: uid,
240239
model: selectedModel,
241240
isAuthenticated,
242241
systemPrompt: systemPrompt || SYSTEM_PROMPT_DEFAULT,
243-
...(effectiveAgentId && { agentId: effectiveAgentId }),
242+
agentId: currentAgent?.id || null,
243+
enableSearch,
244244
},
245245
experimental_attachments: attachments || undefined,
246246
}
@@ -276,7 +276,6 @@ export function Chat() {
276276
cleanupOptimisticAttachments,
277277
ensureChatExists,
278278
handleFileUploads,
279-
searchAgentId,
280279
currentAgent?.id,
281280
selectedModel,
282281
isAuthenticated,
@@ -377,14 +376,6 @@ export function Chat() {
377376
reload(options)
378377
}, [user, chatId, selectedModel, isAuthenticated, systemPrompt, reload])
379378

380-
// Handle search agent toggle
381-
const handleSearchToggle = useCallback(
382-
(enabled: boolean, agentId: string | null) => {
383-
setSearchAgentId(enabled ? agentId : null)
384-
},
385-
[]
386-
)
387-
388379
// Memoize the conversation props to prevent unnecessary rerenders
389380
const conversationProps = useMemo(
390381
() => ({
@@ -415,7 +406,8 @@ export function Chat() {
415406
isUserAuthenticated: isAuthenticated,
416407
stop,
417408
status,
418-
onSearchToggle: handleSearchToggle,
409+
setEnableSearch,
410+
enableSearch,
419411
}),
420412
[
421413
input,
@@ -434,7 +426,8 @@ export function Chat() {
434426
isAuthenticated,
435427
stop,
436428
status,
437-
handleSearchToggle,
429+
setEnableSearch,
430+
enableSearch,
438431
]
439432
)
440433

components/common/model-selector/base.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ export function ModelSelector({
271271
</TooltipTrigger>
272272
<TooltipContent>Switch model ⌘⇧P</TooltipContent>
273273
<DropdownMenuContent
274-
className="flex h-[320px] w-[300px] flex-col space-y-0.5 overflow-visible px-0 pt-0"
274+
className="flex h-[320px] w-[300px] flex-col space-y-0.5 overflow-visible p-0"
275275
align="start"
276276
sideOffset={4}
277277
forceMount
@@ -292,7 +292,7 @@ export function ModelSelector({
292292
/>
293293
</div>
294294
</div>
295-
<div className="flex h-full flex-col space-y-0 overflow-y-auto px-1 pt-1 pb-0">
295+
<div className="flex h-full flex-col space-y-0 overflow-y-auto px-1 pt-0 pb-0">
296296
{isLoadingModels ? (
297297
<div className="flex h-full flex-col items-center justify-center p-6 text-center">
298298
<p className="text-muted-foreground mb-2 text-sm">

components/common/model-selector/sub-menu.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { PROVIDERS } from "@/lib/providers"
44
import {
55
ArrowSquareOutIcon,
66
BrainIcon,
7+
GlobeIcon,
78
ImageIcon,
89
WrenchIcon,
910
} from "@phosphor-icons/react"
@@ -51,6 +52,13 @@ export function SubMenu({ hoveredModelData }: SubMenuProps) {
5152
<span>Reasoning</span>
5253
</div>
5354
)}
55+
56+
{hoveredModelData.webSearch && (
57+
<div className="flex items-center gap-1 rounded-full bg-blue-100 px-2 py-0.5 text-xs text-blue-700 dark:bg-blue-800 dark:text-blue-100">
58+
<GlobeIcon className="size-3" />
59+
<span>Web Search</span>
60+
</div>
61+
)}
5462
</div>
5563
</div>
5664

0 commit comments

Comments
 (0)