mirror of
				https://github.com/ItzCrazyKns/Perplexica.git
				synced 2025-11-03 20:28:14 +00:00 
			
		
		
		
	Compare commits
	
		
			4 Commits
		
	
	
		
			v1.11.0-rc
			...
			e20d5ecc01
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e20d5ecc01 | ||
| 
						 | 
					18533d58c2 | ||
| 
						 | 
					54c71e33e0 | ||
| 
						 | 
					2c56aa3cb3 | 
@@ -26,4 +26,8 @@ API_URL = "" # Ollama API URL - http://host.docker.internal:11434
 | 
				
			|||||||
API_KEY = ""
 | 
					API_KEY = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[API_ENDPOINTS]
 | 
					[API_ENDPOINTS]
 | 
				
			||||||
SEARXNG = "" # SearxNG API URL - http://localhost:32768
 | 
					SEARXNG = "" # SearxNG API URL - http://localhost:32768
 | 
				
			||||||
 | 
					TAVILY = "" # Tavily API key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[SEARCH]
 | 
				
			||||||
 | 
					ENGINE = "searxng" # "searxng" or "tavily"
 | 
				
			||||||
@@ -8,6 +8,8 @@ import {
 | 
				
			|||||||
  getOllamaApiEndpoint,
 | 
					  getOllamaApiEndpoint,
 | 
				
			||||||
  getOpenaiApiKey,
 | 
					  getOpenaiApiKey,
 | 
				
			||||||
  getDeepseekApiKey,
 | 
					  getDeepseekApiKey,
 | 
				
			||||||
 | 
					  getSearchEngine,
 | 
				
			||||||
 | 
					  getTavilyApiKey,
 | 
				
			||||||
  updateConfig,
 | 
					  updateConfig,
 | 
				
			||||||
} from '@/lib/config';
 | 
					} from '@/lib/config';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
