Compare commits
7 Commits
copilot/fi
...
hlink/deep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41ef576d0a | ||
|
|
4d605005a7 | ||
|
|
d2019a32aa | ||
|
|
05d110e4af | ||
|
|
e2a140a99a | ||
|
|
d33e16fa81 | ||
|
|
1b2d15f2e8 |
@@ -159,7 +159,7 @@ class McpService {
|
||||
} else if (server.type === 'sse') {
|
||||
const options: SSEClientTransportOptions = {
|
||||
eventSourceInit: {
|
||||
fetch: (url, init) => fetch(url, { ...init, headers: server.headers || {} }),
|
||||
fetch: (url, init) => fetch(url, { ...init, headers: server.headers || {} })
|
||||
},
|
||||
requestInit: {
|
||||
headers: server.headers || {}
|
||||
|
||||
@@ -6,7 +6,9 @@ import { HashRouter, Route, Routes } from 'react-router-dom'
|
||||
import { PersistGate } from 'redux-persist/integration/react'
|
||||
|
||||
import Sidebar from './components/app/Sidebar'
|
||||
import GeminiInitializer from './components/GeminiInitializer'
|
||||
import TopViewContainer from './components/TopView'
|
||||
import WebSearchInitializer from './components/WebSearchInitializer'
|
||||
import AntdProvider from './context/AntdProvider'
|
||||
import StyleSheetManager from './context/StyleSheetManager'
|
||||
import { SyntaxHighlighterProvider } from './context/SyntaxHighlighterProvider'
|
||||
@@ -14,6 +16,7 @@ import { ThemeProvider } from './context/ThemeProvider'
|
||||
import NavigationHandler from './handler/NavigationHandler'
|
||||
import AgentsPage from './pages/agents/AgentsPage'
|
||||
import AppsPage from './pages/apps/AppsPage'
|
||||
import DeepResearchPage from './pages/deepresearch/DeepResearchPage'
|
||||
import FilesPage from './pages/files/FilesPage'
|
||||
import HomePage from './pages/home/HomePage'
|
||||
import KnowledgePage from './pages/knowledge/KnowledgePage'
|
||||
@@ -29,6 +32,8 @@ function App(): React.ReactElement {
|
||||
<AntdProvider>
|
||||
<SyntaxHighlighterProvider>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<GeminiInitializer />
|
||||
<WebSearchInitializer />
|
||||
<TopViewContainer>
|
||||
<HashRouter>
|
||||
<NavigationHandler />
|
||||
@@ -41,6 +46,7 @@ function App(): React.ReactElement {
|
||||
<Route path="/files" element={<FilesPage />} />
|
||||
<Route path="/knowledge" element={<KnowledgePage />} />
|
||||
<Route path="/apps" element={<AppsPage />} />
|
||||
<Route path="/deepresearch" element={<DeepResearchPage />} />
|
||||
<Route path="/settings/*" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
.deep-research-container {
|
||||
padding: 20px;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.token-stats {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.source-link {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.research-loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.loading-status {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.iteration-info {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
height: 8px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: #1890ff;
|
||||
border-radius: 4px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-percentage {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.direct-answer-card {
|
||||
background-color: #f0f8ff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.direct-answer-title {
|
||||
color: #1890ff;
|
||||
}
|
||||
472
src/renderer/src/components/DeepResearch/DeepResearchPanel.tsx
Normal file
472
src/renderer/src/components/DeepResearch/DeepResearchPanel.tsx
Normal file
@@ -0,0 +1,472 @@
|
||||
import './DeepResearchPanel.css'
|
||||
|
||||
import {
|
||||
BulbOutlined,
|
||||
DownloadOutlined,
|
||||
ExperimentOutlined,
|
||||
FileSearchOutlined,
|
||||
HistoryOutlined,
|
||||
LinkOutlined,
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { DeepResearchProvider } from '@renderer/providers/WebSearchProvider/DeepResearchProvider'
|
||||
import { ResearchIteration, ResearchReport, WebSearchResult } from '@renderer/types'
|
||||
import { Button, Card, Collapse, Divider, Input, List, message, Modal, Space, Spin, Tag, Typography } from 'antd'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
import { useWebSearchStore } from '../../hooks/useWebSearchStore'
|
||||
|
||||
const { Title, Paragraph, Text } = Typography
|
||||
const { Panel } = Collapse
|
||||
|
||||
// 定义历史研究记录的接口
|
||||
interface ResearchHistory {
|
||||
id: string
|
||||
query: string
|
||||
date: string
|
||||
report: ResearchReport
|
||||
}
|
||||
|
||||
const DeepResearchPanel: React.FC = () => {
|
||||
const [query, setQuery] = useState('')
|
||||
const [isResearching, setIsResearching] = useState(false)
|
||||
const [report, setReport] = useState<ResearchReport | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [maxIterations, setMaxIterations] = useState(3)
|
||||
const [historyVisible, setHistoryVisible] = useState(false)
|
||||
const [history, setHistory] = useState<ResearchHistory[]>([])
|
||||
const [currentIteration, setCurrentIteration] = useState(0)
|
||||
const [progressStatus, setProgressStatus] = useState('')
|
||||
const [progressPercent, setProgressPercent] = useState(0)
|
||||
|
||||
const { providers, selectedProvider, websearch } = useWebSearchStore()
|
||||
|
||||
// 加载历史记录
|
||||
useEffect(() => {
|
||||
const loadHistory = async () => {
|
||||
try {
|
||||
const savedHistory = localStorage.getItem('deepResearchHistory')
|
||||
if (savedHistory) {
|
||||
setHistory(JSON.parse(savedHistory))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('加载历史记录失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
loadHistory()
|
||||
}, [])
|
||||
|
||||
// 保存历史记录
|
||||
const saveToHistory = (newReport: ResearchReport) => {
|
||||
try {
|
||||
const newHistory: ResearchHistory = {
|
||||
id: Date.now().toString(),
|
||||
query: newReport.originalQuery,
|
||||
date: new Date().toLocaleString(),
|
||||
report: newReport
|
||||
}
|
||||
|
||||
const updatedHistory = [newHistory, ...history].slice(0, 20) // 只保存20条记录
|
||||
setHistory(updatedHistory)
|
||||
localStorage.setItem('deepResearchHistory', JSON.stringify(updatedHistory))
|
||||
} catch (err) {
|
||||
console.error('保存历史记录失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 导出报告为Markdown文件
|
||||
const exportToMarkdown = (reportToExport: ResearchReport) => {
|
||||
try {
|
||||
let markdown = `# 深度研究报告: ${reportToExport.originalQuery}\n\n`
|
||||
|
||||
// 添加问题回答
|
||||
markdown += `## 问题回答\n\n${reportToExport.directAnswer}\n\n`
|
||||
|
||||
// 添加关键见解
|
||||
markdown += `## 关键见解\n\n`
|
||||
reportToExport.keyInsights.forEach((insight) => {
|
||||
markdown += `- ${insight}\n`
|
||||
})
|
||||
|
||||
// 添加研究总结
|
||||
markdown += `\n## 研究总结\n\n${reportToExport.summary}\n\n`
|
||||
|
||||
// 添加研究过程
|
||||
markdown += `## 研究过程\n\n`
|
||||
reportToExport.iterations.forEach((iteration, index) => {
|
||||
markdown += `### 迭代 ${index + 1}: ${iteration.query}\n\n`
|
||||
markdown += `#### 分析\n\n${iteration.analysis}\n\n`
|
||||
|
||||
if (iteration.followUpQueries.length > 0) {
|
||||
markdown += `#### 后续查询\n\n`
|
||||
iteration.followUpQueries.forEach((q) => {
|
||||
markdown += `- ${q}\n`
|
||||
})
|
||||
markdown += '\n'
|
||||
}
|
||||
})
|
||||
|
||||
// 添加信息来源
|
||||
markdown += `## 信息来源\n\n`
|
||||
reportToExport.sources.forEach((source) => {
|
||||
markdown += `- [${source}](${source})\n`
|
||||
})
|
||||
|
||||
// 添加Token统计
|
||||
if (reportToExport.tokenUsage) {
|
||||
markdown += `\n## Token统计\n\n`
|
||||
markdown += `- 输入Token数: ${reportToExport.tokenUsage.inputTokens.toLocaleString()}\n`
|
||||
markdown += `- 输出Token数: ${reportToExport.tokenUsage.outputTokens.toLocaleString()}\n`
|
||||
markdown += `- 总计Token数: ${reportToExport.tokenUsage.totalTokens.toLocaleString()}\n`
|
||||
}
|
||||
|
||||
// 创建Blob并下载
|
||||
const blob = new Blob([markdown], { type: 'text/markdown' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `深度研究-${reportToExport.originalQuery.substring(0, 20)}-${new Date().toISOString().split('T')[0]}.md`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
message.success('报告导出成功')
|
||||
} catch (err) {
|
||||
console.error('导出报告失败:', err)
|
||||
message.error('导出报告失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 从历史记录中加载报告
|
||||
const loadFromHistory = (historyItem: ResearchHistory) => {
|
||||
setReport(historyItem.report)
|
||||
setQuery(historyItem.query)
|
||||
setHistoryVisible(false)
|
||||
}
|
||||
|
||||
const handleQueryChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(e.target.value)
|
||||
}
|
||||
|
||||
const handleMaxIterationsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseInt(e.target.value)
|
||||
if (!isNaN(value) && value > 0) {
|
||||
setMaxIterations(value)
|
||||
}
|
||||
}
|
||||
|
||||
const startResearch = async () => {
|
||||
if (!query.trim()) {
|
||||
setError('请输入研究查询')
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedProvider) {
|
||||
setError('请选择搜索提供商')
|
||||
return
|
||||
}
|
||||
|
||||
setIsResearching(true)
|
||||
setError(null)
|
||||
setReport(null)
|
||||
setCurrentIteration(0)
|
||||
setProgressStatus('准备中...')
|
||||
setProgressPercent(0)
|
||||
|
||||
try {
|
||||
const provider = providers.find((p) => p.id === selectedProvider)
|
||||
if (!provider) {
|
||||
throw new Error('找不到选定的搜索提供商')
|
||||
}
|
||||
|
||||
const deepResearchProvider = new DeepResearchProvider(provider)
|
||||
deepResearchProvider.setAnalysisConfig({
|
||||
maxIterations,
|
||||
modelId: websearch?.deepResearchConfig?.modelId
|
||||
})
|
||||
|
||||
// 确保 websearch 存在,如果不存在则创建一个空对象
|
||||
const webSearchState = websearch || {
|
||||
defaultProvider: selectedProvider,
|
||||
providers,
|
||||
maxResults: 10,
|
||||
excludeDomains: [],
|
||||
searchWithTime: false,
|
||||
subscribeSources: [],
|
||||
overwrite: false,
|
||||
deepResearchConfig: {
|
||||
maxIterations,
|
||||
maxResultsPerQuery: 50,
|
||||
autoSummary: true,
|
||||
enableQueryOptimization: true
|
||||
}
|
||||
}
|
||||
|
||||
// 添加进度回调
|
||||
const progressCallback = (iteration: number, status: string, percent: number) => {
|
||||
setCurrentIteration(iteration)
|
||||
setProgressStatus(status)
|
||||
setProgressPercent(percent)
|
||||
}
|
||||
|
||||
// 开始研究
|
||||
const researchReport = await deepResearchProvider.research(query, webSearchState, progressCallback)
|
||||
setReport(researchReport)
|
||||
|
||||
// 保存到历史记录
|
||||
saveToHistory(researchReport)
|
||||
} catch (err: any) {
|
||||
console.error('深度研究失败:', err)
|
||||
setError(`研究过程中出错: ${err?.message || '未知错误'}`)
|
||||
} finally {
|
||||
setIsResearching(false)
|
||||
setProgressStatus('')
|
||||
setProgressPercent(100)
|
||||
}
|
||||
}
|
||||
|
||||
const renderResultItem = (result: WebSearchResult) => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={
|
||||
<a href={result.url} target="_blank" rel="noopener noreferrer">
|
||||
{result.title}
|
||||
</a>
|
||||
}
|
||||
size="small"
|
||||
style={{ width: '100%', wordBreak: 'break-word', overflowWrap: 'break-word' }}>
|
||||
<Paragraph ellipsis={{ rows: 3 }}>
|
||||
{result.content ? result.content.substring(0, 200) + '...' : '无内容'}
|
||||
</Paragraph>
|
||||
<Text type="secondary" style={{ wordBreak: 'break-word', overflowWrap: 'break-word', display: 'block' }}>
|
||||
来源: {result.url}
|
||||
</Text>
|
||||
</Card>
|
||||
</List.Item>
|
||||
)
|
||||
|
||||
const renderIteration = (iteration: ResearchIteration, index: number) => (
|
||||
<Panel
|
||||
header={
|
||||
<Space>
|
||||
<FileSearchOutlined />
|
||||
<span>
|
||||
迭代 {index + 1}: {iteration.query}
|
||||
</span>
|
||||
</Space>
|
||||
}
|
||||
key={index}>
|
||||
<Title level={5}>搜索结果</Title>
|
||||
<List dataSource={iteration.results} renderItem={renderResultItem} grid={{ gutter: 16, column: 1 }} />
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={5}>分析</Title>
|
||||
<Card>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} className="markdown">
|
||||
{iteration.analysis}
|
||||
</ReactMarkdown>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={5}>后续查询</Title>
|
||||
<Space wrap>
|
||||
{iteration.followUpQueries.map((q, i) => (
|
||||
<Tag color="blue" key={i}>
|
||||
{q}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Panel>
|
||||
)
|
||||
|
||||
const renderReport = () => {
|
||||
if (!report) return null
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card>
|
||||
<Title level={3}>
|
||||
<ExperimentOutlined /> 深度研究报告: {report.originalQuery}
|
||||
</Title>
|
||||
{report.tokenUsage && (
|
||||
<div className="token-stats">
|
||||
Token统计: 输入 {report.tokenUsage.inputTokens.toLocaleString()} | 输出{' '}
|
||||
{report.tokenUsage.outputTokens.toLocaleString()} | 总计 {report.tokenUsage.totalTokens.toLocaleString()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={4} className="direct-answer-title">
|
||||
问题回答
|
||||
</Title>
|
||||
<Card className="direct-answer-card">
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} className="markdown">
|
||||
{report.directAnswer}
|
||||
</ReactMarkdown>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={4}>
|
||||
<BulbOutlined /> 关键见解
|
||||
</Title>
|
||||
<List
|
||||
dataSource={report.keyInsights}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<Text>{item}</Text>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={4}>研究总结</Title>
|
||||
<Card>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} className="markdown">
|
||||
{report.summary}
|
||||
</ReactMarkdown>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={4}>研究迭代</Title>
|
||||
<Collapse>{report.iterations.map((iteration, index) => renderIteration(iteration, index))}</Collapse>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Title level={4}>
|
||||
<LinkOutlined /> 信息来源
|
||||
</Title>
|
||||
<List
|
||||
dataSource={report.sources}
|
||||
renderItem={(source) => (
|
||||
<List.Item>
|
||||
<a href={source} target="_blank" rel="noopener noreferrer" className="source-link">
|
||||
{source}
|
||||
</a>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 渲染历史记录对话框
|
||||
const renderHistoryModal = () => (
|
||||
<Modal
|
||||
title={
|
||||
<div>
|
||||
<HistoryOutlined /> 历史研究记录
|
||||
</div>
|
||||
}
|
||||
open={historyVisible}
|
||||
onCancel={() => setHistoryVisible(false)}
|
||||
footer={null}
|
||||
width={800}>
|
||||
<List
|
||||
dataSource={history}
|
||||
renderItem={(item) => (
|
||||
<List.Item
|
||||
actions={[
|
||||
<Button key="load" type="link" onClick={() => loadFromHistory(item)}>
|
||||
加载
|
||||
</Button>,
|
||||
<Button key="export" type="link" onClick={() => exportToMarkdown(item.report)}>
|
||||
导出
|
||||
</Button>
|
||||
]}>
|
||||
<List.Item.Meta
|
||||
title={item.query}
|
||||
description={
|
||||
<div>
|
||||
<div>日期: {item.date}</div>
|
||||
<div>迭代次数: {item.report.iterations.length}</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
locale={{ emptyText: '暂无历史记录' }}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="deep-research-container">
|
||||
<Title level={3}>
|
||||
<ExperimentOutlined /> 深度研究
|
||||
</Title>
|
||||
<Paragraph>深度研究功能通过多轮搜索、分析和总结,为您提供全面的研究报告。</Paragraph>
|
||||
|
||||
<Space direction="vertical" style={{ width: '100%', marginBottom: '20px' }}>
|
||||
<Input
|
||||
placeholder="输入研究主题或问题"
|
||||
value={query}
|
||||
onChange={handleQueryChange}
|
||||
prefix={<SearchOutlined />}
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<Space>
|
||||
<Text>最大迭代次数:</Text>
|
||||
<Input type="number" value={maxIterations} onChange={handleMaxIterationsChange} style={{ width: '60px' }} />
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ExperimentOutlined />}
|
||||
onClick={startResearch}
|
||||
loading={isResearching}
|
||||
disabled={!query.trim() || !selectedProvider}>
|
||||
开始深度研究
|
||||
</Button>
|
||||
|
||||
<Button icon={<HistoryOutlined />} onClick={() => setHistoryVisible(true)} disabled={isResearching}>
|
||||
历史记录
|
||||
</Button>
|
||||
|
||||
{report && (
|
||||
<Button icon={<DownloadOutlined />} onClick={() => exportToMarkdown(report)} disabled={isResearching}>
|
||||
导出报告
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</Space>
|
||||
|
||||
{error && <div className="error-message">{error}</div>}
|
||||
|
||||
{isResearching && (
|
||||
<div className="research-loading">
|
||||
<Spin size="large" />
|
||||
<div className="loading-status">
|
||||
<div>正在进行深度研究: {progressStatus}</div>
|
||||
<div className="iteration-info">
|
||||
迭代 {currentIteration}/{maxIterations}
|
||||
</div>
|
||||
<div className="progress-container">
|
||||
<div className="progress-bar-container">
|
||||
<div className="progress-bar" style={{ width: `${progressPercent}%` }} />
|
||||
</div>
|
||||
<div className="progress-percentage">{progressPercent}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{report && renderReport()}
|
||||
|
||||
{/* 渲染历史记录对话框 */}
|
||||
{renderHistoryModal()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeepResearchPanel
|
||||
3
src/renderer/src/components/DeepResearch/index.ts
Normal file
3
src/renderer/src/components/DeepResearch/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import DeepResearchPanel from './DeepResearchPanel'
|
||||
|
||||
export { DeepResearchPanel }
|
||||
35
src/renderer/src/components/GeminiInitializer.tsx
Normal file
35
src/renderer/src/components/GeminiInitializer.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { RootState } from '@renderer/store'
|
||||
import { updateProvider } from '@renderer/store/llm'
|
||||
import { useEffect } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
/**
|
||||
* GeminiInitializer组件
|
||||
* 用于在应用启动时检查Gemini API的配置
|
||||
* 如果没有配置API密钥,则禁用Gemini API
|
||||
*/
|
||||
const GeminiInitializer = () => {
|
||||
const dispatch = useDispatch()
|
||||
const providers = useSelector((state: RootState) => state.llm.providers)
|
||||
|
||||
useEffect(() => {
|
||||
// 检查Gemini提供商
|
||||
const geminiProvider = providers.find((provider) => provider.id === 'gemini')
|
||||
|
||||
// 如果Gemini提供商存在且已启用,但没有API密钥,则禁用它
|
||||
if (geminiProvider && geminiProvider.enabled && !geminiProvider.apiKey) {
|
||||
dispatch(
|
||||
updateProvider({
|
||||
...geminiProvider,
|
||||
enabled: false
|
||||
})
|
||||
)
|
||||
console.log('Gemini API disabled due to missing API key')
|
||||
}
|
||||
}, [dispatch, providers])
|
||||
|
||||
// 这是一个初始化组件,不需要渲染任何UI
|
||||
return null
|
||||
}
|
||||
|
||||
export default GeminiInitializer
|
||||
37
src/renderer/src/components/WebSearchInitializer.tsx
Normal file
37
src/renderer/src/components/WebSearchInitializer.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { RootState } from '@renderer/store'
|
||||
import { addWebSearchProvider } from '@renderer/store/websearch'
|
||||
import { WebSearchProvider } from '@renderer/types'
|
||||
import { useEffect } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
/**
|
||||
* WebSearchInitializer组件
|
||||
* 用于在应用启动时初始化WebSearchService
|
||||
* 确保DeepSearch在应用启动时被正确设置
|
||||
*/
|
||||
const WebSearchInitializer = () => {
|
||||
const dispatch = useDispatch()
|
||||
const providers = useSelector((state: RootState) => state.websearch.providers)
|
||||
|
||||
useEffect(() => {
|
||||
// 检查是否已经存在DeepSearch提供商
|
||||
const hasDeepSearch = providers.some((provider) => provider.id === 'deep-search')
|
||||
|
||||
// 如果不存在,添加DeepSearch提供商
|
||||
if (!hasDeepSearch) {
|
||||
const deepSearchProvider: WebSearchProvider = {
|
||||
id: 'deep-search',
|
||||
name: 'DeepSearch',
|
||||
usingBrowser: true,
|
||||
contentLimit: 10000,
|
||||
description: '多引擎深度搜索'
|
||||
}
|
||||
dispatch(addWebSearchProvider(deepSearchProvider))
|
||||
}
|
||||
}, [dispatch, providers])
|
||||
|
||||
// 这是一个初始化组件,不需要渲染任何UI
|
||||
return null
|
||||
}
|
||||
|
||||
export default WebSearchInitializer
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
Languages,
|
||||
LayoutGrid,
|
||||
MessageSquareQuote,
|
||||
Microscope,
|
||||
Moon,
|
||||
Palette,
|
||||
Settings,
|
||||
@@ -136,7 +137,8 @@ const MainMenus: FC = () => {
|
||||
translate: <Languages size={18} className="icon" />,
|
||||
minapp: <LayoutGrid size={18} className="icon" />,
|
||||
knowledge: <FileSearch size={18} className="icon" />,
|
||||
files: <Folder size={17} className="icon" />
|
||||
files: <Folder size={17} className="icon" />,
|
||||
deepresearch: <Microscope size={18} className="icon" />
|
||||
}
|
||||
|
||||
const pathMap = {
|
||||
@@ -146,7 +148,8 @@ const MainMenus: FC = () => {
|
||||
translate: '/translate',
|
||||
minapp: '/apps',
|
||||
knowledge: '/knowledge',
|
||||
files: '/files'
|
||||
files: '/files',
|
||||
deepresearch: '/deepresearch'
|
||||
}
|
||||
|
||||
return sidebarIcons.visible.map((icon) => {
|
||||
|
||||
13
src/renderer/src/hooks/useWebSearchStore.ts
Normal file
13
src/renderer/src/hooks/useWebSearchStore.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
|
||||
export function useWebSearchStore() {
|
||||
const websearch = useAppSelector((state) => state.websearch)
|
||||
const providers = useAppSelector((state) => state.websearch.providers)
|
||||
const selectedProvider = useAppSelector((state) => state.websearch.defaultProvider)
|
||||
|
||||
return {
|
||||
websearch,
|
||||
providers,
|
||||
selectedProvider
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,29 @@
|
||||
{
|
||||
"translation": {
|
||||
"deepresearch": {
|
||||
"title": "Deep Research",
|
||||
"description": "Provides comprehensive research reports through multiple rounds of search, analysis, and summarization",
|
||||
"start": "Start Deep Research",
|
||||
"query": {
|
||||
"placeholder": "Enter research topic or question",
|
||||
"empty": "Please enter a research query"
|
||||
},
|
||||
"max_iterations": "Maximum Iterations",
|
||||
"researching": "Conducting deep research, this may take a few minutes...",
|
||||
"report": {
|
||||
"title": "Deep Research Report",
|
||||
"key_insights": "Key Insights",
|
||||
"summary": "Research Summary",
|
||||
"iterations": "Research Iterations",
|
||||
"sources": "Information Sources"
|
||||
},
|
||||
"iteration": {
|
||||
"title": "Iteration",
|
||||
"search_results": "Search Results",
|
||||
"analysis": "Analysis",
|
||||
"follow_up_queries": "Follow-up Queries"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"add.button": "Add to Assistant",
|
||||
"add.knowledge_base": "Knowledge Base",
|
||||
@@ -1337,6 +1361,12 @@
|
||||
"tray.show": "Show Tray Icon",
|
||||
"tray.title": "Tray",
|
||||
"websearch": {
|
||||
"deep_research": {
|
||||
"title": "Deep Research Settings",
|
||||
"max_iterations": "Maximum Iterations",
|
||||
"max_results_per_query": "Maximum Results Per Query",
|
||||
"auto_summary": "Auto Summary"
|
||||
},
|
||||
"blacklist": "Blacklist",
|
||||
"blacklist_description": "Results from the following websites will not appear in search results",
|
||||
"blacklist_tooltip": "Please use the following format (separated by line breaks)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
{
|
||||
"translation": {
|
||||
"deepresearch": {
|
||||
"title": "深度研究",
|
||||
"description": "通过多轮搜索、分析和总结,提供全面的研究报告",
|
||||
"start": "开始深度研究",
|
||||
"query": {
|
||||
"placeholder": "输入研究主题或问题",
|
||||
"empty": "请输入研究查询"
|
||||
},
|
||||
"max_iterations": "最大迭代次数",
|
||||
"researching": "正在进行深度研究,这可能需要几分钟时间...",
|
||||
"report": {
|
||||
"title": "深度研究报告",
|
||||
"key_insights": "关键见解",
|
||||
"summary": "研究总结",
|
||||
"iterations": "研究迭代",
|
||||
"sources": "信息来源"
|
||||
},
|
||||
"iteration": {
|
||||
"title": "迭代",
|
||||
"search_results": "搜索结果",
|
||||
"analysis": "分析",
|
||||
"follow_up_queries": "后续查询"
|
||||
},
|
||||
"engine_rotation": "每次迭代使用不同类别的搜索引擎:中文、国际、元搜索和学术搜索",
|
||||
"open": "打开深度研究"
|
||||
},
|
||||
"agents": {
|
||||
"add.button": "添加到助手",
|
||||
"add.knowledge_base": "知识库",
|
||||
@@ -1337,6 +1363,12 @@
|
||||
"tray.show": "显示托盘图标",
|
||||
"tray.title": "托盘",
|
||||
"websearch": {
|
||||
"deep_research": {
|
||||
"title": "深度研究设置",
|
||||
"max_iterations": "最大迭代次数",
|
||||
"max_results_per_query": "每次查询的最大结果数",
|
||||
"auto_summary": "自动生成摘要"
|
||||
},
|
||||
"blacklist": "黑名单",
|
||||
"blacklist_description": "在搜索结果中不会出现以下网站的结果",
|
||||
"blacklist_tooltip": "请使用以下格式(换行分隔)\n匹配模式: *://*.example.com/*\n正则表达式: /example\\.(net|org)/",
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
{
|
||||
"translation": {
|
||||
"deepresearch": {
|
||||
"title": "深度研究",
|
||||
"description": "通過多輪搜索、分析和總結,提供全面的研究報告",
|
||||
"start": "開始深度研究",
|
||||
"query": {
|
||||
"placeholder": "輸入研究主題或問題",
|
||||
"empty": "請輸入研究查詢"
|
||||
},
|
||||
"max_iterations": "最大迭代次數",
|
||||
"researching": "正在進行深度研究,這可能需要幾分鐘時間...",
|
||||
"report": {
|
||||
"title": "深度研究報告",
|
||||
"key_insights": "關鍵見解",
|
||||
"summary": "研究總結",
|
||||
"iterations": "研究迭代",
|
||||
"sources": "信息來源"
|
||||
},
|
||||
"iteration": {
|
||||
"title": "迭代",
|
||||
"search_results": "搜索結果",
|
||||
"analysis": "分析",
|
||||
"follow_up_queries": "後續查詢"
|
||||
}
|
||||
},
|
||||
"agents": {
|
||||
"add.button": "新增到助手",
|
||||
"add.knowledge_base": "知識庫",
|
||||
@@ -1336,6 +1360,12 @@
|
||||
"tray.show": "顯示系统匣圖示",
|
||||
"tray.title": "系统匣",
|
||||
"websearch": {
|
||||
"deep_research": {
|
||||
"title": "深度研究設置",
|
||||
"max_iterations": "最大迭代次數",
|
||||
"max_results_per_query": "每次查詢的最大結果數",
|
||||
"auto_summary": "自動生成摘要"
|
||||
},
|
||||
"check_success": "驗證成功",
|
||||
"get_api_key": "點選這裡取得金鑰",
|
||||
"search_with_time": "搜尋包含日期",
|
||||
|
||||
36
src/renderer/src/pages/deepresearch/DeepResearchPage.tsx
Normal file
36
src/renderer/src/pages/deepresearch/DeepResearchPage.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { DeepResearchPanel } from '../../components/DeepResearch'
|
||||
|
||||
const DeepResearchPage: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Navbar>
|
||||
<NavbarCenter>{t('deepresearch.title', 'Deep Research')}</NavbarCenter>
|
||||
</Navbar>
|
||||
<Content>
|
||||
<DeepResearchPanel />
|
||||
</Content>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 0;
|
||||
`
|
||||
|
||||
export default DeepResearchPage
|
||||
@@ -19,6 +19,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
import EnableDeepResearch from './EnableDeepResearch'
|
||||
import SidebarIconsManager from './SidebarIconsManager'
|
||||
|
||||
const DisplaySettings: FC = () => {
|
||||
@@ -178,6 +179,7 @@ const DisplaySettings: FC = () => {
|
||||
setDisabledIcons={setDisabledIcons}
|
||||
/>
|
||||
</SettingGroup>
|
||||
<EnableDeepResearch />
|
||||
<SettingGroup theme={theme}>
|
||||
<SettingTitle>
|
||||
{t('settings.display.custom.css')}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { SidebarIcon, setSidebarIcons } from '@renderer/store/settings'
|
||||
import { Button, message } from 'antd'
|
||||
import { Microscope } from 'lucide-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
|
||||
const EnableDeepResearch: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const dispatch = useAppDispatch()
|
||||
const { sidebarIcons } = useSettings()
|
||||
const { theme: themeMode } = useTheme()
|
||||
|
||||
const isDeepResearchEnabled = sidebarIcons.visible.includes('deepresearch')
|
||||
|
||||
const handleEnableDeepResearch = () => {
|
||||
if (!isDeepResearchEnabled) {
|
||||
const newVisibleIcons: SidebarIcon[] = [...sidebarIcons.visible, 'deepresearch' as SidebarIcon]
|
||||
dispatch(setSidebarIcons({ visible: newVisibleIcons }))
|
||||
message.success(t('deepresearch.enable_success', '深度研究功能已启用,请查看侧边栏'))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingGroup theme={themeMode}>
|
||||
<SettingTitle>
|
||||
<IconWrapper>
|
||||
<Microscope size={18} />
|
||||
</IconWrapper>
|
||||
{t('deepresearch.title', '深度研究')}
|
||||
</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('deepresearch.description', '通过多轮搜索、分析和总结,提供全面的研究报告')}
|
||||
</SettingRowTitle>
|
||||
{!isDeepResearchEnabled ? (
|
||||
<Button type="primary" onClick={handleEnableDeepResearch}>
|
||||
{t('deepresearch.enable', '启用深度研究')}
|
||||
</Button>
|
||||
) : (
|
||||
<EnabledText>{t('deepresearch.already_enabled', '已启用')}</EnabledText>
|
||||
)}
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
)
|
||||
}
|
||||
|
||||
const IconWrapper = styled.span`
|
||||
margin-right: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const EnabledText = styled.span`
|
||||
color: var(--color-success);
|
||||
font-weight: 500;
|
||||
`
|
||||
|
||||
export default EnableDeepResearch
|
||||
@@ -0,0 +1,175 @@
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { setDeepResearchConfig } from '@renderer/store/websearch'
|
||||
import { Model } from '@renderer/types'
|
||||
import { Button, InputNumber, Space, Switch } from 'antd'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
|
||||
const SubDescription = styled.div`
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
margin-top: 4px;
|
||||
`
|
||||
|
||||
const DeepResearchSettings: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { theme: themeMode } = useTheme()
|
||||
const dispatch = useAppDispatch()
|
||||
const navigate = useNavigate()
|
||||
const providers = useAppSelector((state) => state.llm.providers)
|
||||
const deepResearchConfig = useAppSelector((state) => state.websearch.deepResearchConfig) || {
|
||||
maxIterations: 3,
|
||||
maxResultsPerQuery: 20,
|
||||
autoSummary: true,
|
||||
enableQueryOptimization: true
|
||||
}
|
||||
|
||||
// 当前选择的模型
|
||||
const [selectedModel, setSelectedModel] = useState<Model | null>(null)
|
||||
|
||||
// 初始化时,如果有保存的模型ID,则加载对应的模型
|
||||
useEffect(() => {
|
||||
if (deepResearchConfig.modelId) {
|
||||
const allModels = providers.flatMap((p) => p.models)
|
||||
const model = allModels.find((m) => getModelUniqId(m) === deepResearchConfig.modelId)
|
||||
if (model) {
|
||||
setSelectedModel(model)
|
||||
}
|
||||
}
|
||||
}, [deepResearchConfig.modelId, providers])
|
||||
|
||||
const handleMaxIterationsChange = (value: number | null) => {
|
||||
if (value !== null) {
|
||||
dispatch(
|
||||
setDeepResearchConfig({
|
||||
...deepResearchConfig,
|
||||
maxIterations: value
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleMaxResultsPerQueryChange = (value: number | null) => {
|
||||
if (value !== null) {
|
||||
dispatch(
|
||||
setDeepResearchConfig({
|
||||
...deepResearchConfig,
|
||||
maxResultsPerQuery: value
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAutoSummaryChange = (checked: boolean) => {
|
||||
dispatch(
|
||||
setDeepResearchConfig({
|
||||
...deepResearchConfig,
|
||||
autoSummary: checked
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const handleQueryOptimizationChange = (checked: boolean) => {
|
||||
dispatch(
|
||||
setDeepResearchConfig({
|
||||
...deepResearchConfig,
|
||||
enableQueryOptimization: checked
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const handleOpenDeepResearch = () => {
|
||||
navigate('/deepresearch')
|
||||
}
|
||||
|
||||
const handleSelectModel = async () => {
|
||||
const model = await SelectModelPopup.show({ model: selectedModel || undefined })
|
||||
if (model) {
|
||||
setSelectedModel(model)
|
||||
dispatch(
|
||||
setDeepResearchConfig({
|
||||
...deepResearchConfig,
|
||||
modelId: getModelUniqId(model)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingGroup theme={themeMode}>
|
||||
<SettingTitle>{t('settings.websearch.deep_research.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('deepresearch.description', '通过多轮搜索、分析和总结,提供全面的研究报告')}
|
||||
<SubDescription>
|
||||
{t('deepresearch.engine_rotation', '每次迭代使用不同类别的搜索引擎:中文、国际、元搜索和学术搜索')}
|
||||
</SubDescription>
|
||||
</SettingRowTitle>
|
||||
<Button type="primary" onClick={handleOpenDeepResearch}>
|
||||
{t('deepresearch.open', '打开深度研究')}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
|
||||
<SettingDivider />
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.model.select', '选择模型')}</SettingRowTitle>
|
||||
<Button onClick={handleSelectModel}>
|
||||
{selectedModel ? (
|
||||
<Space>
|
||||
<ModelAvatar model={selectedModel} size={20} />
|
||||
<span>{selectedModel.name}</span>
|
||||
</Space>
|
||||
) : (
|
||||
t('settings.model.select_model', '选择模型')
|
||||
)}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.websearch.deep_research.max_iterations')}</SettingRowTitle>
|
||||
<InputNumber min={1} max={10} value={deepResearchConfig.maxIterations} onChange={handleMaxIterationsChange} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.websearch.deep_research.max_results_per_query')}</SettingRowTitle>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={50}
|
||||
value={deepResearchConfig.maxResultsPerQuery}
|
||||
onChange={handleMaxResultsPerQueryChange}
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.websearch.deep_research.auto_summary')}</SettingRowTitle>
|
||||
<Switch checked={deepResearchConfig.autoSummary} onChange={handleAutoSummaryChange} />
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('settings.websearch.deep_research.enable_query_optimization', '启用查询优化')}
|
||||
<SubDescription>
|
||||
{t(
|
||||
'settings.websearch.deep_research.query_optimization_desc',
|
||||
'使用 AI 分析您的问题并生成更有效的搜索查询'
|
||||
)}
|
||||
</SubDescription>
|
||||
</SettingRowTitle>
|
||||
<Switch checked={deepResearchConfig.enableQueryOptimization} onChange={handleQueryOptimizationChange} />
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeepResearchSettings
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
import { Button, message } from 'antd'
|
||||
import { Microscope } from 'lucide-react'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
|
||||
const DeepResearchShortcut: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { theme: themeMode } = useTheme()
|
||||
|
||||
const handleOpenDeepResearch = async () => {
|
||||
try {
|
||||
await modelGenerating()
|
||||
navigate('/deepresearch')
|
||||
message.success(t('deepresearch.open_success', '正在打开深度研究页面'))
|
||||
} catch (error) {
|
||||
console.error('打开深度研究页面失败:', error)
|
||||
message.error(t('deepresearch.open_error', '打开深度研究页面失败'))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingGroup theme={themeMode}>
|
||||
<SettingTitle>
|
||||
<IconWrapper>
|
||||
<Microscope size={18} />
|
||||
</IconWrapper>
|
||||
{t('deepresearch.title', '深度研究')}
|
||||
</SettingTitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
{t('deepresearch.description', '通过多轮搜索、分析和总结,提供全面的研究报告')}
|
||||
</SettingRowTitle>
|
||||
<Button type="primary" onClick={handleOpenDeepResearch}>
|
||||
{t('deepresearch.open', '打开深度研究')}
|
||||
</Button>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
)
|
||||
}
|
||||
|
||||
const IconWrapper = styled.span`
|
||||
margin-right: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default DeepResearchShortcut
|
||||
@@ -9,6 +9,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
import BasicSettings from './BasicSettings'
|
||||
import BlacklistSettings from './BlacklistSettings'
|
||||
import DeepResearchSettings from './DeepResearchSettings'
|
||||
import WebSearchProviderSetting from './WebSearchProviderSetting'
|
||||
|
||||
const WebSearchSettings: FC = () => {
|
||||
@@ -56,6 +57,7 @@ const WebSearchSettings: FC = () => {
|
||||
</SettingGroup>
|
||||
)}
|
||||
<BasicSettings />
|
||||
<DeepResearchSettings />
|
||||
<BlacklistSettings />
|
||||
</SettingContainer>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,492 @@
|
||||
import { WebSearchProvider } from '@renderer/providers/WebSearchProvider/WebSearchProvider'
|
||||
import { WebSearchResult } from '@renderer/types'
|
||||
|
||||
// 临时定义WebSearchState类型,因为它在@renderer/types中不存在
|
||||
interface WebSearchState {
|
||||
defaultProvider: string
|
||||
providers: any[]
|
||||
maxResults: number
|
||||
excludeDomains: string[]
|
||||
searchWithTime: boolean
|
||||
subscribeSources: string[]
|
||||
overwrite: boolean
|
||||
deepResearchConfig?: {
|
||||
maxIterations?: number
|
||||
maxResultsPerQuery?: number
|
||||
autoSummary?: boolean
|
||||
enableQueryOptimization?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// 临时定义DeepSearchProvider类
|
||||
class DeepSearchProvider {
|
||||
constructor(provider: WebSearchProvider) {}
|
||||
|
||||
async search(query: string, webSearchState?: WebSearchState, category?: string): Promise<{ results: WebSearchResult[] }> {
|
||||
return { results: [] }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析配置接口
|
||||
*/
|
||||
interface AnalysisConfig {
|
||||
maxIterations: number
|
||||
maxResultsPerQuery: number
|
||||
minConfidenceScore: number
|
||||
autoSummary: boolean
|
||||
modelId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 研究迭代接口
|
||||
*/
|
||||
interface ResearchIteration {
|
||||
query: string
|
||||
results: WebSearchResult[]
|
||||
analysis: string
|
||||
followUpQueries: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 研究报告接口
|
||||
*/
|
||||
export interface ResearchReport {
|
||||
originalQuery: string
|
||||
iterations: ResearchIteration[]
|
||||
summary: string
|
||||
keyInsights: string[]
|
||||
sources: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* DeepResearchProvider 类
|
||||
* 提供深度研究功能,包括多轮搜索、分析和总结
|
||||
*/
|
||||
export class DeepResearchProvider {
|
||||
private deepSearchProvider: DeepSearchProvider
|
||||
private analysisConfig: AnalysisConfig
|
||||
constructor(provider: WebSearchProvider) {
|
||||
this.deepSearchProvider = new DeepSearchProvider(provider)
|
||||
this.analysisConfig = {
|
||||
maxIterations: 3, // 默认最大迭代次数
|
||||
maxResultsPerQuery: 20, // 每次查询的最大结果数
|
||||
minConfidenceScore: 0.6, // 最小可信度分数
|
||||
autoSummary: true // 自动生成摘要
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化查询
|
||||
* @param query 用户原始查询
|
||||
* @returns 优化后的查询
|
||||
*/
|
||||
private async optimizeQuery(query: string): Promise<string> {
|
||||
try {
|
||||
console.log(`[DeepResearch] 正在优化查询: "${query}"`)
|
||||
|
||||
// 使用模型优化查询
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个搜索优化专家,负责将用户的问题转化为最有效的搜索查询。
|
||||
|
||||
用户问题: "${query}"
|
||||
|
||||
请分析这个问题,并生成一个更有效的搜索查询。你的查询应该:
|
||||
1. 提取关键概念和术语
|
||||
2. 去除不必要的虚词和介词
|
||||
3. 增加相关的同义词或专业术语(如果适用)
|
||||
4. 保持简洁明确,不超过 10 个关键词
|
||||
|
||||
只返回优化后的查询词,不要添加任何解释或其他文本。`
|
||||
|
||||
const optimizedQuery = await fetchGenerate({
|
||||
prompt,
|
||||
content: ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId
|
||||
})
|
||||
|
||||
// 如果优化失败,返回原始查询
|
||||
if (!optimizedQuery || optimizedQuery.trim() === '') {
|
||||
return query
|
||||
}
|
||||
|
||||
console.log(`[DeepResearch] 查询优化结果: "${optimizedQuery}"`)
|
||||
return optimizedQuery.trim()
|
||||
} catch (error) {
|
||||
console.error('[DeepResearch] 查询优化失败:', error)
|
||||
return query // 出错时返回原始查询
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行深度研究
|
||||
* @param query 初始查询
|
||||
* @param websearch WebSearch状态
|
||||
* @returns 研究报告
|
||||
*/
|
||||
public async research(query: string, websearch?: WebSearchState): Promise<ResearchReport> {
|
||||
// 确保 websearch 存在
|
||||
const webSearchState: WebSearchState = websearch || {
|
||||
defaultProvider: '',
|
||||
providers: [],
|
||||
maxResults: 10,
|
||||
excludeDomains: [],
|
||||
searchWithTime: false,
|
||||
subscribeSources: [],
|
||||
overwrite: false,
|
||||
deepResearchConfig: {
|
||||
maxIterations: this.analysisConfig.maxIterations,
|
||||
maxResultsPerQuery: this.analysisConfig.maxResultsPerQuery,
|
||||
autoSummary: this.analysisConfig.autoSummary || true
|
||||
}
|
||||
}
|
||||
console.log(`[DeepResearch] 开始深度研究: "${query}"`)
|
||||
|
||||
// 根据配置决定是否优化查询
|
||||
let optimizedQuery = query
|
||||
if (webSearchState.deepResearchConfig?.enableQueryOptimization !== false) {
|
||||
console.log(`[DeepResearch] 启用查询优化`)
|
||||
optimizedQuery = await this.optimizeQuery(query)
|
||||
} else {
|
||||
console.log(`[DeepResearch] 未启用查询优化`)
|
||||
}
|
||||
|
||||
const report: ResearchReport = {
|
||||
originalQuery: query,
|
||||
iterations: [],
|
||||
summary: '',
|
||||
keyInsights: [],
|
||||
sources: []
|
||||
}
|
||||
|
||||
let currentQuery = optimizedQuery
|
||||
let iterationCount = 0
|
||||
const allSources = new Set<string>()
|
||||
|
||||
// 定义搜索引擎类别列表
|
||||
const engineCategories = ['chinese', 'international', 'meta', 'academic']
|
||||
|
||||
// 迭代研究过程
|
||||
while (iterationCount < this.analysisConfig.maxIterations) {
|
||||
console.log(`[DeepResearch] 迭代 ${iterationCount + 1}: "${currentQuery}"`)
|
||||
|
||||
// 根据当前迭代选择搜索引擎类别
|
||||
const categoryIndex = iterationCount % engineCategories.length
|
||||
const currentCategory = engineCategories[categoryIndex]
|
||||
console.log(`[DeepResearch] 这一迭代使用 ${currentCategory} 类别的搜索引擎`)
|
||||
|
||||
// 1. 使用DeepSearch获取当前查询的结果,指定搜索引擎类别
|
||||
const searchResponse = await this.deepSearchProvider.search(currentQuery, webSearchState, currentCategory)
|
||||
|
||||
// 限制结果数量
|
||||
const limitedResults = searchResponse.results.slice(0, this.analysisConfig.maxResultsPerQuery)
|
||||
|
||||
// 2. 分析搜索结果
|
||||
const analysis = await this.analyzeResults(limitedResults, currentQuery)
|
||||
|
||||
// 3. 生成后续查询
|
||||
const followUpQueries = await this.generateFollowUpQueries(analysis, currentQuery, report.iterations)
|
||||
|
||||
// 4. 记录这次迭代
|
||||
const iteration: ResearchIteration = {
|
||||
query: currentQuery,
|
||||
results: limitedResults,
|
||||
analysis,
|
||||
followUpQueries
|
||||
}
|
||||
|
||||
report.iterations.push(iteration)
|
||||
|
||||
// 5. 收集源
|
||||
limitedResults.forEach((result) => {
|
||||
if (result.url) {
|
||||
allSources.add(result.url)
|
||||
}
|
||||
})
|
||||
|
||||
// 6. 检查是否继续迭代
|
||||
if (followUpQueries.length === 0) {
|
||||
console.log(`[DeepResearch] 没有更多的后续查询,结束迭代`)
|
||||
break
|
||||
}
|
||||
|
||||
// 7. 更新查询并继续
|
||||
currentQuery = followUpQueries[0] // 使用第一个后续查询
|
||||
iterationCount++
|
||||
}
|
||||
|
||||
// 生成最终总结
|
||||
report.summary = await this.generateSummary(report.iterations)
|
||||
report.keyInsights = await this.extractKeyInsights(report.iterations)
|
||||
report.sources = Array.from(allSources)
|
||||
|
||||
console.log(`[DeepResearch] 完成深度研究,共 ${report.iterations.length} 次迭代`)
|
||||
return report
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析搜索结果
|
||||
* @param results 搜索结果
|
||||
* @param query 当前查询
|
||||
* @returns 分析文本
|
||||
*/
|
||||
private async analyzeResults(results: WebSearchResult[], query: string): Promise<string> {
|
||||
if (results.length === 0) {
|
||||
return `没有找到关于"${query}"的相关信息。`
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[DeepResearch] 分析 ${results.length} 个结果`)
|
||||
|
||||
// 提取关键信息
|
||||
const contentSummaries = results
|
||||
.map((result, index) => {
|
||||
const content = result.content || '无内容'
|
||||
// 提取前300个字符作为摘要
|
||||
const summary = content.length > 300 ? content.substring(0, 300) + '...' : content
|
||||
|
||||
return `[${index + 1}] ${result.title}\n${summary}\n来源: ${result.url}\n`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
// 使用模型分析内容
|
||||
const analysis = await this.analyzeWithModel(contentSummaries, query)
|
||||
|
||||
return analysis
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 分析结果时出错:', error)
|
||||
return `分析过程中出现错误: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用项目中的模型分析搜索结果
|
||||
*/
|
||||
private async analyzeWithModel(contentSummaries: string, query: string): Promise<string> {
|
||||
try {
|
||||
console.log(`[DeepResearch] 使用模型分析搜索结果`)
|
||||
|
||||
// 分析提示词
|
||||
const prompt = `你是一个高级研究分析师,负责深入分析搜索结果并提取全面、详细的见解。
|
||||
请对以下关于"${query}"的搜索结果进行彻底分析,并提供以下内容:
|
||||
|
||||
1. 主要发现(详细列出5-8个要点,每个要点至少包含3-4句解释)
|
||||
2. 争议点或不同观点(详细分析各方观点及其依据)
|
||||
3. 技术细节和实现方法(如适用)
|
||||
4. 历史背景和发展脉络(如适用)
|
||||
5. 最新进展和趋势
|
||||
6. 专家共识和最佳实践
|
||||
7. 需要进一步研究的领域(列出3-5个方向,并解释为什么这些方向值得探索)
|
||||
|
||||
请使用学术性、分析性的语言,提供深入的分析而非简单总结。确保你的分析:
|
||||
- 包含具体的事实、数据和例子
|
||||
- 引用搜索结果中的具体信息源
|
||||
- 区分已确认的事实和推测性内容
|
||||
- 指出信息的可靠性和局限性
|
||||
|
||||
搜索结果内容:
|
||||
${contentSummaries}`
|
||||
|
||||
// 检查内容是否为空
|
||||
if (!contentSummaries || contentSummaries.trim() === '') {
|
||||
return `没有找到关于"${query}"的有效内容可供分析。`
|
||||
}
|
||||
|
||||
// 使用项目中的 fetchGenerate 函数调用模型
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const analysis = await fetchGenerate({
|
||||
prompt,
|
||||
content: contentSummaries || ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
return analysis || `分析失败,无法获取结果。`
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 模型分析失败:', error)
|
||||
return `分析过程中出现错误: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成后续查询
|
||||
* @param analysis 当前分析
|
||||
* @param currentQuery 当前查询
|
||||
* @param previousIterations 之前的迭代
|
||||
* @returns 后续查询列表
|
||||
*/
|
||||
private async generateFollowUpQueries(
|
||||
analysis: string,
|
||||
currentQuery: string,
|
||||
previousIterations: ResearchIteration[]
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
// 避免重复查询
|
||||
const previousQueries = new Set(previousIterations.map((i) => i.query))
|
||||
previousQueries.add(currentQuery)
|
||||
|
||||
// 使用模型生成后续查询
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个研究助手,负责生成后续查询。
|
||||
基于以下关于"${currentQuery}"的分析,生成 2-3 个后续查询,以深入探索该主题。
|
||||
|
||||
分析内容:
|
||||
${analysis}
|
||||
|
||||
请仅返回查询列表,每行一个,不要添加编号或其他标记。确保查询简洁、具体且与原始查询"${currentQuery}"相关。`
|
||||
|
||||
const result = await fetchGenerate({
|
||||
prompt,
|
||||
content: ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 处理生成的查询
|
||||
const candidateQueries = result
|
||||
.split('\n')
|
||||
.map((q) => q.trim())
|
||||
.filter((q) => q.length > 0)
|
||||
|
||||
// 过滤掉已经查询过的
|
||||
const newQueries = candidateQueries.filter((q) => !previousQueries.has(q))
|
||||
|
||||
// 限制查询数量
|
||||
return newQueries.slice(0, 3)
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 生成后续查询失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成研究总结
|
||||
* @param iterations 所有迭代
|
||||
* @returns 总结文本
|
||||
*/
|
||||
private async generateSummary(iterations: ResearchIteration[]): Promise<string> {
|
||||
if (iterations.length === 0) {
|
||||
return '没有足够的研究数据来生成总结。'
|
||||
}
|
||||
|
||||
try {
|
||||
const mainQuery = iterations[0].query
|
||||
|
||||
// 收集所有迭代的分析和查询
|
||||
const iterationsData = iterations
|
||||
.map((iter, index) => {
|
||||
return `迭代 ${index + 1}:\n查询: ${iter.query}\n分析:\n${iter.analysis}\n`
|
||||
})
|
||||
.join('\n---\n\n')
|
||||
|
||||
// 使用模型生成总结
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个研究助手,负责生成研究总结。
|
||||
请基于以下关于"${mainQuery}"的多轮研究迭代,生成一份全面、深入的研究总结。
|
||||
|
||||
总结应包括:
|
||||
1. 主要发现和见解
|
||||
2. 不同观点或争议点
|
||||
3. 该领域的当前状态
|
||||
4. 未来发展趋势
|
||||
|
||||
请使用学术性、客观的语言,并确保总结涵盖所有迭代中的重要发现。
|
||||
|
||||
研究迭代数据:
|
||||
${iterationsData}`
|
||||
|
||||
// 确保内容不为空
|
||||
if (!iterationsData || iterationsData.trim() === '') {
|
||||
return `没有足够的数据来生成关于"${mainQuery}"的研究总结。`
|
||||
}
|
||||
|
||||
const summary = await fetchGenerate({
|
||||
prompt,
|
||||
content: iterationsData || ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
return summary || `无法生成关于 "${mainQuery}" 的研究总结。`
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 生成研究总结失败:', error)
|
||||
return `生成研究总结时出错: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取关键见解
|
||||
* @param iterations 所有迭代
|
||||
* @returns 关键见解列表
|
||||
*/
|
||||
private async extractKeyInsights(iterations: ResearchIteration[]): Promise<string[]> {
|
||||
if (iterations.length === 0) {
|
||||
return ['没有足够的研究数据来提取关键见解。']
|
||||
}
|
||||
|
||||
try {
|
||||
const mainQuery = iterations[0].query
|
||||
|
||||
// 收集所有迭代的分析
|
||||
const allAnalyses = iterations.map((iter) => iter.analysis).join('\n\n')
|
||||
|
||||
// 使用模型提取关键见解
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个研究助手,负责提取关键见解。
|
||||
请从以下关于"${mainQuery}"的研究分析中,提取 4-6 条最重要的关键见解。
|
||||
|
||||
这些见解应该是研究中最有价值、最有洞察力的发现,能够帮助读者快速理解该主题的核心要点。
|
||||
|
||||
请仅返回见解列表,每行一条,不要添加编号或其他标记。确保每条见解简洁、清晰、有洞察力。
|
||||
|
||||
研究分析内容:
|
||||
${allAnalyses}`
|
||||
|
||||
// 确保内容不为空
|
||||
if (!allAnalyses || allAnalyses.trim() === '') {
|
||||
return [`关于${mainQuery}的研究数据不足,无法提取有意义的见解。`, `需要更多的搜索结果来全面分析${mainQuery}。`]
|
||||
}
|
||||
|
||||
const result = await fetchGenerate({
|
||||
prompt,
|
||||
content: allAnalyses || ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return [
|
||||
`${mainQuery}在多个领域都有重要应用。`,
|
||||
`关于${mainQuery}的研究在近年来呈上升趋势。`,
|
||||
`${mainQuery}的最佳实践尚未达成共识。`,
|
||||
`${mainQuery}的未来发展前景广阔。`
|
||||
]
|
||||
}
|
||||
|
||||
// 处理生成的见解
|
||||
return result
|
||||
.split('\n')
|
||||
.map((insight) => insight.trim())
|
||||
.filter((insight) => insight.length > 0)
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 提取关键见解失败:', error)
|
||||
return [
|
||||
`${iterations[0].query}在多个领域都有重要应用。`,
|
||||
`关于${iterations[0].query}的研究在近年来呈上升趋势。`,
|
||||
`${iterations[0].query}的最佳实践尚未达成共识。`,
|
||||
`${iterations[0].query}的未来发展前景广阔。`
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分析配置
|
||||
* @param config 配置对象
|
||||
*/
|
||||
public setAnalysisConfig(config: Partial<AnalysisConfig>): void {
|
||||
this.analysisConfig = {
|
||||
...this.analysisConfig,
|
||||
...config
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
export const getSummaryPrompt = (mainQuery: string, iterationsData: string): string => {
|
||||
return `你是一个高级学术研究分析师,负责生成深入、全面的研究总结。
|
||||
请基于以下关于"${mainQuery}"的多轮研究迭代,生成一份学术水准的综合研究报告。
|
||||
|
||||
报告应包含以下部分,每部分都应详细、深入,并包含具体例证:
|
||||
|
||||
1. 摘要(简要总结整个研究的主要发现)
|
||||
|
||||
2. 背景与历史背景
|
||||
- 该主题的起源与发展过程
|
||||
- 关键里程碑与转折点
|
||||
|
||||
3. 主要发现(详细列出至少8-10个要点)
|
||||
- 每个发现应有充分的说明和例证
|
||||
- 引用具体数据、研究或信息源
|
||||
|
||||
4. 争议点与不同观点
|
||||
- 详细分析各方立场及其依据
|
||||
- 客观评估各种观点的合理性和限制
|
||||
|
||||
5. 技术细节与实现方法(如适用)
|
||||
- 当前技术实现的详细分析
|
||||
- 各种方法的比较与评估
|
||||
|
||||
6. 当前领域状态
|
||||
- 最新研究进展与突破
|
||||
- 主要参与者与机构
|
||||
- 当前面临的挑战与障碍
|
||||
|
||||
7. 专家共识与最佳实践
|
||||
- 行业公认的标准与方法
|
||||
- 成功案例与经验教训
|
||||
|
||||
8. 未来发展趋势
|
||||
- 预测的发展方向与变革
|
||||
- 潜在的机遇与风险
|
||||
|
||||
9. 研究局限性
|
||||
- 当前研究的不足与局限
|
||||
- 数据或方法的可靠性考量
|
||||
|
||||
10. 建议的未来研究方向
|
||||
- 具体的研究问题与方法建议
|
||||
- 为什么这些方向值得探索
|
||||
|
||||
请使用学术性、分析性的语言,提供深入的分析而非简单总结。确保你的分析:
|
||||
- 包含具体的事实、数据和例子
|
||||
- 引用搜索结果中的具体信息源
|
||||
- 区分已确认的事实和推测性内容
|
||||
- 指出信息的可靠性和局限性
|
||||
|
||||
研究迭代数据:
|
||||
${iterationsData}`
|
||||
}
|
||||
@@ -0,0 +1,775 @@
|
||||
import { WebSearchState } from '@renderer/store/websearch'
|
||||
import { WebSearchProvider, WebSearchResult } from '@renderer/types'
|
||||
|
||||
import DeepSearchProvider from './DeepSearchProvider'
|
||||
|
||||
/**
|
||||
* 分析配置接口
|
||||
*/
|
||||
interface AnalysisConfig {
|
||||
maxIterations: number
|
||||
maxResultsPerQuery: number
|
||||
minConfidenceScore: number
|
||||
autoSummary: boolean
|
||||
modelId?: string
|
||||
minOutputTokens?: number // 最小输出token数
|
||||
maxInputTokens?: number // 最大输入token数
|
||||
}
|
||||
|
||||
/**
|
||||
* 研究迭代接口
|
||||
*/
|
||||
interface ResearchIteration {
|
||||
query: string
|
||||
results: WebSearchResult[]
|
||||
analysis: string
|
||||
followUpQueries: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 研究报告接口
|
||||
*/
|
||||
export interface ResearchReport {
|
||||
originalQuery: string
|
||||
iterations: ResearchIteration[]
|
||||
summary: string
|
||||
directAnswer: string // 对原始问题的直接回答
|
||||
keyInsights: string[]
|
||||
sources: string[]
|
||||
tokenUsage?: {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
totalTokens: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DeepResearchProvider 类
|
||||
* 提供深度研究功能,包括多轮搜索、分析和总结
|
||||
*/
|
||||
export class DeepResearchProvider {
|
||||
private deepSearchProvider: DeepSearchProvider
|
||||
private analysisConfig: AnalysisConfig
|
||||
constructor(provider: WebSearchProvider) {
|
||||
this.deepSearchProvider = new DeepSearchProvider(provider)
|
||||
this.analysisConfig = {
|
||||
maxIterations: 3, // 默认最大迭代次数
|
||||
maxResultsPerQuery: 50, // 每次查询的最大结果数
|
||||
minConfidenceScore: 0.6, // 最小可信度分数
|
||||
autoSummary: true, // 自动生成摘要
|
||||
minOutputTokens: 20000, // 最小输出20,000 tokens
|
||||
maxInputTokens: 200000 // 最大输入200,000 tokens
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化查询
|
||||
* @param query 用户原始查询
|
||||
* @returns 优化后的查询
|
||||
*/
|
||||
private async optimizeQuery(query: string): Promise<string> {
|
||||
try {
|
||||
console.log(`[DeepResearch] 正在优化查询: "${query}"`)
|
||||
|
||||
// 使用模型优化查询
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个搜索优化专家,负责将用户的问题转化为最有效的搜索查询。
|
||||
|
||||
用户问题: "${query}"
|
||||
|
||||
请分析这个问题,并生成一个更有效的搜索查询。你的查询应该:
|
||||
1. 提取关键概念和术语
|
||||
2. 去除不必要的虚词和介词
|
||||
3. 增加相关的同义词或专业术语(如果适用)
|
||||
4. 保持简洁明确,不超过 10 个关键词
|
||||
|
||||
只返回优化后的查询词,不要添加任何解释或其他文本。`
|
||||
|
||||
const optimizedQuery = await fetchGenerate({
|
||||
prompt,
|
||||
content: ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId
|
||||
})
|
||||
|
||||
// 如果优化失败,返回原始查询
|
||||
if (!optimizedQuery || optimizedQuery.trim() === '') {
|
||||
return query
|
||||
}
|
||||
|
||||
console.log(`[DeepResearch] 查询优化结果: "${optimizedQuery}"`)
|
||||
return optimizedQuery.trim()
|
||||
} catch (error) {
|
||||
console.error('[DeepResearch] 查询优化失败:', error)
|
||||
return query // 出错时返回原始查询
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行深度研究
|
||||
* @param query 初始查询
|
||||
* @param websearch WebSearch状态
|
||||
* @param progressCallback 进度回调函数
|
||||
* @returns 研究报告
|
||||
*/
|
||||
public async research(
|
||||
query: string,
|
||||
websearch?: WebSearchState,
|
||||
progressCallback?: (iteration: number, status: string, percent: number) => void
|
||||
): Promise<ResearchReport> {
|
||||
// 确保 websearch 存在
|
||||
const webSearchState: WebSearchState = websearch || {
|
||||
defaultProvider: '',
|
||||
providers: [],
|
||||
maxResults: 10,
|
||||
excludeDomains: [],
|
||||
searchWithTime: false,
|
||||
subscribeSources: [],
|
||||
overwrite: false,
|
||||
deepResearchConfig: {
|
||||
maxIterations: this.analysisConfig.maxIterations,
|
||||
maxResultsPerQuery: this.analysisConfig.maxResultsPerQuery,
|
||||
autoSummary: this.analysisConfig.autoSummary || true
|
||||
}
|
||||
}
|
||||
console.log(`[DeepResearch] 开始深度研究: "${query}"`)
|
||||
|
||||
// 根据配置决定是否优化查询
|
||||
let optimizedQuery = query
|
||||
if (webSearchState.deepResearchConfig?.enableQueryOptimization !== false) {
|
||||
console.log(`[DeepResearch] 启用查询优化`)
|
||||
optimizedQuery = await this.optimizeQuery(query)
|
||||
} else {
|
||||
console.log(`[DeepResearch] 未启用查询优化`)
|
||||
}
|
||||
|
||||
const report: ResearchReport = {
|
||||
originalQuery: query,
|
||||
iterations: [],
|
||||
summary: '',
|
||||
directAnswer: '', // 初始化为空字符串
|
||||
keyInsights: [],
|
||||
sources: [],
|
||||
tokenUsage: {
|
||||
inputTokens: 0,
|
||||
outputTokens: 0,
|
||||
totalTokens: 0
|
||||
}
|
||||
}
|
||||
|
||||
let currentQuery = optimizedQuery
|
||||
let iterationCount = 0
|
||||
const allSources = new Set<string>()
|
||||
|
||||
// 定义搜索引擎类别列表
|
||||
const engineCategories = ['chinese', 'international', 'meta', 'academic']
|
||||
|
||||
// 迭代研究过程
|
||||
while (iterationCount < this.analysisConfig.maxIterations) {
|
||||
// 调用进度回调
|
||||
if (progressCallback) {
|
||||
const percent = Math.round((iterationCount / this.analysisConfig.maxIterations) * 100)
|
||||
progressCallback(iterationCount + 1, `迭代 ${iterationCount + 1}: ${currentQuery}`, percent)
|
||||
}
|
||||
|
||||
console.log(`[DeepResearch] 迭代 ${iterationCount + 1}: "${currentQuery}"`)
|
||||
|
||||
// 根据当前迭代选择搜索引擎类别
|
||||
const categoryIndex = iterationCount % engineCategories.length
|
||||
const currentCategory = engineCategories[categoryIndex]
|
||||
console.log(`[DeepResearch] 这一迭代使用 ${currentCategory} 类别的搜索引擎`)
|
||||
|
||||
// 1. 使用DeepSearch获取当前查询的结果,指定搜索引擎类别
|
||||
if (progressCallback) {
|
||||
progressCallback(
|
||||
iterationCount + 1,
|
||||
`正在搜索: ${currentQuery}`,
|
||||
Math.round((iterationCount / this.analysisConfig.maxIterations) * 100)
|
||||
)
|
||||
}
|
||||
const searchResponse = await this.deepSearchProvider.search(currentQuery, webSearchState, currentCategory)
|
||||
|
||||
// 限制结果数量
|
||||
const limitedResults = searchResponse.results.slice(0, this.analysisConfig.maxResultsPerQuery)
|
||||
|
||||
// 2. 分析搜索结果
|
||||
if (progressCallback) {
|
||||
progressCallback(
|
||||
iterationCount + 1,
|
||||
`正在分析 ${limitedResults.length} 个结果...`,
|
||||
Math.round((iterationCount / this.analysisConfig.maxIterations) * 100)
|
||||
)
|
||||
}
|
||||
const analysis = await this.analyzeResults(limitedResults, currentQuery, report)
|
||||
|
||||
// 3. 生成后续查询
|
||||
if (progressCallback) {
|
||||
progressCallback(
|
||||
iterationCount + 1,
|
||||
`正在生成后续查询...`,
|
||||
Math.round((iterationCount / this.analysisConfig.maxIterations) * 100)
|
||||
)
|
||||
}
|
||||
const followUpQueries = await this.generateFollowUpQueries(analysis, currentQuery, report.iterations)
|
||||
|
||||
// 4. 记录这次迭代
|
||||
const iteration: ResearchIteration = {
|
||||
query: currentQuery,
|
||||
results: limitedResults,
|
||||
analysis,
|
||||
followUpQueries
|
||||
}
|
||||
|
||||
report.iterations.push(iteration)
|
||||
|
||||
// 5. 收集源
|
||||
limitedResults.forEach((result) => {
|
||||
if (result.url) {
|
||||
allSources.add(result.url)
|
||||
}
|
||||
})
|
||||
|
||||
// 6. 检查是否继续迭代
|
||||
if (followUpQueries.length === 0) {
|
||||
console.log(`[DeepResearch] 没有更多的后续查询,结束迭代`)
|
||||
if (progressCallback) {
|
||||
progressCallback(
|
||||
iterationCount + 1,
|
||||
`迭代完成,没有更多后续查询`,
|
||||
Math.round(((iterationCount + 1) / this.analysisConfig.maxIterations) * 100)
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// 7. 更新查询并继续
|
||||
currentQuery = followUpQueries[0] // 使用第一个后续查询
|
||||
iterationCount++
|
||||
}
|
||||
|
||||
// 生成最终总结
|
||||
if (progressCallback) {
|
||||
progressCallback(iterationCount, `正在生成研究总结...`, 70)
|
||||
}
|
||||
report.summary = await this.generateSummary(report.iterations, report)
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback(iterationCount, `正在提取关键见解...`, 80)
|
||||
}
|
||||
report.keyInsights = await this.extractKeyInsights(report.iterations, report)
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback(iterationCount, `正在生成问题回答...`, 90)
|
||||
}
|
||||
report.directAnswer = await this.generateDirectAnswer(query, report.summary, report.keyInsights, report)
|
||||
|
||||
report.sources = Array.from(allSources)
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback(iterationCount, `研究完成`, 100)
|
||||
}
|
||||
|
||||
console.log(`[DeepResearch] 完成深度研究,共 ${report.iterations.length} 次迭代`)
|
||||
return report
|
||||
}
|
||||
|
||||
/**
|
||||
* 分析搜索结果
|
||||
* @param results 搜索结果
|
||||
* @param query 当前查询
|
||||
* @returns 分析文本
|
||||
*/
|
||||
private async analyzeResults(results: WebSearchResult[], query: string, report?: ResearchReport): Promise<string> {
|
||||
if (results.length === 0) {
|
||||
return `没有找到关于"${query}"的相关信息。`
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[DeepResearch] 分析 ${results.length} 个结果`)
|
||||
|
||||
// 提取关键信息
|
||||
const contentSummaries = results
|
||||
.map((result, index) => {
|
||||
const content = result.content || '无内容'
|
||||
// 提取前300个字符作为摘要
|
||||
const summary = content.length > 300 ? content.substring(0, 300) + '...' : content
|
||||
|
||||
return `[${index + 1}] ${result.title}\n${summary}\n来源: ${result.url}\n`
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
// 使用模型分析内容
|
||||
const analysis = await this.analyzeWithModel(contentSummaries, query, report)
|
||||
|
||||
return analysis
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 分析结果时出错:', error)
|
||||
return `分析过程中出现错误: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用项目中的模型分析搜索结果
|
||||
*/
|
||||
private async analyzeWithModel(contentSummaries: string, query: string, report?: ResearchReport): Promise<string> {
|
||||
try {
|
||||
console.log(`[DeepResearch] 使用模型分析搜索结果`)
|
||||
|
||||
// 分析提示词
|
||||
const prompt = `你是一个高级研究分析师,负责深入分析搜索结果并提取全面、详细的见解。
|
||||
请对以下关于"${query}"的搜索结果进行彻底分析,并提供以下内容:
|
||||
|
||||
1. 主要发现(详细列出5-8个要点,每个要点至少包含3-4句解释)
|
||||
2. 争议点或不同观点(详细分析各方观点及其依据)
|
||||
3. 技术细节和实现方法(如适用)
|
||||
4. 历史背景和发展脉络(如适用)
|
||||
5. 最新进展和趋势
|
||||
6. 专家共识和最佳实践
|
||||
7. 需要进一步研究的领域(列出3-5个方向,并解释为什么这些方向值得探索)
|
||||
|
||||
请使用学术性、分析性的语言,提供深入的分析而非简单总结。确保你的分析:
|
||||
- 包含具体的事实、数据和例子
|
||||
- 引用搜索结果中的具体信息源
|
||||
- 区分已确认的事实和推测性内容
|
||||
- 指出信息的可靠性和局限性
|
||||
|
||||
搜索结果内容:
|
||||
${contentSummaries}`
|
||||
|
||||
// 检查内容是否为空
|
||||
if (!contentSummaries || contentSummaries.trim() === '') {
|
||||
return `没有找到关于"${query}"的有效内容可供分析。`
|
||||
}
|
||||
|
||||
// 限制输入token数量
|
||||
let trimmedContent = contentSummaries
|
||||
const maxInputTokens = this.analysisConfig.maxInputTokens || 200000
|
||||
const inputTokens = this.estimateTokens(prompt + trimmedContent)
|
||||
|
||||
if (inputTokens > maxInputTokens) {
|
||||
console.log(`[DeepResearch] 输入内容超过最大token限制(${inputTokens} > ${maxInputTokens}),进行裁剪`)
|
||||
// 计算需要保留的比例
|
||||
const ratio = maxInputTokens / inputTokens
|
||||
const contentTokens = this.estimateTokens(trimmedContent)
|
||||
const targetContentTokens = Math.floor(contentTokens * ratio) - 1000 // 留出一些空间给提示词
|
||||
|
||||
// 按比例裁剪内容
|
||||
const contentLines = trimmedContent.split('\n')
|
||||
let currentTokens = 0
|
||||
const truncatedLines: string[] = []
|
||||
|
||||
for (const line of contentLines) {
|
||||
const lineTokens = this.estimateTokens(line)
|
||||
if (currentTokens + lineTokens <= targetContentTokens) {
|
||||
truncatedLines.push(line)
|
||||
currentTokens += lineTokens
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
trimmedContent = truncatedLines.join('\n')
|
||||
console.log(`[DeepResearch] 内容已裁剪至约 ${this.estimateTokens(trimmedContent)} tokens`)
|
||||
}
|
||||
|
||||
// 使用项目中的 fetchGenerate 函数调用模型
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const analysis = await fetchGenerate({
|
||||
prompt,
|
||||
content: trimmedContent || ' ', // 使用裁剪后的内容
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
// 更新token统计
|
||||
if (report?.tokenUsage) {
|
||||
report.tokenUsage.inputTokens += this.estimateTokens(prompt + trimmedContent)
|
||||
report.tokenUsage.outputTokens += this.estimateTokens(analysis || '')
|
||||
report.tokenUsage.totalTokens = report.tokenUsage.inputTokens + report.tokenUsage.outputTokens
|
||||
}
|
||||
|
||||
return analysis || `分析失败,无法获取结果。`
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 模型分析失败:', error)
|
||||
return `分析过程中出现错误: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成后续查询
|
||||
* @param analysis 当前分析
|
||||
* @param currentQuery 当前查询
|
||||
* @param previousIterations 之前的迭代
|
||||
* @returns 后续查询列表
|
||||
*/
|
||||
private async generateFollowUpQueries(
|
||||
analysis: string,
|
||||
currentQuery: string,
|
||||
previousIterations: ResearchIteration[]
|
||||
): Promise<string[]> {
|
||||
try {
|
||||
// 避免重复查询
|
||||
const previousQueries = new Set(previousIterations.map((i) => i.query))
|
||||
previousQueries.add(currentQuery)
|
||||
|
||||
// 使用模型生成后续查询
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个研究助手,负责生成后续查询。
|
||||
基于以下关于"${currentQuery}"的分析,生成 2-3 个后续查询,以深入探索该主题。
|
||||
|
||||
分析内容:
|
||||
${analysis}
|
||||
|
||||
请仅返回查询列表,每行一个,不要添加编号或其他标记。确保查询简洁、具体且与原始查询"${currentQuery}"相关。`
|
||||
|
||||
const result = await fetchGenerate({
|
||||
prompt,
|
||||
content: ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 处理生成的查询
|
||||
const candidateQueries = result
|
||||
.split('\n')
|
||||
.map((q) => q.trim())
|
||||
.filter((q) => q.length > 0)
|
||||
|
||||
// 过滤掉已经查询过的
|
||||
const newQueries = candidateQueries.filter((q) => !previousQueries.has(q))
|
||||
|
||||
// 限制查询数量
|
||||
return newQueries.slice(0, 3)
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 生成后续查询失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成研究总结
|
||||
* @param iterations 所有迭代
|
||||
* @returns 总结文本
|
||||
*/
|
||||
private async generateSummary(iterations: ResearchIteration[], report?: ResearchReport): Promise<string> {
|
||||
if (iterations.length === 0) {
|
||||
return '没有足够的研究数据来生成总结。'
|
||||
}
|
||||
|
||||
try {
|
||||
const mainQuery = iterations[0].query
|
||||
|
||||
// 收集所有迭代的分析和查询
|
||||
const iterationsData = iterations
|
||||
.map((iter, index) => {
|
||||
return `迭代 ${index + 1}:\n查询: ${iter.query}\n分析:\n${iter.analysis}\n`
|
||||
})
|
||||
.join('\n---\n\n')
|
||||
|
||||
// 使用模型生成总结
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个高级学术研究分析师,负责生成深入、全面的研究总结。
|
||||
请基于以下关于"${mainQuery}"的多轮研究迭代,生成一份学术水准的综合研究报告。
|
||||
|
||||
报告应包含以下部分,每部分都应详细、深入,并包含具体例证:
|
||||
|
||||
1. 摘要(简要总结整个研究的主要发现)
|
||||
|
||||
2. 背景与历史背景
|
||||
- 该主题的起源与发展过程
|
||||
- 关键里程碑与转折点
|
||||
|
||||
3. 主要发现(详细列出至少8-10个要点)
|
||||
- 每个发现应有充分的说明和例证
|
||||
- 引用具体数据、研究或信息源
|
||||
|
||||
4. 争议点与不同观点
|
||||
- 详细分析各方立场及其依据
|
||||
- 客观评估各种观点的合理性和限制
|
||||
|
||||
5. 技术细节与实现方法(如适用)
|
||||
- 当前技术实现的详细分析
|
||||
- 各种方法的比较与评估
|
||||
|
||||
6. 当前领域状态
|
||||
- 最新研究进展与突破
|
||||
- 主要参与者与机构
|
||||
- 当前面临的挑战与障碍
|
||||
|
||||
7. 专家共识与最佳实践
|
||||
- 行业公认的标准与方法
|
||||
- 成功案例与经验教训
|
||||
|
||||
8. 未来发展趋势
|
||||
- 预测的发展方向与变革
|
||||
- 潜在的机遇与风险
|
||||
|
||||
9. 研究局限性
|
||||
- 当前研究的不足与局限
|
||||
- 数据或方法的可靠性考量
|
||||
|
||||
10. 建议的未来研究方向
|
||||
- 具体的研究问题与方法建议
|
||||
- 为什么这些方向值得探索
|
||||
|
||||
请使用学术性、分析性的语言,提供深入的分析而非简单总结。确保你的分析:
|
||||
- 包含具体的事实、数据和例子
|
||||
- 引用搜索结果中的具体信息源
|
||||
- 区分已确认的事实和推测性内容
|
||||
- 指出信息的可靠性和局限性
|
||||
|
||||
研究迭代数据:
|
||||
${iterationsData}`
|
||||
|
||||
// 确保内容不为空
|
||||
if (!iterationsData || iterationsData.trim() === '') {
|
||||
return `没有足够的数据来生成关于"${mainQuery}"的研究总结。`
|
||||
}
|
||||
|
||||
// 限制输入token数量
|
||||
let trimmedData = iterationsData
|
||||
const maxInputTokens = this.analysisConfig.maxInputTokens || 200000
|
||||
const inputTokens = this.estimateTokens(prompt + trimmedData)
|
||||
|
||||
if (inputTokens > maxInputTokens) {
|
||||
console.log(`[DeepResearch] 总结输入超过最大token限制(${inputTokens} > ${maxInputTokens}),进行裁剪`)
|
||||
const ratio = maxInputTokens / inputTokens
|
||||
const contentTokens = this.estimateTokens(trimmedData)
|
||||
const targetContentTokens = Math.floor(contentTokens * ratio) - 1000
|
||||
|
||||
const contentLines = trimmedData.split('\n')
|
||||
let currentTokens = 0
|
||||
const truncatedLines: string[] = []
|
||||
|
||||
for (const line of contentLines) {
|
||||
const lineTokens = this.estimateTokens(line)
|
||||
if (currentTokens + lineTokens <= targetContentTokens) {
|
||||
truncatedLines.push(line)
|
||||
currentTokens += lineTokens
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
trimmedData = truncatedLines.join('\n')
|
||||
console.log(`[DeepResearch] 总结内容已裁剪至约 ${this.estimateTokens(trimmedData)} tokens`)
|
||||
}
|
||||
|
||||
const summary = await fetchGenerate({
|
||||
prompt,
|
||||
content: trimmedData || ' ',
|
||||
modelId: this.analysisConfig.modelId
|
||||
})
|
||||
|
||||
// 更新token统计
|
||||
if (report?.tokenUsage) {
|
||||
report.tokenUsage.inputTokens += this.estimateTokens(prompt + trimmedData)
|
||||
report.tokenUsage.outputTokens += this.estimateTokens(summary || '')
|
||||
report.tokenUsage.totalTokens = report.tokenUsage.inputTokens + report.tokenUsage.outputTokens
|
||||
}
|
||||
|
||||
return summary || `无法生成关于 "${mainQuery}" 的研究总结。`
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 生成研究总结失败:', error)
|
||||
return `生成研究总结时出错: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成对原始问题的直接回答
|
||||
* @param originalQuery 原始查询
|
||||
* @param summary 研究总结
|
||||
* @param keyInsights 关键见解
|
||||
* @returns 直接回答文本
|
||||
*/
|
||||
private async generateDirectAnswer(
|
||||
originalQuery: string,
|
||||
summary: string,
|
||||
keyInsights: string[],
|
||||
report?: ResearchReport
|
||||
): Promise<string> {
|
||||
try {
|
||||
console.log(`[DeepResearch] 正在生成对原始问题的直接回答`)
|
||||
|
||||
// 使用模型生成直接回答
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个专业的问题解答专家,负责提供清晰、准确、全面的回答。
|
||||
|
||||
用户原始问题: "${originalQuery}"
|
||||
|
||||
基于以下研究总结和关键见解,请直接回答用户的问题。你的回答应该:
|
||||
|
||||
1. 直接回应用户的原始问题,而不是提供一般性的信息
|
||||
2. 简洁明确,但包含充分的细节和深度
|
||||
3. 如果问题有多个方面,请全面考虑每个方面
|
||||
4. 如果有不同观点,请客观地呈现各种观点
|
||||
5. 如果问题有具体答案,请直接提供答案
|
||||
6. 如果问题没有单一答案,请提供最佳建议或解决方案
|
||||
|
||||
请在回答中使用二级标题和项目符号来组织内容,使其易于阅读。回答应在500-1000字左右,不要过长。
|
||||
|
||||
研究总结:
|
||||
${summary}
|
||||
|
||||
关键见解:
|
||||
${keyInsights.join('\n')}`
|
||||
|
||||
// 确保内容不为空
|
||||
if (!summary || summary.trim() === '') {
|
||||
return `没有足够的数据来生成关于"${originalQuery}"的直接回答。`
|
||||
}
|
||||
|
||||
const directAnswer = await fetchGenerate({
|
||||
prompt,
|
||||
content: ' ', // 确保内容不为空
|
||||
modelId: this.analysisConfig.modelId // 使用指定的模型
|
||||
})
|
||||
|
||||
// 更新token统计
|
||||
if (report?.tokenUsage) {
|
||||
report.tokenUsage.inputTokens += this.estimateTokens(prompt)
|
||||
report.tokenUsage.outputTokens += this.estimateTokens(directAnswer || '')
|
||||
report.tokenUsage.totalTokens = report.tokenUsage.inputTokens + report.tokenUsage.outputTokens
|
||||
}
|
||||
|
||||
return directAnswer || `无法生成关于 "${originalQuery}" 的直接回答。`
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 生成直接回答失败:', error)
|
||||
return `生成直接回答时出错: ${error?.message || '未知错误'}`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取关键见解
|
||||
* @param iterations 所有迭代
|
||||
* @returns 关键见解列表
|
||||
*/
|
||||
private async extractKeyInsights(iterations: ResearchIteration[], report?: ResearchReport): Promise<string[]> {
|
||||
if (iterations.length === 0) {
|
||||
return ['没有足够的研究数据来提取关键见解。']
|
||||
}
|
||||
|
||||
try {
|
||||
const mainQuery = iterations[0].query
|
||||
|
||||
// 收集所有迭代的分析
|
||||
const allAnalyses = iterations.map((iter) => iter.analysis).join('\n\n')
|
||||
|
||||
// 使用模型提取关键见解
|
||||
const { fetchGenerate } = await import('@renderer/services/ApiService')
|
||||
const prompt = `你是一个高级研究分析师,负责提取全面、深入的关键见解。
|
||||
请从以下关于"${mainQuery}"的研究分析中,提取 10-20 条最重要的关键见解。
|
||||
|
||||
这些见解应该是研究中最有价值、最有洞察力的发现,能够帮助读者全面理解该主题的核心要点。确保见解涵盖不同的角度和方面,包括:
|
||||
|
||||
- 核心概念和定义
|
||||
- 历史背景和发展脉络
|
||||
- 技术细节和实现方法
|
||||
- 争议点和不同观点
|
||||
- 最新进展和突破
|
||||
- 实际应用和案例
|
||||
- 挑战和限制
|
||||
- 未来趋势和发展方向
|
||||
|
||||
请仅返回见解列表,每行一条,不要添加编号或其他标记。确保每条见解简洁、清晰、有洞察力。
|
||||
|
||||
研究分析内容:
|
||||
${allAnalyses}`
|
||||
|
||||
// 确保内容不为空
|
||||
if (!allAnalyses || allAnalyses.trim() === '') {
|
||||
return [`关于${mainQuery}的研究数据不足,无法提取有意义的见解。`, `需要更多的搜索结果来全面分析${mainQuery}。`]
|
||||
}
|
||||
|
||||
// 限制输入token数量
|
||||
let trimmedAnalyses = allAnalyses
|
||||
const maxInputTokens = this.analysisConfig.maxInputTokens || 200000
|
||||
const inputTokens = this.estimateTokens(prompt + trimmedAnalyses)
|
||||
|
||||
if (inputTokens > maxInputTokens) {
|
||||
console.log(`[DeepResearch] 关键见解输入超过最大token限制(${inputTokens} > ${maxInputTokens}),进行裁剪`)
|
||||
const ratio = maxInputTokens / inputTokens
|
||||
const contentTokens = this.estimateTokens(trimmedAnalyses)
|
||||
const targetContentTokens = Math.floor(contentTokens * ratio) - 1000
|
||||
|
||||
const contentLines = trimmedAnalyses.split('\n')
|
||||
let currentTokens = 0
|
||||
const truncatedLines: string[] = []
|
||||
|
||||
for (const line of contentLines) {
|
||||
const lineTokens = this.estimateTokens(line)
|
||||
if (currentTokens + lineTokens <= targetContentTokens) {
|
||||
truncatedLines.push(line)
|
||||
currentTokens += lineTokens
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
trimmedAnalyses = truncatedLines.join('\n')
|
||||
console.log(`[DeepResearch] 关键见解内容已裁剪至约 ${this.estimateTokens(trimmedAnalyses)} tokens`)
|
||||
}
|
||||
|
||||
const result = await fetchGenerate({
|
||||
prompt,
|
||||
content: trimmedAnalyses || ' ',
|
||||
modelId: this.analysisConfig.modelId
|
||||
})
|
||||
|
||||
// 更新token统计
|
||||
if (report?.tokenUsage) {
|
||||
report.tokenUsage.inputTokens += this.estimateTokens(prompt + trimmedAnalyses)
|
||||
report.tokenUsage.outputTokens += this.estimateTokens(result || '')
|
||||
report.tokenUsage.totalTokens = report.tokenUsage.inputTokens + report.tokenUsage.outputTokens
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return [
|
||||
`${mainQuery}在多个领域都有重要应用。`,
|
||||
`关于${mainQuery}的研究在近年来呈上升趋势。`,
|
||||
`${mainQuery}的最佳实践尚未达成共识。`,
|
||||
`${mainQuery}的未来发展前景广阔。`
|
||||
]
|
||||
}
|
||||
|
||||
// 处理生成的见解
|
||||
return result
|
||||
.split('\n')
|
||||
.map((insight) => insight.trim())
|
||||
.filter((insight) => insight.length > 0)
|
||||
} catch (error: any) {
|
||||
console.error('[DeepResearch] 提取关键见解失败:', error)
|
||||
return [
|
||||
`${iterations[0].query}在多个领域都有重要应用。`,
|
||||
`关于${iterations[0].query}的研究在近年来呈上升趋势。`,
|
||||
`${iterations[0].query}的最佳实践尚未达成共识。`,
|
||||
`${iterations[0].query}的未来发展前景广阔。`
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算文本的token数量
|
||||
* @param text 要估算的文本
|
||||
* @returns 估算的token数量
|
||||
*/
|
||||
private estimateTokens(text: string): number {
|
||||
// 简单估算:英文大约每4个字符为1个token,中文大约每1.5个字符为1个token
|
||||
const englishChars = text.replace(/[\u4e00-\u9fa5]/g, '').length
|
||||
const chineseChars = text.length - englishChars
|
||||
|
||||
return Math.ceil(englishChars / 4 + chineseChars / 1.5)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分析配置
|
||||
* @param config 配置对象
|
||||
*/
|
||||
public setAnalysisConfig(config: Partial<AnalysisConfig>): void {
|
||||
this.analysisConfig = {
|
||||
...this.analysisConfig,
|
||||
...config
|
||||
}
|
||||
}
|
||||
}
|
||||
1432
src/renderer/src/providers/WebSearchProvider/DeepSearchProvider.ts
Normal file
1432
src/renderer/src/providers/WebSearchProvider/DeepSearchProvider.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
import { WebSearchProvider } from '@renderer/types'
|
||||
|
||||
import BaseWebSearchProvider from './BaseWebSearchProvider'
|
||||
import DeepSearchProvider from './DeepSearchProvider'
|
||||
import DefaultProvider from './DefaultProvider'
|
||||
import ExaProvider from './ExaProvider'
|
||||
import LocalBaiduProvider from './LocalBaiduProvider'
|
||||
@@ -18,6 +19,8 @@ export default class WebSearchProviderFactory {
|
||||
return new SearxngProvider(provider)
|
||||
case 'exa':
|
||||
return new ExaProvider(provider)
|
||||
case 'deep-search':
|
||||
return new DeepSearchProvider(provider)
|
||||
case 'local-google':
|
||||
return new LocalGoogleProvider(provider)
|
||||
case 'local-baidu':
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@renderer/config/models'
|
||||
import { SEARCH_SUMMARY_PROMPT } from '@renderer/config/prompts'
|
||||
import i18n from '@renderer/i18n'
|
||||
import { getModelUniqId } from '@renderer/services/ModelService'
|
||||
import store from '@renderer/store'
|
||||
import { setGenerating } from '@renderer/store/runtime'
|
||||
import {
|
||||
@@ -391,8 +392,27 @@ export async function fetchSearchSummary({ messages, assistant }: { messages: Me
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchGenerate({ prompt, content }: { prompt: string; content: string }): Promise<string> {
|
||||
const model = getDefaultModel()
|
||||
export async function fetchGenerate({
|
||||
prompt,
|
||||
content,
|
||||
modelId
|
||||
}: {
|
||||
prompt: string
|
||||
content: string
|
||||
modelId?: string
|
||||
}): Promise<string> {
|
||||
let model = getDefaultModel()
|
||||
|
||||
// 如果提供了指定的模型 ID,尝试使用该模型
|
||||
if (modelId) {
|
||||
const { providers } = store.getState().llm
|
||||
const allModels = providers.flatMap((p) => p.models)
|
||||
const specifiedModel = allModels.find((m) => getModelUniqId(m) === modelId)
|
||||
if (specifiedModel) {
|
||||
model = specifiedModel
|
||||
}
|
||||
}
|
||||
|
||||
const provider = getProviderByModel(model)
|
||||
|
||||
if (!hasApiKey(provider)) {
|
||||
@@ -404,6 +424,7 @@ export async function fetchGenerate({ prompt, content }: { prompt: string; conte
|
||||
try {
|
||||
return await AI.generateText({ prompt, content })
|
||||
} catch (error: any) {
|
||||
console.error('Generate text error:', error)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ class WebSearchService {
|
||||
return provider.apiHost !== ''
|
||||
}
|
||||
|
||||
// DeepSearch提供商不需要API密钥或主机
|
||||
if (provider.id === 'deep-search') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,15 @@ import { WebDAVSyncState } from './backup'
|
||||
|
||||
export type SendMessageShortcut = 'Enter' | 'Shift+Enter' | 'Ctrl+Enter' | 'Command+Enter'
|
||||
|
||||
export type SidebarIcon = 'assistants' | 'agents' | 'paintings' | 'translate' | 'minapp' | 'knowledge' | 'files'
|
||||
export type SidebarIcon =
|
||||
| 'assistants'
|
||||
| 'agents'
|
||||
| 'paintings'
|
||||
| 'translate'
|
||||
| 'minapp'
|
||||
| 'knowledge'
|
||||
| 'files'
|
||||
| 'deepresearch'
|
||||
|
||||
export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [
|
||||
'assistants',
|
||||
@@ -16,7 +24,8 @@ export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [
|
||||
'translate',
|
||||
'minapp',
|
||||
'knowledge',
|
||||
'files'
|
||||
'files',
|
||||
'deepresearch'
|
||||
]
|
||||
|
||||
export interface NutstoreSyncRuntime extends WebDAVSyncState {}
|
||||
|
||||
@@ -7,6 +7,14 @@ export interface SubscribeSource {
|
||||
blacklist?: string[] // 存储从该订阅源获取的黑名单
|
||||
}
|
||||
|
||||
export interface DeepResearchConfig {
|
||||
maxIterations: number
|
||||
maxResultsPerQuery: number
|
||||
autoSummary: boolean
|
||||
modelId?: string
|
||||
enableQueryOptimization?: boolean
|
||||
}
|
||||
|
||||
export interface WebSearchState {
|
||||
// 默认搜索提供商的ID
|
||||
defaultProvider: string
|
||||
@@ -23,6 +31,8 @@ export interface WebSearchState {
|
||||
// 是否覆盖服务商搜索
|
||||
overwrite: boolean
|
||||
contentLimit?: number
|
||||
// Deep Research 配置
|
||||
deepResearchConfig: DeepResearchConfig
|
||||
}
|
||||
|
||||
const initialState: WebSearchState = {
|
||||
@@ -43,6 +53,13 @@ const initialState: WebSearchState = {
|
||||
name: 'Exa',
|
||||
apiKey: ''
|
||||
},
|
||||
{
|
||||
id: 'deep-search',
|
||||
name: 'DeepSearch',
|
||||
usingBrowser: true,
|
||||
contentLimit: 10000,
|
||||
description: '多引擎深度搜索'
|
||||
},
|
||||
{
|
||||
id: 'local-google',
|
||||
name: 'Google',
|
||||
@@ -60,10 +77,16 @@ const initialState: WebSearchState = {
|
||||
}
|
||||
],
|
||||
searchWithTime: true,
|
||||
maxResults: 5,
|
||||
maxResults: 10,
|
||||
excludeDomains: [],
|
||||
subscribeSources: [],
|
||||
overwrite: false
|
||||
overwrite: false,
|
||||
deepResearchConfig: {
|
||||
maxIterations: 3,
|
||||
maxResultsPerQuery: 5,
|
||||
autoSummary: true,
|
||||
enableQueryOptimization: true
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultWebSearchProviders = initialState.providers
|
||||
@@ -137,6 +160,9 @@ const websearchSlice = createSlice({
|
||||
},
|
||||
setContentLimit: (state, action: PayloadAction<number>) => {
|
||||
state.contentLimit = action.payload
|
||||
},
|
||||
setDeepResearchConfig: (state, action: PayloadAction<DeepResearchConfig>) => {
|
||||
state.deepResearchConfig = action.payload
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -155,7 +181,8 @@ export const {
|
||||
setSubscribeSources,
|
||||
setOverwrite,
|
||||
addWebSearchProvider,
|
||||
setContentLimit
|
||||
setContentLimit,
|
||||
setDeepResearchConfig
|
||||
} = websearchSlice.actions
|
||||
|
||||
export default websearchSlice.reducer
|
||||
|
||||
@@ -351,6 +351,7 @@ export type WebSearchProvider = {
|
||||
basicAuthPassword?: string
|
||||
contentLimit?: number
|
||||
usingBrowser?: boolean
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type WebSearchResponse = {
|
||||
@@ -362,6 +363,24 @@ export type WebSearchResult = {
|
||||
title: string
|
||||
content: string
|
||||
url: string
|
||||
source?: string
|
||||
meta?: Record<string, any> // 添加meta字段用于存储元数据
|
||||
}
|
||||
|
||||
export interface ResearchIteration {
|
||||
query: string
|
||||
results: WebSearchResult[]
|
||||
analysis: string
|
||||
followUpQueries: string[]
|
||||
}
|
||||
|
||||
export interface ResearchReport {
|
||||
originalQuery: string
|
||||
iterations: ResearchIteration[]
|
||||
summary: string
|
||||
directAnswer: string // 对原始问题的直接回答
|
||||
keyInsights: string[]
|
||||
sources: string[]
|
||||
}
|
||||
|
||||
export type KnowledgeReference = {
|
||||
@@ -512,3 +531,25 @@ export interface Citation {
|
||||
}
|
||||
|
||||
export type MathEngine = 'KaTeX' | 'MathJax' | 'none'
|
||||
|
||||
// Deep Research 类型定义
|
||||
export interface ResearchIteration {
|
||||
query: string
|
||||
results: WebSearchResult[]
|
||||
analysis: string
|
||||
followUpQueries: string[]
|
||||
}
|
||||
|
||||
export interface ResearchReport {
|
||||
originalQuery: string
|
||||
iterations: ResearchIteration[]
|
||||
summary: string
|
||||
directAnswer: string // 对原始问题的直接回答
|
||||
keyInsights: string[]
|
||||
sources: string[]
|
||||
tokenUsage?: {
|
||||
inputTokens: number
|
||||
outputTokens: number
|
||||
totalTokens: number
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user