mirror of
				https://github.com/ItzCrazyKns/Perplexica.git
				synced 2025-11-04 04:38:15 +00:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			feat/deeps
			...
			e20d5ecc01
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					e20d5ecc01 | ||
| 
						 | 
					41b258e4d8 | ||
| 
						 | 
					18533d58c2 | ||
| 
						 | 
					54c71e33e0 | ||
| 
						 | 
					da1123d84b | ||
| 
						 | 
					627775c430 | ||
| 
						 | 
					245573efca | ||
| 
						 | 
					2c56aa3cb3 | ||
| 
						 | 
					a85f762c58 | ||
| 
						 | 
					3ddcceda0a | ||
| 
						 | 
					e226645bc7 | ||
| 
						 | 
					5447530ece | ||
| 
						 | 
					ed6d46a440 | ||
| 
						 | 
					bf705afc21 | ||
| 
						 | 
					2e4433a6b3 | 
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "perplexica-frontend",
 | 
			
		||||
  "version": "1.10.1",
 | 
			
		||||
  "version": "1.10.2",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "author": "ItzCrazyKns",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,3 +27,7 @@ API_KEY = ""
 | 
			
		||||
 | 
			
		||||
[API_ENDPOINTS]
 | 
			
		||||
SEARXNG = "" # SearxNG API URL - http://localhost:32768
 | 
			
		||||
TAVILY = "" # Tavily API key
 | 
			
		||||
 | 
			
		||||
[SEARCH]
 | 
			
		||||
ENGINE = "searxng" # "searxng" or "tavily"
 | 
			
		||||
@@ -8,6 +8,8 @@ import {
 | 
			
		||||
  getOllamaApiEndpoint,
 | 
			
		||||
  getOpenaiApiKey,
 | 
			
		||||
  getDeepseekApiKey,
 | 
			
		||||
  getSearchEngine,
 | 
			
		||||
  getTavilyApiKey,
 | 
			
		||||
  updateConfig,
 | 
			
		||||
} from '@/lib/config';
 | 
			
		||||