@@ -58,6 +60,8 @@ export const GET = async (req: Request) => {
 | 
				
			|||||||
    config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl();
 | 
					    config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl();
 | 
				
			||||||
    config['customOpenaiApiKey'] = getCustomOpenaiApiKey();
 | 
					    config['customOpenaiApiKey'] = getCustomOpenaiApiKey();
 | 
				
			||||||
    config['customOpenaiModelName'] = getCustomOpenaiModelName();
 | 
					    config['customOpenaiModelName'] = getCustomOpenaiModelName();
 | 
				
			||||||
 | 
					    config['searchEngine'] = getSearchEngine();
 | 
				
			||||||
 | 
					    config['tavilyApiKey'] = getTavilyApiKey();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Response.json({ ...config }, { status: 200 });
 | 
					    return Response.json({ ...config }, { status: 200 });
 | 
				
			||||||
  } catch (err) {
 | 
					  } catch (err) {
 | 
				
			||||||
@@ -99,6 +103,12 @@ export const POST = async (req: Request) => {
 | 
				
			|||||||
          MODEL_NAME: config.customOpenaiModelName,
 | 
					          MODEL_NAME: config.customOpenaiModelName,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					      SEARCH: {
 | 
				
			||||||
 | 
					        ENGINE: config.searchEngine,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      API_ENDPOINTS: {
 | 
				
			||||||
 | 
					        TAVILY: config.tavilyApiKey || '',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updateConfig(updatedConfig);
 | 
					    updateConfig(updatedConfig);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { searchSearxng } from '@/lib/searxng';
 | 
					import { searchSearxng } from '../../../lib/searchEngines/searxng';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const articleWebsites = [
 | 
					const articleWebsites = [
 | 
				
			||||||
  'yahoo.com',
 | 
					  'yahoo.com',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,6 +24,8 @@ interface SettingsType {
 | 
				
			|||||||
  customOpenaiApiKey: string;
 | 
					  customOpenaiApiKey: string;
 | 
				
			||||||
  customOpenaiApiUrl: string;
 | 
					  customOpenaiApiUrl: string;
 | 
				
			||||||
  customOpenaiModelName: string;
 | 
					  customOpenaiModelName: string;
 | 
				
			||||||
 | 
					  searchEngine: string;
 | 
				
			||||||
 | 
					  tavilyApiKey?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 | 
					interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 | 
				
			||||||
@@ -145,6 +147,7 @@ const Page = () => {
 | 
				
			|||||||
  const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
 | 
					  const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
 | 
				
			||||||
  const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
 | 
					  const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
 | 
				
			||||||
  const [systemInstructions, setSystemInstructions] = useState<string>('');
 | 
					  const [systemInstructions, setSystemInstructions] = useState<string>('');
 | 
				
			||||||
 | 
					  const [searchEngine, setSearchEngine] = useState<string>('searxng');
 | 
				
			||||||
  const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
 | 
					  const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
@@ -207,6 +210,7 @@ const Page = () => {
 | 
				
			|||||||
      );
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      setSystemInstructions(localStorage.getItem('systemInstructions')!);
 | 
					      setSystemInstructions(localStorage.getItem('systemInstructions')!);
 | 
				
			||||||
 | 
					      setSearchEngine(localStorage.getItem('searchEngine') || 'searxng');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      setIsLoading(false);
 | 
					      setIsLoading(false);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -366,6 +370,10 @@ const Page = () => {
 | 
				
			|||||||
        localStorage.setItem('embeddingModel', value);
 | 
					        localStorage.setItem('embeddingModel', value);
 | 
				
			||||||
      } else if (key === 'systemInstructions') {
 | 
					      } else if (key === 'systemInstructions') {
 | 
				
			||||||
        localStorage.setItem('systemInstructions', value);
 | 
					        localStorage.setItem('systemInstructions', value);
 | 
				
			||||||
 | 
					      } else if (key === 'searchEngine') {
 | 
				
			||||||
 | 
					        localStorage.setItem('searchEngine', value);
 | 
				
			||||||
 | 
					      } else if (key === 'tavilyApiKey') {
 | 
				
			||||||
 | 
					        localStorage.setItem('tavilyApiKey', value);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (err) {
 | 
					    } catch (err) {
 | 
				
			||||||
      console.error('Failed to save:', err);
 | 
					      console.error('Failed to save:', err);
 | 
				
			||||||
@@ -508,6 +516,32 @@ const Page = () => {
 | 
				
			|||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  </Switch>
 | 
					                  </Switch>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1 mt-2">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    Search Engine
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Select
 | 
				
			||||||
 | 
					                    value={searchEngine}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      const value = e.target.value;
 | 
				
			||||||
 | 
					                      setSearchEngine(value);
 | 
				
			||||||
 | 
					                      saveConfig('searchEngine', value);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    options={[
 | 
				
			||||||
 | 
					                      { value: 'searxng', label: 'SearxNG' },
 | 
				
			||||||
 | 
					                      ...(config.tavilyApiKey ? [{ value: 'tavily', label: 'Tavily' }] : []),
 | 
				
			||||||
 | 
					                    ]}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                  <p className="text-xs text-black/60 dark:text-white/60 mt-1">
 | 
				
			||||||
 | 
					                    Select which search engine to use for web searches
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  {searchEngine === 'tavily' && !config.tavilyApiKey && (
 | 
				
			||||||
 | 
					                    <p className="text-xs text-red-500 mt-1">
 | 
				
			||||||
 | 
					                      Tavily API key is required to use this search engine
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </SettingsSection>
 | 
					            </SettingsSection>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -858,6 +892,32 @@ const Page = () => {
 | 
				
			|||||||
                    onSave={(value) => saveConfig('deepseekApiKey', value)}
 | 
					                    onSave={(value) => saveConfig('deepseekApiKey', value)}
 | 
				
			||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1 mt-4 pt-4 border-t border-light-200 dark:border-dark-200">
 | 
				
			||||||
 | 
					                  <p className="text-black/90 dark:text-white/90 font-medium">Search Engine API Keys</p>
 | 
				
			||||||
 | 
					                  <p className="text-sm text-black/60 dark:text-white/60 mt-0.5">
 | 
				
			||||||
 | 
					                    API keys for search engines used in the application
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <div className="flex flex-col space-y-1">
 | 
				
			||||||
 | 
					                  <p className="text-black/70 dark:text-white/70 text-sm">
 | 
				
			||||||
 | 
					                    Tavily API Key
 | 
				
			||||||
 | 
					                  </p>
 | 
				
			||||||
 | 
					                  <Input
 | 
				
			||||||
 | 
					                    type="text"
 | 
				
			||||||
 | 
					                    placeholder="Tavily API key"
 | 
				
			||||||
 | 
					                    value={config.tavilyApiKey || ''}
 | 
				
			||||||
 | 
					                    isSaving={savingStates['tavilyApiKey']}
 | 
				
			||||||
 | 
					                    onChange={(e) => {
 | 
				
			||||||
 | 
					                      setConfig((prev) => ({
 | 
				
			||||||
 | 
					                        ...prev!,
 | 
				
			||||||
 | 
					                        tavilyApiKey: e.target.value,
 | 
				
			||||||
 | 
					                      }));
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                    onSave={(value) => saveConfig('tavilyApiKey', value)}
 | 
				
			||||||
 | 
					                  />
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            </SettingsSection>
 | 
					            </SettingsSection>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ import { PromptTemplate } from '@langchain/core/prompts';
 | 
				
			|||||||
import formatChatHistoryAsString from '../utils/formatHistory';
 | 
					import formatChatHistoryAsString from '../utils/formatHistory';
 | 
				
			||||||
import { BaseMessage } from '@langchain/core/messages';
 | 
					import { BaseMessage } from '@langchain/core/messages';
 | 
				
			||||||
import { StringOutputParser } from '@langchain/core/output_parsers';
 | 
					import { StringOutputParser } from '@langchain/core/output_parsers';
 | 
				
			||||||
import { searchSearxng } from '../searxng';
 | 
					import { searchSearxng } from '../searchEngines/searxng';
 | 
				
			||||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
					import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const imageSearchChainPrompt = `
 | 
					const imageSearchChainPrompt = `
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ import { PromptTemplate } from '@langchain/core/prompts';
 | 
				
			|||||||
import formatChatHistoryAsString from '../utils/formatHistory';
 | 
					import formatChatHistoryAsString from '../utils/formatHistory';
 | 
				
			||||||
import { BaseMessage } from '@langchain/core/messages';
 | 
					import { BaseMessage } from '@langchain/core/messages';
 | 
				
			||||||
import { StringOutputParser } from '@langchain/core/output_parsers';
 | 
					import { StringOutputParser } from '@langchain/core/output_parsers';
 | 
				
			||||||
import { searchSearxng } from '../searxng';
 | 
					import { searchSearxng } from '../searchEngines/searxng';
 | 
				
			||||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
					import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const VideoSearchChainPrompt = `
 | 
					const VideoSearchChainPrompt = `
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,6 +36,10 @@ interface Config {
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
  API_ENDPOINTS: {
 | 
					  API_ENDPOINTS: {
 | 
				
			||||||
    SEARXNG: string;
 | 
					    SEARXNG: string;
 | 
				
			||||||
 | 
					    TAVILY: string;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  SEARCH: {
 | 
				
			||||||
 | 
					    ENGINE: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,6 +68,12 @@ export const getGeminiApiKey = () => loadConfig().MODELS.GEMINI.API_KEY;
 | 
				
			|||||||
export const getSearxngApiEndpoint = () =>
 | 
					export const getSearxngApiEndpoint = () =>
 | 
				
			||||||
  process.env.SEARXNG_API_URL || loadConfig().API_ENDPOINTS.SEARXNG;
 | 
					  process.env.SEARXNG_API_URL || loadConfig().API_ENDPOINTS.SEARXNG;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getTavilyApiKey = () =>
 | 
				
			||||||
 | 
					  process.env.TAVILY_API_KEY || loadConfig().API_ENDPOINTS.TAVILY;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getSearchEngine = () =>
 | 
				
			||||||
 | 
					  process.env.SEARCH_ENGINE || loadConfig().SEARCH?.ENGINE || 'searxng';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getOllamaApiEndpoint = () => loadConfig().MODELS.OLLAMA.API_URL;
 | 
					export const getOllamaApiEndpoint = () => loadConfig().MODELS.OLLAMA.API_URL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getDeepseekApiKey = () => loadConfig().MODELS.DEEPSEEK.API_KEY;
 | 
					export const getDeepseekApiKey = () => loadConfig().MODELS.DEEPSEEK.API_KEY;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,9 @@ import LineListOutputParser from '../outputParsers/listLineOutputParser';
 | 
				
			|||||||
import LineOutputParser from '../outputParsers/lineOutputParser';
 | 
					import LineOutputParser from '../outputParsers/lineOutputParser';
 | 
				
			||||||
import { getDocumentsFromLinks } from '../utils/documents';
 | 
					import { getDocumentsFromLinks } from '../utils/documents';
 | 
				
			||||||
import { Document } from 'langchain/document';
 | 
					import { Document } from 'langchain/document';
 | 
				
			||||||
import { searchSearxng } from '../searxng';
 | 
					import { searchTavily } from '../searchEngines/tavily';
 | 
				
			||||||
 | 
					import { searchSearxng } from '../searchEngines/searxng';
 | 
				
			||||||
 | 
					import { getSearchEngine } from '../config';
 | 
				
			||||||
import path from 'node:path';
 | 
					import path from 'node:path';
 | 
				
			||||||
import fs from 'node:fs';
 | 
					import fs from 'node:fs';
 | 
				
			||||||
import computeSimilarity from '../utils/computeSimilarity';
 | 
					import computeSimilarity from '../utils/computeSimilarity';
 | 
				
			||||||
@@ -205,25 +207,42 @@ class MetaSearchAgent implements MetaSearchAgentType {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          question = question.replace(/<think>.*?<\/think>/g, '');
 | 
					          question = question.replace(/<think>.*?<\/think>/g, '');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const res = await searchSearxng(question, {
 | 
					          const searchEngine = getSearchEngine();
 | 
				
			||||||
            language: 'en',
 | 
					 | 
				
			||||||
            engines: this.config.activeEngines,
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const documents = res.results.map(
 | 
					          let res;
 | 
				
			||||||
            (result) =>
 | 
					          
 | 
				
			||||||
              new Document({
 | 
					          if (searchEngine === 'tavily') {
 | 
				
			||||||
                pageContent:
 | 
					            res = await searchTavily(question, {
 | 
				
			||||||
                  result.content ||
 | 
					              search_depth: 'basic',
 | 
				
			||||||
                  (this.config.activeEngines.includes('youtube')
 | 
					              max_results: 15,
 | 
				
			||||||
                    ? result.title
 | 
					              include_images: true,
 | 
				
			||||||
                    : '') /* Todo: Implement transcript grabbing using Youtubei (source: https://www.npmjs.com/package/youtubei) */,
 | 
					            });
 | 
				
			||||||
                metadata: {
 | 
					          } else {
 | 
				
			||||||
                  title: result.title,
 | 
					            // Default to SearxNG
 | 
				
			||||||
                  url: result.url,
 | 
					            res = await searchSearxng(question, {
 | 
				
			||||||
                  ...(result.img_src && { img_src: result.img_src }),
 | 
					              language: 'en',
 | 
				
			||||||
                },
 | 
					              engines: this.config.activeEngines,
 | 
				
			||||||
              }),
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          let documents: Document[] = [];
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					          documents = documents.concat(
 | 
				
			||||||
 | 
					            res.results.map(
 | 
				
			||||||
 | 
					              (result) =>
 | 
				
			||||||
 | 
					                new Document({
 | 
				
			||||||
 | 
					                  pageContent:
 | 
				
			||||||
 | 
					                    result.content ||
 | 
				
			||||||
 | 
					                    (this.config.activeEngines.includes('youtube')
 | 
				
			||||||
 | 
					                      ? result.title
 | 
				
			||||||
 | 
					                      : ''),
 | 
				
			||||||
 | 
					                  metadata: {
 | 
				
			||||||
 | 
					                    title: result.title,
 | 
				
			||||||
 | 
					                    url: result.url,
 | 
				
			||||||
 | 
					                    ...(result.img_src ? { img_src: result.img_src } : {}),
 | 
				
			||||||
 | 
					                  },
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
          );
 | 
					          );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          return { query: question, docs: documents };
 | 
					          return { query: question, docs: documents };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
import { getSearxngApiEndpoint } from './config';
 | 
					import { getSearxngApiEndpoint } from '../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface SearxngSearchOptions {
 | 
					interface SearxngSearchOptions {
 | 
				
			||||||
  categories?: string[];
 | 
					  categories?: string[];
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/lib/searchEngines/tavily.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/lib/searchEngines/tavily.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					import { getTavilyApiKey } from '../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TavilySearchOptions {
 | 
				
			||||||
 | 
					  topic?: 'general' | 'news';
 | 
				
			||||||
 | 
					  search_depth?: 'basic' | 'advanced';
 | 
				
			||||||
 | 
					  chunks_per_source?: number;
 | 
				
			||||||
 | 
					  max_results?: number;
 | 
				
			||||||
 | 
					  time_range?: 'day' | 'week' | 'month' | 'year' | 'd' | 'w' | 'm' | 'y';
 | 
				
			||||||
 | 
					  days?: number;
 | 
				
			||||||
 | 
					  include_answer?: boolean | 'basic' | 'advanced';
 | 
				
			||||||
 | 
					  include_raw_content?: boolean;
 | 
				
			||||||
 | 
					  include_images?: boolean;
 | 
				
			||||||
 | 
					  include_image_descriptions?: boolean;
 | 
				
			||||||
 | 
					  include_domains?: string[];
 | 
				
			||||||
 | 
					  exclude_domains?: string[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TavilySearchResult {
 | 
				
			||||||
 | 
					  title: string;
 | 
				
			||||||
 | 
					  url: string;
 | 
				
			||||||
 | 
					  content: string;
 | 
				
			||||||
 | 
					  score: number;
 | 
				
			||||||
 | 
					  raw_content?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface TavilySearchResponse {
 | 
				
			||||||
 | 
					  query: string;
 | 
				
			||||||
 | 
					  answer?: string;
 | 
				
			||||||
 | 
					  images?: Array<{
 | 
				
			||||||
 | 
					    url: string;
 | 
				
			||||||
 | 
					    description?: string;
 | 
				
			||||||
 | 
					  }>;
 | 
				
			||||||
 | 
					  results: TavilySearchResult[];
 | 
				
			||||||
 | 
					  response_time: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const searchTavily = async (
 | 
				
			||||||
 | 
					  query: string,
 | 
				
			||||||
 | 
					  opts?: TavilySearchOptions,
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  const tavilyApiKey = getTavilyApiKey();
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (!tavilyApiKey) {
 | 
				
			||||||
 | 
					    throw new Error('Tavily API key is not configured');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const url = 'https://api.tavily.com/search';
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  const response = await axios.post<TavilySearchResponse>(
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      query,
 | 
				
			||||||
 | 
					      ...opts,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      headers: {
 | 
				
			||||||
 | 
					        'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					        'Authorization': `Bearer ${tavilyApiKey}`,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const results = response.data.results;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  // Convert Tavily results to match the format expected by the rest of the application
 | 
				
			||||||
 | 
					  const formattedResults = results.map(result => ({
 | 
				
			||||||
 | 
					    title: result.title,
 | 
				
			||||||
 | 
					    url: result.url,
 | 
				
			||||||
 | 
					    content: result.content,
 | 
				
			||||||
 | 
					    img_src: undefined, // Tavily doesn't provide image URLs in the standard response
 | 
				
			||||||
 | 
					  }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { 
 | 
				
			||||||
 | 
					    results: formattedResults, 
 | 
				
			||||||
 | 
					    suggestions: [], // Tavily doesn't provide suggestions, so return empty array
 | 
				
			||||||
 | 
					    answer: response.data.answer, // Include the AI-generated answer if available
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}; 
 | 
				
			||||||
		Reference in New Issue
	
	Block a user