mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
refactor: remove mac ocr
This commit is contained in:
parent
c214a6e56e
commit
1efefad3ee
@ -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
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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.')
|
||||
}
|
||||
}
|
||||
@ -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 }
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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> {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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}`)
|
||||
|
||||
// 确保提取目录存在
|
||||
|
||||
@ -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 }> => {
|
||||
|
||||
@ -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')
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
}
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -3304,7 +3304,7 @@
|
||||
"provider_placeholder": "Επιλέξτε έναν πάροχο προεπεξεργασίας εγγράφων",
|
||||
"title": "Προεπεξεργασία Εγγράφων"
|
||||
},
|
||||
"preprocessOrOcr": {
|
||||
"preprocess": {
|
||||
"tooltip": "Ορίστε πάροχο προεπεξεργασίας εγγράφων ή OCR στις Ρυθμίσεις -> Εργαλεία. Η προεπεξεργασία εγγράφων μπορεί να βελτιώσει σημαντικά την απόδοση αναζήτησης για έγγραφα πολύπλοκης μορφής ή εγγράφων σε μορφή σάρωσης. Το OCR μπορεί να αναγνωρίσει μόνο κείμενο μέσα σε εικόνες εγγράφων ή σε PDF σε μορφή σάρωσης."
|
||||
},
|
||||
"title": "Ρυθμίσεις Εργαλείων",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -195,7 +195,7 @@ class KnowledgeQueue {
|
||||
updateBaseItemIsPreprocessed({
|
||||
baseId,
|
||||
itemId: item.id,
|
||||
isPreprocessed: !!base.preprocessOrOcrProvider
|
||||
isPreprocessed: !!base.preprocessProvider
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -5,7 +5,7 @@ export interface Tab {
|
||||
path: string
|
||||
}
|
||||
|
||||
interface TabsState {
|
||||
export interface TabsState {
|
||||
tabs: Tab[]
|
||||
activeTabId: string
|
||||
}
|
||||
|
||||
@ -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
223
yarn.lock
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user