refactor(Settings): Refactored knowledge settings to utilize the new OCR provider management.

This commit is contained in:
suyao
2025-03-23 19:00:38 +08:00
parent 2941651189
commit 4775a3a77d
26 changed files with 537 additions and 327 deletions
@@ -0,0 +1,20 @@
import { FC } from 'react'
const OcrIcon: FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
className="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="3083"
width="16"
height="16"
strokeWidth="1"
{...props}>
<path
d="M640.189 960.294l-0.131-64 254.651-0.522 1.285-255.968 64 0.322-1.605 319.515-318.2 0.653z m-256.239-0.075l-318.197-1.237 0.251-319.042 64 0.051-0.201 255.239 254.396 0.989-0.249 64zM66.004 383.837L65.53 64.399l318.728 1.829-0.367 63.999-254.265-1.459 0.378 254.975-64 0.094zM897.495 383l-0.661-252.989-254.683 0.217-0.055-64 318.569-0.271 0.829 316.876-63.999 0.167zM404.866 510.522c0 24.75-5.328 46.895-15.984 66.43-10.656 19.537-25.553 34.719-44.688 45.547-19.137 10.828-40.562 16.242-64.281 16.242-23.146 0-44.201-5.242-63.164-15.727-18.965-10.484-33.717-25.207-44.258-44.172-10.543-18.963-15.812-40.418-15.812-64.367 0-25.093 5.328-47.665 15.984-67.718 10.656-20.05 25.609-35.549 44.859-46.492 19.25-10.941 41.135-16.414 65.656-16.414 23.604 0 44.714 5.242 63.336 15.727 18.619 10.484 33 25.438 43.141 44.859s15.211 41.451 15.211 66.085z m-78.719 2.062c0-20.281-3.896-36.265-11.688-47.953-7.793-11.688-18.45-17.531-31.969-17.531-14.781 0-26.297 5.615-34.547 16.844-8.25 11.231-12.375 27.1-12.375 47.609 0 20.053 4.096 35.693 12.289 46.922 8.191 11.23 19.336 16.844 33.43 16.844 8.594 0 16.328-2.52 23.203-7.562 6.875-5.041 12.203-12.26 15.984-21.656 3.782-9.396 5.673-20.566 5.673-33.517zM623.147 627.741c-20.396 7.332-43.943 11-70.641 11-26.24 0-48.872-5.012-67.891-15.039-19.021-10.025-33.545-24.291-43.57-42.797-10.028-18.504-15.039-39.846-15.039-64.023 0-26.009 5.613-49.156 16.844-69.437 11.229-20.281 27.097-35.949 47.609-47.008 20.51-11.057 44.114-16.586 70.813-16.586 21.312 0 41.938 2.578 61.875 7.734v68.578c-6.875-4.125-15.068-7.332-24.578-9.625a122.892 122.892 0 0 0-28.875-3.438c-20.168 0-36.066 5.787-47.695 17.359-11.631 11.575-17.446 27.271-17.446 47.093 0 19.709 5.814 35.264 17.446 46.664 11.629 11.402 27.184 17.102 46.664 17.102 17.988 0 36.15-4.582 54.484-13.75v66.173zM789.351 634.444l-18.391-53.109c-3.553-10.426-8.164-18.619-13.836-24.578-5.672-5.957-11.832-8.938-18.477-8.938h-2.922v86.625h-74.25V387.975h98.656c34.488 0 59.955 5.645 76.398 16.93 16.441 11.287 24.664 28.217 24.664 50.789 0 16.959-4.785 31.168-14.352 42.625-9.568 11.458-23.805 19.765-42.711 24.921v0.688c10.426 3.209 19.105 8.422 26.039 15.641 6.932 7.219 13.148 17.934 18.648 32.141l24.234 62.734h-83.7z m-7.047-168.265c0-8.25-2.521-14.781-7.562-19.594-5.043-4.812-12.949-7.219-23.719-7.219h-15.297v56.375h13.406c9.969 0 17.988-2.807 24.062-8.422 6.073-5.613 9.11-12.659 9.11-21.14z"
p-id="3084"></path>
</svg>
)
export default OcrIcon
@@ -0,0 +1,16 @@
import { FC } from 'react'
const ToolIcon: FC<React.SVGProps<SVGSVGElement>> = (props) => (
<svg
viewBox="150 150 724 724"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
className="icon"
{...props}>
<path d="M732.16 450.048h-12.288V377.856c0-37.888-31.744-69.12-70.656-69.12h-73.216c1.024-7.168 1.024-14.848 1.024-22.016 0-75.776-62.976-137.728-140.288-137.728S296.448 210.944 296.448 286.72c0 7.168 0 14.848 1.024 22.016H224.256c-38.912 0-70.656 31.232-70.656 69.12v102.4c0 9.216 3.584 15.872 10.752 21.504 7.168 5.632 14.848 7.168 23.552 5.632 7.68-1.536 16.384-3.072 22.528-3.072 46.08 0 84.992 38.4 84.992 83.456 0 46.08-37.888 83.456-84.992 83.456-6.656 0-14.848-1.536-22.528-3.072-7.68-2.048-16.896 0.512-23.552 5.632-7.168 5.12-10.752 12.288-10.752 21.504v102.4c0 37.888 31.744 69.12 70.656 69.12H650.24c19.456 0 38.4-8.192 51.712-22.016 12.288-13.312 18.432-29.696 17.92-47.104v-72.192h12.288c77.312 0 140.288-61.952 140.288-137.728 0-75.776-62.976-137.728-140.288-137.728z m-66.048 239.616v105.984c0 8.192-7.168 15.36-15.872 15.36H224.768c-8.704 0-15.872-6.656-15.872-15.36v-70.656c0.512 0 1.536 0 2.048-0.512 77.312-0.512 139.776-61.952 139.776-137.728 0-37.376-14.848-72.704-41.984-98.816-27.136-26.112-62.976-39.936-100.352-38.912V378.368c0-8.192 7.168-15.36 15.872-15.36h110.08c8.704 0 17.92-4.608 23.552-12.288 4.608-6.656 5.632-15.36 2.048-25.088-5.12-11.264-8.192-24.064-8.192-36.352 0-46.08 37.888-83.456 84.992-83.456 46.08 0 84.992 38.4 84.992 83.456 0 10.752-1.024 23.04-7.68 35.84-4.608 7.68-3.584 18.432 1.536 25.6 5.12 6.144 13.824 12.288 23.552 12.288H650.24c8.704 0 15.872 6.656 15.872 15.36v107.52c0 9.216 3.584 15.872 10.752 21.504 7.68 5.632 16.896 6.656 25.6 3.072 8.192-4.096 17.408-6.144 28.672-6.144 46.08 0 84.992 38.4 84.992 83.456 0 46.08-37.888 83.456-84.992 83.456-8.192 0-17.408-3.072-26.112-5.632l-3.072-1.024c-10.24-2.56-18.432-1.536-25.088 3.584-6.656 3.072-10.752 11.264-10.752 21.504z" />
</svg>
)
export default ToolIcon
+2 -32
View File
@@ -17,11 +17,9 @@ import {
updateBases,
updateItem as updateItemAction,
updateItemProcessingStatus,
updateNotes,
updateOcrProvider as _updateOcrProvider,
updateOcrProviders as _updateOcrProviders
updateNotes
} from '@renderer/store/knowledge'
import { FileType, KnowledgeBase, KnowledgeItem, OcrProvider, ProcessingStatus } from '@renderer/types'
import { FileType, KnowledgeBase, KnowledgeItem, ProcessingStatus } from '@renderer/types'
import { runAsyncFunction } from '@renderer/utils'
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
@@ -394,31 +392,3 @@ export const useKnowledgeBases = () => {
updateKnowledgeBases
}
}
export const useOcrProviders = () => {
const dispatch = useDispatch()
const ocrProviders = useSelector((state: RootState) => state.knowledge.ocrProviders)
const updateOcrProviders = (ocrProviders: OcrProvider[]) => {
dispatch(_updateOcrProviders(ocrProviders))
}
return {
ocrProviders,
updateOcrProviders
}
}
export const useOcrProvider = (id: string) => {
const dispatch = useDispatch()
const ocrProviders = useSelector((state: RootState) => state.knowledge.ocrProviders)
const ocrProvider = ocrProviders.find((provider) => provider.id === id)
if (!ocrProvider) {
throw new Error(`ocr provider with id ${id} not found`)
}
const updateOcrProvider = (ocrProvider: OcrProvider) => {
dispatch(_updateOcrProvider(ocrProvider))
}
return {
ocrProvider,
updateOcrProvider
}
}
+45
View File
@@ -0,0 +1,45 @@
import { RootState } from '@renderer/store'
import {
setDefaultOcrProvider as _setDefaultOcrProvider,
updateOcrProvider as _updateOcrProvider,
updateOcrProviders as _updateOcrProviders
} from '@renderer/store/ocr'
import { OcrProvider } from '@renderer/types'
import { useDispatch, useSelector } from 'react-redux'
export const useOcrProvider = (id: string) => {
const dispatch = useDispatch()
const ocrProviders = useSelector((state: RootState) => state.ocr.providers)
const provider = ocrProviders.find((provider) => provider.id === id)
if (!provider) {
throw new Error(`ocr provider with id ${id} not found`)
}
const updateOcrProvider = (ocrProvider: OcrProvider) => {
dispatch(_updateOcrProvider(ocrProvider))
}
return { provider, updateOcrProvider }
}
export const useOcrProviders = () => {
const dispatch = useDispatch()
const ocrProviders = useSelector((state: RootState) => state.ocr.providers)
return {
ocrProviders,
updateOcrProviders: (ocrProviders: OcrProvider[]) => dispatch(_updateOcrProviders(ocrProviders))
}
}
export const useDefaultOcrProvider = () => {
const defaultProviderId = useSelector((state: RootState) => state.ocr.defaultProvider)
const { ocrProviders } = useOcrProviders()
const dispatch = useDispatch()
const provider = defaultProviderId ? ocrProviders.find((provider) => provider.id === defaultProviderId) : undefined
const setDefaultOcrProvider = (ocrProvider: OcrProvider) => {
dispatch(_setDefaultOcrProvider(ocrProvider.id))
}
const updateDefaultOcrProvider = (ocrProvider: OcrProvider) => {
dispatch(_updateOcrProvider(ocrProvider))
}
return { provider, setDefaultOcrProvider, updateDefaultOcrProvider }
}
+39 -26
View File
@@ -369,7 +369,11 @@
"rename": "Rename",
"search": "Search knowledge base",
"search_placeholder": "Enter text to search",
"settings": "Knowledge Base Settings",
"settings": {
"title": "Knowledge Base Settings",
"preprocessing": "Preprocessing",
"preprocessing_tooltip": "Preprocess uploaded files with OCR"
},
"sitemap_placeholder": "Enter Website Map URL",
"sitemaps": "Websites",
"source": "Source",
@@ -1101,37 +1105,46 @@
"theme.window.style.title": "Window Style",
"theme.window.style.transparent": "Transparent Window",
"title": "Settings",
"tool": {
"websearch": {
"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",
"check": "Check",
"check_failed": "Verification failed",
"check_success": "Verification successful",
"enhance_mode": "Search enhance mode",
"enhance_mode_tooltip": "Use the default model to extract search keywords from the problem and search",
"get_api_key": "Get API Key",
"no_provider_selected": "Please select a search service provider before checking.",
"search_max_result": "Number of search results",
"search_provider": "Search service provider",
"search_provider_placeholder": "Choose a search service provider.",
"search_result_default": "Default",
"search_with_time": "Search with dates included",
"tavily": {
"api_key": "Tavily API Key",
"api_key.placeholder": "Enter Tavily API Key",
"description": "Tavily is a search engine tailored for AI agents, delivering real-time, accurate results, intelligent query suggestions, and in-depth research capabilities.",
"title": "Tavily"
},
"title": "Web Search"
},
"ocr": {
"title": "OCR",
"provider": "OCR Provider",
"ocr": "OCR Settings",
"provider_placeholder": "Choose a OCR provider"
},
"title": "Tool Settings"
},
"topic.position": "Topic position",
"topic.position.left": "Left",
"topic.position.right": "Right",
"topic.show.time": "Show topic time",
"tray.onclose": "Minimize to Tray on Close",
"tray.show": "Show Tray Icon",
"tray.title": "Tray",
"websearch": {
"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",
"check": "Check",
"check_failed": "Verification failed",
"check_success": "Verification successful",
"enhance_mode": "Search enhance mode",
"enhance_mode_tooltip": "Use the default model to extract search keywords from the problem and search",
"get_api_key": "Get API Key",
"no_provider_selected": "Please select a search service provider before checking.",
"search_max_result": "Number of search results",
"search_provider": "Search service provider",
"search_provider_placeholder": "Choose a search service provider.",
"search_result_default": "Default",
"search_with_time": "Search with dates included",
"tavily": {
"api_key": "Tavily API Key",
"api_key.placeholder": "Enter Tavily API Key",
"description": "Tavily is a search engine tailored for AI agents, delivering real-time, accurate results, intelligent query suggestions, and in-depth research capabilities.",
"title": "Tavily"
},
"title": "Web Search"
}
"tray.title": "Tray"
},
"translate": {
"any.language": "Any language",
+40 -26
View File
@@ -369,7 +369,11 @@
"rename": "名前を変更",
"search": "ナレッジベースを検索",
"search_placeholder": "検索するテキストを入力",
"settings": "ナレッジベース設定",
"settings": {
"title": "ナレッジベース設定",
"preprocessing": "預処理",
"preprocessing_tooltip": "アップロードされたファイルのOCR預処理"
},
"sitemap_placeholder": "サイトマップURLを入力",
"sitemaps": "サイトマップ",
"source": "ソース",
@@ -1101,37 +1105,47 @@
"theme.window.style.title": "ウィンドウスタイル",
"theme.window.style.transparent": "透明ウィンドウ",
"title": "設定",
"tool": {
"title": "ツール設定",
"ocr": {
"title": "ナレッジベース設定",
"preprocessing": "預処理",
"preprocessing_tooltip": "アップロードされたファイルのOCR預処理",
"provider": "OCRプロバイダー",
"provider_placeholder": "OCRプロバイダーを選択する"
},
"websearch": {
"blacklist": "ブラックリスト",
"blacklist_description": "以下のウェブサイトの結果は検索結果に表示されません",
"blacklist_tooltip": "以下の形式を使用してください(改行区切り)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "チェック",
"check_failed": "検証に失敗しました",
"check_success": "検証に成功しました",
"enhance_mode": "検索強化モード",
"enhance_mode_tooltip": "デフォルトモデルを使用して問題から検索キーワードを抽出し、検索を実行します",
"get_api_key": "APIキーを取得",
"no_provider_selected": "検索サービスプロバイダーを選択してから再確認してください。",
"search_max_result": "検索結果の数",
"search_provider": "検索サービスプロバイダー",
"search_provider_placeholder": "検索サービスプロバイダーを選択する",
"search_result_default": "デフォルト",
"search_with_time": "日付を含む検索",
"tavily": {
"api_key": "Tavily API キー",
"api_key.placeholder": "Tavily API キーを入力してください",
"description": "Tavily は、AI エージェントのために特別に開発された検索エンジンで、最新の結果、インテリジェントな検索提案、そして深い研究能力を提供します",
"title": "Tavily"
},
"title": "ウェブ検索"
}
},
"topic.position": "トピックの位置",
"topic.position.left": "左",
"topic.position.right": "右",
"topic.show.time": "トピックの時間を表示",
"tray.onclose": "閉じるときにトレイに最小化",
"tray.show": "トレイアイコンを表示",
"tray.title": "トレイ",
"websearch": {
"blacklist": "ブラックリスト",
"blacklist_description": "以下のウェブサイトの結果は検索結果に表示されません",
"blacklist_tooltip": "以下の形式を使用してください(改行区切り)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "チェック",
"check_failed": "検証に失敗しました",
"check_success": "検証に成功しました",
"enhance_mode": "検索強化モード",
"enhance_mode_tooltip": "デフォルトモデルを使用して問題から検索キーワードを抽出し、検索を実行します",
"get_api_key": "APIキーを取得",
"no_provider_selected": "検索サービスプロバイダーを選択してから再確認してください。",
"search_max_result": "検索結果の数",
"search_provider": "検索サービスプロバイダー",
"search_provider_placeholder": "検索サービスプロバイダーを選択する",
"search_result_default": "デフォルト",
"search_with_time": "日付を含む検索",
"tavily": {
"api_key": "Tavily API キー",
"api_key.placeholder": "Tavily API キーを入力してください",
"description": "Tavily は、AI エージェントのために特別に開発された検索エンジンで、最新の結果、インテリジェントな検索提案、そして深い研究能力を提供します",
"title": "Tavily"
},
"title": "ウェブ検索"
}
"tray.title": "トレイ"
},
"translate": {
"any.language": "任意の言語",
+38 -26
View File
@@ -369,7 +369,11 @@
"rename": "Переименовать",
"search": "Поиск в базе знаний",
"search_placeholder": "Введите текст для поиска",
"settings": "Настройки базы знаний",
"settings": {
"title": "Настройки базы знаний",
"preprocessing": "Предварительная обработка",
"preprocessing_tooltip": "Предварительная обработка изображений с помощью OCR"
},
"sitemap_placeholder": "Введите URL карты сайта",
"sitemaps": "Сайты",
"source": "Источник",
@@ -1101,37 +1105,45 @@
"theme.window.style.title": "Стиль окна",
"theme.window.style.transparent": "Прозрачное окно",
"title": "Настройки",
"tool": {
"title": "Настройки инструментов",
"ocr": {
"title": "Настройки OCR",
"provider": "Поставщик OCR",
"provider_placeholder": "Выберите поставщика OCR"
},
"websearch": {
"blacklist": "Черный список",
"blacklist_description": "Результаты из следующих веб-сайтов не будут отображаться в результатах поиска",
"blacklist_tooltip": "Пожалуйста, используйте следующий формат (разделенный переносами строк)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "проверка",
"check_failed": "Проверка не прошла",
"check_success": "Проверка успешна",
"enhance_mode": "Режим улучшения поиска",
"enhance_mode_tooltip": "Используйте модель по умолчанию для извлечения ключевых слов из проблемы и поиска",
"get_api_key": "Получить ключ API",
"no_provider_selected": "Пожалуйста, выберите поставщика поисковых услуг, затем проверьте.",
"search_max_result": "Количество результатов поиска",
"search_provider": "поиск сервисного провайдера",
"search_provider_placeholder": "Выберите поставщика поисковых услуг",
"search_result_default": "По умолчанию",
"search_with_time": "Поиск, содержащий дату",
"tavily": {
"api_key": "Ключ API Tavily",
"api_key.placeholder": "Введите ключ API Tavily",
"description": "Tavily — это поисковая система, специально разработанная для ИИ-агентов, предоставляющая актуальные результаты, умные предложения по запросам и глубокие исследовательские возможности",
"title": "Tavily"
},
"title": "Поиск в Интернете"
}
},
"topic.position": "Позиция топиков",
"topic.position.left": "Слева",
"topic.position.right": "Справа",
"topic.show.time": "Показывать время топика",
"tray.onclose": "Свернуть в трей при закрытии",
"tray.show": "Показать значок в трее",
"tray.title": "Трей",
"websearch": {
"blacklist": "Черный список",
"blacklist_description": "Результаты из следующих веб-сайтов не будут отображаться в результатах поиска",
"blacklist_tooltip": "Пожалуйста, используйте следующий формат (разделенный переносами строк)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "проверка",
"check_failed": "Проверка не прошла",
"check_success": "Проверка успешна",
"enhance_mode": "Режим улучшения поиска",
"enhance_mode_tooltip": "Используйте модель по умолчанию для извлечения ключевых слов из проблемы и поиска",
"get_api_key": "Получить ключ API",
"no_provider_selected": "Пожалуйста, выберите поставщика поисковых услуг, затем проверьте.",
"search_max_result": "Количество результатов поиска",
"search_provider": "поиск сервисного провайдера",
"search_provider_placeholder": "Выберите поставщика поисковых услуг",
"search_result_default": "По умолчанию",
"search_with_time": "Поиск, содержащий дату",
"tavily": {
"api_key": "Ключ API Tavily",
"api_key.placeholder": "Введите ключ API Tavily",
"description": "Tavily — это поисковая система, специально разработанная для ИИ-агентов, предоставляющая актуальные результаты, умные предложения по запросам и глубокие исследовательские возможности",
"title": "Tavily"
},
"title": "Поиск в Интернете"
}
"tray.title": "Трей"
},
"translate": {
"any.language": "Любой язык",
+35 -37
View File
@@ -369,6 +369,7 @@
"rename": "重命名",
"search": "搜索知识库",
"search_placeholder": "输入查询内容",
"settings": "知识库设置",
"sitemap_placeholder": "请输入站点地图 URL",
"sitemaps": "网站",
"source": "来源",
@@ -389,18 +390,7 @@
"topN_tooltip": "返回的匹配结果数量,数值越大,匹配结果越多,但消耗的 Token 也越多",
"url_added": "网址已添加",
"url_placeholder": "请输入网址, 多个网址用回车分隔",
"urls": "网址",
"settings": {
"title": "知识库设置",
"preprocessing": "预处理",
"preprocessing_tooltip": "对上传的文件进行ocr预处理",
"general": "常规设置",
"ocr": "OCR 设置",
"advanced": "高级设置",
"doc2x_api_key": "Doc2X API Key",
"doc2x_api_key_placeholder": "请输入 Doc2X API Key",
"doc2x_api_key_tooltip": "使用doc2x进行文档预处理"
}
"urls": "网址"
},
"languages": {
"arabic": "阿拉伯文",
@@ -1118,37 +1108,45 @@
"theme.window.style.title": "窗口样式",
"theme.window.style.transparent": "透明窗口",
"title": "设置",
"tool": {
"title": "工具设置",
"ocr": {
"title": "知识库设置",
"provider": "OCR服务商",
"provider_placeholder": "选择一个OCR服务商"
},
"websearch": {
"blacklist": "黑名单",
"blacklist_description": "在搜索结果中不会出现以下网站的结果",
"blacklist_tooltip": "请使用以下格式(换行分隔)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "检查",
"check_failed": "验证失败",
"check_success": "验证成功",
"enhance_mode": "搜索增强模式",
"enhance_mode_tooltip": "使用默认模型提取关键词后搜索",
"get_api_key": "点击这里获取密钥",
"no_provider_selected": "请选择搜索服务商后再检查",
"search_max_result": "搜索结果个数",
"search_provider": "搜索服务商",
"search_provider_placeholder": "选择一个搜索服务商",
"search_result_default": "默认",
"search_with_time": "搜索包含日期",
"tavily": {
"api_key": "Tavily API 密钥",
"api_key.placeholder": "请输入 Tavily API 密钥",
"description": "Tavily 是一个为 AI 代理量身定制的搜索引擎,提供实时、准确的结果、智能查询建议和深入的研究能力",
"title": "Tavily"
},
"title": "网络搜索"
}
},
"topic.position": "话题位置",
"topic.position.left": "左侧",
"topic.position.right": "右侧",
"topic.show.time": "显示话题时间",
"tray.onclose": "关闭时最小化到托盘",
"tray.show": "显示托盘图标",
"tray.title": "托盘",
"websearch": {
"blacklist": "黑名单",
"blacklist_description": "在搜索结果中不会出现以下网站的结果",
"blacklist_tooltip": "请使用以下格式(换行分隔)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "检查",
"check_failed": "验证失败",
"check_success": "验证成功",
"enhance_mode": "搜索增强模式",
"enhance_mode_tooltip": "使用默认模型提取关键词后搜索",
"get_api_key": "点击这里获取密钥",
"no_provider_selected": "请选择搜索服务商后再检查",
"search_max_result": "搜索结果个数",
"search_provider": "搜索服务商",
"search_provider_placeholder": "选择一个搜索服务商",
"search_result_default": "默认",
"search_with_time": "搜索包含日期",
"tavily": {
"api_key": "Tavily API 密钥",
"api_key.placeholder": "请输入 Tavily API 密钥",
"description": "Tavily 是一个为 AI 代理量身定制的搜索引擎,提供实时、准确的结果、智能查询建议和深入的研究能力",
"title": "Tavily"
},
"title": "网络搜索"
}
"tray.title": "托盘"
},
"translate": {
"any.language": "任意语言",
+40 -27
View File
@@ -369,7 +369,11 @@
"rename": "重新命名",
"search": "搜尋知識庫",
"search_placeholder": "輸入查詢內容",
"settings": "知識庫設定",
"settings": {
"title": "知識庫設定",
"preprocessing": "預處理",
"preprocessing_tooltip": "預處理上傳的文件"
},
"sitemap_placeholder": "請輸入網站地圖 URL",
"sitemaps": "網站",
"source": "來源",
@@ -1101,37 +1105,46 @@
"theme.window.style.title": "視窗樣式",
"theme.window.style.transparent": "透明視窗",
"title": "設定",
"tool": {
"title": "工具",
"ocr": {
"title": "OCR",
"description": "OCR 設定",
"provider": "OCR 提供者",
"provider_placeholder": "選擇一個 OCR 提供者"
},
"websearch": {
"blacklist": "黑名單",
"blacklist_description": "以下網站不會出現在搜尋結果中",
"blacklist_tooltip": "請使用以下格式 (換行符號分隔)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "檢查",
"check_failed": "驗證失敗",
"check_success": "驗證成功",
"enhance_mode": "搜索增強模式",
"enhance_mode_tooltip": "使用預設模型提取關鍵詞後搜索",
"get_api_key": "點選這裡取得金鑰",
"no_provider_selected": "請選擇搜尋服務商後再檢查",
"search_max_result": "搜尋結果個數",
"search_provider": "搜尋服務商",
"search_provider_placeholder": "選擇一個搜尋服務商",
"search_result_default": "預設",
"search_with_time": "搜尋包含日期",
"tavily": {
"api_key": "Tavily API 金鑰",
"api_key.placeholder": "請輸入 Tavily API 金鑰",
"description": "Tavily 是一個為 AI 代理量身訂製的搜尋引擎,提供即時、準確的結果、智慧查詢建議和深入的研究能力",
"title": "Tavily"
},
"title": "網路搜尋"
}
},
"topic.position": "話題位置",
"topic.position.left": "左側",
"topic.position.right": "右側",
"topic.show.time": "顯示話題時間",
"tray.onclose": "關閉時最小化到系统匣",
"tray.show": "顯示系统匣圖示",
"tray.title": "系统匣",
"websearch": {
"blacklist": "黑名單",
"blacklist_description": "以下網站不會出現在搜尋結果中",
"blacklist_tooltip": "請使用以下格式 (換行符號分隔)\nexample.com\nhttps://www.example.com\nhttps://example.com\n*://*.example.com",
"check": "檢查",
"check_failed": "驗證失敗",
"check_success": "驗證成功",
"enhance_mode": "搜索增強模式",
"enhance_mode_tooltip": "使用預設模型提取關鍵詞後搜索",
"get_api_key": "點選這裡取得金鑰",
"no_provider_selected": "請選擇搜尋服務商後再檢查",
"search_max_result": "搜尋結果個數",
"search_provider": "搜尋服務商",
"search_provider_placeholder": "選擇一個搜尋服務商",
"search_result_default": "預設",
"search_with_time": "搜尋包含日期",
"tavily": {
"api_key": "Tavily API 金鑰",
"api_key.placeholder": "請輸入 Tavily API 金鑰",
"description": "Tavily 是一個為 AI 代理量身訂製的搜尋引擎,提供即時、準確的結果、智慧查詢建議和深入的研究能力",
"title": "Tavily"
},
"title": "網路搜尋"
}
"tray.title": "系统匣"
},
"translate": {
"any.language": "任意語言",
@@ -1170,4 +1183,4 @@
"visualization": "視覺化"
}
}
}
}
@@ -28,7 +28,6 @@ import styled from 'styled-components'
import KnowledgeSearchPopup from './components/KnowledgeSearchPopup'
import KnowledgeSettings from './components/KnowledgeSettings'
import StatusIcon from './components/StatusIcon'
import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup'
const { Dragger } = Upload
const { Title } = Typography
@@ -463,7 +462,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
<ModelInfo>
<div className="model-header">
<label>{t('knowledge.model_info')}</label>
<Button icon={<SettingOutlined />} onClick={() => KnowledgeSettingsPopup.show({ base })} size="small" />
<Button icon={<SettingOutlined />} onClick={() => KnowledgeSettings.show({ base })} size="small" />
</div>
<div className="model-row">
@@ -24,7 +24,7 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import AddKnowledgePopup from './components/AddKnowledgePopup'
import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup'
import KnowledgeSettings from './components/KnowledgeSettings'
import KnowledgeContent from './KnowledgeContent'
const KnowledgePage: FC = () => {
@@ -64,10 +64,10 @@ const KnowledgePage: FC = () => {
}
},
{
label: t('knowledge.settings'),
label: t('knowledge.settings.title'),
key: 'settings',
icon: <SettingOutlined />,
onClick: () => KnowledgeSettingsPopup.show({ base })
onClick: () => KnowledgeSettings.show({ base })
},
{ type: 'divider' },
{
@@ -3,7 +3,8 @@ import { TopView } from '@renderer/components/TopView'
import { DEFAULT_KNOWLEDGE_DOCUMENT_COUNT } from '@renderer/config/constant'
import { getEmbeddingMaxContext } from '@renderer/config/embedings'
import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
import { useKnowledge, useOcrProviders } from '@renderer/hooks/useKnowledge'
import { useKnowledge } from '@renderer/hooks/useKnowledge'
import { useOcrProviders } from '@renderer/hooks/useOcr'
import { useProviders } from '@renderer/hooks/useProvider'
import { SettingRowTitle } from '@renderer/pages/settings'
import { getModelUniqId } from '@renderer/services/ModelService'
@@ -87,7 +88,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
const settingItems: TabsProps['items'] = [
{
key: '1',
label: t('knowledge.settings.general'),
label: t('settings.general'),
children: (
<SettingsPanel>
<SettingsItem>
@@ -172,11 +173,11 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
},
{
key: '2',
label: t('knowledge.settings.ocr'),
label: t('settings.tool.ocr.title'),
children: (
<SettingsPanel>
<SettingsItem>
<SettingRowTitle>{t('settings.knowledge.ocr.provider')}</SettingRowTitle>
<SettingRowTitle>{t('settings.tool.ocr.provider')}</SettingRowTitle>
<div style={{ display: 'flex', gap: '8px' }}>
<Select
value={selectedProvider?.id}
@@ -187,7 +188,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
setSelectedProvider(provider)
setNewBase({ ...newBase, ocrProvider: provider })
}}
placeholder={t('settings.websearch.search_provider_placeholder')}
placeholder={t('settings.tool.ocr.provider_placeholder')}
options={ocrProviders.filter((p) => p.apiKey !== '').map((p) => ({ value: p.id, label: p.name }))}
/>
</div>
@@ -198,7 +199,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
},
{
key: '3',
label: t('knowledge.settings.advanced'),
label: t('settings.advanced.title'),
children: (
<SettingsPanel>
<SettingsItem>
@@ -336,6 +337,9 @@ const SettingsModal = styled(Modal)`
max-height: auto;
}
}
.ant-tabs-tab {
padding-inline-start: 0px !important;
}
`
export default class KnowledgeSettings {
@@ -115,7 +115,7 @@ const PopupContainer: React.FC<Props> = ({ base: _base, resolve }) => {
return (
<Modal
title={t('knowledge.settings')}
title={t('knowledge.settings.title')}
open={open}
onOk={onOk}
onCancel={onCancel}
@@ -1,8 +1,6 @@
import {
CloudOutlined,
CodeOutlined,
FileSearchOutlined,
GlobalOutlined,
InfoCircleOutlined,
LayoutOutlined,
MacCommandOutlined,
@@ -11,6 +9,7 @@ import {
SettingOutlined
} from '@ant-design/icons'
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
import ToolIcon from '@renderer/components/Icons/ToolIcon'
import { isLocalAi } from '@renderer/config/env'
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
import { FC } from 'react'
@@ -22,12 +21,11 @@ import AboutSettings from './AboutSettings'
import DataSettings from './DataSettings/DataSettings'
import DisplaySettings from './DisplaySettings/DisplaySettings'
import GeneralSettings from './GeneralSettings'
import KnowledgeSettings from './KnowledgeSettings'
import MCPSettings from './MCPSettings'
import ProvidersList from './ProviderSettings'
import QuickAssistantSettings from './QuickAssistantSettings'
import ShortcutSettings from './ShortcutSettings'
import WebSearchSettings from './WebSearchSettings'
import ToolSettings from './ToolSettings'
const SettingsPage: FC = () => {
const { pathname } = useLocation()
@@ -58,10 +56,10 @@ const SettingsPage: FC = () => {
</MenuItemLink>
</>
)}
<MenuItemLink to="/settings/web-search">
<MenuItem className={isRoute('/settings/web-search')}>
<GlobalOutlined />
{t('settings.websearch.title')}
<MenuItemLink to="/settings/tool">
<MenuItem className={isRoute('/settings/tool')}>
<ToolIcon />
{t('settings.tool.title')}
</MenuItem>
</MenuItemLink>
<MenuItemLink to="/settings/mcp">
@@ -70,13 +68,6 @@ const SettingsPage: FC = () => {
{t('settings.mcp.title')}
</MenuItem>
</MenuItemLink>
<MenuItemLink to="/settings/knowledge">
<MenuItem className={isRoute('/settings/knowledge')}>
<FileSearchOutlined />
{t('settings.knowledge.title')}
</MenuItem>
</MenuItemLink>
<MenuItemLink to="/settings/general">
<MenuItem className={isRoute('/settings/general')}>
<SettingOutlined />
@@ -118,9 +109,8 @@ const SettingsPage: FC = () => {
<Routes>
<Route path="provider" element={<ProvidersList />} />
<Route path="model" element={<ModelSettings />} />
<Route path="web-search" element={<WebSearchSettings />} />
<Route path="tool/*" element={<ToolSettings />} />
<Route path="mcp" element={<MCPSettings />} />
<Route path="knowledge" element={<KnowledgeSettings />} />
<Route path="general/*" element={<GeneralSettings />} />
<Route path="display" element={<DisplaySettings />} />
<Route path="data/*" element={<DataSettings />} />
@@ -1,6 +1,6 @@
import { ExportOutlined } from '@ant-design/icons'
import { getOcrProviderLogo, OCR_PROVIDER_CONFIG } from '@renderer/config/ocrProviders'
import { useOcrProvider } from '@renderer/hooks/useKnowledge'
import { useOcrProvider } from '@renderer/hooks/useOcr'
import { formatApiKeys } from '@renderer/services/ApiService'
import { OcrProvider } from '@renderer/types'
import { hasObjectKey } from '@renderer/utils'
@@ -10,14 +10,14 @@ import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle, SettingTitle } from '..'
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle, SettingTitle } from '../..'
interface Props {
provider: OcrProvider
}
const OcrProviderSetting: FC<Props> = ({ provider: _provider }) => {
const { ocrProvider, updateOcrProvider } = useOcrProvider(_provider.id)
const { provider: ocrProvider, updateOcrProvider } = useOcrProvider(_provider.id)
const { t } = useTranslation()
const [apiKey, setApiKey] = useState(ocrProvider.apiKey || '')
const [apiHost, setApiHost] = useState(ocrProvider.apiHost || '')
@@ -80,7 +80,7 @@ const OcrProviderSetting: FC<Props> = ({ provider: _provider }) => {
</Flex>
<SettingHelpTextRow style={{ justifyContent: 'space-between', marginTop: 5 }}>
<SettingHelpLink target="_blank" href={apiKeyWebsite}>
{t('settings.websearch.get_api_key')}
{t('settings.provider.get_api_key')}
</SettingHelpLink>
<SettingHelpText>{t('settings.provider.api_key.tip')}</SettingHelpText>
</SettingHelpTextRow>
@@ -1,44 +1,52 @@
import { useTheme } from '@renderer/context/ThemeProvider'
import { useOcrProviders } from '@renderer/hooks/useKnowledge'
import { useDefaultOcrProvider, useOcrProviders } from '@renderer/hooks/useOcr'
import { OcrProvider } from '@renderer/types'
import { Select } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '../..'
import OcrProviderSettings from './OcrProviderSettings'
const KnowledgeSettings: FC = () => {
const { ocrProviders } = useOcrProviders()
const { provider: defaultProvider, setDefaultOcrProvider } = useDefaultOcrProvider()
const { t } = useTranslation()
const [selectedProvider, setSelectedProvider] = useState<OcrProvider>(ocrProviders[0])
const [selectedProvider, setSelectedProvider] = useState<OcrProvider | undefined>(defaultProvider)
const { theme: themeMode } = useTheme()
function updateSelectedOcrProvider(providerId: string) {
const provider = ocrProviders.find((p) => p.id === providerId)
if (!provider) {
return
}
setDefaultOcrProvider(provider)
setSelectedProvider(provider)
}
return (
<SettingContainer theme={themeMode}>
<SettingGroup theme={themeMode}>
<SettingTitle>{t('settings.knowledge.ocr.title')}</SettingTitle>
<SettingTitle>{t('settings.tool.ocr.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.knowledge.ocr.provider')}</SettingRowTitle>
<SettingRowTitle>{t('settings.tool.ocr.provider')}</SettingRowTitle>
<div style={{ display: 'flex', gap: '8px' }}>
<Select
value={selectedProvider?.id}
style={{ width: '200px' }}
onChange={(value: string) => {
const provider = ocrProviders.find((p) => p.id === value)
if (!provider) return
setSelectedProvider(provider)
}}
placeholder={t('settings.websearch.search_provider_placeholder')}
onChange={(value: string) => updateSelectedOcrProvider(value)}
placeholder={t('settings.tool.ocr.provider_placeholder')}
options={ocrProviders.map((p) => ({ value: p.id, label: p.name }))}
/>
</div>
</SettingRow>
</SettingGroup>
<SettingGroup theme={themeMode}>
{selectedProvider && <OcrProviderSettings provider={selectedProvider} />}
</SettingGroup>
{selectedProvider && (
<SettingGroup theme={themeMode}>
<OcrProviderSettings provider={selectedProvider} />
</SettingGroup>
)}
</SettingContainer>
)
}
@@ -0,0 +1,53 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setEnhanceMode, setMaxResult, setSearchWithTime } from '@renderer/store/websearch'
import { Slider, Switch, Tooltip } from 'antd'
import { t } from 'i18next'
import { FC } from 'react'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '../..'
const BasicSettings: FC = () => {
const { theme } = useTheme()
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
const enhanceMode = useAppSelector((state) => state.websearch.enhanceMode)
const maxResults = useAppSelector((state) => state.websearch.maxResults)
const dispatch = useAppDispatch()
return (
<SettingGroup theme={theme} style={{ paddingBottom: 8 }}>
<SettingTitle>{t('settings.general.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tool.websearch.search_with_time')}</SettingRowTitle>
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>
{t('settings.tool.websearch.enhance_mode')}
<Tooltip title={t('settings.tool.websearch.enhance_mode_tooltip')} placement="right">
<InfoCircleOutlined style={{ marginLeft: 5, color: 'var(--color-icon)', cursor: 'pointer' }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={enhanceMode} onChange={(checked) => dispatch(setEnhanceMode(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>{t('settings.tool.websearch.search_max_result')}</SettingRowTitle>
<Slider
defaultValue={maxResults}
style={{ width: '200px' }}
min={1}
max={20}
step={1}
marks={{ 1: '1', 5: t('settings.tool.websearch.search_result_default'), 20: '20' }}
onChangeComplete={(value) => dispatch(setMaxResult(value))}
/>
</SettingRow>
</SettingGroup>
)
}
export default BasicSettings
@@ -7,7 +7,7 @@ import TextArea from 'antd/es/input/TextArea'
import { t } from 'i18next'
import { FC, useEffect, useState } from 'react'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '../..'
const BlacklistSettings: FC = () => {
const [errFormat, setErrFormat] = useState(false)
@@ -45,22 +45,22 @@ const BlacklistSettings: FC = () => {
return (
<>
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.websearch.blacklist')}</SettingTitle>
<SettingTitle>{t('settings.tool.websearch.blacklist')}</SettingTitle>
<SettingDivider />
<SettingRow style={{ marginBottom: 10 }}>
<SettingRowTitle>{t('settings.websearch.blacklist_description')}</SettingRowTitle>
<SettingRowTitle>{t('settings.tool.websearch.blacklist_description')}</SettingRowTitle>
</SettingRow>
<TextArea
value={blacklistInput}
onChange={(e) => setBlacklistInput(e.target.value)}
placeholder={t('settings.websearch.blacklist_tooltip')}
placeholder={t('settings.tool.websearch.blacklist_tooltip')}
autoSize={{ minRows: 4, maxRows: 8 }}
rows={4}
/>
<Button onClick={() => updateManualBlacklist(blacklistInput)} style={{ marginTop: 10 }}>
{t('common.save')}
</Button>
{errFormat && <Alert message={t('settings.websearch.blacklist_tooltip')} type="error" />}
{errFormat && <Alert message={t('settings.tool.websearch.blacklist_tooltip')} type="error" />}
</SettingGroup>
</>
)
@@ -11,8 +11,8 @@ import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle, SettingTitle } from '..'
import ApiCheckPopup from '../ProviderSettings/ApiCheckPopup'
import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle, SettingTitle } from '../..'
import ApiCheckPopup from '../../ProviderSettings/ApiCheckPopup'
interface Props {
provider: WebSearchProvider
@@ -51,7 +51,7 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
async function checkSearch() {
if (!provider) {
window.message.error({
content: t('settings.websearch.no_provider_selected'),
content: t('settings.no_provider_selected'),
duration: 3,
icon: <InfoCircleOutlined />,
key: 'no-provider-selected'
@@ -88,7 +88,9 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
key: 'api-check',
style: { marginTop: '3vh' },
duration: valid ? 2 : 8,
content: valid ? t('settings.websearch.check_success') : t('settings.websearch.check_failed') + errorMessage
content: valid
? t('settings.tool.websearch.check_success')
: t('settings.tool.websearch.check_failed') + errorMessage
})
setApiValid(valid)
@@ -99,7 +101,7 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
key: 'check-search-error',
style: { marginTop: '3vh' },
duration: 8,
content: t('settings.websearch.check_failed')
content: t('settings.tool.websearch.check_failed')
})
} finally {
setApiChecking(false)
@@ -145,12 +147,18 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
type={apiValid ? 'primary' : 'default'}
onClick={checkSearch}
disabled={apiChecking}>
{apiChecking ? <LoadingOutlined spin /> : apiValid ? <CheckOutlined /> : t('settings.websearch.check')}
{apiChecking ? (
<LoadingOutlined spin />
) : apiValid ? (
<CheckOutlined />
) : (
t('settings.tool.websearch.check')
)}
</Button>
</Flex>
<SettingHelpTextRow style={{ justifyContent: 'space-between', marginTop: 5 }}>
<SettingHelpLink target="_blank" href={apiKeyWebsite}>
{t('settings.websearch.get_api_key')}
{t('settings.tool.websearch.get_api_key')}
</SettingHelpLink>
<SettingHelpText>{t('settings.provider.api_key.tip')}</SettingHelpText>
</SettingHelpTextRow>
@@ -160,12 +168,12 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
{hasObjectKey(provider, 'apiHost') && (
<>
<SettingSubtitle style={{ marginTop: 5, marginBottom: 10 }}>
{t('settings.provider.api_host')}
{t('settings.tool.provider.api_host')}
</SettingSubtitle>
<Flex>
<Input
value={apiHost}
placeholder={t('settings.provider.api_host')}
placeholder={t('settings.tool.provider.api_host')}
onChange={(e) => setApiHost(e.target.value)}
onBlur={onUpdateApiHost}
/>
@@ -174,7 +182,13 @@ const WebSearchProviderSetting: FC<Props> = ({ provider: _provider }) => {
type={apiValid ? 'primary' : 'default'}
onClick={checkSearch}
disabled={apiChecking}>
{apiChecking ? <LoadingOutlined spin /> : apiValid ? <CheckOutlined /> : t('settings.websearch.check')}
{apiChecking ? (
<LoadingOutlined spin />
) : apiValid ? (
<CheckOutlined />
) : (
t('settings.tool.websearch.check')
)}
</Button>
</Flex>
</>
@@ -5,7 +5,7 @@ import { Select } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '../..'
import BasicSettings from './BasicSettings'
import BlacklistSettings from './BlacklistSettings'
import WebSearchProviderSetting from './WebSearchProviderSetting'
@@ -29,24 +29,26 @@ const WebSearchSettings: FC = () => {
return (
<SettingContainer theme={themeMode}>
<SettingGroup theme={themeMode}>
<SettingTitle>{t('settings.websearch.title')}</SettingTitle>
<SettingTitle>{t('settings.tool.websearch.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.search_provider')}</SettingRowTitle>
<SettingRowTitle>{t('settings.tool.websearch.search_provider')}</SettingRowTitle>
<div style={{ display: 'flex', gap: '8px' }}>
<Select
value={selectedProvider?.id}
style={{ width: '200px' }}
onChange={(value: string) => updateSelectedWebSearchProvider(value)}
placeholder={t('settings.websearch.search_provider_placeholder')}
placeholder={t('settings.tool.websearch.search_provider_placeholder')}
options={providers.map((p) => ({ value: p.id, label: p.name }))}
/>
</div>
</SettingRow>
</SettingGroup>
<SettingGroup theme={themeMode}>
{selectedProvider && <WebSearchProviderSetting provider={selectedProvider} />}
</SettingGroup>
{selectedProvider && (
<SettingGroup theme={themeMode}>
<WebSearchProviderSetting provider={selectedProvider} />
</SettingGroup>
)}
<BasicSettings />
<BlacklistSettings />
</SettingContainer>
@@ -0,0 +1,59 @@
import { GlobalOutlined } from '@ant-design/icons'
import OcrIcon from '@renderer/components/Icons/OcrIcon'
import { HStack } from '@renderer/components/Layout'
import ListItem from '@renderer/components/ListItem'
import { theme } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingContainer } from '..'
import OcrSettings from './OcrSettings'
import WebSearchSettings from './WebSearchSettings'
const ToolSettings: FC = () => {
const { t } = useTranslation()
const [menu, setMenu] = useState<string>('web-search')
const menuItems = [
{ key: 'web-search', title: 'settings.tool.websearch.title', icon: <GlobalOutlined style={{ fontSize: 16 }} /> },
{ key: 'ocr', title: 'settings.tool.ocr.title', icon: <OcrIcon /> }
]
return (
<Container>
<MenuList>
{menuItems.map((item) => (
<ListItem
key={item.key}
title={t(item.title)}
active={menu === item.key}
onClick={() => setMenu(item.key)}
titleStyle={{ fontWeight: 500 }}
icon={item.icon}
/>
))}
</MenuList>
<SettingContainer theme={theme} style={{ display: 'flex', flex: 1 }}>
{menu == 'web-search' && <WebSearchSettings />}
{menu == 'ocr' && <OcrSettings />}
</SettingContainer>
</Container>
)
}
const Container = styled(HStack)`
flex: 1;
`
const MenuList = styled.div`
display: flex;
flex-direction: column;
gap: 5px;
width: var(--settings-width);
padding: 12px;
border-right: 0.5px solid var(--color-border);
height: 100%;
.iconfont {
color: var(--color-text-2);
line-height: 16px;
}
`
export default ToolSettings
@@ -1,55 +0,0 @@
import { InfoCircleOutlined } from '@ant-design/icons'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setEnhanceMode, setMaxResult, setSearchWithTime } from '@renderer/store/websearch'
import { Slider, Switch, Tooltip } from 'antd'
import { t } from 'i18next'
import { FC } from 'react'
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
const BasicSettings: FC = () => {
const { theme } = useTheme()
const searchWithTime = useAppSelector((state) => state.websearch.searchWithTime)
const enhanceMode = useAppSelector((state) => state.websearch.enhanceMode)
const maxResults = useAppSelector((state) => state.websearch.maxResults)
const dispatch = useAppDispatch()
return (
<>
<SettingGroup theme={theme} style={{ paddingBottom: 8 }}>
<SettingTitle>{t('settings.general.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.search_with_time')}</SettingRowTitle>
<Switch checked={searchWithTime} onChange={(checked) => dispatch(setSearchWithTime(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>
{t('settings.websearch.enhance_mode')}
<Tooltip title={t('settings.websearch.enhance_mode_tooltip')} placement="right">
<InfoCircleOutlined style={{ marginLeft: 5, color: 'var(--color-icon)', cursor: 'pointer' }} />
</Tooltip>
</SettingRowTitle>
<Switch checked={enhanceMode} onChange={(checked) => dispatch(setEnhanceMode(checked))} />
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>{t('settings.websearch.search_max_result')}</SettingRowTitle>
<Slider
defaultValue={maxResults}
style={{ width: '200px' }}
min={1}
max={20}
step={1}
marks={{ 1: '1', 5: t('settings.websearch.search_result_default'), 20: '20' }}
onChangeComplete={(value) => dispatch(setMaxResult(value))}
/>
</SettingRow>
</SettingGroup>
</>
)
}
export default BasicSettings
+4 -2
View File
@@ -13,6 +13,7 @@ import mcp from './mcp'
import messagesReducer from './messages'
import migrate from './migrate'
import minapps from './minapps'
import ocr from './ocr'
import paintings from './paintings'
import runtime from './runtime'
import settings from './settings'
@@ -33,14 +34,15 @@ const rootReducer = combineReducers({
websearch,
mcp,
copilot,
messages: messagesReducer
messages: messagesReducer,
ocr
})
const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 83,
version: 84,
blacklist: ['runtime', 'messages'],
migrate
},
+3 -18
View File
@@ -1,15 +1,13 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import FileManager from '@renderer/services/FileManager'
import { FileType, KnowledgeBase, KnowledgeItem, OcrProvider, ProcessingStatus } from '@renderer/types'
import { FileType, KnowledgeBase, KnowledgeItem, ProcessingStatus } from '@renderer/types'
export interface KnowledgeState {
bases: KnowledgeBase[]
ocrProviders: OcrProvider[]
}
const initialState: KnowledgeState = {
bases: [],
ocrProviders: []
bases: []
}
const knowledgeSlice = createSlice({
@@ -185,17 +183,6 @@ const knowledgeSlice = createSlice({
item.uniqueIds = action.payload.uniqueIds
}
}
},
updateOcrProviders(state, action: PayloadAction<OcrProvider[]>) {
state.ocrProviders = action.payload
},
updateOcrProvider(state, action: PayloadAction<OcrProvider>) {
const index = state.ocrProviders.findIndex((provider) => provider.id === action.payload.id)
if (index !== -1) {
state.ocrProviders[index] = action.payload
}
}
}
})
@@ -214,9 +201,7 @@ export const {
updateItemProcessingStatus,
clearCompletedProcessing,
clearAllProcessing,
updateBaseItemUniqueId,
updateOcrProviders,
updateOcrProvider
updateBaseItemUniqueId
} = knowledgeSlice.actions
export default knowledgeSlice.reducer
+13 -9
View File
@@ -774,15 +774,6 @@ const migrateConfig = {
return state
},
'82': (state: RootState) => {
if (!state.knowledge.ocrProviders) {
state.knowledge.ocrProviders = []
}
state.knowledge.ocrProviders.push({
id: 'doc2x',
name: 'Doc2x',
apiKey: '',
apiHost: 'https://v2.doc2x.noedgeai.com'
})
const runtimeState = state.runtime as any
if (runtimeState?.webdavSync) {
state.backup = state.backup || {}
@@ -804,6 +795,19 @@ const migrateConfig = {
state.settings.launchToTray = false
state.settings.trayOnClose = true
return state
},
'84': (state: RootState) => {
if (!state.ocr.providers) {
state.ocr.providers = [
{
id: 'doc2x',
name: 'Doc2x',
apiKey: '',
apiHost: 'https://v2.doc2x.noedgeai.com'
}
]
}
return state
}
}
+44
View File
@@ -0,0 +1,44 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { OcrProvider } from '@renderer/types'
export interface OcrState {
providers: OcrProvider[]
defaultProvider: string
}
const initialState: OcrState = {
providers: [
{
id: 'doc2x',
name: 'Doc2x',
apiKey: '',
apiHost: 'https://v2.doc2x.noedgeai.com'
}
],
defaultProvider: ''
}
const ocrSlice = createSlice({
name: 'ocr',
initialState,
reducers: {
setDefaultOcrProvider(state, action: PayloadAction<string>) {
state.defaultProvider = action.payload
},
setOcrProviders(state, action: PayloadAction<OcrProvider[]>) {
state.providers = action.payload
},
updateOcrProviders(state, action: PayloadAction<OcrProvider[]>) {
state.providers = action.payload
},
updateOcrProvider(state, action: PayloadAction<OcrProvider>) {
const index = state.providers.findIndex((provider) => provider.id === action.payload.id)
if (index !== -1) {
state.providers[index] = action.payload
}
}
}
})
export const { updateOcrProviders, updateOcrProvider, setDefaultOcrProvider, setOcrProviders } = ocrSlice.actions
export default ocrSlice.reducer