From c280683a9d3367708d928ff0ab70d196b2e2422e Mon Sep 17 00:00:00 2001 From: techjarves Date: Mon, 22 Jun 2026 10:34:02 +0530 Subject: [PATCH] implement modular web search capabilities with provider support, content fetching, and file-based caching. --- .gitignore | 1 + app/frontend/src/components/TextChat.jsx | 65 ++++++++- app/frontend/src/services/api.js | 30 ++++- scripts/search/cache.js | 37 ++++++ scripts/search/content.js | 159 +++++++++++++++++++++++ scripts/search/core.js | 88 +++++++++++++ scripts/search/providers.js | 106 +++++++++++++++ scripts/server/serve.cjs | 99 +++++++++++++- 8 files changed, 580 insertions(+), 5 deletions(-) create mode 100644 scripts/search/cache.js create mode 100644 scripts/search/content.js create mode 100644 scripts/search/core.js create mode 100644 scripts/search/providers.js diff --git a/.gitignore b/.gitignore index 1cb9fa2..7cb43f3 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ app/tools/ # Generated files app/config/ +app/cache/ app/dist/ app/outputs/ app/transcriptions/ diff --git a/app/frontend/src/components/TextChat.jsx b/app/frontend/src/components/TextChat.jsx index eb576f1..2fcf7d3 100644 --- a/app/frontend/src/components/TextChat.jsx +++ b/app/frontend/src/components/TextChat.jsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"; -import { Bot, LoaderCircle, Send, Trash2, Square, History, Paperclip, X, ChevronDown } from "lucide-react"; +import { Bot, LoaderCircle, Send, Trash2, Square, History, Paperclip, X, ChevronDown, Globe2 } from "lucide-react"; import { getDownloadProgress, getLlmStatus, @@ -149,6 +149,8 @@ function TextChat({ const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [isBusy, setIsBusy] = useState(false); + const [useWebSearch, setUseWebSearch] = useState(false); + const [webTimeFilter, setWebTimeFilter] = useState("any"); const [loadingModel, setLoadingModel] = useState(null); const [tokenUsage, setTokenUsage] = useState({ prompt_tokens: 0, @@ -639,7 +641,7 @@ function TextChat({ setMessages([...nextMessages, { role: "assistant", content: "", - generationStats: { status: "starting", tokens: 0, tokensPerSecond: 0, seconds: 0, vision: requestHasImage }, + generationStats: { status: "starting", tokens: 0, tokensPerSecond: 0, seconds: 0, vision: requestHasImage, web: useWebSearch }, }]); const controller = new AbortController(); @@ -691,6 +693,8 @@ function TextChat({ repeatPenalty: textSettings?.repeatPenalty, seed: textSettings?.seed, enableThinking: thinkingEnabled, + useWeb: useWebSearch, + timeFilter: webTimeFilter, signal: controller.signal, }, (_token, fullText, _reasoningToken, fullReasoning) => { const now = performance.now(); @@ -790,6 +794,7 @@ function TextChat({ reasoning: processed.reasoning, thinkingDuration: finalThinkingDuration, generationStats, + webSources: response.webSources || [], }]; setMessages(finalMessages); saveConversationState(convId, finalMessages, selectedModel); @@ -1073,6 +1078,34 @@ function TextChat({ )} )} + {message.role === "assistant" && Array.isArray(message.webSources) && message.webSources.length > 0 && ( +
+
+ + Sources +
+
+ {message.webSources.map((source, sourceIndex) => ( + + [{source.index || sourceIndex + 1}] {source.title || source.url} + + ))} +
+
+ )} {/* Generation stats pill */} {message.role === "assistant" && message.generationStats && !message.error && ( @@ -1084,7 +1117,7 @@ function TextChat({ )}
{message.generationStats.status === "starting" ? ( - <> {message.generationStats.vision ? "Processing image..." : "Waiting for first token..."} + <> {message.generationStats.web ? "Searching web..." : message.generationStats.vision ? "Processing image..." : "Waiting for first token..."} ) : message.generationStats.status === "streaming" ? ( <>⚡ {message.generationStats.tokensPerSecond.toFixed(1)} tok/s ) : ( @@ -1150,6 +1183,32 @@ function TextChat({ > + + {useWebSearch && ( + + )}