From 5c0bb7ec1fca7cbf6d696fe85def79218b0096d5 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:05:52 +0800 Subject: [PATCH 01/18] =?UTF-8?q?fix(deps):=20=E6=9B=B4=E6=96=B0=20tessera?= =?UTF-8?q?ct.js=20=E4=BE=9D=E8=B5=96=E5=B9=B6=E6=B7=BB=E5=8A=A0=E8=A1=A5?= =?UTF-8?q?=E4=B8=81=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复 tesseract.js 类型定义问题并添加语言常量支持 --- .../tesseract.js-npm-6.0.1-2562a7e46d.patch | 348 ++++++++++++++++++ package.json | 5 +- yarn.lock | 21 +- 3 files changed, 370 insertions(+), 4 deletions(-) create mode 100644 .yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch diff --git a/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch b/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch new file mode 100644 index 0000000000..0cb156ee99 --- /dev/null +++ b/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch @@ -0,0 +1,348 @@ +diff --git a/src/constants/languages.d.ts b/src/constants/languages.d.ts +new file mode 100644 +index 0000000000000000000000000000000000000000..6a2ba5086187622b8ca8887bcc7406018fba8a89 +--- /dev/null ++++ b/src/constants/languages.d.ts +@@ -0,0 +1,43 @@ ++/** ++ * Languages with existing tesseract traineddata ++ * https://tesseract-ocr.github.io/tessdoc/Data-Files#data-files-for-version-400-november-29-2016 ++ */ ++ ++// Define the language codes as string literals ++type LanguageCode = ++ | 'afr' | 'amh' | 'ara' | 'asm' | 'aze' | 'aze_cyrl' | 'bel' | 'ben' | 'bod' | 'bos' ++ | 'bul' | 'cat' | 'ceb' | 'ces' | 'chi_sim' | 'chi_tra' | 'chr' | 'cym' | 'dan' | 'deu' ++ | 'dzo' | 'ell' | 'eng' | 'enm' | 'epo' | 'est' | 'eus' | 'fas' | 'fin' | 'fra' ++ | 'frk' | 'frm' | 'gle' | 'glg' | 'grc' | 'guj' | 'hat' | 'heb' | 'hin' | 'hrv' ++ | 'hun' | 'iku' | 'ind' | 'isl' | 'ita' | 'ita_old' | 'jav' | 'jpn' | 'kan' | 'kat' ++ | 'kat_old' | 'kaz' | 'khm' | 'kir' | 'kor' | 'kur' | 'lao' | 'lat' | 'lav' | 'lit' ++ | 'mal' | 'mar' | 'mkd' | 'mlt' | 'msa' | 'mya' | 'nep' | 'nld' | 'nor' | 'ori' ++ | 'pan' | 'pol' | 'por' | 'pus' | 'ron' | 'rus' | 'san' | 'sin' | 'slk' | 'slv' ++ | 'spa' | 'spa_old' | 'sqi' | 'srp' | 'srp_latn' | 'swa' | 'swe' | 'syr' | 'tam' | 'tel' ++ | 'tgk' | 'tgl' | 'tha' | 'tir' | 'tur' | 'uig' | 'ukr' | 'urd' | 'uzb' | 'uzb_cyrl' ++ | 'vie' | 'yid'; ++ ++// Define the language keys as string literals ++type LanguageKey = ++ | 'AFR' | 'AMH' | 'ARA' | 'ASM' | 'AZE' | 'AZE_CYRL' | 'BEL' | 'BEN' | 'BOD' | 'BOS' ++ | 'BUL' | 'CAT' | 'CEB' | 'CES' | 'CHI_SIM' | 'CHI_TRA' | 'CHR' | 'CYM' | 'DAN' | 'DEU' ++ | 'DZO' | 'ELL' | 'ENG' | 'ENM' | 'EPO' | 'EST' | 'EUS' | 'FAS' | 'FIN' | 'FRA' ++ | 'FRK' | 'FRM' | 'GLE' | 'GLG' | 'GRC' | 'GUJ' | 'HAT' | 'HEB' | 'HIN' | 'HRV' ++ | 'HUN' | 'IKU' | 'IND' | 'ISL' | 'ITA' | 'ITA_OLD' | 'JAV' | 'JPN' | 'KAN' | 'KAT' ++ | 'KAT_OLD' | 'KAZ' | 'KHM' | 'KIR' | 'KOR' | 'KUR' | 'LAO' | 'LAT' | 'LAV' | 'LIT' ++ | 'MAL' | 'MAR' | 'MKD' | 'MLT' | 'MSA' | 'MYA' | 'NEP' | 'NLD' | 'NOR' | 'ORI' ++ | 'PAN' | 'POL' | 'POR' | 'PUS' | 'RON' | 'RUS' | 'SAN' | 'SIN' | 'SLK' | 'SLV' ++ | 'SPA' | 'SPA_OLD' | 'SQI' | 'SRP' | 'SRP_LATN' | 'SWA' | 'SWE' | 'SYR' | 'TAM' | 'TEL' ++ | 'TGK' | 'TGL' | 'THA' | 'TIR' | 'TUR' | 'UIG' | 'UKR' | 'URD' | 'UZB' | 'UZB_CYRL' ++ | 'VIE' | 'YID'; ++ ++// Create a mapped type to ensure each key maps to its specific value ++type LanguagesMap = { ++ [K in LanguageKey]: LanguageCode; ++}; ++ ++// Declare the exported constant with the specific type ++export const LANGUAGES: LanguagesMap; ++ ++// Export the individual types for use in other modules ++export type { LanguageCode, LanguageKey, LanguagesMap }; +\ No newline at end of file +diff --git a/src/index.d.ts b/src/index.d.ts +index 1f5a9c8094fe4de7983467f9efb43bdb4de535f2..16dc95cf68663673e37e189b719cb74897b7735f 100644 +--- a/src/index.d.ts ++++ b/src/index.d.ts +@@ -1,31 +1,74 @@ ++// Import the languages types ++import { LanguagesMap } from "./constants/languages"; ++ ++/// ++ + declare namespace Tesseract { +- function createScheduler(): Scheduler +- function createWorker(langs?: string | string[] | Lang[], oem?: OEM, options?: Partial, config?: string | Partial): Promise +- function setLogging(logging: boolean): void +- function recognize(image: ImageLike, langs?: string, options?: Partial): Promise +- function detect(image: ImageLike, options?: Partial): any ++ function createScheduler(): Scheduler; ++ function createWorker( ++ langs?: LanguageCode | LanguageCode[] | Lang[], ++ oem?: OEM, ++ options?: Partial, ++ config?: string | Partial ++ ): Promise; ++ function setLogging(logging: boolean): void; ++ function recognize( ++ image: ImageLike, ++ langs?: LanguageCode, ++ options?: Partial ++ ): Promise; ++ function detect(image: ImageLike, options?: Partial): any; ++ ++ // Export languages constant ++ const languages: LanguagesMap; ++ ++ type LanguageCode = import("./constants/languages").LanguageCode; ++ type LanguageKey = import("./constants/languages").LanguageKey; + + interface Scheduler { +- addWorker(worker: Worker): string +- addJob(action: 'recognize', ...args: Parameters): Promise +- addJob(action: 'detect', ...args: Parameters): Promise +- terminate(): Promise +- getQueueLen(): number +- getNumWorkers(): number ++ addWorker(worker: Worker): string; ++ addJob( ++ action: "recognize", ++ ...args: Parameters ++ ): Promise; ++ addJob( ++ action: "detect", ++ ...args: Parameters ++ ): Promise; ++ terminate(): Promise; ++ getQueueLen(): number; ++ getNumWorkers(): number; + } + + interface Worker { +- load(jobId?: string): Promise +- writeText(path: string, text: string, jobId?: string): Promise +- readText(path: string, jobId?: string): Promise +- removeText(path: string, jobId?: string): Promise +- FS(method: string, args: any[], jobId?: string): Promise +- reinitialize(langs?: string | Lang[], oem?: OEM, config?: string | Partial, jobId?: string): Promise +- setParameters(params: Partial, jobId?: string): Promise +- getImage(type: imageType): string +- recognize(image: ImageLike, options?: Partial, output?: Partial, jobId?: string): Promise +- detect(image: ImageLike, jobId?: string): Promise +- terminate(jobId?: string): Promise ++ load(jobId?: string): Promise; ++ writeText( ++ path: string, ++ text: string, ++ jobId?: string ++ ): Promise; ++ readText(path: string, jobId?: string): Promise; ++ removeText(path: string, jobId?: string): Promise; ++ FS(method: string, args: any[], jobId?: string): Promise; ++ reinitialize( ++ langs?: string | Lang[], ++ oem?: OEM, ++ config?: string | Partial, ++ jobId?: string ++ ): Promise; ++ setParameters( ++ params: Partial, ++ jobId?: string ++ ): Promise; ++ getImage(type: imageType): string; ++ recognize( ++ image: ImageLike, ++ options?: Partial, ++ output?: Partial, ++ jobId?: string ++ ): Promise; ++ detect(image: ImageLike, jobId?: string): Promise; ++ terminate(jobId?: string): Promise; + } + + interface Lang { +@@ -34,43 +77,43 @@ declare namespace Tesseract { + } + + interface InitOptions { +- load_system_dawg: string +- load_freq_dawg: string +- load_unambig_dawg: string +- load_punc_dawg: string +- load_number_dawg: string +- load_bigram_dawg: string +- } +- +- type LoggerMessage = { +- jobId: string +- progress: number +- status: string +- userJobId: string +- workerId: string ++ load_system_dawg: string; ++ load_freq_dawg: string; ++ load_unambig_dawg: string; ++ load_punc_dawg: string; ++ load_number_dawg: string; ++ load_bigram_dawg: string; + } +- ++ ++ type LoggerMessage = { ++ jobId: string; ++ progress: number; ++ status: string; ++ userJobId: string; ++ workerId: string; ++ }; ++ + interface WorkerOptions { +- corePath: string +- langPath: string +- cachePath: string +- dataPath: string +- workerPath: string +- cacheMethod: string +- workerBlobURL: boolean +- gzip: boolean +- legacyLang: boolean +- legacyCore: boolean +- logger: (arg: LoggerMessage) => void, +- errorHandler: (arg: any) => void ++ corePath: string; ++ langPath: string; ++ cachePath: string; ++ dataPath: string; ++ workerPath: string; ++ cacheMethod: string; ++ workerBlobURL: boolean; ++ gzip: boolean; ++ legacyLang: boolean; ++ legacyCore: boolean; ++ logger: (arg: LoggerMessage) => void; ++ errorHandler: (arg: any) => void; + } + interface WorkerParams { +- tessedit_pageseg_mode: PSM +- tessedit_char_whitelist: string +- tessedit_char_blacklist: string +- preserve_interword_spaces: string +- user_defined_dpi: string +- [propName: string]: any ++ tessedit_pageseg_mode: PSM; ++ tessedit_char_whitelist: string; ++ tessedit_char_blacklist: string; ++ preserve_interword_spaces: string; ++ user_defined_dpi: string; ++ [propName: string]: any; + } + interface OutputFormats { + text: boolean; +@@ -88,36 +131,36 @@ declare namespace Tesseract { + debug: boolean; + } + interface RecognizeOptions { +- rectangle: Rectangle +- pdfTitle: string +- pdfTextOnly: boolean +- rotateAuto: boolean +- rotateRadians: number ++ rectangle: Rectangle; ++ pdfTitle: string; ++ pdfTextOnly: boolean; ++ rotateAuto: boolean; ++ rotateRadians: number; + } + interface ConfigResult { +- jobId: string +- data: any ++ jobId: string; ++ data: any; + } + interface RecognizeResult { +- jobId: string +- data: Page ++ jobId: string; ++ data: Page; + } + interface DetectResult { +- jobId: string +- data: DetectData ++ jobId: string; ++ data: DetectData; + } + interface DetectData { +- tesseract_script_id: number | null +- script: string | null +- script_confidence: number | null +- orientation_degrees: number | null +- orientation_confidence: number | null ++ tesseract_script_id: number | null; ++ script: string | null; ++ script_confidence: number | null; ++ orientation_degrees: number | null; ++ orientation_confidence: number | null; + } + interface Rectangle { +- left: number +- top: number +- width: number +- height: number ++ left: number; ++ top: number; ++ width: number; ++ height: number; + } + enum OEM { + TESSERACT_ONLY, +@@ -126,28 +169,36 @@ declare namespace Tesseract { + DEFAULT, + } + enum PSM { +- OSD_ONLY = '0', +- AUTO_OSD = '1', +- AUTO_ONLY = '2', +- AUTO = '3', +- SINGLE_COLUMN = '4', +- SINGLE_BLOCK_VERT_TEXT = '5', +- SINGLE_BLOCK = '6', +- SINGLE_LINE = '7', +- SINGLE_WORD = '8', +- CIRCLE_WORD = '9', +- SINGLE_CHAR = '10', +- SPARSE_TEXT = '11', +- SPARSE_TEXT_OSD = '12', +- RAW_LINE = '13' ++ OSD_ONLY = "0", ++ AUTO_OSD = "1", ++ AUTO_ONLY = "2", ++ AUTO = "3", ++ SINGLE_COLUMN = "4", ++ SINGLE_BLOCK_VERT_TEXT = "5", ++ SINGLE_BLOCK = "6", ++ SINGLE_LINE = "7", ++ SINGLE_WORD = "8", ++ CIRCLE_WORD = "9", ++ SINGLE_CHAR = "10", ++ SPARSE_TEXT = "11", ++ SPARSE_TEXT_OSD = "12", ++ RAW_LINE = "13", + } + const enum imageType { + COLOR = 0, + GREY = 1, +- BINARY = 2 ++ BINARY = 2, + } +- type ImageLike = string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement +- | CanvasRenderingContext2D | File | Blob | Buffer | OffscreenCanvas; ++ type ImageLike = ++ | string ++ | HTMLImageElement ++ | HTMLCanvasElement ++ | HTMLVideoElement ++ | CanvasRenderingContext2D ++ | File ++ | Blob ++ | (typeof Buffer extends undefined ? never : Buffer) ++ | OffscreenCanvas; + interface Block { + paragraphs: Paragraph[]; + text: string; +@@ -179,7 +230,7 @@ declare namespace Tesseract { + text: string; + confidence: number; + baseline: Baseline; +- rowAttributes: RowAttributes ++ rowAttributes: RowAttributes; + bbox: Bbox; + } + interface Paragraph { diff --git a/package.json b/package.json index 49f7340559..c734271de5 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "officeparser": "^4.2.0", "os-proxy-config": "^1.1.2", "selection-hook": "^1.0.11", - "tesseract.js": "^6.0.1", + "tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch", "turndown": "7.2.0" }, "devDependencies": { @@ -293,7 +293,8 @@ "pdf-parse@npm:1.1.1": "patch:pdf-parse@npm%3A1.1.1#~/.yarn/patches/pdf-parse-npm-1.1.1-04a6109b2a.patch", "pkce-challenge@npm:^4.1.0": "patch:pkce-challenge@npm%3A4.1.0#~/.yarn/patches/pkce-challenge-npm-4.1.0-fbc51695a3.patch", "undici": "6.21.2", - "vite": "npm:rolldown-vite@latest" + "vite": "npm:rolldown-vite@latest", + "tesseract.js@npm:*": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch" }, "packageManager": "yarn@4.9.1", "lint-staged": { diff --git a/yarn.lock b/yarn.lock index 7b9986f6c0..f57df818e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8579,7 +8579,7 @@ __metadata: string-width: "npm:^7.2.0" styled-components: "npm:^6.1.11" tar: "npm:^7.4.3" - tesseract.js: "npm:^6.0.1" + tesseract.js: "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch" tiny-pinyin: "npm:^1.3.2" tokenx: "npm:^1.1.0" tsx: "npm:^4.20.3" @@ -20978,7 +20978,7 @@ __metadata: languageName: node linkType: hard -"tesseract.js@npm:*, tesseract.js@npm:^6.0.1": +"tesseract.js@npm:6.0.1": version: 6.0.1 resolution: "tesseract.js@npm:6.0.1" dependencies: @@ -20995,6 +20995,23 @@ __metadata: languageName: node linkType: hard +"tesseract.js@patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch": + version: 6.0.1 + resolution: "tesseract.js@patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch::version=6.0.1&hash=a9cf7b" + dependencies: + bmp-js: "npm:^0.1.0" + idb-keyval: "npm:^6.2.0" + is-url: "npm:^1.2.4" + node-fetch: "npm:^2.6.9" + opencollective-postinstall: "npm:^2.0.3" + regenerator-runtime: "npm:^0.13.3" + tesseract.js-core: "npm:^6.0.0" + wasm-feature-detect: "npm:^1.2.11" + zlibjs: "npm:^0.3.1" + checksum: 10c0/8a94fcc688ff21a9e82b721563d8fa174837ba807d0f01290fe9a1bb6a1c96ecaf7dc1c83510510f3d5185abd15f1cc5fc3cb7ad6c0eee0c4b3e278106f8a5da + languageName: node + linkType: hard + "test-exclude@npm:^7.0.1": version: 7.0.1 resolution: "test-exclude@npm:7.0.1" From 0e0796ca8b95c6f5abc6c4db871fa33b8e6411bc Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:24:05 +0800 Subject: [PATCH 02/18] =?UTF-8?q?refactor(ocr):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=8E=89=E7=9A=84tesseract=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E6=98=A0=E5=B0=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用Tesseract.js的LanguageCode类型替代硬编码的语言列表,提高类型安全性 --- .../ocr/tesseract/TesseractService.ts | 109 +----------------- 1 file changed, 2 insertions(+), 107 deletions(-) diff --git a/src/main/services/ocr/tesseract/TesseractService.ts b/src/main/services/ocr/tesseract/TesseractService.ts index 77225d4afe..d354f85428 100644 --- a/src/main/services/ocr/tesseract/TesseractService.ts +++ b/src/main/services/ocr/tesseract/TesseractService.ts @@ -5,118 +5,13 @@ import { ImageFileMetadata, isImageFile, OcrResult, SupportedOcrFile } from '@ty import { app } from 'electron' import fs from 'fs' import path from 'path' -import Tesseract, { createWorker } from 'tesseract.js' +import Tesseract, { createWorker, LanguageCode } from 'tesseract.js' const logger = loggerService.withContext('TesseractService') -// const languageCodeMap: Record = { -// 'af-za': 'afr', -// 'am-et': 'amh', -// 'ar-sa': 'ara', -// 'as-in': 'asm', -// 'az-az': 'aze', -// 'az-cyrl-az': 'aze_cyrl', -// 'be-by': 'bel', -// 'bn-bd': 'ben', -// 'bo-cn': 'bod', -// 'bs-ba': 'bos', -// 'bg-bg': 'bul', -// 'ca-es': 'cat', -// 'ceb-ph': 'ceb', -// 'cs-cz': 'ces', -// 'zh-cn': 'chi_sim', -// 'zh-tw': 'chi_tra', -// 'chr-us': 'chr', -// 'cy-gb': 'cym', -// 'da-dk': 'dan', -// 'de-de': 'deu', -// 'dz-bt': 'dzo', -// 'el-gr': 'ell', -// 'en-us': 'eng', -// 'enm-gb': 'enm', -// 'eo-world': 'epo', -// 'et-ee': 'est', -// 'eu-es': 'eus', -// 'fa-ir': 'fas', -// 'fi-fi': 'fin', -// 'fr-fr': 'fra', -// 'frk-de': 'frk', -// 'frm-fr': 'frm', -// 'ga-ie': 'gle', -// 'gl-es': 'glg', -// 'grc-gr': 'grc', -// 'gu-in': 'guj', -// 'ht-ht': 'hat', -// 'he-il': 'heb', -// 'hi-in': 'hin', -// 'hr-hr': 'hrv', -// 'hu-hu': 'hun', -// 'iu-ca': 'iku', -// 'id-id': 'ind', -// 'is-is': 'isl', -// 'it-it': 'ita', -// 'ita-it': 'ita_old', -// 'jv-id': 'jav', -// 'ja-jp': 'jpn', -// 'kn-in': 'kan', -// 'ka-ge': 'kat', -// 'kat-ge': 'kat_old', -// 'kk-kz': 'kaz', -// 'km-kh': 'khm', -// 'ky-kg': 'kir', -// 'ko-kr': 'kor', -// 'ku-tr': 'kur', -// 'la-la': 'lao', -// 'la-va': 'lat', -// 'lv-lv': 'lav', -// 'lt-lt': 'lit', -// 'ml-in': 'mal', -// 'mr-in': 'mar', -// 'mk-mk': 'mkd', -// 'mt-mt': 'mlt', -// 'ms-my': 'msa', -// 'my-mm': 'mya', -// 'ne-np': 'nep', -// 'nl-nl': 'nld', -// 'no-no': 'nor', -// 'or-in': 'ori', -// 'pa-in': 'pan', -// 'pl-pl': 'pol', -// 'pt-pt': 'por', -// 'ps-af': 'pus', -// 'ro-ro': 'ron', -// 'ru-ru': 'rus', -// 'sa-in': 'san', -// 'si-lk': 'sin', -// 'sk-sk': 'slk', -// 'sl-si': 'slv', -// 'es-es': 'spa', -// 'spa-es': 'spa_old', -// 'sq-al': 'sqi', -// 'sr-rs': 'srp', -// 'sr-latn-rs': 'srp_latn', -// 'sw-tz': 'swa', -// 'sv-se': 'swe', -// 'syr-sy': 'syr', -// 'ta-in': 'tam', -// 'te-in': 'tel', -// 'tg-tj': 'tgk', -// 'tl-ph': 'tgl', -// 'th-th': 'tha', -// 'ti-er': 'tir', -// 'tr-tr': 'tur', -// 'ug-cn': 'uig', -// 'uk-ua': 'ukr', -// 'ur-pk': 'urd', -// 'uz-uz': 'uzb', -// 'uz-cyrl-uz': 'uzb_cyrl', -// 'vi-vn': 'vie', -// 'yi-us': 'yid' -// } - // config const MB_SIZE_THRESHOLD = 50 -const tesseractLangs = ['chi_sim', 'chi_tra', 'eng'] +const tesseractLangs = ['chi_sim', 'chi_tra', 'eng'] satisfies LanguageCode[] enum TesseractLangsDownloadUrl { CN = 'https://gitcode.com/beyondkmp/tessdata/releases/download/4.1.0/', GLOBAL = 'https://github.com/tesseract-ocr/tessdata/raw/main/' From 63bb26cae65f201c217547b2ab6a49feb18f06f3 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:25:02 +0800 Subject: [PATCH 03/18] =?UTF-8?q?feat(ocr):=20=E6=B7=BB=E5=8A=A0=20Tessera?= =?UTF-8?q?ct=20OCR=20=E9=85=8D=E7=BD=AE=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/types/ocr.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/renderer/src/types/ocr.ts b/src/renderer/src/types/ocr.ts index e443e1597f..737fef9364 100644 --- a/src/renderer/src/types/ocr.ts +++ b/src/renderer/src/types/ocr.ts @@ -1,3 +1,5 @@ +import Tesseract from 'tesseract.js' + import { FileMetadata, ImageFileMetadata, isImageFile } from '.' export const BuiltinOcrProviderIds = { @@ -70,6 +72,10 @@ export type OcrProviderConfig = { enabled?: boolean } +export type OcrTesseractConfig = OcrProviderConfig & { + langs: Record +} + export type OcrProvider = { id: string name: string From 45d3550492ca6f3e199f36a033be870c8d89074e Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:26:22 +0800 Subject: [PATCH 04/18] =?UTF-8?q?refactor(OCR=E8=AE=BE=E7=BD=AE):=20?= =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8DOcrImageProviderSettings=E4=B8=BAOcr?= =?UTF-8?q?ImageSettings=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{OcrImageProviderSettings.tsx => OcrImageSettings.tsx} | 6 +++--- .../src/pages/settings/DocProcessSettings/OcrSettings.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/renderer/src/pages/settings/DocProcessSettings/{OcrImageProviderSettings.tsx => OcrImageSettings.tsx} (90%) diff --git a/src/renderer/src/pages/settings/DocProcessSettings/OcrImageProviderSettings.tsx b/src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx similarity index 90% rename from src/renderer/src/pages/settings/DocProcessSettings/OcrImageProviderSettings.tsx rename to src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx index ad150cc666..d5e3bf09a4 100644 --- a/src/renderer/src/pages/settings/DocProcessSettings/OcrImageProviderSettings.tsx +++ b/src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx @@ -8,9 +8,9 @@ import { useDispatch } from 'react-redux' import { SettingRow, SettingRowTitle } from '..' -const logger = loggerService.withContext('OcrImageProviderSettings') +const logger = loggerService.withContext('OcrImageSettings') -const OcrImageProviderSettings = () => { +const OcrImageSettings = () => { const { t } = useTranslation() const providers = useAppSelector((state) => state.ocr.providers) const imageProvider = useAppSelector((state) => state.ocr.imageProvider) @@ -48,4 +48,4 @@ const OcrImageProviderSettings = () => { ) } -export default OcrImageProviderSettings +export default OcrImageSettings diff --git a/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx b/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx index d8763f0e2f..825e5fb86d 100644 --- a/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx +++ b/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx @@ -5,7 +5,7 @@ import { FC } from 'react' import { useTranslation } from 'react-i18next' import { SettingDivider, SettingGroup, SettingTitle } from '..' -import OcrImageProviderSettings from './OcrImageProviderSettings' +import OcrImageSettings from './OcrImageSettings' const OcrSettings: FC = () => { const { t } = useTranslation() @@ -16,7 +16,7 @@ const OcrSettings: FC = () => { key: 'image', label: t('settings.tool.ocr.image.title'), icon: , - children: + children: } ] From 428de0836d8c565550c310c45f08b7f0d08ed0dd Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:34:50 +0800 Subject: [PATCH 05/18] =?UTF-8?q?refactor(ocr):=20=E5=B0=86=20Tesseract=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=B1=BB=E5=9E=8B=E7=A7=BB=E5=8A=A8=E5=88=B0?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=BA=95=E9=83=A8=E4=BB=A5=E6=94=B9=E5=96=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/types/ocr.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/types/ocr.ts b/src/renderer/src/types/ocr.ts index 737fef9364..06b6bbbdb0 100644 --- a/src/renderer/src/types/ocr.ts +++ b/src/renderer/src/types/ocr.ts @@ -72,10 +72,6 @@ export type OcrProviderConfig = { enabled?: boolean } -export type OcrTesseractConfig = OcrProviderConfig & { - langs: Record -} - export type OcrProvider = { id: string name: string @@ -129,3 +125,12 @@ export type OcrResult = { export type OcrHandler = (file: SupportedOcrFile) => Promise export type OcrImageHandler = (file: ImageFileMetadata) => Promise + +// Tesseract Types +export type OcrTesseractConfig = OcrProviderConfig & { + langs: Record +} + +export type OcrTesseractProvider = BuiltinOcrProvider & { + config: OcrTesseractConfig +} From fd5c06d46f0aebeef1e0d068630e75882e7dc47d Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:36:19 +0800 Subject: [PATCH 06/18] =?UTF-8?q?feat(ocr):=20=E6=B7=BB=E5=8A=A0=20Tessera?= =?UTF-8?q?ct=20OCR=20=E6=8F=90=E4=BE=9B=E8=80=85=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/types/ocr.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/src/types/ocr.ts b/src/renderer/src/types/ocr.ts index 06b6bbbdb0..947b993549 100644 --- a/src/renderer/src/types/ocr.ts +++ b/src/renderer/src/types/ocr.ts @@ -134,3 +134,7 @@ export type OcrTesseractConfig = OcrProviderConfig & { export type OcrTesseractProvider = BuiltinOcrProvider & { config: OcrTesseractConfig } + +export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => { + return p.id === BuiltinOcrProviderIds.tesseract +} From 0be8b73ccfa26e6067355235c73fabc03ff78b57 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 19:50:15 +0800 Subject: [PATCH 07/18] =?UTF-8?q?feat(ocr):=20=E6=B7=BB=E5=8A=A0=E6=9B=B4?= =?UTF-8?q?=E6=96=B0OCR=E6=8F=90=E4=BE=9B=E8=80=85=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/store/ocr.ts | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/store/ocr.ts b/src/renderer/src/store/ocr.ts index 87d9b790bf..173a42e8b0 100644 --- a/src/renderer/src/store/ocr.ts +++ b/src/renderer/src/store/ocr.ts @@ -1,6 +1,6 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { BUILTIN_OCR_PROVIDERS, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr' -import { ImageOcrProvider, OcrProvider } from '@renderer/types' +import { ImageOcrProvider, OcrProvider, OcrProviderConfig } from '@renderer/types' export interface OcrState { providers: OcrProvider[] @@ -31,13 +31,31 @@ const ocrSlice = createSlice({ Object.assign(state.providers[index], action.payload) } }, + updateOcrProviderConfig( + state, + action: PayloadAction<{ id: string; update: Omit, 'id'> }> + ) { + const index = state.providers.findIndex((provider) => provider.id === action.payload.id) + if (index !== -1) { + if (!state.providers[index].config) { + state.providers[index].config = {} + } + Object.assign(state.providers[index].config, action.payload.update) + } + }, setImageOcrProvider(state, action: PayloadAction) { state.imageProvider = action.payload } } }) -export const { setOcrProviders, addOcrProvider, removeOcrProvider, updateOcrProvider, setImageOcrProvider } = - ocrSlice.actions +export const { + setOcrProviders, + addOcrProvider, + removeOcrProvider, + updateOcrProvider, + updateOcrProviderConfig, + setImageOcrProvider +} = ocrSlice.actions export default ocrSlice.reducer From 5ee7718054a787cc6b234f34504501649c4ac069 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 20:23:36 +0800 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0OCR=E6=8F=90?= =?UTF-8?q?=E4=BE=9B=E8=80=85=E9=92=A9=E5=AD=90=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现useOcrProvider钩子用于获取和更新OCR提供者配置 --- src/renderer/src/hooks/useOcrProvider.ts | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/renderer/src/hooks/useOcrProvider.ts diff --git a/src/renderer/src/hooks/useOcrProvider.ts b/src/renderer/src/hooks/useOcrProvider.ts new file mode 100644 index 0000000000..2407666f04 --- /dev/null +++ b/src/renderer/src/hooks/useOcrProvider.ts @@ -0,0 +1,30 @@ +import { loggerService } from '@logger' +import { useAppSelector } from '@renderer/store' +import { updateOcrProviderConfig } from '@renderer/store/ocr' +import { OcrProviderConfig } from '@renderer/types' +import { useTranslation } from 'react-i18next' +import { useDispatch } from 'react-redux' + +const logger = loggerService.withContext('useOcrProvider') + +export const useOcrProvider = (id: string) => { + const { t } = useTranslation() + const dispatch = useDispatch() + const providers = useAppSelector((state) => state.ocr.providers) + const provider = providers.find((p) => p.id === id) + + if (!provider) { + logger.error(`Ocr Provider ${id} not found`) + window.message.error(t('ocr.provider.not_found')) + throw new Error(`Ocr Provider ${id} not found`) + } + + const updateConfig = (update: Partial) => { + dispatch(updateOcrProviderConfig({ id: provider.id, update })) + } + + return { + provider, + updateConfig + } +} From e89d245e395b6f198c1d04bca11eb69d391b9d3d Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 20:34:53 +0800 Subject: [PATCH 09/18] =?UTF-8?q?refactor(ocr):=20=E4=BF=AE=E6=94=B9remove?= =?UTF-8?q?OcrProvider=E5=8F=82=E6=95=B0=E4=B8=BA=E5=AD=97=E7=AC=A6?= =?UTF-8?q?=E4=B8=B2id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 简化removeOcrProvider方法的参数类型,直接使用字符串id进行过滤,提高代码简洁性 --- src/renderer/src/store/ocr.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/store/ocr.ts b/src/renderer/src/store/ocr.ts index 173a42e8b0..7e4ba3d348 100644 --- a/src/renderer/src/store/ocr.ts +++ b/src/renderer/src/store/ocr.ts @@ -22,8 +22,8 @@ const ocrSlice = createSlice({ addOcrProvider(state, action: PayloadAction) { state.providers.push(action.payload) }, - removeOcrProvider(state, action: PayloadAction) { - state.providers = state.providers.filter((provider) => provider.id !== action.payload.id) + removeOcrProvider(state, action: PayloadAction) { + state.providers = state.providers.filter((provider) => provider.id !== action.payload) }, updateOcrProvider(state, action: PayloadAction>) { const index = state.providers.findIndex((provider) => provider.id === action.payload.id) From ca08b11b5c68d13fff5d3933c46199768c308e0b Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 20:42:02 +0800 Subject: [PATCH 10/18] =?UTF-8?q?refactor(ocr):=20=E5=B0=86=E5=86=85?= =?UTF-8?q?=E7=BD=AEOCR=E6=8F=90=E4=BE=9B=E8=80=85=E4=BB=8E=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E6=94=B9=E4=B8=BA=E6=98=A0=E5=B0=84=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构OCR配置模块,使用映射结构存储内置OCR提供者以便于扩展和维护 --- src/renderer/src/config/ocr.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/config/ocr.ts b/src/renderer/src/config/ocr.ts index 1187b49dc0..a3a8943f41 100644 --- a/src/renderer/src/config/ocr.ts +++ b/src/renderer/src/config/ocr.ts @@ -1,4 +1,4 @@ -import { BuiltinOcrProvider, ImageOcrProvider, OcrProviderCapability } from '@renderer/types' +import { BuiltinOcrProvider, BuiltinOcrProviderId, ImageOcrProvider, OcrProviderCapability } from '@renderer/types' const tesseract: BuiltinOcrProvider & ImageOcrProvider = { id: 'tesseract', @@ -8,7 +8,11 @@ const tesseract: BuiltinOcrProvider & ImageOcrProvider = { } } as const -export const BUILTIN_OCR_PROVIDERS: BuiltinOcrProvider[] = [tesseract] as const +export const BUILTIN_OCR_PROVIDERS_MAP = { + tesseract +} as const satisfies Record + +export const BUILTIN_OCR_PROVIDERS: BuiltinOcrProvider[] = Object.values(BUILTIN_OCR_PROVIDERS_MAP) export const DEFAULT_OCR_PROVIDER = { image: tesseract From f23e37941abba4fcc703b31e955b67bff565c432 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 21:13:57 +0800 Subject: [PATCH 11/18] =?UTF-8?q?refactor(ocr):=20=E5=B0=86BUILTIN=5FOCR?= =?UTF-8?q?=5FPROVIDERS=E6=94=B9=E4=B8=BA=E5=8F=AA=E8=AF=BB=E6=95=B0?= =?UTF-8?q?=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用Object.freeze确保数组不可变,提高代码安全性 --- src/renderer/src/config/ocr.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/config/ocr.ts b/src/renderer/src/config/ocr.ts index a3a8943f41..0f6d305f3c 100644 --- a/src/renderer/src/config/ocr.ts +++ b/src/renderer/src/config/ocr.ts @@ -12,7 +12,9 @@ export const BUILTIN_OCR_PROVIDERS_MAP = { tesseract } as const satisfies Record -export const BUILTIN_OCR_PROVIDERS: BuiltinOcrProvider[] = Object.values(BUILTIN_OCR_PROVIDERS_MAP) +export const BUILTIN_OCR_PROVIDERS: readonly BuiltinOcrProvider[] = Object.freeze( + Object.values(BUILTIN_OCR_PROVIDERS_MAP) +) export const DEFAULT_OCR_PROVIDER = { image: tesseract From e61bfef366c5f6b23ea04bd0fd7a1422b7bb1484 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 21:14:08 +0800 Subject: [PATCH 12/18] =?UTF-8?q?feat(ocr):=20=E6=B7=BB=E5=8A=A0OCR?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E8=80=85=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E6=94=B9=E8=BF=9B=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加useOcrProviders钩子用于管理OCR提供者的添加和删除 当内置OCR提供者不存在时自动恢复默认配置 改进错误提示信息并增加国际化支持 --- src/renderer/src/hooks/useOcrProvider.ts | 66 +++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/hooks/useOcrProvider.ts b/src/renderer/src/hooks/useOcrProvider.ts index 2407666f04..ce2eb5b8fc 100644 --- a/src/renderer/src/hooks/useOcrProvider.ts +++ b/src/renderer/src/hooks/useOcrProvider.ts @@ -1,22 +1,76 @@ import { loggerService } from '@logger' +import { BUILTIN_OCR_PROVIDERS_MAP } from '@renderer/config/ocr' import { useAppSelector } from '@renderer/store' -import { updateOcrProviderConfig } from '@renderer/store/ocr' -import { OcrProviderConfig } from '@renderer/types' +import { addOcrProvider, removeOcrProvider, updateOcrProviderConfig } from '@renderer/store/ocr' +import { isBuiltinOcrProviderId, OcrProvider, OcrProviderConfig } from '@renderer/types' import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' const logger = loggerService.withContext('useOcrProvider') +export const useOcrProviders = () => { + const providers = useAppSelector((state) => state.ocr.providers) + const dispatch = useDispatch() + const { t } = useTranslation() + + /** + * 添加一个新的OCR服务提供者 + * @param provider - OCR提供者对象,包含id和其他配置信息 + * @throws {Error} 当尝试添加一个已存在ID的提供者时抛出错误 + */ + const addProvider = (provider: OcrProvider) => { + if (providers.some((p) => p.id === provider.id)) { + const msg = `Provider with id ${provider.id} already exists` + logger.error(msg) + window.message.error(t('ocr.error.provider.existing')) + throw new Error(msg) + } + dispatch(addOcrProvider(provider)) + } + + /** + * 移除一个OCR服务提供者 + * @param id - 要移除的OCR提供者ID + * @throws {Error} 当尝试移除一个内置提供商时抛出错误 + */ + const removeProvider = (id: string) => { + if (isBuiltinOcrProviderId(id)) { + const msg = `Cannot remove builtin provider ${id}` + logger.error(msg) + window.message.error(t('ocr.error.provider.cannot_remove_builtin')) + throw new Error(msg) + } + + dispatch(removeOcrProvider(id)) + } + + return { providers, addProvider, removeProvider } +} + export const useOcrProvider = (id: string) => { const { t } = useTranslation() const dispatch = useDispatch() - const providers = useAppSelector((state) => state.ocr.providers) - const provider = providers.find((p) => p.id === id) + const { providers, addProvider } = useOcrProviders() + let provider = providers.find((p) => p.id === id) + // safely fallback if (!provider) { logger.error(`Ocr Provider ${id} not found`) - window.message.error(t('ocr.provider.not_found')) - throw new Error(`Ocr Provider ${id} not found`) + window.message.error(t('ocr.error.provider.not_found')) + if (isBuiltinOcrProviderId(id)) { + try { + addProvider(BUILTIN_OCR_PROVIDERS_MAP[id]) + } catch (e) { + logger.warn(`Add ${BUILTIN_OCR_PROVIDERS_MAP[id].name} failed. Just use temp provider from config.`) + window.message.warning(t('ocr.warning.provider.fallback', { name: BUILTIN_OCR_PROVIDERS_MAP[id].name })) + } finally { + provider = BUILTIN_OCR_PROVIDERS_MAP[id] + } + } else { + logger.warn(`Fallback to tesseract`) + window.message.warning(t('ocr.warning.provider.fallback', { name: 'Tesseract' })) + provider = BUILTIN_OCR_PROVIDERS_MAP.tesseract + } } const updateConfig = (update: Partial) => { From ef1cd39477c5025d8306f466c4755fa3c12af49d Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 23:09:52 +0800 Subject: [PATCH 13/18] =?UTF-8?q?Revert=20"refactor(ocr):=20=E5=B0=86BUILT?= =?UTF-8?q?IN=5FOCR=5FPROVIDERS=E6=94=B9=E4=B8=BA=E5=8F=AA=E8=AF=BB?= =?UTF-8?q?=E6=95=B0=E7=BB=84"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit f23e37941abba4fcc703b31e955b67bff565c432. --- src/renderer/src/config/ocr.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/renderer/src/config/ocr.ts b/src/renderer/src/config/ocr.ts index 0f6d305f3c..a3a8943f41 100644 --- a/src/renderer/src/config/ocr.ts +++ b/src/renderer/src/config/ocr.ts @@ -12,9 +12,7 @@ export const BUILTIN_OCR_PROVIDERS_MAP = { tesseract } as const satisfies Record -export const BUILTIN_OCR_PROVIDERS: readonly BuiltinOcrProvider[] = Object.freeze( - Object.values(BUILTIN_OCR_PROVIDERS_MAP) -) +export const BUILTIN_OCR_PROVIDERS: BuiltinOcrProvider[] = Object.values(BUILTIN_OCR_PROVIDERS_MAP) export const DEFAULT_OCR_PROVIDER = { image: tesseract From 42923d51e98936f7b6ccb04b37e2dc414c68d14d Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 23:16:32 +0800 Subject: [PATCH 14/18] =?UTF-8?q?feat(ocr):=20=E4=B8=BATesseract=20OCR?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A4=9A=E8=AF=AD=E8=A8=80=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加对简体中文、繁体中文和英文的语言支持配置,扩展OCR功能以满足多语言识别需求 --- src/renderer/src/config/ocr.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/config/ocr.ts b/src/renderer/src/config/ocr.ts index a3a8943f41..b899cbb5f0 100644 --- a/src/renderer/src/config/ocr.ts +++ b/src/renderer/src/config/ocr.ts @@ -1,12 +1,25 @@ -import { BuiltinOcrProvider, BuiltinOcrProviderId, ImageOcrProvider, OcrProviderCapability } from '@renderer/types' +import { + BuiltinOcrProvider, + BuiltinOcrProviderId, + ImageOcrProvider, + OcrProviderCapability, + OcrTesseractProvider +} from '@renderer/types' -const tesseract: BuiltinOcrProvider & ImageOcrProvider = { +const tesseract: BuiltinOcrProvider & ImageOcrProvider & OcrTesseractProvider = { id: 'tesseract', name: 'Tesseract', capabilities: { image: true + }, + config: { + langs: { + chi_sim: true, + chi_tra: true, + eng: true + } } -} as const +} as const satisfies OcrTesseractProvider export const BUILTIN_OCR_PROVIDERS_MAP = { tesseract From c549fff44a15d9d69b293ede89fb5d8b250a4b87 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 23:50:37 +0800 Subject: [PATCH 15/18] =?UTF-8?q?refactor(types):=20=E5=B0=86Tesseract.Lan?= =?UTF-8?q?guageCode=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BATesseractLangCode?= =?UTF-8?q?=E4=BB=A5=E6=8F=90=E9=AB=98=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/renderer/src/types/ocr.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/types/ocr.ts b/src/renderer/src/types/ocr.ts index 947b993549..38c4ba6c5b 100644 --- a/src/renderer/src/types/ocr.ts +++ b/src/renderer/src/types/ocr.ts @@ -128,7 +128,7 @@ export type OcrImageHandler = (file: ImageFileMetadata) => Promise // Tesseract Types export type OcrTesseractConfig = OcrProviderConfig & { - langs: Record + langs: Record } export type OcrTesseractProvider = BuiltinOcrProvider & { @@ -138,3 +138,5 @@ export type OcrTesseractProvider = BuiltinOcrProvider & { export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => { return p.id === BuiltinOcrProviderIds.tesseract } + +export type TesseractLangCode = Tesseract.LanguageCode From cb362a4e2e60347c73aca63b91a60836d8cd7198 Mon Sep 17 00:00:00 2001 From: icarus Date: Sat, 23 Aug 2025 23:51:05 +0800 Subject: [PATCH 16/18] =?UTF-8?q?feat(OCR=E8=AE=BE=E7=BD=AE):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0OCR=E6=8F=90=E4=BE=9B=E5=95=86=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=8F=8A=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增OCR提供商设置组件,支持显示当前选择的OCR提供商信息 在OCR图片设置中添加状态管理,同步提供商选择到父组件 添加Tesseract OCR设置组件,支持多语言选择(暂不可用) --- .../DocProcessSettings/OcrImageSettings.tsx | 15 +++++- .../OcrProviderSettings.tsx | 51 ++++++++++++++++++ .../DocProcessSettings/OcrSettings.tsx | 12 ++++- .../OcrTesseractSettings.tsx | 52 +++++++++++++++++++ 4 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 src/renderer/src/pages/settings/DocProcessSettings/OcrProviderSettings.tsx create mode 100644 src/renderer/src/pages/settings/DocProcessSettings/OcrTesseractSettings.tsx diff --git a/src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx b/src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx index d5e3bf09a4..3efdf94fa0 100644 --- a/src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx +++ b/src/renderer/src/pages/settings/DocProcessSettings/OcrImageSettings.tsx @@ -1,8 +1,9 @@ import { loggerService } from '@logger' import { useAppSelector } from '@renderer/store' import { setImageOcrProvider } from '@renderer/store/ocr' -import { isImageOcrProvider } from '@renderer/types' +import { isImageOcrProvider, OcrProvider } from '@renderer/types' import { Select } from 'antd' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' @@ -10,13 +11,22 @@ import { SettingRow, SettingRowTitle } from '..' const logger = loggerService.withContext('OcrImageSettings') -const OcrImageSettings = () => { +type Props = { + setProvider: (provider: OcrProvider) => void +} + +const OcrImageSettings = ({ setProvider }: Props) => { const { t } = useTranslation() const providers = useAppSelector((state) => state.ocr.providers) const imageProvider = useAppSelector((state) => state.ocr.imageProvider) const imageProviders = providers.filter((p) => isImageOcrProvider(p)) const dispatch = useDispatch() + // 挂载时更新外部状态 + useEffect(() => { + setProvider(imageProvider) + }, [imageProvider, setProvider]) + const updateImageProvider = (id: string) => { const provider = imageProviders.find((p) => p.id === id) if (!provider) { @@ -25,6 +35,7 @@ const OcrImageSettings = () => { return } + setProvider(provider) dispatch(setImageOcrProvider(provider)) } diff --git a/src/renderer/src/pages/settings/DocProcessSettings/OcrProviderSettings.tsx b/src/renderer/src/pages/settings/DocProcessSettings/OcrProviderSettings.tsx new file mode 100644 index 0000000000..a9ba128d7a --- /dev/null +++ b/src/renderer/src/pages/settings/DocProcessSettings/OcrProviderSettings.tsx @@ -0,0 +1,51 @@ +// import { loggerService } from '@logger' +import { isBuiltinOcrProvider, OcrProvider } from '@renderer/types' +import { getOcrProviderLogo } from '@renderer/utils/ocr' +import { Avatar, Divider, Flex } from 'antd' +import styled from 'styled-components' + +import { SettingTitle } from '..' +import { OcrTesseractSettings } from './OcrTesseractSettings' + +// const logger = loggerService.withContext('OcrTesseractSettings') + +type Props = { + provider: OcrProvider +} + +const OcrProviderSettings = ({ provider }: Props) => { + // const { t } = useTranslation() + const getProviderSettings = () => { + if (isBuiltinOcrProvider(provider)) { + switch (provider.id) { + case 'tesseract': + return + } + } else { + throw new Error('Not supported OCR provider') + } + } + + return ( + <> + + + + {provider.name} + + + + {getProviderSettings()} + + ) +} + +const ProviderName = styled.span` + font-size: 14px; + font-weight: 500; +` +const ProviderLogo = styled(Avatar)` + border: 0.5px solid var(--color-border); +` + +export default OcrProviderSettings diff --git a/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx b/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx index 825e5fb86d..9ad2d111ad 100644 --- a/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx +++ b/src/renderer/src/pages/settings/DocProcessSettings/OcrSettings.tsx @@ -1,22 +1,27 @@ import { PictureOutlined } from '@ant-design/icons' import { useTheme } from '@renderer/context/ThemeProvider' +import { useAppSelector } from '@renderer/store' +import { OcrProvider } from '@renderer/types' import { Tabs, TabsProps } from 'antd' -import { FC } from 'react' +import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import { SettingDivider, SettingGroup, SettingTitle } from '..' import OcrImageSettings from './OcrImageSettings' +import OcrProviderSettings from './OcrProviderSettings' const OcrSettings: FC = () => { const { t } = useTranslation() const { theme: themeMode } = useTheme() + const imageProvider = useAppSelector((state) => state.ocr.imageProvider) + const [provider, setProvider] = useState(imageProvider) // since default to image provider const tabs: TabsProps['items'] = [ { key: 'image', label: t('settings.tool.ocr.image.title'), icon: , - children: + children: } ] @@ -27,6 +32,9 @@ const OcrSettings: FC = () => { + + + ) } diff --git a/src/renderer/src/pages/settings/DocProcessSettings/OcrTesseractSettings.tsx b/src/renderer/src/pages/settings/DocProcessSettings/OcrTesseractSettings.tsx new file mode 100644 index 0000000000..3d51c82c45 --- /dev/null +++ b/src/renderer/src/pages/settings/DocProcessSettings/OcrTesseractSettings.tsx @@ -0,0 +1,52 @@ +// import { loggerService } from '@logger' +import InfoTooltip from '@renderer/components/InfoTooltip' +import { useOcrProvider } from '@renderer/hooks/useOcrProvider' +import { BuiltinOcrProviderIds, isOcrTesseractProvider } from '@renderer/types' +import { Flex, Select } from 'antd' +import { useTranslation } from 'react-i18next' + +import { SettingRow, SettingRowTitle } from '..' + +// const logger = loggerService.withContext('OcrTesseractSettings') + +export const OcrTesseractSettings = () => { + const { t } = useTranslation() + const { provider } = useOcrProvider(BuiltinOcrProviderIds.tesseract) + + // TODO: use error boundary + if (!isOcrTesseractProvider(provider)) { + throw new Error('Not tesseract provider.') + } + + // const [langs, setLangs] = useState(provider.config?.langs ?? {}) + + // currently static + const options = [ + { value: 'chi_sim', label: t('languages.chinese') }, + { value: 'chi_tra', label: t('languages.chinese-traditional') }, + { value: 'eng', label: t('languages.english') } + ] + + return ( + <> + + + + {t('settings.tool.ocr.image.tesseract.langs')} + + + +
+