From bdace57cfd07b76ab9ecd8d036c7b8084f0c2685 Mon Sep 17 00:00:00 2001 From: Willie Zutz Date: Wed, 7 May 2025 23:35:07 -0600 Subject: [PATCH] feat(search): Add searchUrl to message --- src/app/api/chat/route.ts | 7 +++++++ src/components/ChatWindow.tsx | 6 ++++-- src/components/MessageBox.tsx | 17 ++++++++++++++--- src/lib/search/metaSearchAgent.ts | 12 ++++++++---- src/lib/searxng.ts | 19 ++++++++++++++++++- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index 00b720b..d71856d 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -66,6 +66,7 @@ const handleEmitterEvents = async ( let recievedMessage = ''; let sources: any[] = []; let searchQuery: string | undefined; + let searchUrl: string | undefined; stream.on('data', (data) => { const parsedData = JSON.parse(data); @@ -86,6 +87,9 @@ const handleEmitterEvents = async ( if (parsedData.searchQuery) { searchQuery = parsedData.searchQuery; } + if (parsedData.searchUrl) { + searchUrl = parsedData.searchUrl; + } writer.write( encoder.encode( @@ -94,6 +98,7 @@ const handleEmitterEvents = async ( data: parsedData.data, searchQuery: parsedData.searchQuery, messageId: aiMessageId, + searchUrl: searchUrl, }) + '\n', ), ); @@ -128,6 +133,7 @@ const handleEmitterEvents = async ( messageId: aiMessageId, modelStats: modelStats, searchQuery: searchQuery, + searchUrl: searchUrl, }) + '\n', ), ); @@ -144,6 +150,7 @@ const handleEmitterEvents = async ( ...(sources && sources.length > 0 && { sources }), ...(searchQuery && { searchQuery }), modelStats: modelStats, + ...(searchUrl && { searchUrl }), }), }) .execute(); diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index 803d720..fa76796 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -28,6 +28,7 @@ export type Message = { sources?: Document[]; modelStats?: ModelStats; searchQuery?: string; + searchUrl?: string; }; export interface File { @@ -417,7 +418,6 @@ const ChatWindow = ({ id }: { id?: string }) => { if (data.type === 'sources') { sources = data.data; - const searchQuery = data.searchQuery; if (!added) { setMessages((prevMessages) => [ ...prevMessages, @@ -427,7 +427,8 @@ const ChatWindow = ({ id }: { id?: string }) => { chatId: chatId!, role: 'assistant', sources: sources, - searchQuery: searchQuery, + searchQuery: data.searchQuery, + searchUrl: data.searchUrl, createdAt: new Date(), }, ]); @@ -486,6 +487,7 @@ const ChatWindow = ({ id }: { id?: string }) => { modelStats: data.modelStats || null, // Make sure the searchQuery is preserved (if available in the message data) searchQuery: message.searchQuery || data.searchQuery, + searchUrl: message.searchUrl || data.searchUrl, }; } return message; diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index c657345..aff79c7 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -283,9 +283,20 @@ const MessageBox = ({ Search query: {' '} - - {message.searchQuery} - + {message.searchUrl ? ( + + {message.searchQuery} + + ) : ( + + {message.searchQuery} + + )} )} diff --git a/src/lib/search/metaSearchAgent.ts b/src/lib/search/metaSearchAgent.ts index f452739..bf1c565 100644 --- a/src/lib/search/metaSearchAgent.ts +++ b/src/lib/search/metaSearchAgent.ts @@ -56,6 +56,7 @@ class MetaSearchAgent implements MetaSearchAgentType { private config: Config; private strParser = new StringOutputParser(); private searchQuery?: string; + private searxngUrl?: string; constructor(config: Config) { this.config = config; @@ -81,6 +82,7 @@ class MetaSearchAgent implements MetaSearchAgentType { let question = this.config.summarizer ? await questionOutputParser.parse(input) : input; + console.log('question', question); if (question === 'not_needed') { return { query: '', docs: [] }; @@ -206,12 +208,15 @@ class MetaSearchAgent implements MetaSearchAgentType { } else { question = question.replace(/.*?<\/think>/g, ''); - const res = await searchSearxng(question, { + const searxngResult = await searchSearxng(question, { language: 'en', engines: this.config.activeEngines, }); - const documents = res.results.map( + // Store the SearXNG URL for later use in emitting to the client + this.searxngUrl = searxngResult.searchUrl; + + const documents = searxngResult.results.map( (result) => new Document({ pageContent: @@ -447,9 +452,7 @@ class MetaSearchAgent implements MetaSearchAgentType { event.event === 'on_chain_end' && event.name === 'FinalSourceRetriever' ) { - // Add searchQuery to the sources data if it exists const sourcesData = event.data.output; - // @ts-ignore - we added searchQuery property if (this.searchQuery) { emitter.emit( 'data', @@ -457,6 +460,7 @@ class MetaSearchAgent implements MetaSearchAgentType { type: 'sources', data: sourcesData, searchQuery: this.searchQuery, + searchUrl: this.searxngUrl, }), ); } else { diff --git a/src/lib/searxng.ts b/src/lib/searxng.ts index ae19db2..92dc2fc 100644 --- a/src/lib/searxng.ts +++ b/src/lib/searxng.ts @@ -19,6 +19,12 @@ interface SearxngSearchResult { iframe_src?: string; } +interface SearxngResponse { + results: SearxngSearchResult[]; + suggestions: string[]; + searchUrl: string; +} + export const searchSearxng = async ( query: string, opts?: SearxngSearchOptions, @@ -44,5 +50,16 @@ export const searchSearxng = async ( const results: SearxngSearchResult[] = res.data.results; const suggestions: string[] = res.data.suggestions; - return { results, suggestions }; + // Create a URL for viewing the search results in the SearXNG web interface + const searchUrl = new URL(searxngURL); + searchUrl.pathname = '/search'; + searchUrl.searchParams.append('q', query); + if (opts?.engines?.length) { + searchUrl.searchParams.append('engines', opts.engines.join(',')); + } + if (opts?.language) { + searchUrl.searchParams.append('language', opts.language); + } + + return { results, suggestions, searchUrl: searchUrl.toString() }; };