import {
 | 
			
		||||
@@ -58,6 +60,8 @@ export const GET = async (req: Request) => {
 | 
			
		||||
    config['customOpenaiApiUrl'] = getCustomOpenaiApiUrl();
 | 
			
		||||
    config['customOpenaiApiKey'] = getCustomOpenaiApiKey();
 | 
			
		||||
    config['customOpenaiModelName'] = getCustomOpenaiModelName();
 | 
			
		||||
    config['searchEngine'] = getSearchEngine();
 | 
			
		||||
    config['tavilyApiKey'] = getTavilyApiKey();
 | 
			
		||||
 | 
			
		||||
    return Response.json({ ...config }, { status: 200 });
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
@@ -99,6 +103,12 @@ export const POST = async (req: Request) => {
 | 
			
		||||
          MODEL_NAME: config.customOpenaiModelName,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      SEARCH: {
 | 
			
		||||
        ENGINE: config.searchEngine,
 | 
			
		||||
      },
 | 
			
		||||
      API_ENDPOINTS: {
 | 
			
		||||
        TAVILY: config.tavilyApiKey || '',
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    updateConfig(updatedConfig);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { searchSearxng } from '@/lib/searxng';
 | 
			
		||||
import { searchSearxng } from '../../../lib/searchEngines/searxng';
 | 
			
		||||
 | 
			
		||||
const articleWebsites = [
 | 
			
		||||
  'yahoo.com',
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,8 @@ interface SettingsType {
 | 
			
		||||
  customOpenaiApiKey: string;
 | 
			
		||||
  customOpenaiApiUrl: string;
 | 
			
		||||
  customOpenaiModelName: string;
 | 
			
		||||
  searchEngine: string;
 | 
			
		||||
  tavilyApiKey?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
 | 
			
		||||
@@ -145,6 +147,7 @@ const Page = () => {
 | 
			
		||||
  const [automaticImageSearch, setAutomaticImageSearch] = useState(false);
 | 
			
		||||
  const [automaticVideoSearch, setAutomaticVideoSearch] = useState(false);
 | 
			
		||||
  const [systemInstructions, setSystemInstructions] = useState<string>('');
 | 
			
		||||
  const [searchEngine, setSearchEngine] = useState<string>('searxng');
 | 
			
		||||
  const [savingStates, setSavingStates] = useState<Record<string, boolean>>({});
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@@ -207,6 +210,7 @@ const Page = () => {
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      setSystemInstructions(localStorage.getItem('systemInstructions')!);
 | 
			
		||||
      setSearchEngine(localStorage.getItem('searchEngine') || 'searxng');
 | 
			
		||||
 | 
			
		||||
      setIsLoading(false);
 | 
			
		||||
    };
 | 
			
		||||
@@ -366,6 +370,10 @@ const Page = () => {
 | 
			
		||||
        localStorage.setItem('embeddingModel', value);
 | 
			
		||||
      } else if (key === 'systemInstructions') {
 | 
			
		||||
        localStorage.setItem('systemInstructions', value);
 | 
			
		||||
      } else if (key === 'searchEngine') {
 | 
			
		||||
        localStorage.setItem('searchEngine', value);
 | 
			
		||||
      } else if (key === 'tavilyApiKey') {
 | 
			
		||||
        localStorage.setItem('tavilyApiKey', value);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      console.error('Failed to save:', err);
 | 
			
		||||
@@ -508,6 +516,32 @@ const Page = () => {
 | 
			
		||||
                    />
 | 
			
		||||
                  </Switch>
 | 
			
		||||
                </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>
 | 
			
		||||
            </SettingsSection>
 | 
			
		||||
 | 
			
		||||
@@ -858,6 +892,32 @@ const Page = () => {
 | 
			
		||||
                    onSave={(value) => saveConfig('deepseekApiKey', value)}
 | 
			
		||||
                  />
 | 
			
		||||
                </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>
 | 
			
		||||
            </SettingsSection>
 | 
			
		||||
          </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,7 @@ const MessageBox = ({
 | 
			
		||||
  const [speechMessage, setSpeechMessage] = useState(message.content);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const citationRegex = /\[([^\]]+)\]/g;
 | 
			
		||||
    const regex = /\[(\d+)\]/g;
 | 
			
		||||
    let processedMessage = message.content;
 | 
			
		||||
 | 
			
		||||
@@ -67,13 +68,36 @@ const MessageBox = ({
 | 
			
		||||
    ) {
 | 
			
		||||
      setParsedMessage(
 | 
			
		||||
        processedMessage.replace(
 | 
			
		||||
          regex,
 | 
			
		||||
          (_, number) =>
 | 
			
		||||
            `<a href="${
 | 
			
		||||
              message.sources?.[number - 1]?.metadata?.url
 | 
			
		||||
            }" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${number}</a>`,
 | 
			
		||||
          citationRegex,
 | 
			
		||||
          (_, capturedContent: string) => {
 | 
			
		||||
            const numbers = capturedContent
 | 
			
		||||
              .split(',')
 | 
			
		||||
              .map((numStr) => numStr.trim());
 | 
			
		||||
 | 
			
		||||
            const linksHtml = numbers
 | 
			
		||||
              .map((numStr) => {
 | 
			
		||||
                const number = parseInt(numStr);
 | 
			
		||||
 | 
			
		||||
                if (isNaN(number) || number <= 0) {
 | 
			
		||||
                  return `[${numStr}]`;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                const source = message.sources?.[number - 1];
 | 
			
		||||
                const url = source?.metadata?.url;
 | 
			
		||||
 | 
			
		||||
                if (url) {
 | 
			
		||||
                  return `<a href="${url}" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${numStr}</a>`;
 | 
			
		||||
                } else {
 | 
			
		||||
                  return `[${numStr}]`;
 | 
			
		||||
                }
 | 
			
		||||
              })
 | 
			
		||||
              .join('');
 | 
			
		||||
 | 
			
		||||
            return linksHtml;
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      setSpeechMessage(message.content.replace(regex, ''));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import { PromptTemplate } from '@langchain/core/prompts';
 | 
			
		||||
import formatChatHistoryAsString from '../utils/formatHistory';
 | 
			
		||||
import { BaseMessage } from '@langchain/core/messages';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
const imageSearchChainPrompt = `
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import { PromptTemplate } from '@langchain/core/prompts';
 | 
			
		||||
import formatChatHistoryAsString from '../utils/formatHistory';
 | 
			
		||||
import { BaseMessage } from '@langchain/core/messages';
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
const VideoSearchChainPrompt = `
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,10 @@ interface Config {
 | 
			
		||||
  };
 | 
			
		||||
  API_ENDPOINTS: {
 | 
			
		||||
    SEARXNG: string;
 | 
			
		||||
    TAVILY: string;
 | 
			
		||||
  };
 | 
			
		||||
  SEARCH: {
 | 
			
		||||
    ENGINE: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -64,6 +68,12 @@ export const getGeminiApiKey = () => loadConfig().MODELS.GEMINI.API_KEY;
 | 
			
		||||
export const getSearxngApiEndpoint = () =>
 | 
			
		||||
  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 getDeepseekApiKey = () => loadConfig().MODELS.DEEPSEEK.API_KEY;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,8 +40,12 @@ const geminiChatModels: Record<string, string>[] = [
 | 
			
		||||
 | 
			
		||||
const geminiEmbeddingModels: Record<string, string>[] = [
 | 
			
		||||
  {
 | 
			
		||||
    displayName: 'Gemini Embedding',
 | 
			
		||||
    key: 'gemini-embedding-exp',
 | 
			
		||||
    displayName: 'Text Embedding 004',
 | 
			
		||||
    key: 'models/text-embedding-004',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    displayName: 'Embedding 001',
 | 
			
		||||
    key: 'models/embedding-001',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -72,6 +72,14 @@ const groqChatModels: Record<string, string>[] = [
 | 
			
		||||
    displayName: 'Llama 3.2 90B Vision Preview (Preview)',
 | 
			
		||||
    key: 'llama-3.2-90b-vision-preview',
 | 
			
		||||
  },
 | 
			
		||||
  /* {
 | 
			
		||||
    displayName: 'Llama 4 Maverick 17B 128E Instruct (Preview)',
 | 
			
		||||
    key: 'meta-llama/llama-4-maverick-17b-128e-instruct',
 | 
			
		||||
  }, */
 | 
			
		||||
  {
 | 
			
		||||
    displayName: 'Llama 4 Scout 17B 16E Instruct (Preview)',
 | 
			
		||||
    key: 'meta-llama/llama-4-scout-17b-16e-instruct',
 | 
			
		||||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const loadGroqChatModels = async () => {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,9 @@ import LineListOutputParser from '../outputParsers/listLineOutputParser';
 | 
			
		||||
import LineOutputParser from '../outputParsers/lineOutputParser';
 | 
			
		||||
import { getDocumentsFromLinks } from '../utils/documents';
 | 
			
		||||
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 fs from 'node:fs';
 | 
			
		||||
import computeSimilarity from '../utils/computeSimilarity';
 | 
			
		||||
@@ -205,25 +207,42 @@ class MetaSearchAgent implements MetaSearchAgentType {
 | 
			
		||||
        } else {
 | 
			
		||||
          question = question.replace(/<think>.*?<\/think>/g, '');
 | 
			
		||||
 | 
			
		||||
          const res = await searchSearxng(question, {
 | 
			
		||||
          const searchEngine = getSearchEngine();
 | 
			
		||||
 | 
			
		||||
          let res;
 | 
			
		||||
          
 | 
			
		||||
          if (searchEngine === 'tavily') {
 | 
			
		||||
            res = await searchTavily(question, {
 | 
			
		||||
              search_depth: 'basic',
 | 
			
		||||
              max_results: 15,
 | 
			
		||||
              include_images: true,
 | 
			
		||||
            });
 | 
			
		||||
          } else {
 | 
			
		||||
            // Default to SearxNG
 | 
			
		||||
            res = await searchSearxng(question, {
 | 
			
		||||
              language: 'en',
 | 
			
		||||
              engines: this.config.activeEngines,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          const documents = res.results.map(
 | 
			
		||||
          let documents: Document[] = [];
 | 
			
		||||
          
 | 
			
		||||
          documents = documents.concat(
 | 
			
		||||
            res.results.map(
 | 
			
		||||
              (result) =>
 | 
			
		||||
                new Document({
 | 
			
		||||
                  pageContent:
 | 
			
		||||
                    result.content ||
 | 
			
		||||
                    (this.config.activeEngines.includes('youtube')
 | 
			
		||||
                      ? result.title
 | 
			
		||||
                    : '') /* Todo: Implement transcript grabbing using Youtubei (source: https://www.npmjs.com/package/youtubei) */,
 | 
			
		||||
                      : ''),
 | 
			
		||||
                  metadata: {
 | 
			
		||||
                    title: result.title,
 | 
			
		||||
                    url: result.url,
 | 
			
		||||
                  ...(result.img_src && { img_src: result.img_src }),
 | 
			
		||||
                    ...(result.img_src ? { img_src: result.img_src } : {}),
 | 
			
		||||
                  },
 | 
			
		||||
                }),
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          return { query: question, docs: documents };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import { getSearxngApiEndpoint } from './config';
 | 
			
		||||
import { getSearxngApiEndpoint } from '../config';
 | 
			
		||||
 | 
			
		||||
interface SearxngSearchOptions {
 | 
			
		||||
  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