refactor: remove mac ocr

This commit is contained in:
kangfenmao 2025-07-31 20:54:56 +08:00
parent c214a6e56e
commit 1efefad3ee
40 changed files with 145 additions and 1014 deletions

View File

@ -50,11 +50,8 @@ files:
- '!node_modules/rollup-plugin-visualizer'
- '!node_modules/js-tiktoken'
- '!node_modules/@tavily/core/node_modules/js-tiktoken'
- '!node_modules/pdf-parse/lib/pdf.js/{v1.9.426,v1.10.88,v2.0.550}'
- '!node_modules/mammoth/{mammoth.browser.js,mammoth.browser.min.js}'
- '!node_modules/selection-hook/prebuilds/**/*' # we rebuild .node, don't use prebuilds
- '!node_modules/pdfjs-dist/web/**/*'
- '!node_modules/pdfjs-dist/legacy/**/*'
- '!node_modules/selection-hook/node_modules' # we don't need what in the node_modules dir
- '!node_modules/selection-hook/src' # we don't need source files
- '!**/*.{h,iobj,ipdb,tlog,recipe,vcxproj,vcxproj.filters,Makefile,*.Makefile}' # filter .node build files

View File

@ -26,13 +26,11 @@ export default defineConfig({
},
build: {
rollupOptions: {
external: ['@libsql/client', 'bufferutil', 'utf-8-validate', '@cherrystudio/mac-system-ocr'],
output: isProd
? {
manualChunks: undefined, // 彻底禁用代码分割 - 返回 null 强制单文件打包
inlineDynamicImports: true // 内联所有动态导入,这是关键配置
}
: undefined
external: ['@libsql/client', 'bufferutil', 'utf-8-validate'],
output: {
manualChunks: undefined, // 彻底禁用代码分割 - 返回 null 强制单文件打包
inlineDynamicImports: true // 内联所有动态导入,这是关键配置
}
},
sourcemap: isDev
},

View File

@ -70,7 +70,6 @@
"prepare": "git config blame.ignoreRevsFile .git-blame-ignore-revs && husky"
},
"dependencies": {
"@cherrystudio/pdf-to-img-napi": "^0.0.1",
"@libsql/client": "0.14.0",
"@libsql/win32-x64-msvc": "^0.4.7",
"@strongtz/win32-arm64-msvc": "^0.4.7",
@ -80,7 +79,6 @@
"node-stream-zip": "^1.15.0",
"officeparser": "^4.2.0",
"os-proxy-config": "^1.1.2",
"pdfjs-dist": "4.10.38",
"selection-hook": "^1.0.8",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.1",
@ -229,6 +227,7 @@
"npx-scope-finder": "^1.2.0",
"openai": "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch",
"p-queue": "^8.1.0",
"pdf-lib": "^1.17.1",
"playwright": "^1.52.0",
"prettier": "^3.5.3",
"prettier-plugin-sort-json": "^4.1.1",
@ -279,11 +278,7 @@
"zipread": "^1.3.3",
"zod": "^3.25.74"
},
"optionalDependencies": {
"@cherrystudio/mac-system-ocr": "^0.2.2"
},
"resolutions": {
"pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch",
"@langchain/openai@npm:^0.3.16": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"@langchain/openai@npm:>=0.1.0 <0.4.0": "patch:@langchain/openai@npm%3A0.3.16#~/.yarn/patches/@langchain-openai-npm-0.3.16-e525b59526.patch",
"libsql@npm:^0.4.4": "patch:libsql@npm%3A0.4.7#~/.yarn/patches/libsql-npm-0.4.7-444e260fb1.patch",

View File

@ -53,7 +53,7 @@ exports.default = async function (context) {
* @param {string} nodeModulesPath
*/
function removeMacOnlyPackages(nodeModulesPath) {
const macOnlyPackages = ['@cherrystudio/mac-system-ocr']
const macOnlyPackages = []
macOnlyPackages.forEach((packageName) => {
const packagePath = path.join(nodeModulesPath, packageName)

View File

@ -1,122 +0,0 @@
import fs from 'node:fs'
import path from 'node:path'
import { windowService } from '@main/services/WindowService'
import { getFileExt } from '@main/utils/file'
import { FileMetadata, OcrProvider } from '@types'
import { app } from 'electron'
import pdfjs from 'pdfjs-dist'
import { TypedArray } from 'pdfjs-dist/types/src/display/api'
export default abstract class BaseOcrProvider {
protected provider: OcrProvider
public storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
constructor(provider: OcrProvider) {
if (!provider) {
throw new Error('OCR provider is not set')
}
this.provider = provider
}
abstract parseFile(sourceId: string, file: FileMetadata): Promise<{ processedFile: FileMetadata; quota?: number }>
/**
*
* Data/Files/{file.id}
* @param file
* @returns null
*/
public async checkIfAlreadyProcessed(file: FileMetadata): Promise<FileMetadata | null> {
try {
// 检查 Data/Files/{file.id} 是否是目录
const preprocessDirPath = path.join(this.storageDir, file.id)
if (fs.existsSync(preprocessDirPath)) {
const stats = await fs.promises.stat(preprocessDirPath)
// 如果是目录,说明已经被预处理过
if (stats.isDirectory()) {
// 查找目录中的处理结果文件
const files = await fs.promises.readdir(preprocessDirPath)
// 查找主要的处理结果文件(.md 或 .txt
const processedFile = files.find((fileName) => fileName.endsWith('.md') || fileName.endsWith('.txt'))
if (processedFile) {
const processedFilePath = path.join(preprocessDirPath, processedFile)
const processedStats = await fs.promises.stat(processedFilePath)
const ext = getFileExt(processedFile)
return {
...file,
name: file.name.replace(file.ext, ext),
path: processedFilePath,
ext: ext,
size: processedStats.size,
created_at: processedStats.birthtime.toISOString()
}
}
}
}
return null
} catch (error) {
// 如果检查过程中出现错误返回null表示未处理
return null
}
}
/**
*
*/
public delay = (ms: number): Promise<void> => {
return new Promise((resolve) => setTimeout(resolve, ms))
}
public async readPdf(
source: string | URL | TypedArray,
passwordCallback?: (fn: (password: string) => void, reason: string) => string
) {
const documentLoadingTask = pdfjs.getDocument(source)
if (passwordCallback) {
documentLoadingTask.onPassword = passwordCallback
}
const document = await documentLoadingTask.promise
return document
}
public async sendOcrProgress(sourceId: string, progress: number): Promise<void> {
const mainWindow = windowService.getMainWindow()
mainWindow?.webContents.send('file-ocr-progress', {
itemId: sourceId,
progress: progress
})
}
/**
*
* @param fileId id
* @param filePaths
* @returns
*/
public moveToAttachmentsDir(fileId: string, filePaths: string[]): string[] {
const attachmentsPath = path.join(this.storageDir, fileId)
if (!fs.existsSync(attachmentsPath)) {
fs.mkdirSync(attachmentsPath, { recursive: true })
}
const movedPaths: string[] = []
for (const filePath of filePaths) {
if (fs.existsSync(filePath)) {
const fileName = path.basename(filePath)
const destPath = path.join(attachmentsPath, fileName)
fs.copyFileSync(filePath, destPath)
fs.unlinkSync(filePath) // 删除原文件,实现"移动"
movedPaths.push(destPath)
}
}
return movedPaths
}
}

View File

@ -1,12 +0,0 @@
import { FileMetadata, OcrProvider } from '@types'
import BaseOcrProvider from './BaseOcrProvider'
export default class DefaultOcrProvider extends BaseOcrProvider {
constructor(provider: OcrProvider) {
super(provider)
}
public parseFile(): Promise<{ processedFile: FileMetadata }> {
throw new Error('Method not implemented.')
}
}

View File

@ -1,130 +0,0 @@
import { loggerService } from '@logger'
import { isMac } from '@main/constant'
import { FileMetadata, OcrProvider } from '@types'
import * as fs from 'fs'
import * as path from 'path'
import { TextItem } from 'pdfjs-dist/types/src/display/api'
import BaseOcrProvider from './BaseOcrProvider'
const logger = loggerService.withContext('MacSysOcrProvider')
export default class MacSysOcrProvider extends BaseOcrProvider {
private readonly MIN_TEXT_LENGTH = 1000
private MacOCR: any
private async initMacOCR() {
if (!isMac) {
throw new Error('MacSysOcrProvider is only available on macOS')
}
if (!this.MacOCR) {
try {
// @ts-ignore This module is optional and only installed/available on macOS. Runtime checks prevent execution on other platforms.
const module = await import('@cherrystudio/mac-system-ocr')
this.MacOCR = module.default
} catch (error) {
logger.error('Failed to load mac-system-ocr:', error as Error)
throw error
}
}
return this.MacOCR
}
private getRecognitionLevel(level?: number) {
return level === 0 ? this.MacOCR.RECOGNITION_LEVEL_FAST : this.MacOCR.RECOGNITION_LEVEL_ACCURATE
}
constructor(provider: OcrProvider) {
super(provider)
}
private async processPages(
results: any,
totalPages: number,
sourceId: string,
writeStream: fs.WriteStream
): Promise<void> {
await this.initMacOCR()
// TODO: 下个版本后面使用批处理以及p-queue来优化
for (let i = 0; i < totalPages; i++) {
// Convert pages to buffers
const pageNum = i + 1
const pageBuffer = await results.getPage(pageNum)
// Process batch
const ocrResult = await this.MacOCR.recognizeFromBuffer(pageBuffer, {
ocrOptions: {
recognitionLevel: this.getRecognitionLevel(this.provider.options?.recognitionLevel),
minConfidence: this.provider.options?.minConfidence || 0.5
}
})
// Write results in order
writeStream.write(ocrResult.text + '\n')
// Update progress
await this.sendOcrProgress(sourceId, (pageNum / totalPages) * 100)
}
}
public async isScanPdf(buffer: Buffer): Promise<boolean> {
const doc = await this.readPdf(new Uint8Array(buffer))
const pageLength = doc.numPages
let counts = 0
const pagesToCheck = Math.min(pageLength, 10)
for (let i = 0; i < pagesToCheck; i++) {
const page = await doc.getPage(i + 1)
const pageData = await page.getTextContent()
const pageText = pageData.items.map((item) => (item as TextItem).str).join('')
counts += pageText.length
if (counts >= this.MIN_TEXT_LENGTH) {
return false
}
}
return true
}
public async parseFile(sourceId: string, file: FileMetadata): Promise<{ processedFile: FileMetadata }> {
logger.info(`Starting OCR process for file: ${file.name}`)
if (file.ext === '.pdf') {
try {
const { pdf } = await import('@cherrystudio/pdf-to-img-napi')
const pdfBuffer = await fs.promises.readFile(file.path)
const results = await pdf(pdfBuffer, {
scale: 2
})
const totalPages = results.length
const baseDir = path.dirname(file.path)
const baseName = path.basename(file.path, path.extname(file.path))
const txtFileName = `${baseName}.txt`
const txtFilePath = path.join(baseDir, txtFileName)
const writeStream = fs.createWriteStream(txtFilePath)
await this.processPages(results, totalPages, sourceId, writeStream)
await new Promise<void>((resolve, reject) => {
writeStream.end(() => {
logger.info(`OCR process completed successfully for ${file.origin_name}`)
resolve()
})
writeStream.on('error', reject)
})
const movedPaths = this.moveToAttachmentsDir(file.id, [txtFilePath])
return {
processedFile: {
...file,
name: txtFileName,
path: movedPaths[0],
ext: '.txt',
size: fs.statSync(movedPaths[0]).size
}
}
} catch (error) {
logger.error('Error during OCR process:', error as Error)
throw error
}
}
return { processedFile: file }
}
}

View File

@ -1,26 +0,0 @@
import { FileMetadata, OcrProvider as Provider } from '@types'
import BaseOcrProvider from './BaseOcrProvider'
import OcrProviderFactory from './OcrProviderFactory'
export default class OcrProvider {
private sdk: BaseOcrProvider
constructor(provider: Provider) {
this.sdk = OcrProviderFactory.create(provider)
}
public async parseFile(
sourceId: string,
file: FileMetadata
): Promise<{ processedFile: FileMetadata; quota?: number }> {
return this.sdk.parseFile(sourceId, file)
}
/**
*
* @param file
* @returns null
*/
public async checkIfAlreadyProcessed(file: FileMetadata): Promise<FileMetadata | null> {
return this.sdk.checkIfAlreadyProcessed(file)
}
}

View File

@ -1,23 +0,0 @@
import { loggerService } from '@logger'
import { isMac } from '@main/constant'
import { OcrProvider } from '@types'
import BaseOcrProvider from './BaseOcrProvider'
import DefaultOcrProvider from './DefaultOcrProvider'
import MacSysOcrProvider from './MacSysOcrProvider'
const logger = loggerService.withContext('OcrProviderFactory')
export default class OcrProviderFactory {
static create(provider: OcrProvider): BaseOcrProvider {
switch (provider.id) {
case 'system':
if (!isMac) {
logger.warn('System OCR provider is only available on macOS')
}
return new MacSysOcrProvider(provider)
default:
return new DefaultOcrProvider(provider)
}
}
}

View File

@ -1,17 +1,18 @@
import fs from 'node:fs'
import path from 'node:path'
import { loggerService } from '@logger'
import { windowService } from '@main/services/WindowService'
import { getFileExt } from '@main/utils/file'
import { getFileExt, getTempDir } from '@main/utils/file'
import { FileMetadata, PreprocessProvider } from '@types'
import { app } from 'electron'
import pdfjs from 'pdfjs-dist'
import { TypedArray } from 'pdfjs-dist/types/src/display/api'
import { PDFDocument } from 'pdf-lib'
const logger = loggerService.withContext('BasePreprocessProvider')
export default abstract class BasePreprocessProvider {
protected provider: PreprocessProvider
protected userId?: string
public storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
public storageDir = path.join(getTempDir(), 'preprocess')
constructor(provider: PreprocessProvider, userId?: string) {
if (!provider) {
@ -19,7 +20,19 @@ export default abstract class BasePreprocessProvider {
}
this.provider = provider
this.userId = userId
this.ensureDirectories()
}
private ensureDirectories() {
try {
if (!fs.existsSync(this.storageDir)) {
fs.mkdirSync(this.storageDir, { recursive: true })
}
} catch (error) {
logger.error('Failed to create directories:', error as Error)
}
}
abstract parseFile(sourceId: string, file: FileMetadata): Promise<{ processedFile: FileMetadata; quota?: number }>
abstract checkQuota(): Promise<number>
@ -77,17 +90,11 @@ export default abstract class BasePreprocessProvider {
return new Promise((resolve) => setTimeout(resolve, ms))
}
public async readPdf(
source: string | URL | TypedArray,
passwordCallback?: (fn: (password: string) => void, reason: string) => string
) {
const documentLoadingTask = pdfjs.getDocument(source)
if (passwordCallback) {
documentLoadingTask.onPassword = passwordCallback
public async readPdf(buffer: Buffer) {
const pdfDoc = await PDFDocument.load(buffer)
return {
numPages: pdfDoc.getPageCount()
}
const document = await documentLoadingTask.promise
return document
}
public async sendPreprocessProgress(sourceId: string, progress: number): Promise<void> {

View File

@ -39,7 +39,7 @@ export default class Doc2xPreprocessProvider extends BasePreprocessProvider {
private async validateFile(filePath: string): Promise<void> {
const pdfBuffer = await fs.promises.readFile(filePath)
const doc = await this.readPdf(new Uint8Array(pdfBuffer))
const doc = await this.readPdf(pdfBuffer)
// 文件页数小于1000页
if (doc.numPages >= 1000) {

View File

@ -115,7 +115,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider {
private async validateFile(filePath: string): Promise<void> {
const pdfBuffer = await fs.promises.readFile(filePath)
const doc = await this.readPdf(new Uint8Array(pdfBuffer))
const doc = await this.readPdf(pdfBuffer)
// 文件页数小于600页
if (doc.numPages >= 600) {
@ -178,7 +178,7 @@ export default class MineruPreprocessProvider extends BasePreprocessProvider {
try {
// 下载ZIP文件
const response = await axios.get(zipUrl, { responseType: 'arraybuffer' })
fs.writeFileSync(zipPath, response.data)
fs.writeFileSync(zipPath, Buffer.from(response.data))
logger.info(`Downloaded ZIP file: ${zipPath}`)
// 确保提取目录存在

View File

@ -16,7 +16,7 @@ import { writeFileSync } from 'fs'
import { readFile } from 'fs/promises'
import officeParser from 'officeparser'
import * as path from 'path'
import pdfjs from 'pdfjs-dist'
import { PDFDocument } from 'pdf-lib'
import { chdir } from 'process'
import { v4 as uuidv4 } from 'uuid'
import WordExtractor from 'word-extractor'
@ -367,10 +367,8 @@ class FileStorage {
const filePath = path.join(this.storageDir, id)
const buffer = await fs.promises.readFile(filePath)
const doc = await pdfjs.getDocument({ data: buffer }).promise
const pages = doc.numPages
await doc.destroy()
return pages
const pdfDoc = await PDFDocument.load(buffer)
return pdfDoc.getPageCount()
}
public binaryImage = async (_: Electron.IpcMainInvokeEvent, id: string): Promise<{ data: Buffer; mime: string }> => {

View File

@ -25,7 +25,6 @@ import { loggerService } from '@logger'
import Embeddings from '@main/knowledge/embeddings/Embeddings'
import { addFileLoader } from '@main/knowledge/loader'
import { NoteLoader } from '@main/knowledge/loader/noteLoader'
import OcrProvider from '@main/knowledge/ocr/OcrProvider'
import PreprocessProvider from '@main/knowledge/preprocess/PreprocessProvider'
import Reranker from '@main/knowledge/reranker/Reranker'
import { windowService } from '@main/services/WindowService'
@ -687,14 +686,9 @@ class KnowledgeService {
userId: string
): Promise<FileMetadata> => {
let fileToProcess: FileMetadata = file
if (base.preprocessOrOcrProvider && file.ext.toLowerCase() === '.pdf') {
if (base.preprocessProvider && file.ext.toLowerCase() === '.pdf') {
try {
let provider: PreprocessProvider | OcrProvider
if (base.preprocessOrOcrProvider.type === 'preprocess') {
provider = new PreprocessProvider(base.preprocessOrOcrProvider.provider, userId)
} else {
provider = new OcrProvider(base.preprocessOrOcrProvider.provider)
}
const provider = new PreprocessProvider(base.preprocessProvider.provider, userId)
// Check if file has already been preprocessed
const alreadyProcessed = await provider.checkIfAlreadyProcessed(file)
if (alreadyProcessed) {
@ -728,8 +722,8 @@ class KnowledgeService {
userId: string
): Promise<number> => {
try {
if (base.preprocessOrOcrProvider && base.preprocessOrOcrProvider.type === 'preprocess') {
const provider = new PreprocessProvider(base.preprocessOrOcrProvider.provider, userId)
if (base.preprocessProvider && base.preprocessProvider.type === 'preprocess') {
const provider = new PreprocessProvider(base.preprocessProvider.provider, userId)
return await provider.checkQuota()
}
throw new Error('No preprocess provider configured')

View File

@ -1,6 +1,4 @@
import { isMac } from '@renderer/config/constant'
import { getEmbeddingMaxContext } from '@renderer/config/embedings'
import { useOcrProviders } from '@renderer/hooks/useOcr'
import { usePreprocessProviders } from '@renderer/hooks/usePreprocess'
import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService'
@ -42,11 +40,10 @@ export const useKnowledgeBaseForm = (base?: KnowledgeBase) => {
const [newBase, setNewBase] = useState<KnowledgeBase>(base || createInitialKnowledgeBase())
const { providers } = useProviders()
const { preprocessProviders } = usePreprocessProviders()
const { ocrProviders } = useOcrProviders()
const selectedDocPreprocessProvider = useMemo(
() => newBase.preprocessOrOcrProvider?.provider,
[newBase.preprocessOrOcrProvider]
() => newBase.preprocessProvider?.provider,
[newBase.preprocessProvider]
)
const docPreprocessSelectOptions = useMemo(() => {
@ -57,14 +54,8 @@ export const useKnowledgeBaseForm = (base?: KnowledgeBase) => {
.filter((p) => p.apiKey !== '' || p.id === 'mineru')
.map((p) => ({ value: p.id, label: p.name }))
}
const ocrOptions = {
label: t('settings.tool.ocr.provider'),
title: t('settings.tool.ocr.provider'),
options: ocrProviders.filter((p) => p.apiKey !== '').map((p) => ({ value: p.id, label: p.name }))
}
return isMac ? [preprocessOptions, ocrOptions] : [preprocessOptions]
}, [ocrProviders, preprocessProviders, t])
return [preprocessOptions]
}, [preprocessProviders, t])
const handleEmbeddingModelChange = useCallback(
(value: string) => {
@ -92,21 +83,20 @@ export const useKnowledgeBaseForm = (base?: KnowledgeBase) => {
const handleDocPreprocessChange = useCallback(
(value: string) => {
const type = preprocessProviders.find((p) => p.id === value) ? 'preprocess' : 'ocr'
const provider = (type === 'preprocess' ? preprocessProviders : ocrProviders).find((p) => p.id === value)
const provider = preprocessProviders.find((p) => p.id === value)
if (!provider) {
setNewBase((prev) => ({ ...prev, preprocessOrOcrProvider: undefined }))
setNewBase((prev) => ({ ...prev, preprocessProvider: undefined }))
return
}
setNewBase((prev) => ({
...prev,
preprocessOrOcrProvider: {
type,
preprocessProvider: {
type: 'preprocess',
provider
}
}))
},
[preprocessProviders, ocrProviders]
[preprocessProviders]
)
const handleChunkSizeChange = useCallback(
@ -152,7 +142,6 @@ export const useKnowledgeBaseForm = (base?: KnowledgeBase) => {
const providerData = {
providers,
preprocessProviders,
ocrProviders,
selectedDocPreprocessProvider,
docPreprocessSelectOptions
}

View File

@ -1,45 +0,0 @@
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: 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 }
}

View File

@ -922,7 +922,7 @@
"search_placeholder": "Enter text to search",
"settings": {
"preprocessing": "Preprocessing",
"preprocessing_tooltip": "Preprocess uploaded files with OCR",
"preprocessing_tooltip": "Preprocess uploaded files",
"title": "Knowledge Base Settings"
},
"sitemap_added": "Added successfully",
@ -3302,26 +3302,11 @@
},
"title": "Settings",
"tool": {
"ocr": {
"mac_system_ocr_options": {
"min_confidence": "Minimum Confidence",
"mode": {
"accurate": "Accurate",
"fast": "Fast",
"title": "Recognition Mode"
}
},
"provider": "OCR Provider",
"provider_placeholder": "Choose an OCR provider",
"title": "OCR Settings"
},
"preprocess": {
"provider": "Pre Process Provider",
"provider_placeholder": "Choose a Pre Process provider",
"title": "Pre Process"
},
"preprocessOrOcr": {
"tooltip": "In Settings -> Tools, set a document preprocessing service provider or OCR. Document preprocessing can effectively improve the retrieval performance of complex format documents and scanned documents. OCR can only recognize text within images in documents or scanned PDF text."
"title": "Pre Process",
"tooltip": "In Settings -> Tools, set a document preprocessing service provider. Document preprocessing can effectively improve the retrieval performance of complex format documents and scanned documents."
},
"title": "Tools Settings",
"websearch": {

View File

@ -922,7 +922,7 @@
"search_placeholder": "検索するテキストを入力",
"settings": {
"preprocessing": "預処理",
"preprocessing_tooltip": "アップロードされたファイルのOCR預処理",
"preprocessing_tooltip": "アップロードされたファイルの預処理",
"title": "ナレッジベース設定"
},
"sitemap_added": "追加成功",
@ -3302,26 +3302,11 @@
},
"title": "設定",
"tool": {
"ocr": {
"mac_system_ocr_options": {
"min_confidence": "最小信頼度",
"mode": {
"accurate": "正確",
"fast": "速い",
"title": "認識モード"
}
},
"provider": "OCRプロバイダー",
"provider_placeholder": "OCRプロバイダーを選択",
"title": "OCRオーシーアール"
},
"preprocess": {
"provider": "プレプロセスプロバイダー",
"provider_placeholder": "前処理プロバイダーを選択してください",
"title": "前処理"
},
"preprocessOrOcr": {
"tooltip": "設定 → ツールで、ドキュメント前処理サービスプロバイダーまたはOCRを設定します。ドキュメント前処理は、複雑な形式のドキュメントやスキャンされたドキュメントの検索性能を効果的に向上させます。OCRは、ドキュメント内の画像内のテキストまたはスキャンされたPDFテキストのみを認識できます。"
"title": "前処理",
"tooltip": "設定 → ツールで、ドキュメント前処理サービスプロバイダーを設定します。ドキュメント前処理は、複雑な形式のドキュメントやスキャンされたドキュメントの検索性能を効果的に向上させます。"
},
"title": "ツール設定",
"websearch": {

View File

@ -922,7 +922,7 @@
"search_placeholder": "Введите текст для поиска",
"settings": {
"preprocessing": "Предварительная обработка",
"preprocessing_tooltip": "Предварительная обработка изображений с помощью OCR",
"preprocessing_tooltip": "Предварительная обработка документов",
"title": "Настройки базы знаний"
},
"sitemap_added": "添加成功",
@ -3302,26 +3302,11 @@
},
"title": "Настройки",
"tool": {
"ocr": {
"mac_system_ocr_options": {
"min_confidence": "Минимальная достоверность",
"mode": {
"accurate": "Точный",
"fast": "Быстро",
"title": "Режим распознавания"
}
},
"provider": "Поставщик OCR",
"provider_placeholder": "Выберите провайдера OCR",
"title": "OCR (оптическое распознавание символов)"
},
"preprocess": {
"provider": "Предварительная обработка Поставщик",
"provider_placeholder": "Выберите поставщика услуг предварительной обработки",
"title": "Предварительная обработка"
},
"preprocessOrOcr": {
"tooltip": "В настройках (Настройки -> Инструменты) укажите поставщика услуги предварительной обработки документов или OCR. Предварительная обработка документов может значительно повысить эффективность поиска для документов сложных форматов и отсканированных документов. OCR способен распознавать только текст внутри изображений в документах или текст в отсканированных PDF."
"title": "Предварительная обработка",
"tooltip": "В настройках (Настройки -> Инструменты) укажите поставщика услуги предварительной обработки документов. Предварительная обработка документов может значительно повысить эффективность поиска для документов сложных форматов и отсканированных документов."
},
"title": "Настройки инструментов",
"websearch": {

View File

@ -3302,26 +3302,11 @@
},
"title": "设置",
"tool": {
"ocr": {
"mac_system_ocr_options": {
"min_confidence": "最低置信度",
"mode": {
"accurate": "准确",
"fast": "快速",
"title": "识别模式"
}
},
"provider": "OCR 服务商",
"provider_placeholder": "选择一个 OCR 服务商",
"title": "OCR 文字识别"
},
"preprocess": {
"provider": "文档预处理服务商",
"provider_placeholder": "选择一个文档预处理服务商",
"title": "文档预处理"
},
"preprocessOrOcr": {
"tooltip": "在设置 -> 工具中设置文档预处理服务商或OCR文档预处理可以有效提升复杂格式文档与扫描版文档的检索效果OCR仅可识别文档内图片或扫描版PDF的文本"
"title": "文档预处理",
"tooltip": "在设置 -> 工具中设置文档预处理服务商,文档预处理可以有效提升复杂格式文档与扫描版文档的检索效果"
},
"title": "工具设置",
"websearch": {

View File

@ -3302,26 +3302,11 @@
},
"title": "設定",
"tool": {
"ocr": {
"mac_system_ocr_options": {
"min_confidence": "最小置信度",
"mode": {
"accurate": "準確",
"fast": "快速",
"title": "識別模式"
}
},
"provider": "OCR 供應商",
"provider_placeholder": "選擇一個OCR服務提供商",
"title": "OCR 文字識別"
},
"preprocess": {
"provider": "前置處理供應商",
"provider_placeholder": "選擇一個預處理供應商",
"title": "前置處理"
},
"preprocessOrOcr": {
"tooltip": "在「設定」->「工具」中設定文件預處理服務供應商或OCR。文件預處理可有效提升複雜格式文件及掃描文件的檢索效能而OCR僅能辨識文件內圖片文字或掃描PDF文字。"
"title": "前置處理",
"tooltip": "在「設定」->「工具」中設定文件預處理服務供應商。文件預處理可有效提升複雜格式文件及掃描文件的檢索效能"
},
"title": "工具設定",
"websearch": {

View File

@ -3304,7 +3304,7 @@
"provider_placeholder": "Επιλέξτε έναν πάροχο προεπεξεργασίας εγγράφων",
"title": "Προεπεξεργασία Εγγράφων"
},
"preprocessOrOcr": {
"preprocess": {
"tooltip": "Ορίστε πάροχο προεπεξεργασίας εγγράφων ή OCR στις Ρυθμίσεις -> Εργαλεία. Η προεπεξεργασία εγγράφων μπορεί να βελτιώσει σημαντικά την απόδοση αναζήτησης για έγγραφα πολύπλοκης μορφής ή εγγράφων σε μορφή σάρωσης. Το OCR μπορεί να αναγνωρίσει μόνο κείμενο μέσα σε εικόνες εγγράφων ή σε PDF σε μορφή σάρωσης."
},
"title": "Ρυθμίσεις Εργαλείων",

View File

@ -3304,7 +3304,7 @@
"provider_placeholder": "Selecciona un proveedor de preprocesamiento de documentos",
"title": "Preprocesamiento de Documentos"
},
"preprocessOrOcr": {
"preprocess": {
"tooltip": "Configure un proveedor de preprocesamiento de documentos o OCR en Configuración -> Herramientas. El preprocesamiento de documentos puede mejorar significativamente la eficacia de búsqueda en documentos con formatos complejos o versiones escaneadas. El OCR solo puede reconocer texto en imágenes o en archivos PDF escaneados."
},
"title": "Configuración de Herramientas",

View File

@ -3304,7 +3304,7 @@
"provider_placeholder": "Sélectionnez un fournisseur de traitement préalable de documents",
"title": "Traitement Préliminaire de Documents"
},
"preprocessOrOcr": {
"preprocess": {
"tooltip": "Configurer un fournisseur de prétraitement de documents ou OCR dans Paramètres -> Outils. Le prétraitement des documents améliore efficacement la précision de recherche pour les documents à format complexe ou les versions scannées, tandis que l'OCR permet uniquement d'extraire le texte contenu dans les images ou les PDF scannés."
},
"title": "Paramètres des outils",

View File

@ -3304,7 +3304,7 @@
"provider_placeholder": "Selecione um prestador de serviços de pré-processamento de documentos",
"title": "Pré-processamento de Documentos"
},
"preprocessOrOcr": {
"preprocess": {
"tooltip": "Configure o provedor de pré-processamento de documentos ou OCR em Configurações -> Ferramentas. O pré-processamento de documentos pode melhorar significativamente a eficácia da busca em documentos com formatos complexos ou versões escaneadas. O OCR só consegue reconhecer texto em imagens ou PDFs escaneados."
},
"title": "Configurações de Ferramentas",

View File

@ -139,8 +139,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
</div>
</Tooltip>
{base.rerankModel && <Tag style={{ borderRadius: 20, margin: 0 }}>{base.rerankModel.name}</Tag>}
{base.preprocessOrOcrProvider && base.preprocessOrOcrProvider.type === 'preprocess' && (
<QuotaTag base={base} providerId={base.preprocessOrOcrProvider?.provider.id} quota={quota} />
{base.preprocessProvider && base.preprocessProvider.type === 'preprocess' && (
<QuotaTag base={base} providerId={base.preprocessProvider?.provider.id} quota={quota} />
)}
</div>
</ModelInfo>

View File

@ -41,12 +41,10 @@ exports[`GeneralSettingsPanel > basic rendering > should match snapshot 1`] = `
class="settings-label"
>
settings.tool.preprocess.title
/
settings.tool.ocr.title
<span
data-placement="right"
data-testid="info-tooltip"
title="settings.tool.preprocessOrOcr.tooltip"
title="settings.tool.preprocess.tooltip"
>
</span>

View File

@ -6,7 +6,7 @@ import { isEmbeddingModel, isRerankModel } from '@renderer/config/models'
import { useProviders } from '@renderer/hooks/useProvider'
import { getModelUniqId } from '@renderer/services/ModelService'
import { KnowledgeBase, PreprocessProvider } from '@renderer/types'
import { Input, Select, Slider } from 'antd'
import { Input, Select, SelectProps, Slider } from 'antd'
import { useTranslation } from 'react-i18next'
import { SettingsItem, SettingsPanel } from './styles'
@ -15,7 +15,7 @@ interface GeneralSettingsPanelProps {
newBase: KnowledgeBase
setNewBase: React.Dispatch<React.SetStateAction<KnowledgeBase>>
selectedDocPreprocessProvider?: PreprocessProvider
docPreprocessSelectOptions: any[]
docPreprocessSelectOptions: SelectProps['options']
handlers: {
handleEmbeddingModelChange: (value: string) => void
handleDimensionChange: (value: number | null) => void
@ -49,8 +49,8 @@ const GeneralSettingsPanel: React.FC<GeneralSettingsPanelProps> = ({
<SettingsItem>
<div className="settings-label">
{t('settings.tool.preprocess.title')} / {t('settings.tool.ocr.title')}
<InfoTooltip title={t('settings.tool.preprocessOrOcr.tooltip')} placement="right" />
{t('settings.tool.preprocess.title')}
<InfoTooltip title={t('settings.tool.preprocess.tooltip')} placement="right" />
</div>
<Select
value={selectedDocPreprocessProvider?.id}

View File

@ -122,10 +122,10 @@ const KnowledgeFiles: FC<KnowledgeContentProps> = ({ selectedBase, progressMap,
}
const showPreprocessIcon = (item: KnowledgeItem) => {
if (base.preprocessOrOcrProvider && item.isPreprocessed !== false) {
if (base.preprocessProvider && item.isPreprocessed !== false) {
return true
}
if (!base.preprocessOrOcrProvider && item.isPreprocessed === true) {
if (!base.preprocessProvider && item.isPreprocessed === true) {
return true
}
return false

View File

@ -1,168 +0,0 @@
import { ExportOutlined } from '@ant-design/icons'
import { getOcrProviderLogo, OCR_PROVIDER_CONFIG } from '@renderer/config/ocrProviders'
import { useOcrProvider } from '@renderer/hooks/useOcr'
import { OcrProvider } from '@renderer/types'
import { formatApiKeys, hasObjectKey } from '@renderer/utils'
import { Avatar, Divider, Flex, Input, InputNumber, Segmented } from 'antd'
import Link from 'antd/es/typography/Link'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import {
SettingDivider,
SettingHelpLink,
SettingHelpText,
SettingHelpTextRow,
SettingRow,
SettingRowTitle,
SettingSubtitle,
SettingTitle
} from '../..'
interface Props {
provider: OcrProvider
}
const OcrProviderSettings: FC<Props> = ({ provider: _provider }) => {
const { provider: ocrProvider, updateOcrProvider } = useOcrProvider(_provider.id)
const { t } = useTranslation()
const [apiKey, setApiKey] = useState(ocrProvider.apiKey || '')
const [apiHost, setApiHost] = useState(ocrProvider.apiHost || '')
const [options, setOptions] = useState(ocrProvider.options || {})
const ocrProviderConfig = OCR_PROVIDER_CONFIG[ocrProvider.id]
const apiKeyWebsite = ocrProviderConfig?.websites?.apiKey
const officialWebsite = ocrProviderConfig?.websites?.official
useEffect(() => {
setApiKey(ocrProvider.apiKey ?? '')
setApiHost(ocrProvider.apiHost ?? '')
setOptions(ocrProvider.options ?? {})
}, [ocrProvider.apiKey, ocrProvider.apiHost, ocrProvider.options])
const onUpdateApiKey = () => {
if (apiKey !== ocrProvider.apiKey) {
updateOcrProvider({ ...ocrProvider, apiKey })
}
}
const onUpdateApiHost = () => {
let trimmedHost = apiHost?.trim() || ''
if (trimmedHost.endsWith('/')) {
trimmedHost = trimmedHost.slice(0, -1)
}
if (trimmedHost !== ocrProvider.apiHost) {
updateOcrProvider({ ...ocrProvider, apiHost: trimmedHost })
} else {
setApiHost(ocrProvider.apiHost || '')
}
}
const onUpdateOptions = (key: string, value: any) => {
const newOptions = { ...options, [key]: value }
setOptions(newOptions)
updateOcrProvider({ ...ocrProvider, options: newOptions })
}
return (
<>
<SettingTitle>
<Flex align="center" gap={8}>
<ProviderLogo shape="square" src={getOcrProviderLogo(ocrProvider.id)} size={16} />
<ProviderName> {ocrProvider.name}</ProviderName>
{officialWebsite && ocrProviderConfig?.websites && (
<Link target="_blank" href={ocrProviderConfig.websites.official}>
<ExportOutlined style={{ color: 'var(--color-text)', fontSize: '12px' }} />
</Link>
)}
</Flex>
</SettingTitle>
<Divider style={{ width: '100%', margin: '10px 0' }} />
{hasObjectKey(ocrProvider, 'apiKey') && (
<>
<SettingSubtitle style={{ marginTop: 5, marginBottom: 10 }}>
{t('settings.provider.api_key.label')}
</SettingSubtitle>
<Flex gap={8}>
<Input.Password
value={apiKey}
placeholder={t('settings.provider.api_key.label')}
onChange={(e) => setApiKey(formatApiKeys(e.target.value))}
onBlur={onUpdateApiKey}
spellCheck={false}
type="password"
autoFocus={apiKey === ''}
/>
</Flex>
<SettingHelpTextRow style={{ justifyContent: 'space-between', marginTop: 5 }}>
<SettingHelpLink target="_blank" href={apiKeyWebsite}>
{t('settings.provider.get_api_key')}
</SettingHelpLink>
<SettingHelpText>{t('settings.provider.api_key.tip')}</SettingHelpText>
</SettingHelpTextRow>
</>
)}
{hasObjectKey(ocrProvider, 'apiHost') && (
<>
<SettingSubtitle style={{ marginTop: 5, marginBottom: 10 }}>
{t('settings.provider.api_host')}
</SettingSubtitle>
<Flex>
<Input
value={apiHost}
placeholder={t('settings.provider.api_host')}
onChange={(e) => setApiHost(e.target.value)}
onBlur={onUpdateApiHost}
/>
</Flex>
</>
)}
{hasObjectKey(ocrProvider, 'options') && ocrProvider.id === 'system' && (
<>
<SettingRow>
<SettingRowTitle>{t('settings.tool.ocr.mac_system_ocr_options.mode.title')}</SettingRowTitle>
<Segmented
options={[
{
label: t('settings.tool.ocr.mac_system_ocr_options.mode.accurate'),
value: 1
},
{
label: t('settings.tool.ocr.mac_system_ocr_options.mode.fast'),
value: 0
}
]}
value={options.recognitionLevel}
onChange={(value) => onUpdateOptions('recognitionLevel', value)}
/>
</SettingRow>
<SettingDivider style={{ marginTop: 15, marginBottom: 12 }} />
<SettingRow>
<SettingRowTitle>{t('settings.tool.ocr.mac_system_ocr_options.min_confidence')}</SettingRowTitle>
<InputNumber
value={options.minConfidence}
onChange={(value) => onUpdateOptions('minConfidence', value)}
min={0}
max={1}
step={0.1}
/>
</SettingRow>
</>
)}
</>
)
}
const ProviderName = styled.span`
font-size: 14px;
font-weight: 500;
`
const ProviderLogo = styled(Avatar)`
border: 0.5px solid var(--color-border);
`
export default OcrProviderSettings

View File

@ -1,58 +0,0 @@
import { isMac } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useDefaultOcrProvider, useOcrProviders } from '@renderer/hooks/useOcr'
import { PreprocessProvider } 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 OcrProviderSettings from './OcrSettings'
const OcrSettings: FC = () => {
const { ocrProviders } = useOcrProviders()
const { provider: defaultProvider, setDefaultOcrProvider } = useDefaultOcrProvider()
const { t } = useTranslation()
const [selectedProvider, setSelectedProvider] = useState<PreprocessProvider | 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.tool.ocr.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.tool.ocr.provider')}</SettingRowTitle>
<div style={{ display: 'flex', gap: '8px' }}>
<Select
value={selectedProvider?.id}
style={{ width: '200px' }}
onChange={(value: string) => updateSelectedOcrProvider(value)}
placeholder={t('settings.tool.ocr.provider_placeholder')}
options={ocrProviders.map((p) => ({
value: p.id,
label: p.name,
disabled: !isMac && p.id === 'system' // 在非 Mac 系统下禁用 system 选项
}))}
/>
</div>
</SettingRow>
</SettingGroup>
{selectedProvider && (
<SettingGroup theme={themeMode}>
<OcrProviderSettings provider={selectedProvider} />
</SettingGroup>
)}
</SettingContainer>
)
}
export default OcrSettings

View File

@ -1,5 +1,4 @@
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 { FileCode, Server } from 'lucide-react'
@ -8,7 +7,6 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import ApiServerSettings from './ApiServerSettings/ApiServerSettings'
import OcrSettings from './OcrSettings'
import PreprocessSettings from './PreprocessSettings'
import WebSearchSettings from './WebSearchSettings'
@ -20,7 +18,6 @@ const ToolSettings: FC = () => {
const menuItems = [
{ key: 'web-search', title: 'settings.tool.websearch.title', icon: <GlobalOutlined style={{ fontSize: 16 }} /> },
{ key: 'preprocess', title: 'settings.tool.preprocess.title', icon: <FileCode size={16} /> },
{ key: 'ocr', title: 'settings.tool.ocr.title', icon: <OcrIcon /> },
{ key: 'api-server', title: 'apiServer.title', icon: <Server size={16} /> }
]
@ -42,7 +39,6 @@ const ToolSettings: FC = () => {
</MenuList>
{menu == 'web-search' && <WebSearchSettings />}
{menu == 'preprocess' && <PreprocessSettings />}
{menu == 'ocr' && <OcrSettings />}
{menu == 'api-server' && <ApiServerSettings />}
</Container>
)

View File

@ -195,7 +195,7 @@ class KnowledgeQueue {
updateBaseItemIsPreprocessed({
baseId,
itemId: item.id,
isPreprocessed: !!base.preprocessOrOcrProvider
isPreprocessed: !!base.preprocessProvider
})
)
}

View File

@ -57,7 +57,7 @@ export const getKnowledgeBaseParams = (base: KnowledgeBase): KnowledgeBaseParams
apiKey: rerankAiProvider.getApiKey() || 'secret',
baseURL: rerankHost
},
preprocessOrOcrProvider: base.preprocessOrOcrProvider,
preprocessProvider: base.preprocessProvider,
documentCount: base.documentCount
}
}

View File

@ -18,7 +18,6 @@ import migrate from './migrate'
import minapps from './minapps'
import newMessagesReducer from './newMessage'
import nutstore from './nutstore'
import ocr from './ocr'
import paintings from './paintings'
import preprocess from './preprocess'
import runtime from './runtime'
@ -38,7 +37,6 @@ const rootReducer = combineReducers({
llm,
settings,
runtime,
ocr,
shortcuts,
knowledge,
minapps,
@ -48,7 +46,6 @@ const rootReducer = combineReducers({
copilot,
selectionStore,
tabs,
// messages: messagesReducer,
preprocess,
messages: newMessagesReducer,
messageBlocks: messageBlocksReducer,
@ -60,7 +57,7 @@ const persistedReducer = persistReducer(
{
key: 'cherry-studio',
storage,
version: 125,
version: 126,
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
migrate
},

View File

@ -1936,6 +1936,28 @@ const migrateConfig = {
logger.error('migrate 125 error', error as Error)
return state
}
},
'126': (state: RootState) => {
try {
state.knowledge.bases.forEach((base) => {
// @ts-ignore eslint-disable-next-line
if (base.preprocessOrOcrProvider) {
// @ts-ignore eslint-disable-next-line
base.preprocessProvider = base.preprocessOrOcrProvider
// @ts-ignore eslint-disable-next-line
delete base.preprocessOrOcrProvider
// @ts-ignore eslint-disable-next-line
if (base.preprocessProvider.type === 'ocr') {
// @ts-ignore eslint-disable-next-line
delete base.preprocessProvider
}
}
})
return state
} catch (error) {
logger.error('migrate 126 error', error as Error)
return state
}
}
}

View File

@ -1,46 +0,0 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { OcrProvider } from '@renderer/types'
export interface OcrState {
providers: OcrProvider[]
defaultProvider: string
}
const initialState: OcrState = {
providers: [
{
id: 'system',
name: 'System(Mac Only)',
options: {
recognitionLevel: 0,
minConfidence: 0.5
}
}
],
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

View File

@ -5,7 +5,7 @@ export interface Tab {
path: string
}
interface TabsState {
export interface TabsState {
tabs: Tab[]
activeTabId: string
}

View File

@ -444,9 +444,9 @@ export interface KnowledgeBase {
rerankModel?: Model
// topN?: number
// preprocessing?: boolean
preprocessOrOcrProvider?: {
type: 'preprocess' | 'ocr'
provider: PreprocessProvider | OcrProvider
preprocessProvider?: {
type: 'preprocess'
provider: PreprocessProvider
}
}
@ -467,9 +467,9 @@ export type KnowledgeBaseParams = {
rerankApiClient?: ApiClient
documentCount?: number
// preprocessing?: boolean
preprocessOrOcrProvider?: {
type: 'preprocess' | 'ocr'
provider: PreprocessProvider | OcrProvider
preprocessProvider?: {
type: 'preprocess'
provider: PreprocessProvider
}
}
@ -483,16 +483,6 @@ export interface PreprocessProvider {
quota?: number
}
export interface OcrProvider {
id: string
name: string
apiKey?: string
apiHost?: string
model?: string
options?: any
quota?: number
}
export type GenerateImageParams = {
model: string
prompt: string

223
yarn.lock
View File

@ -1782,29 +1782,6 @@ __metadata:
languageName: node
linkType: hard
"@cherrystudio/mac-system-ocr@npm:^0.2.2":
version: 0.2.2
resolution: "@cherrystudio/mac-system-ocr@npm:0.2.2"
dependencies:
bindings: "npm:^1.5.0"
node-api-headers: "npm:^1.0.1"
node-gyp: "npm:latest"
conditions: os=darwin
languageName: node
linkType: hard
"@cherrystudio/pdf-to-img-napi@npm:^0.0.1":
version: 0.0.1
resolution: "@cherrystudio/pdf-to-img-napi@npm:0.0.1"
dependencies:
"@napi-rs/canvas": "npm:^0.1.71"
pdfjs-dist: "npm:^4.10.38"
bin:
pdf2img: bin/cli.mjs
checksum: 10c0/d4cd5600960ef42e7b43e3fcd2698b4c874523a0654991e78ebd2bae54cde62258c12ac8dbf60f25e1bf6e5286cf4ccb5d4341c4fc9153369ebf86771e451a5a
languageName: node
linkType: hard
"@chevrotain/cst-dts-gen@npm:11.0.3":
version: 11.0.3
resolution: "@chevrotain/cst-dts-gen@npm:11.0.3"
@ -4065,115 +4042,6 @@ __metadata:
languageName: node
linkType: hard
"@napi-rs/canvas-android-arm64@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-android-arm64@npm:0.1.71"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
"@napi-rs/canvas-darwin-arm64@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-darwin-arm64@npm:0.1.71"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@napi-rs/canvas-darwin-x64@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-darwin-x64@npm:0.1.71"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-linux-arm-gnueabihf@npm:0.1.71"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@napi-rs/canvas-linux-arm64-gnu@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-linux-arm64-gnu@npm:0.1.71"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@napi-rs/canvas-linux-arm64-musl@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-linux-arm64-musl@npm:0.1.71"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-linux-riscv64-gnu@npm:0.1.71"
conditions: os=linux & cpu=riscv64 & libc=glibc
languageName: node
linkType: hard
"@napi-rs/canvas-linux-x64-gnu@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-linux-x64-gnu@npm:0.1.71"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@napi-rs/canvas-linux-x64-musl@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-linux-x64-musl@npm:0.1.71"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@napi-rs/canvas-win32-x64-msvc@npm:0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas-win32-x64-msvc@npm:0.1.71"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@napi-rs/canvas@npm:^0.1.65, @napi-rs/canvas@npm:^0.1.71":
version: 0.1.71
resolution: "@napi-rs/canvas@npm:0.1.71"
dependencies:
"@napi-rs/canvas-android-arm64": "npm:0.1.71"
"@napi-rs/canvas-darwin-arm64": "npm:0.1.71"
"@napi-rs/canvas-darwin-x64": "npm:0.1.71"
"@napi-rs/canvas-linux-arm-gnueabihf": "npm:0.1.71"
"@napi-rs/canvas-linux-arm64-gnu": "npm:0.1.71"
"@napi-rs/canvas-linux-arm64-musl": "npm:0.1.71"
"@napi-rs/canvas-linux-riscv64-gnu": "npm:0.1.71"
"@napi-rs/canvas-linux-x64-gnu": "npm:0.1.71"
"@napi-rs/canvas-linux-x64-musl": "npm:0.1.71"
"@napi-rs/canvas-win32-x64-msvc": "npm:0.1.71"
dependenciesMeta:
"@napi-rs/canvas-android-arm64":
optional: true
"@napi-rs/canvas-darwin-arm64":
optional: true
"@napi-rs/canvas-darwin-x64":
optional: true
"@napi-rs/canvas-linux-arm-gnueabihf":
optional: true
"@napi-rs/canvas-linux-arm64-gnu":
optional: true
"@napi-rs/canvas-linux-arm64-musl":
optional: true
"@napi-rs/canvas-linux-riscv64-gnu":
optional: true
"@napi-rs/canvas-linux-x64-gnu":
optional: true
"@napi-rs/canvas-linux-x64-musl":
optional: true
"@napi-rs/canvas-win32-x64-msvc":
optional: true
checksum: 10c0/839b07a338b63965dd2dd4d9726c932d87572843c9868e340d6dd6166daa7943571982f8118bcc49b772c88b0b2d948e4dd91d37f72e437d6dcf0bbbfda96e90
languageName: node
linkType: hard
"@napi-rs/wasm-runtime@npm:^1.0.1":
version: 1.0.1
resolution: "@napi-rs/wasm-runtime@npm:1.0.1"
@ -4656,6 +4524,24 @@ __metadata:
languageName: node
linkType: hard
"@pdf-lib/standard-fonts@npm:^1.0.0":
version: 1.0.0
resolution: "@pdf-lib/standard-fonts@npm:1.0.0"
dependencies:
pako: "npm:^1.0.6"
checksum: 10c0/c683adfb764cd235a8370a0c1d5a8d7e90e3499ad33cdecfb92e4d48b0d36cfd038e3a875ebd0937a5646ee1578d793ab98f9c374be360c9a05d2699c1caedf4
languageName: node
linkType: hard
"@pdf-lib/upng@npm:^1.0.1":
version: 1.0.1
resolution: "@pdf-lib/upng@npm:1.0.1"
dependencies:
pako: "npm:^1.0.10"
checksum: 10c0/9c300c513c1089e561c0cccac01f396a24efb9b0e9c922a39248cb09dfced70c05b9facdfce11a7f22cbedb4129593630a18111b90a57ef34ea4c3df98f2ac1d
languageName: node
linkType: hard
"@pkgjs/parseargs@npm:^0.11.0":
version: 0.11.0
resolution: "@pkgjs/parseargs@npm:0.11.0"
@ -7942,8 +7828,6 @@ __metadata:
"@cherrystudio/embedjs-loader-xml": "npm:^0.1.31"
"@cherrystudio/embedjs-ollama": "npm:^0.1.31"
"@cherrystudio/embedjs-openai": "npm:^0.1.31"
"@cherrystudio/mac-system-ocr": "npm:^0.2.2"
"@cherrystudio/pdf-to-img-napi": "npm:^0.0.1"
"@codemirror/view": "npm:^6.0.0"
"@electron-toolkit/eslint-config-prettier": "npm:^3.0.0"
"@electron-toolkit/eslint-config-ts": "npm:^3.0.0"
@ -8075,7 +7959,7 @@ __metadata:
openai: "patch:openai@npm%3A5.1.0#~/.yarn/patches/openai-npm-5.1.0-0e7b3ccb07.patch"
os-proxy-config: "npm:^1.1.2"
p-queue: "npm:^8.1.0"
pdfjs-dist: "npm:4.10.38"
pdf-lib: "npm:^1.17.1"
playwright: "npm:^1.52.0"
prettier: "npm:^3.5.3"
prettier-plugin-sort-json: "npm:^4.1.1"
@ -8129,9 +8013,6 @@ __metadata:
word-extractor: "npm:^1.0.4"
zipread: "npm:^1.3.3"
zod: "npm:^3.25.74"
dependenciesMeta:
"@cherrystudio/mac-system-ocr":
optional: true
languageName: unknown
linkType: soft
@ -8816,15 +8697,6 @@ __metadata:
languageName: node
linkType: hard
"bindings@npm:^1.5.0":
version: 1.5.0
resolution: "bindings@npm:1.5.0"
dependencies:
file-uri-to-path: "npm:1.0.0"
checksum: 10c0/3dab2491b4bb24124252a91e656803eac24292473e56554e35bbfe3cc1875332cfa77600c3bac7564049dc95075bf6fcc63a4609920ff2d64d0fe405fcf0d4ba
languageName: node
linkType: hard
"birecord@npm:^0.1.1":
version: 0.1.1
resolution: "birecord@npm:0.1.1"
@ -12407,13 +12279,6 @@ __metadata:
languageName: node
linkType: hard
"file-uri-to-path@npm:1.0.0":
version: 1.0.0
resolution: "file-uri-to-path@npm:1.0.0"
checksum: 10c0/3b545e3a341d322d368e880e1c204ef55f1d45cdea65f7efc6c6ce9e0c4d22d802d5629320eb779d006fe59624ac17b0e848d83cc5af7cd101f206cb704f5519
languageName: node
linkType: hard
"filelist@npm:^1.0.4":
version: 1.0.4
resolution: "filelist@npm:1.0.4"
@ -16702,13 +16567,6 @@ __metadata:
languageName: node
linkType: hard
"node-api-headers@npm:^1.0.1":
version: 1.5.0
resolution: "node-api-headers@npm:1.5.0"
checksum: 10c0/e8dfe99e8e3ca92cd5d37989413dfc96551e8f7883110b948917dad07e554cfbf1119130e96d0167f5cb5a05f651a4ac735402a305ff25d9ace422a2e429ae3b
languageName: node
linkType: hard
"node-api-version@npm:^0.2.0":
version: 0.2.1
resolution: "node-api-version@npm:0.2.1"
@ -17329,7 +17187,7 @@ __metadata:
languageName: node
linkType: hard
"pako@npm:~1.0.2":
"pako@npm:^1.0.10, pako@npm:^1.0.11, pako@npm:^1.0.6, pako@npm:~1.0.2":
version: 1.0.11
resolution: "pako@npm:1.0.11"
checksum: 10c0/86dd99d8b34c3930345b8bbeb5e1cd8a05f608eeb40967b293f72fe469d0e9c88b783a8777e4cc7dc7c91ce54c5e93d88ff4b4f060e6ff18408fd21030d9ffbe
@ -17480,6 +17338,18 @@ __metadata:
languageName: node
linkType: hard
"pdf-lib@npm:^1.17.1":
version: 1.17.1
resolution: "pdf-lib@npm:1.17.1"
dependencies:
"@pdf-lib/standard-fonts": "npm:^1.0.0"
"@pdf-lib/upng": "npm:^1.0.1"
pako: "npm:^1.0.11"
tslib: "npm:^1.11.1"
checksum: 10c0/a9489a402880dacd1389a3e14ff8f6139d58e3bc82f26b0fcbd0798b841aee6ccb7fcfab0992b574e57b40404ced0330a7170b3c6467363461a9df5d9daec489
languageName: node
linkType: hard
"pdf-parse@npm:1.1.1":
version: 1.1.1
resolution: "pdf-parse@npm:1.1.1"
@ -17490,28 +17360,6 @@ __metadata:
languageName: node
linkType: hard
"pdf-parse@patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch":
version: 1.1.1
resolution: "pdf-parse@patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch::version=1.1.1&hash=d6cd03"
dependencies:
debug: "npm:^3.1.0"
node-ensure: "npm:^0.0.0"
checksum: 10c0/ba96bfcc9f67b6605bac3a657e4c0bae7a08ad73bab33bfc19ec742753e0a0cabd5e69cb9d5c6227965bc074905cda54367d61be2011cb30abd76f40fd5571fc
languageName: node
linkType: hard
"pdfjs-dist@npm:4.10.38, pdfjs-dist@npm:^4.10.38":
version: 4.10.38
resolution: "pdfjs-dist@npm:4.10.38"
dependencies:
"@napi-rs/canvas": "npm:^0.1.65"
dependenciesMeta:
"@napi-rs/canvas":
optional: true
checksum: 10c0/77b022109be7aac00372750a53decea3979409e6ef1cf93bf554351569cd4d1fafc70afae4a9a3e4b4de3facf59d3acd54d324b0fcff781374bcb00493d449ce
languageName: node
linkType: hard
"pe-library@npm:^0.4.1":
version: 0.4.1
resolution: "pe-library@npm:0.4.1"
@ -20882,6 +20730,13 @@ __metadata:
languageName: node
linkType: hard
"tslib@npm:^1.11.1":
version: 1.14.1
resolution: "tslib@npm:1.14.1"
checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2
languageName: node
linkType: hard
"tslib@npm:^2.0.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.1":
version: 2.8.1
resolution: "tslib@npm:2.8.1"