mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-05-02 01:02:45 +00:00
Compare commits
2 Commits
e20d5ecc01
...
72450b9217
Author | SHA1 | Date | |
---|---|---|---|
|
72450b9217 | ||
|
28b9cca413 |
@ -159,6 +159,7 @@ Perplexica runs on Next.js and handles all API requests. It works right away on
|
|||||||
|
|
||||||
[](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)
|
[](https://usw.sealos.io/?openapp=system-template%3FtemplateName%3Dperplexica)
|
||||||
[](https://repocloud.io/details/?app_id=267)
|
[](https://repocloud.io/details/?app_id=267)
|
||||||
|
[](https://template.run.claw.cloud/?referralCode=U11MRQ8U9RM4&openapp=system-fastdeploy%3FtemplateName%3Dperplexica)
|
||||||
|
|
||||||
## Upcoming Features
|
## Upcoming Features
|
||||||
|
|
||||||
|
@ -27,7 +27,3 @@ 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,8 +8,6 @@ import {
|
|||||||
getOllamaApiEndpoint,
|
getOllamaApiEndpoint,
|
||||||
getOpenaiApiKey,
|
getOpenaiApiKey,
|
||||||
getDeepseekApiKey,
|
getDeepseekApiKey,
|
||||||
getSearchEngine,
|
|
||||||
getTavilyApiKey,
|
|
||||||
updateConfig,
|
updateConfig,
|
||||||
} from '@/lib/config';
|
} from '@/lib/config';
|
||||||
import {
|
import {
|
||||||
@ -60,8 +58,6 @@ 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) {
|
||||||
@ -103,12 +99,6 @@ 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/searchEngines/searxng';
|
import { searchSearxng } from '@/lib/searxng';
|
||||||
|
|
||||||
const articleWebsites = [
|
const articleWebsites = [
|
||||||
'yahoo.com',
|
'yahoo.com',
|
||||||
|
@ -24,8 +24,6 @@ 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> {
|
||||||
@ -147,7 +145,6 @@ 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(() => {
|
||||||
@ -210,7 +207,6 @@ const Page = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setSystemInstructions(localStorage.getItem('systemInstructions')!);
|
setSystemInstructions(localStorage.getItem('systemInstructions')!);
|
||||||
setSearchEngine(localStorage.getItem('searchEngine') || 'searxng');
|
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
@ -370,10 +366,6 @@ 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);
|
||||||
@ -516,32 +508,6 @@ 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>
|
||||||
|
|
||||||
@ -892,32 +858,6 @@ 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 '../searchEngines/searxng';
|
import { searchSearxng } from '../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 '../searchEngines/searxng';
|
import { searchSearxng } from '../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,10 +36,6 @@ interface Config {
|
|||||||
};
|
};
|
||||||
API_ENDPOINTS: {
|
API_ENDPOINTS: {
|
||||||
SEARXNG: string;
|
SEARXNG: string;
|
||||||
TAVILY: string;
|
|
||||||
};
|
|
||||||
SEARCH: {
|
|
||||||
ENGINE: string;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,12 +64,6 @@ 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,9 +17,7 @@ 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 { searchTavily } from '../searchEngines/tavily';
|
import { searchSearxng } from '../searxng';
|
||||||
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';
|
||||||
@ -207,42 +205,25 @@ class MetaSearchAgent implements MetaSearchAgentType {
|
|||||||
} else {
|
} else {
|
||||||
question = question.replace(/<think>.*?<\/think>/g, '');
|
question = question.replace(/<think>.*?<\/think>/g, '');
|
||||||
|
|
||||||
const searchEngine = getSearchEngine();
|
const res = await searchSearxng(question, {
|
||||||
|
|
||||||
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',
|
language: 'en',
|
||||||
engines: this.config.activeEngines,
|
engines: this.config.activeEngines,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
let documents: Document[] = [];
|
const documents = res.results.map(
|
||||||
|
|
||||||
documents = documents.concat(
|
|
||||||
res.results.map(
|
|
||||||
(result) =>
|
(result) =>
|
||||||
new Document({
|
new Document({
|
||||||
pageContent:
|
pageContent:
|
||||||
result.content ||
|
result.content ||
|
||||||
(this.config.activeEngines.includes('youtube')
|
(this.config.activeEngines.includes('youtube')
|
||||||
? result.title
|
? result.title
|
||||||
: ''),
|
: '') /* Todo: Implement transcript grabbing using Youtubei (source: https://www.npmjs.com/package/youtubei) */,
|
||||||
metadata: {
|
metadata: {
|
||||||
title: result.title,
|
title: result.title,
|
||||||
url: result.url,
|
url: result.url,
|
||||||
...(result.img_src ? { img_src: result.img_src } : {}),
|
...(result.img_src && { img_src: result.img_src }),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return { query: question, docs: documents };
|
return { query: question, docs: documents };
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
@ -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[];
|
Reference in New Issue
Block a user