From da488f1c3921e4f03bc62021621db0547b3bfbbd Mon Sep 17 00:00:00 2001 From: duanfuxiang Date: Mon, 17 Mar 2025 10:40:25 +0800 Subject: [PATCH] update settings for deep research mode --- src/components/chat-view/Chat.tsx | 4 +-- src/constants.ts | 3 +- src/settings/SettingTab.tsx | 53 +++++++++++++++++++++++++++++++ src/types/settings.ts | 4 +++ src/utils/web-search.ts | 42 ++++++++++++------------ 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/src/components/chat-view/Chat.tsx b/src/components/chat-view/Chat.tsx index 6746862..98c4c14 100644 --- a/src/components/chat-view/Chat.tsx +++ b/src/components/chat-view/Chat.tsx @@ -535,7 +535,7 @@ const Chat = forwardRef((props, ref) => { } } } else if (toolArgs.type === 'search_web') { - const results = await webSearch(toolArgs.query) + const results = await webSearch(toolArgs.query, settings.serperApiKey) const formattedContent = `[search_web for '${toolArgs.query}'] Result:\n${results}\n`; return { type: 'search_web', @@ -551,7 +551,7 @@ const Chat = forwardRef((props, ref) => { } } } else if (toolArgs.type === 'fetch_urls_content') { - const results = await fetchUrlsContent(toolArgs.urls) + const results = await fetchUrlsContent(toolArgs.urls, settings.jinaApiKey) const formattedContent = `[ fetch_urls_content ] Result:\n${results}\n`; return { type: 'fetch_urls_content', diff --git a/src/constants.ts b/src/constants.ts index f4a4b60..0cdaf4a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -32,7 +32,8 @@ export const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1' export const SILICONFLOW_BASE_URL = 'https://api.siliconflow.cn/v1' export const ALIBABA_QWEN_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1' export const INFIO_BASE_URL = 'https://api.infio.com/api/raw_message' - +export const JINA_BASE_URL = 'https://r.jina.ai' +export const SERPER_BASE_URL = 'https://serpapi.com/search' // Pricing in dollars per million tokens type ModelPricing = { input: number diff --git a/src/settings/SettingTab.tsx b/src/settings/SettingTab.tsx index c47a7da..9dc41e5 100644 --- a/src/settings/SettingTab.tsx +++ b/src/settings/SettingTab.tsx @@ -37,6 +37,7 @@ export class InfioSettingTab extends PluginSettingTab { const { containerEl } = this containerEl.empty() this.renderModelsSection(containerEl) + this.renderDeepResearchSection(containerEl) this.renderRAGSection(containerEl) this.renderAutoCompleteSection(containerEl) } @@ -63,6 +64,58 @@ export class InfioSettingTab extends PluginSettingTab { this.renderModelsContent(modelsDiv); } + renderDeepResearchSection(containerEl: HTMLElement): void { + new Setting(containerEl) + .setHeading() + .setName('Deep Research') + + new Setting(containerEl) + .setName('Serper Api Key') + .setDesc(createFragment(el => { + el.appendText('API key for web search functionality. Serper allows the plugin to search the internet for information, similar to a search engine. Get your key from '); + const a = el.createEl('a', { + href: 'https://serpapi.com/manage-api-key', + text: 'https://serpapi.com/manage-api-key' + }); + a.setAttr('target', '_blank'); + a.setAttr('rel', 'noopener'); + })) + .setClass('setting-item-heading-smaller') + .addText((text) => + text + .setValue(this.plugin.settings.serperApiKey) + .onChange(async (value) => { + await this.plugin.setSettings({ + ...this.plugin.settings, + serperApiKey: value, + }) + }), + ) + + new Setting(containerEl) + .setName('Jina Api Key (Optional)') + .setDesc(createFragment(el => { + el.appendText('API key for parsing web pages into markdown format. If not provided, local parsing will be used. Get your key from '); + const a = el.createEl('a', { + href: 'https://jina.ai/api-key', + text: 'https://jina.ai/api-key' + }); + a.setAttr('target', '_blank'); + a.setAttr('rel', 'noopener'); + })) + .setClass('setting-item-heading-smaller') + .addText((text) => + text + .setValue(this.plugin.settings.jinaApiKey) + .onChange(async (value) => { + await this.plugin.setSettings({ + ...this.plugin.settings, + jinaApiKey: value, + }) + }), + ) + } + renderRAGSection(containerEl: HTMLElement): void { new Setting(containerEl).setHeading().setName('RAG') new Setting(containerEl) diff --git a/src/types/settings.ts b/src/types/settings.ts index 2ece36c..80923af 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -222,6 +222,10 @@ export const InfioSettingsSchema = z.object({ // Mode mode: z.string().catch('ask'), + // Web Search + serperApiKey: z.string().catch(''), + jinaApiKey: z.string().catch(''), + /// [compatible] // activeModels [compatible] activeModels: z.array( diff --git a/src/utils/web-search.ts b/src/utils/web-search.ts index 4a31700..0e3a9fa 100644 --- a/src/utils/web-search.ts +++ b/src/utils/web-search.ts @@ -2,9 +2,9 @@ import https from 'https'; import { htmlToMarkdown, requestUrl } from 'obsidian'; -import { YoutubeTranscript, isYoutubeUrl } from './youtube-transcript'; +import { JINA_BASE_URL, SERPER_BASE_URL } from '../constants'; -const SERPER_API_KEY = 'a6fd4dc4b79f10b1e5008b688c81bacef0d24b4d5cd4e52071afa8329a67497c' +import { YoutubeTranscript, isYoutubeUrl } from './youtube-transcript'; interface SearchResult { title: string; @@ -16,9 +16,9 @@ interface SearchResponse { organic_results?: SearchResult[]; } -export async function webSearch(query: string): Promise { +export async function webSearch(query: string, serperApiKey: string): Promise { return new Promise((resolve, reject) => { - const url = `https://serpapi.com/search?q=${encodeURIComponent(query)}&engine=google&api_key=${SERPER_API_KEY}&num=20`; + const url = `${SERPER_BASE_URL}?q=${encodeURIComponent(query)}&engine=google&api_key=${serperApiKey}&num=20`; console.log(url) @@ -76,37 +76,37 @@ ${transcript.map((t) => `${t.offset}: ${t.text}`).join('\n')}` return htmlToMarkdown(response.text) } -const USE_JINA = true -export async function fetchUrlsContent(urls: string[]): Promise { +export async function fetchUrlsContent(urls: string[], apiKey: string): Promise { + const use_jina = apiKey && apiKey != '' ? true : false return new Promise((resolve) => { const results = urls.map(async (url) => { try { - const content = USE_JINA ? await fetchJina(url) : await getWebsiteContent(url); + const content = use_jina ? await fetchJina(url, apiKey) : await getWebsiteContent(url); return `\n${content}\n`; } catch (error) { - console.error(`获取URL内容失败: ${url}`, error); - return `\n获取内容失败: ${error}\n`; + console.error(`Failed to fetch URL content: ${url}`, error); + return `\n fetch content error: ${error}\n`; } }); - + console.log('fetchUrlsContent', results); - + Promise.all(results).then((texts) => { resolve(texts.join('\n\n')); }).catch((error) => { - console.error('获取URLs内容时出错', error); - resolve('fetch urls content error'); // 即使出错也返回一些内容 + console.error('fetch urls content error', error); + resolve('fetch urls content error'); // even if error, return some content }); }); } -function fetchJina(url: string): Promise { +function fetchJina(url: string, apiKey: string): Promise { return new Promise((resolve) => { - const jinaUrl = `https://r.jina.ai/${url}`; + const jinaUrl = `${JINA_BASE_URL}/${url}`; const jinaHeaders = { - 'Authorization': 'Bearer jina_1d721eb8c4814a938b4351ae0c3a0f117FlTTAz1GOmpOsIDN7HvIyLbiOCe', + 'Authorization': `Bearer ${apiKey}`, 'X-No-Cache': 'true', }; @@ -124,18 +124,18 @@ function fetchJina(url: string): Promise { res.on('end', () => { console.log(data); - + try { - // 检查是否有错误响应 + // check if there is an error response const response = JSON.parse(data); if (response.code && response.message) { - console.error(`JINA API 错误: ${response.message}`); - resolve(`无法获取内容: ${response.message}`); + console.error(`JINA API error: ${response.message}`); + resolve(`fetch jina content error: ${response.message}`); return; } resolve(data); } catch (e) { - // 如果不是JSON格式,可能是正常的内容 + // if not json format, maybe normal content resolve(data); } });