mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 14:31:35 +08:00
Merge branch 'feat/ocr' into feat/ocr-translate
This commit is contained in:
commit
07603ae9db
348
.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch
vendored
Normal file
348
.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch
vendored
Normal file
@ -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";
|
||||
+
|
||||
+/// <reference types="node" />
|
||||
+
|
||||
declare namespace Tesseract {
|
||||
- function createScheduler(): Scheduler
|
||||
- function createWorker(langs?: string | string[] | Lang[], oem?: OEM, options?: Partial<WorkerOptions>, config?: string | Partial<InitOptions>): Promise<Worker>
|
||||
- function setLogging(logging: boolean): void
|
||||
- function recognize(image: ImageLike, langs?: string, options?: Partial<WorkerOptions>): Promise<RecognizeResult>
|
||||
- function detect(image: ImageLike, options?: Partial<WorkerOptions>): any
|
||||
+ function createScheduler(): Scheduler;
|
||||
+ function createWorker(
|
||||
+ langs?: LanguageCode | LanguageCode[] | Lang[],
|
||||
+ oem?: OEM,
|
||||
+ options?: Partial<WorkerOptions>,
|
||||
+ config?: string | Partial<InitOptions>
|
||||
+ ): Promise<Worker>;
|
||||
+ function setLogging(logging: boolean): void;
|
||||
+ function recognize(
|
||||
+ image: ImageLike,
|
||||
+ langs?: LanguageCode,
|
||||
+ options?: Partial<WorkerOptions>
|
||||
+ ): Promise<RecognizeResult>;
|
||||
+ function detect(image: ImageLike, options?: Partial<WorkerOptions>): 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<Worker['recognize']>): Promise<RecognizeResult>
|
||||
- addJob(action: 'detect', ...args: Parameters<Worker['detect']>): Promise<DetectResult>
|
||||
- terminate(): Promise<any>
|
||||
- getQueueLen(): number
|
||||
- getNumWorkers(): number
|
||||
+ addWorker(worker: Worker): string;
|
||||
+ addJob(
|
||||
+ action: "recognize",
|
||||
+ ...args: Parameters<Worker["recognize"]>
|
||||
+ ): Promise<RecognizeResult>;
|
||||
+ addJob(
|
||||
+ action: "detect",
|
||||
+ ...args: Parameters<Worker["detect"]>
|
||||
+ ): Promise<DetectResult>;
|
||||
+ terminate(): Promise<any>;
|
||||
+ getQueueLen(): number;
|
||||
+ getNumWorkers(): number;
|
||||
}
|
||||
|
||||
interface Worker {
|
||||
- load(jobId?: string): Promise<ConfigResult>
|
||||
- writeText(path: string, text: string, jobId?: string): Promise<ConfigResult>
|
||||
- readText(path: string, jobId?: string): Promise<ConfigResult>
|
||||
- removeText(path: string, jobId?: string): Promise<ConfigResult>
|
||||
- FS(method: string, args: any[], jobId?: string): Promise<ConfigResult>
|
||||
- reinitialize(langs?: string | Lang[], oem?: OEM, config?: string | Partial<InitOptions>, jobId?: string): Promise<ConfigResult>
|
||||
- setParameters(params: Partial<WorkerParams>, jobId?: string): Promise<ConfigResult>
|
||||
- getImage(type: imageType): string
|
||||
- recognize(image: ImageLike, options?: Partial<RecognizeOptions>, output?: Partial<OutputFormats>, jobId?: string): Promise<RecognizeResult>
|
||||
- detect(image: ImageLike, jobId?: string): Promise<DetectResult>
|
||||
- terminate(jobId?: string): Promise<ConfigResult>
|
||||
+ load(jobId?: string): Promise<ConfigResult>;
|
||||
+ writeText(
|
||||
+ path: string,
|
||||
+ text: string,
|
||||
+ jobId?: string
|
||||
+ ): Promise<ConfigResult>;
|
||||
+ readText(path: string, jobId?: string): Promise<ConfigResult>;
|
||||
+ removeText(path: string, jobId?: string): Promise<ConfigResult>;
|
||||
+ FS(method: string, args: any[], jobId?: string): Promise<ConfigResult>;
|
||||
+ reinitialize(
|
||||
+ langs?: string | Lang[],
|
||||
+ oem?: OEM,
|
||||
+ config?: string | Partial<InitOptions>,
|
||||
+ jobId?: string
|
||||
+ ): Promise<ConfigResult>;
|
||||
+ setParameters(
|
||||
+ params: Partial<WorkerParams>,
|
||||
+ jobId?: string
|
||||
+ ): Promise<ConfigResult>;
|
||||
+ getImage(type: imageType): string;
|
||||
+ recognize(
|
||||
+ image: ImageLike,
|
||||
+ options?: Partial<RecognizeOptions>,
|
||||
+ output?: Partial<OutputFormats>,
|
||||
+ jobId?: string
|
||||
+ ): Promise<RecognizeResult>;
|
||||
+ detect(image: ImageLike, jobId?: string): Promise<DetectResult>;
|
||||
+ terminate(jobId?: string): Promise<ConfigResult>;
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -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": {
|
||||
|
||||
@ -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<string, string> = {
|
||||
// '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/'
|
||||
|
||||
@ -1,14 +1,31 @@
|
||||
import { BuiltinOcrProvider, 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: BuiltinOcrProvider[] = [tesseract] as const
|
||||
export const BUILTIN_OCR_PROVIDERS_MAP = {
|
||||
tesseract
|
||||
} as const satisfies Record<BuiltinOcrProviderId, BuiltinOcrProvider>
|
||||
|
||||
export const BUILTIN_OCR_PROVIDERS: BuiltinOcrProvider[] = Object.values(BUILTIN_OCR_PROVIDERS_MAP)
|
||||
|
||||
export const DEFAULT_OCR_PROVIDER = {
|
||||
image: tesseract
|
||||
|
||||
84
src/renderer/src/hooks/useOcrProvider.ts
Normal file
84
src/renderer/src/hooks/useOcrProvider.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { BUILTIN_OCR_PROVIDERS_MAP } from '@renderer/config/ocr'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
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, 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.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<OcrProviderConfig>) => {
|
||||
dispatch(updateOcrProviderConfig({ id: provider.id, update }))
|
||||
}
|
||||
|
||||
return {
|
||||
provider,
|
||||
updateConfig
|
||||
}
|
||||
}
|
||||
@ -1566,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Cannot delete built-in provider",
|
||||
"existing": "The provider already exists",
|
||||
"not_found": "OCR provider does not exist",
|
||||
"update_failed": "Failed to update configuration"
|
||||
},
|
||||
"unknown": "An error occurred during the OCR process"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Unsupported file type {{type}}"
|
||||
},
|
||||
"processing": "OCR processing..."
|
||||
"processing": "OCR processing...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Reverted to {{name}}, which may cause issues"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3498,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "The provider does not exist"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Supported languages",
|
||||
"temp_tooltip": "Currently only Chinese and English are supported"
|
||||
},
|
||||
"title": "Image"
|
||||
},
|
||||
"image_provider": "OCR service provider",
|
||||
|
||||
@ -1566,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "組み込みプロバイダーは削除できません",
|
||||
"existing": "プロバイダーはすでに存在します",
|
||||
"not_found": "OCRプロバイダーが存在しません",
|
||||
"update_failed": "更新構成に失敗しました"
|
||||
},
|
||||
"unknown": "OCR処理中にエラーが発生しました"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "サポートされていないファイルタイプ {{type}}"
|
||||
},
|
||||
"processing": "OCR処理中..."
|
||||
"processing": "OCR処理中...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "{{name}} に戻されました。これにより問題が発生する可能性があります。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3498,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "該提供者は存在しません"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "サポートされている言語",
|
||||
"temp_tooltip": "現在のところ、中国語と英語のみをサポートしています"
|
||||
},
|
||||
"title": "画像"
|
||||
},
|
||||
"image_provider": "OCRサービスプロバイダー",
|
||||
|
||||
@ -1566,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Не удается удалить встроенного поставщика",
|
||||
"existing": "Поставщик уже существует",
|
||||
"not_found": "Поставщик OCR отсутствует",
|
||||
"update_failed": "Обновление конфигурации не удалось"
|
||||
},
|
||||
"unknown": "Произошла ошибка в процессе распознавания текста"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Неподдерживаемый тип файла {{type}}"
|
||||
},
|
||||
"processing": "Обработка OCR..."
|
||||
"processing": "Обработка OCR...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Возвращено к {{name}}, это может вызвать проблемы"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3498,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "Поставщик не существует"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Поддерживаемые языки",
|
||||
"temp_tooltip": "На данный момент поддерживаются только китайский и английский языки"
|
||||
},
|
||||
"title": "Изображение"
|
||||
},
|
||||
"image_provider": "Поставщик услуг OCR",
|
||||
|
||||
@ -1566,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"unknown": "OCR过程发生错误"
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "不能删除内置提供商",
|
||||
"existing": "提供商已存在",
|
||||
"not_found": "OCR 提供商不存在",
|
||||
"update_failed": "更新配置失败"
|
||||
},
|
||||
"unknown": "OCR 过程发生错误"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "不支持的文件类型 {{type}}"
|
||||
},
|
||||
"processing": "OCR 处理中..."
|
||||
"processing": "OCR 处理中...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "已回退到 {{name}},这可能导致问题"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3498,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "该提供商不存在"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "支持的语言",
|
||||
"temp_tooltip": "目前暂时只支持中文和英文"
|
||||
},
|
||||
"title": "图片"
|
||||
},
|
||||
"image_provider": "OCR 服务提供商",
|
||||
|
||||
@ -1566,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "不能刪除內建提供者",
|
||||
"existing": "提供商已存在",
|
||||
"not_found": "OCR 提供商不存在",
|
||||
"update_failed": "更新配置失敗"
|
||||
},
|
||||
"unknown": "OCR過程發生錯誤"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "不支持的文件類型 {{type}}"
|
||||
},
|
||||
"processing": "OCR 處理中..."
|
||||
"processing": "OCR 處理中...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "已回退到 {{name}},這可能導致問題"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3498,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "該提供商不存在"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "支援的語言",
|
||||
"temp_tooltip": "目前暫時只支援中文和英文"
|
||||
},
|
||||
"title": "圖片"
|
||||
},
|
||||
"image_provider": "OCR 服務提供商",
|
||||
|
||||
@ -888,6 +888,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Συνεχίστε το συνομιλημένο",
|
||||
"error": {
|
||||
"topic_not_found": "Το θέμα δεν υπάρχει"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Εφαρμογή στο μήνυμα"
|
||||
},
|
||||
@ -1563,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Δεν είναι δυνατή η διαγραφή του ενσωματωμένου παρόχου",
|
||||
"existing": "Ο πάροχος υπηρεσιών υπάρχει ήδη",
|
||||
"not_found": "Ο πάροχος OCR δεν υπάρχει",
|
||||
"update_failed": "Αποτυχία ενημέρωσης της διαμόρφωσης"
|
||||
},
|
||||
"unknown": "Η διαδικασία OCR εμφάνισε σφάλμα"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Μη υποστηριζόμενος τύπος αρχείου {{type}}"
|
||||
},
|
||||
"processing": "Η επεξεργασία OCR βρίσκεται σε εξέλιξη..."
|
||||
"processing": "Η επεξεργασία OCR βρίσκεται σε εξέλιξη...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Επαναφέρθηκε στο {{name}}, το οποίο μπορεί να προκαλέσει προβλήματα"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3495,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "Ο πάροχος δεν υπάρχει"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Υποστηριζόμενες γλώσσες",
|
||||
"temp_tooltip": "Προς το παρόν υποστηρίζονται μόνο η κινεζική και η αγγλική γλώσσα"
|
||||
},
|
||||
"title": "Εικόνα"
|
||||
},
|
||||
"image_provider": "Πάροχοι υπηρεσιών OCR",
|
||||
|
||||
@ -888,6 +888,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continuar chat",
|
||||
"error": {
|
||||
"topic_not_found": "El tema no existe"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Localizar mensaje"
|
||||
},
|
||||
@ -1563,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "No se puede eliminar el proveedor integrado",
|
||||
"existing": "El proveedor ya existe",
|
||||
"not_found": "El proveedor de OCR no existe",
|
||||
"update_failed": "Actualización de la configuración fallida"
|
||||
},
|
||||
"unknown": "El proceso OCR ha fallado"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Tipo de archivo no compatible {{type}}"
|
||||
},
|
||||
"processing": "Procesando OCR..."
|
||||
"processing": "Procesando OCR...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Se ha revertido a {{name}}, lo que podría causar problemas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3495,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "El proveedor no existe"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Idiomas compatibles",
|
||||
"temp_tooltip": "Actualmente solo se admiten chino e inglés."
|
||||
},
|
||||
"title": "Imagen"
|
||||
},
|
||||
"image_provider": "Proveedor de servicios OCR",
|
||||
|
||||
@ -888,6 +888,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continuer la conversation",
|
||||
"error": {
|
||||
"topic_not_found": "Le sujet n'existe pas"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Localiser le message"
|
||||
},
|
||||
@ -1563,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Impossible de supprimer le fournisseur intégré",
|
||||
"existing": "Le fournisseur existe déjà",
|
||||
"not_found": "Le fournisseur OCR n'existe pas",
|
||||
"update_failed": "Échec de la mise à jour de la configuration"
|
||||
},
|
||||
"unknown": "Une erreur s'est produite lors du processus OCR"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Type de fichier non pris en charge {{type}}"
|
||||
},
|
||||
"processing": "Traitement OCR en cours..."
|
||||
"processing": "Traitement OCR en cours...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Revenu à {{name}}, ce qui pourrait entraîner des problèmes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3495,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "Ce fournisseur n'existe pas"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Langues prises en charge",
|
||||
"temp_tooltip": "Pour le moment, seuls le chinois et l'anglais sont pris en charge."
|
||||
},
|
||||
"title": "Image"
|
||||
},
|
||||
"image_provider": "Fournisseur de service OCR",
|
||||
|
||||
@ -888,6 +888,9 @@
|
||||
},
|
||||
"history": {
|
||||
"continue_chat": "Continuar conversando",
|
||||
"error": {
|
||||
"topic_not_found": "Tópico inexistente"
|
||||
},
|
||||
"locate": {
|
||||
"message": "Localizar mensagem"
|
||||
},
|
||||
@ -1563,12 +1566,23 @@
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Não é possível excluir o provedor integrado",
|
||||
"existing": "O provedor já existe",
|
||||
"not_found": "O provedor OCR não existe",
|
||||
"update_failed": "Falha ao atualizar a configuração"
|
||||
},
|
||||
"unknown": "O processo OCR apresentou um erro"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Tipo de arquivo não suportado {{type}}"
|
||||
},
|
||||
"processing": "Processamento OCR em andamento..."
|
||||
"processing": "Processamento OCR em andamento...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Revertido para {{name}}, o que pode causar problemas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
@ -3495,6 +3509,10 @@
|
||||
"error": {
|
||||
"provider_not_found": "O provedor não existe"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Idiomas suportados",
|
||||
"temp_tooltip": "No momento, apenas chinês e inglês são suportados."
|
||||
},
|
||||
"title": "Imagem"
|
||||
},
|
||||
"image_provider": "Provedor de serviços OCR",
|
||||
|
||||
@ -1,22 +1,32 @@
|
||||
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'
|
||||
|
||||
import { SettingRow, SettingRowTitle } from '..'
|
||||
|
||||
const logger = loggerService.withContext('OcrImageProviderSettings')
|
||||
const logger = loggerService.withContext('OcrImageSettings')
|
||||
|
||||
const OcrImageProviderSettings = () => {
|
||||
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 OcrImageProviderSettings = () => {
|
||||
return
|
||||
}
|
||||
|
||||
setProvider(provider)
|
||||
dispatch(setImageOcrProvider(provider))
|
||||
}
|
||||
|
||||
@ -48,4 +59,4 @@ const OcrImageProviderSettings = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export default OcrImageProviderSettings
|
||||
export default OcrImageSettings
|
||||
@ -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 <OcrTesseractSettings />
|
||||
}
|
||||
} else {
|
||||
throw new Error('Not supported OCR provider')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingTitle>
|
||||
<Flex align="center" gap={8}>
|
||||
<ProviderLogo shape="square" src={getOcrProviderLogo(provider.id)} size={16} />
|
||||
<ProviderName> {provider.name}</ProviderName>
|
||||
</Flex>
|
||||
</SettingTitle>
|
||||
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
||||
{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
|
||||
@ -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 OcrImageProviderSettings from './OcrImageProviderSettings'
|
||||
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<OcrProvider>(imageProvider) // since default to image provider
|
||||
|
||||
const tabs: TabsProps['items'] = [
|
||||
{
|
||||
key: 'image',
|
||||
label: t('settings.tool.ocr.image.title'),
|
||||
icon: <PictureOutlined />,
|
||||
children: <OcrImageProviderSettings />
|
||||
children: <OcrImageSettings setProvider={setProvider} />
|
||||
}
|
||||
]
|
||||
|
||||
@ -27,6 +32,9 @@ const OcrSettings: FC = () => {
|
||||
<SettingDivider />
|
||||
<Tabs defaultActiveKey="image" items={tabs} />
|
||||
</SettingGroup>
|
||||
<SettingGroup theme={themeMode}>
|
||||
<OcrProviderSettings provider={provider} />
|
||||
</SettingGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -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<OcrTesseractConfig['langs']>(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 (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitle>
|
||||
<Flex align="center" gap={4}>
|
||||
{t('settings.tool.ocr.image.tesseract.langs')}
|
||||
<InfoTooltip title={t('settings.tool.ocr.image.tesseract.temp_tooltip')} />
|
||||
</Flex>
|
||||
</SettingRowTitle>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<Select
|
||||
mode="multiple"
|
||||
disabled
|
||||
style={{ width: '100%' }}
|
||||
placeholder="Please select"
|
||||
value={['chi_sim', 'chi_tra', 'eng']}
|
||||
options={options}
|
||||
/>
|
||||
</div>
|
||||
</SettingRow>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -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[]
|
||||
@ -22,8 +22,8 @@ const ocrSlice = createSlice({
|
||||
addOcrProvider(state, action: PayloadAction<OcrProvider>) {
|
||||
state.providers.push(action.payload)
|
||||
},
|
||||
removeOcrProvider(state, action: PayloadAction<OcrProvider>) {
|
||||
state.providers = state.providers.filter((provider) => provider.id !== action.payload.id)
|
||||
removeOcrProvider(state, action: PayloadAction<string>) {
|
||||
state.providers = state.providers.filter((provider) => provider.id !== action.payload)
|
||||
},
|
||||
updateOcrProvider(state, action: PayloadAction<Partial<OcrProvider>>) {
|
||||
const index = state.providers.findIndex((provider) => provider.id === action.payload.id)
|
||||
@ -31,13 +31,31 @@ const ocrSlice = createSlice({
|
||||
Object.assign(state.providers[index], action.payload)
|
||||
}
|
||||
},
|
||||
updateOcrProviderConfig(
|
||||
state,
|
||||
action: PayloadAction<{ id: string; update: Omit<Partial<OcrProviderConfig>, '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<ImageOcrProvider>) {
|
||||
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
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import Tesseract from 'tesseract.js'
|
||||
|
||||
import { FileMetadata, ImageFileMetadata, isImageFile } from '.'
|
||||
|
||||
export const BuiltinOcrProviderIds = {
|
||||
@ -123,3 +125,18 @@ export type OcrResult = {
|
||||
export type OcrHandler = (file: SupportedOcrFile) => Promise<OcrResult>
|
||||
|
||||
export type OcrImageHandler = (file: ImageFileMetadata) => Promise<OcrResult>
|
||||
|
||||
// Tesseract Types
|
||||
export type OcrTesseractConfig = OcrProviderConfig & {
|
||||
langs: Record<TesseractLangCode, boolean>
|
||||
}
|
||||
|
||||
export type OcrTesseractProvider = BuiltinOcrProvider & {
|
||||
config: OcrTesseractConfig
|
||||
}
|
||||
|
||||
export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => {
|
||||
return p.id === BuiltinOcrProviderIds.tesseract
|
||||
}
|
||||
|
||||
export type TesseractLangCode = Tesseract.LanguageCode
|
||||
|
||||
21
yarn.lock
21
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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user