From 37c93c3c9be0e8385a8e3e50314d01f1bfd5709e Mon Sep 17 00:00:00 2001 From: Willie Zutz Date: Thu, 3 Jul 2025 00:27:32 -0600 Subject: [PATCH] feat(search): Add ability to set default provider, model, and optimization mode when coming from a search query --- README.md | 3 + package-lock.json | 75 ++- package.json | 1 + src/app/settings/page.tsx | 110 +++++ src/components/ChatWindow.tsx | 43 +- src/components/MessageInput.tsx | 14 +- src/components/MessageInputActions/Attach.tsx | 24 +- .../MessageInputActions/ModelSelector.tsx | 28 +- .../MessageInputActions/Optimization.tsx | 27 +- src/lib/agents/analyzerAgent.ts | 8 +- src/lib/agents/contentRouterAgent.ts | 4 +- src/lib/agents/taskManagerAgent.ts | 4 +- src/lib/agents/urlSummarizationAgent.ts | 32 +- src/lib/agents/webSearchAgent.ts | 25 +- src/lib/providers/groq.ts | 35 +- src/lib/utils/summarizeWebContent.ts | 23 +- yarn.lock | 466 +++++++++--------- 17 files changed, 603 insertions(+), 319 deletions(-) diff --git a/README.md b/README.md index bad2709..eed404f 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,9 @@ This fork adds several enhancements to the original Perplexica project: - ✅ Enhanced Balance mode that uses a headless web browser to retrieve web content and use relevant excerpts to enhance responses - ✅ Adds Agent mode that uses the full content of web pages to answer queries and an agentic flow to intelligently answer complex queries with accuracy - See the [README.md](docs/architecture/README.md) in the docs architecture directory for more info +- ✅ Query-based settings override for browser search engine integration + - Automatically applies user's saved optimization mode and AI model preferences when accessing via URL with `q` parameter + - Enables seamless browser search bar integration with personalized settings ### AI Functionality diff --git a/package-lock.json b/package-lock.json index 428c4c7..65d1c02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@langchain/community": "^0.3.45", "@langchain/core": "^0.3.57", "@langchain/google-genai": "^0.2.10", + "@langchain/groq": "^0.2.3", "@langchain/langgraph": "^0.3.1", "@langchain/ollama": "^0.2.0", "@langchain/openai": "^0.5.12", @@ -2574,9 +2575,9 @@ } }, "node_modules/@langchain/core": { - "version": "0.3.57", - "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.57.tgz", - "integrity": "sha512-jz28qCTKJmi47b6jqhQ6vYRTG5jRpqhtPQjriRTB5wR8mgvzo6xKs0fG/kExS3ZvM79ytD1npBvgf8i19xOo9Q==", + "version": "0.3.61", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.61.tgz", + "integrity": "sha512-4O7fw5SXNSE+uBnathLQrhm3t+7dZGagt/5kt37A+pXw0AkudxEBvveg73sSnpBd9SIz3/Vc7F4k8rCKXGbEDA==", "license": "MIT", "dependencies": { "@cfworker/json-schema": "^4.0.2", @@ -2584,12 +2585,12 @@ "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", - "langsmith": "^0.3.29", + "langsmith": "^0.3.33", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", - "zod": "^3.22.4", + "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" }, "engines": { @@ -2626,6 +2627,22 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/@langchain/groq": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@langchain/groq/-/groq-0.2.3.tgz", + "integrity": "sha512-r+yjysG36a0IZxTlCMr655Feumfb4IrOyA0jLLq4l7gEhVyMpYXMwyE6evseyU2LRP+7qOPbGRVpGqAIK0MsUA==", + "license": "MIT", + "dependencies": { + "groq-sdk": "^0.19.0", + "zod": "^3.22.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.58 <0.4.0" + } + }, "node_modules/@langchain/langgraph": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@langchain/langgraph/-/langgraph-0.3.1.tgz", @@ -7072,6 +7089,36 @@ "graphql": "14 - 16" } }, + "node_modules/groq-sdk": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/groq-sdk/-/groq-sdk-0.19.0.tgz", + "integrity": "sha512-vdh5h7ORvwvOvutA80dKF81b0gPWHxu6K/GOJBOM0n6p6CSqAVLhFfeS79Ef0j/yCycDR09jqY7jkYz9dLiS6w==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + } + }, + "node_modules/groq-sdk/node_modules/@types/node": { + "version": "18.19.115", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.115.tgz", + "integrity": "sha512-kNrFiTgG4a9JAn1LMQeLOv3MvXIPokzXziohMrMsvpYgLpdEt/mMiVYc4sGKtDfyxM5gIDF4VgrPRyCw4fHOYg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/groq-sdk/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/guid-typescript": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", @@ -8347,9 +8394,9 @@ } }, "node_modules/langsmith": { - "version": "0.3.30", - "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.30.tgz", - "integrity": "sha512-ZaiaOx9MysuSQlAkRw8mjm7iqhrlF7HI0LCTLxiNBEWBPywdkgI7UnN+s7KtlRiM0tP1cOLm+dQY++Fi33jkPQ==", + "version": "0.3.37", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.37.tgz", + "integrity": "sha512-aDFM+LbT01gP8hsJNs4QJjmbRNfoifqhpCSpk8j4k/V8wejEgvgATbgj9W9DQsfQFdtfwx+8G48sK5/0PqQisg==", "license": "MIT", "dependencies": { "@types/uuid": "^10.0.0", @@ -8361,9 +8408,21 @@ "uuid": "^10.0.0" }, "peerDependencies": { + "@opentelemetry/api": "*", + "@opentelemetry/exporter-trace-otlp-proto": "*", + "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@opentelemetry/exporter-trace-otlp-proto": { + "optional": true + }, + "@opentelemetry/sdk-trace-base": { + "optional": true + }, "openai": { "optional": true } diff --git a/package.json b/package.json index c7be961..174acba 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@langchain/community": "^0.3.45", "@langchain/core": "^0.3.57", "@langchain/google-genai": "^0.2.10", + "@langchain/groq": "^0.2.3", "@langchain/langgraph": "^0.3.1", "@langchain/ollama": "^0.2.0", "@langchain/openai": "^0.5.12", diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 6611f39..7211048 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -10,6 +10,7 @@ import { PlusCircle, Save, X, + RotateCcw, } from 'lucide-react'; import { useEffect, useState, useRef } from 'react'; import { cn } from '@/lib/utils'; @@ -18,6 +19,8 @@ import ThemeSwitcher from '@/components/theme/Switcher'; import { ImagesIcon, VideoIcon, Layers3 } from 'lucide-react'; import Link from 'next/link'; import { PROVIDER_METADATA } from '@/lib/providers'; +import Optimization from '@/components/MessageInputActions/Optimization'; +import ModelSelector from '@/components/MessageInputActions/ModelSelector'; interface SettingsType { chatModelProviders: { @@ -242,6 +245,13 @@ export default function SettingsPage() { ); const [isAddingNewPrompt, setIsAddingNewPrompt] = useState(false); + // Default Search Settings state variables + const [searchOptimizationMode, setSearchOptimizationMode] = + useState(''); + const [searchChatModelProvider, setSearchChatModelProvider] = + useState(''); + const [searchChatModel, setSearchChatModel] = useState(''); + useEffect(() => { const fetchConfig = async () => { setIsLoading(true); @@ -311,6 +321,29 @@ export default function SettingsPage() { fetchConfig(); + // Load search settings from localStorage + const loadSearchSettings = () => { + const storedSearchOptimizationMode = localStorage.getItem( + 'searchOptimizationMode', + ); + const storedSearchChatModelProvider = localStorage.getItem( + 'searchChatModelProvider', + ); + const storedSearchChatModel = localStorage.getItem('searchChatModel'); + + if (storedSearchOptimizationMode) { + setSearchOptimizationMode(storedSearchOptimizationMode); + } + if (storedSearchChatModelProvider) { + setSearchChatModelProvider(storedSearchChatModelProvider); + } + if (storedSearchChatModel) { + setSearchChatModel(storedSearchChatModel); + } + }; + + loadSearchSettings(); + const fetchSystemPrompts = async () => { setIsLoading(true); try { @@ -492,6 +525,10 @@ export default function SettingsPage() { } }; + const saveSearchSetting = (key: string, value: string) => { + localStorage.setItem(key, value); + }; + const handleAddOrUpdateSystemPrompt = async () => { const currentPrompt = editingPrompt || { name: newPromptName, @@ -997,6 +1034,79 @@ export default function SettingsPage() { + +
+
+

+ Optimization Mode +

+
+ { + setSearchOptimizationMode(mode); + saveSearchSetting('searchOptimizationMode', mode); + }} + showTitle={true} + /> + {searchOptimizationMode && ( + + )} +
+
+ +
+

+ Chat Model +

+
+ { + setSearchChatModelProvider(model.provider); + setSearchChatModel(model.model); + saveSearchSetting( + 'searchChatModelProvider', + model.provider, + ); + saveSearchSetting('searchChatModel', model.model); + }} + truncateModelName={false} + /> + {(searchChatModelProvider || searchChatModel) && ( + + )} +
+
+
+
+ {config.chatModelProviders && (
diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index 7581706..e7065c5 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -7,7 +7,7 @@ import Chat from './Chat'; import EmptyChat from './EmptyChat'; import crypto from 'crypto'; import { toast } from 'sonner'; -import { useSearchParams } from 'next/navigation'; +import { useSearchParams, useRouter } from 'next/navigation'; import { getSuggestions } from '@/lib/actions'; import { Settings } from 'lucide-react'; import Link from 'next/link'; @@ -250,6 +250,7 @@ const loadMessages = async ( const ChatWindow = ({ id }: { id?: string }) => { const searchParams = useSearchParams(); + const router = useRouter(); const initialMessage = searchParams.get('q'); const [chatId, setChatId] = useState(id); @@ -585,6 +586,9 @@ const ChatWindow = ({ id }: { id?: string }) => { currentChatModelProvider || chatModelProvider.provider; const modelName = currentChatModel || chatModelProvider.name; + const currentOptimizationMode = + localStorage.getItem('optimizationMode') || optimizationMode; + const res = await fetch('/api/chat', { method: 'POST', headers: { @@ -600,7 +604,7 @@ const ChatWindow = ({ id }: { id?: string }) => { chatId: chatId!, files: fileIds, focusMode: focusMode, - optimizationMode: optimizationMode, + optimizationMode: currentOptimizationMode, history: messageChatHistory, chatModel: { name: modelName, @@ -674,7 +678,42 @@ const ChatWindow = ({ id }: { id?: string }) => { useEffect(() => { if (isReady && initialMessage && isConfigReady) { + // Check if we have an initial query and apply saved search settings + const searchOptimizationMode = localStorage.getItem( + 'searchOptimizationMode', + ); + const searchChatModelProvider = localStorage.getItem( + 'searchChatModelProvider', + ); + const searchChatModel = localStorage.getItem('searchChatModel'); + + // Apply saved optimization mode if valid + if ( + searchOptimizationMode && + (searchOptimizationMode === 'speed' || + searchOptimizationMode === 'agent') + ) { + setOptimizationMode(searchOptimizationMode); + localStorage.setItem('optimizationMode', searchOptimizationMode); + } + + // Apply saved chat model if valid + if (searchChatModelProvider && searchChatModel) { + setChatModelProvider({ + name: searchChatModel, + provider: searchChatModelProvider, + }); + // Also update localStorage to ensure consistency + localStorage.setItem('chatModelProvider', searchChatModelProvider); + localStorage.setItem('chatModel', searchChatModel); + } + sendMessage(initialMessage); + + // Remove the query parameter from the URL to prevent re-execution on page reload + const url = new URL(window.location.href); + url.searchParams.delete('q'); + router.replace(url.pathname + url.search, { scroll: false }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isConfigReady, isReady, initialMessage]); diff --git a/src/components/MessageInput.tsx b/src/components/MessageInput.tsx index d02d19a..949abb9 100644 --- a/src/components/MessageInput.tsx +++ b/src/components/MessageInput.tsx @@ -164,7 +164,14 @@ const MessageInput = ({
{ + setSelectedModel(selectedModel); + localStorage.setItem( + 'chatModelProvider', + selectedModel.provider, + ); + localStorage.setItem('chatModel', selectedModel.model); + }} /> { + setOptimizationMode(optimizationMode); + localStorage.setItem('optimizationMode', optimizationMode); + }} /> {loading ? (