mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
feat: Image OCR (#9409)
* build: 添加 tesseract.js 及其类型定义依赖
* feat(ocr): 添加OCR类型定义文件以支持OCR功能扩展
* feat(ocr): 添加 Tesseract OCR 提供程序配置
* feat(ocr): 添加Tesseract.js的logo
* refactor(settings): 重构文档预处理设置模块结构
将PreprocessSettings重命名为DocProcessSettings并调整文件结构
更新相关路由和组件引用以保持功能一致性
* refactor(config): 重命名OCR_PROVIDER_CONFIG为BUILTIN_OCR_PROVIDERS以更准确描述用途
* refactor(ocr): 更改文件名
* refactor(ocr): 将获取OCR提供商logo的功能移动到utils目录
将getOcrProviderLogo函数从config/ocr.ts移动到utils/ocr.ts,保持功能集中
* refactor(ocr): 重构OCR配置结构以支持默认提供者
将内置OCR提供者数组重构为单独定义的常量,并添加默认OCR提供者映射。这提高了代码的可维护性并支持未来扩展。
* feat(store): 添加OCR状态管理切片
实现OCR提供商的增删改查功能,使用Redux Toolkit管理OCR相关状态
* feat(types): 添加图片文件类型守卫函数
添加 ImageFileMetadata 类型和 isImageFile 类型守卫函数,用于检查文件是否为图片类型
* feat(ocr): 添加对OCR支持文件类型的类型定义和校验函数
添加SupportedOcrFileType类型和isSupportedOcrFileType校验函数
添加SupportedOcrFile类型和isSupportedOcrFile校验函数
* feat(ocr): 添加OCR功能支持
实现基于Tesseract的OCR功能,包括文件类型检查、服务接口和IPC通信
新增OCR相关类型定义和服务实现
* refactor(OcrService): 更新日志上下文为'main:OcrService'
* feat(ocr): 添加OCR服务基础功能
实现OCR服务的基础功能,通过调用window.api.ocr接口处理支持的文件类型
* feat(store): 添加ocr模块到redux store
* feat(ocr): 添加OCR功能支持及文件类型校验
添加OCR功能钩子useOcr,支持图片文件识别
添加不支持文件类型的错误提示国际化文案
* refactor(ocr): 重命名updatePreprocessProvider为updateOcrProvider以保持命名一致性
* feat(ocr): 添加设置图片OCR提供商的功能
* refactor(ocr): 统一OCR类型导入路径
将所有OCR相关类型从'@renderer/types/ocr'改为从'@renderer/types'或'@types'导入
优化DEFAULT_OCR_PROVIDER类型定义
* feat(store): 更新持久化存储版本并添加OCR配置迁移
添加137版本迁移逻辑,初始化OCR提供者和默认图像提供者配置
* feat(ocr): 添加OCR服务设置界面及提供商选择功能
实现OCR服务设置界面,包含图片OCR提供商的选择功能
修复ocr.ts中imageProvider的类型定义
添加相关国际化文本
* fix(ocr): 添加图像大小检查并优化错误处理
检查图像文件大小是否超过50MB限制
使用buffer读取文件替代直接路径识别
简化错误处理逻辑,直接抛出原始错误
* feat(OCR服务): 支持base64字符串作为OCR输入
扩展tesseractOcr函数以接受base64字符串或图像文件作为输入
* build: 将 tesseract.js 从 devDependencies 移至 dependencies
确保生产环境能正确使用 tesseract.js 功能
* refactor(ocr): 将Tesseract服务文件移动到tesseract子目录并更新配置
* refactor(TesseractService): 添加日志记录并更新worker配置
添加loggerService用于记录worker日志,并更新createWorker配置以使用自定义logger
* feat(i18n): 添加OCR功能的多语言支持
* refactor(preload): 移动OCR类型定义到共享类型文件
将OCR相关的类型定义(OcrProvider, OcrResult, SupportedOcrFile)从渲染进程类型文件移动到共享类型文件@types,以提高代码复用性和维护性
* refactor(ocr): 修改tesseractOcr返回完整识别结果而非仅文本
返回完整识别结果以便后续处理使用更多OCR信息,同时简化imageOcr中的条件判断逻辑
* fix(ocr): 修复文件类型与OCR提供者能力不匹配时的错误抛出位置
将错误抛出语句移至else分支
* refactor(ocr): 简化 DEFAULT_OCR_PROVIDER 的类型定义
* fix(ocr): 改进OCR处理中的消息管理和错误处理
在useOcr钩子中统一管理OCR处理的消息提示,并完善错误处理逻辑
移除TranslatePage中重复的消息管理代码,简化OCR处理流程
* feat(i18n): 添加OCR相关的错误和状态翻译文本
* fix(useOcr): 修复未支持文件类型错误抛出位置
将不支持的OCR文件类型错误抛出逻辑移至条件判断内
* refactor(ocr): ocrImage实现使用OcrService并更新日志上下文
将ocrImage函数从useOcr钩子移动到OcrService中,提高代码复用性
更新日志服务上下文从'main'改为'renderer'以更准确反映模块位置
* style(TabContainer): 移除多余的空行并保持代码整洁
* refactor(ocr): 简化OCR文件类型检查逻辑
使用现有的isImageFile函数替代冗余的类型检查逻辑,提高代码复用性
* fix: 将迁移错误日志从136更新为137
* feat(ocr): enhance Tesseract service with language support and worker management
- Added support for multiple Tesseract languages: Chinese (Simplified and Traditional) and English.
- Refactored Tesseract worker management into a class for better encapsulation and reuse.
- Introduced methods to dynamically determine language path based on IP country and manage worker lifecycle.
* update cn url
* support cn data
* change to asyn
* use register design mode
* add type
* use bind function
* refactor(ipc): 简化OCR处理程序参数
* refactor(ocr): 修改ocrProviderCapabilityRecord类型定义
允许只定义部分能力
* refactor(ocr): 将Tesseract相关配置移至服务内部
将语言列表和下载URL常量从共享配置移至Tesseract服务内部
使用常量定义图片大小阈值以提高可读性
* refactor(ocr): 统一使用 SupportedOcrFile 类型替换 FileMetadata
更新 OCR 服务及其 Tesseract 实现,使用 SupportedOcrFile 类型替代原有的 FileMetadata 类型,以提高类型安全性和一致性。同时在 OcrService 中添加重复注册的警告日志。
* refactor(ocr): 重构OCR类型定义以支持模型和API配置
将OCR提供者配置拆分为独立类型,增加模型能力记录和API配置类型检查
添加OCR处理程序类型定义,为未来扩展提供更好的类型支持
* refactor(OcrService): 移除重复的OcrHandler类型定义
已在@types中定义OcrHandler类型,移除重复定义以提高代码一致性
* refactor(ocr): 将OcrService移动到ocr目录下并更新引用路径
* feat(ocr): 添加OCR API客户端工厂及示例实现
实现OCR API客户端工厂模式,支持根据不同提供商创建对应的客户端
新增OcrBaseApiClient作为基础类,提供通用功能
添加OcrExampleApiClient作为示例实现
修改OcrService以使用新的客户端工厂
* refactor(ocr): 添加日志记录以跟踪OCR文件处理
在OCR服务中添加日志记录功能,便于跟踪文件处理过程
* fix(deps): 更新 tesseract.js 依赖并添加补丁文件
修复 tesseract.js 类型定义问题并添加语言常量支持
* refactor(ocr): 移除注释掉的tesseract语言映射代码
使用Tesseract.js的LanguageCode类型替代硬编码的语言列表,提高类型安全性
* feat(ocr): 添加 Tesseract OCR 配置类型
* refactor(OCR设置): 重命名OcrImageProviderSettings为OcrImageSettings并优化代码结构
* refactor(ocr): 将 Tesseract 相关类型移动到文件底部以改善代码组织
* feat(ocr): 添加 Tesseract OCR 提供者类型检查函数
* feat(ocr): 添加更新OCR提供者配置的功能
* feat: 添加OCR提供者钩子函数
实现useOcrProvider钩子用于获取和更新OCR提供者配置
* refactor(ocr): 修改removeOcrProvider参数为字符串id
简化removeOcrProvider方法的参数类型,直接使用字符串id进行过滤,提高代码简洁性
* refactor(ocr): 将内置OCR提供者从数组改为映射结构
重构OCR配置模块,使用映射结构存储内置OCR提供者以便于扩展和维护
* refactor(ocr): 将BUILTIN_OCR_PROVIDERS改为只读数组
使用Object.freeze确保数组不可变,提高代码安全性
* feat(ocr): 添加OCR提供者管理功能并改进错误处理
添加useOcrProviders钩子用于管理OCR提供者的添加和删除
当内置OCR提供者不存在时自动恢复默认配置
改进错误提示信息并增加国际化支持
* Revert "refactor(ocr): 将BUILTIN_OCR_PROVIDERS改为只读数组"
This reverts commit f23e37941a.
* feat(ocr): 为Tesseract OCR添加多语言支持配置
添加对简体中文、繁体中文和英文的语言支持配置,扩展OCR功能以满足多语言识别需求
* refactor(types): 将Tesseract.LanguageCode重命名为TesseractLangCode以提高可读性
* feat(OCR设置): 添加OCR提供商设置组件及状态管理
新增OCR提供商设置组件,支持显示当前选择的OCR提供商信息
在OCR图片设置中添加状态管理,同步提供商选择到父组件
添加Tesseract OCR设置组件,支持多语言选择(暂不可用)
* fix(DocProcessSettings): 修复OCR语言选择默认值问题
* feat(i18n): 添加OCR提供商相关错误和警告的翻译
* fix(ocr): 将 Tesseract 语言配置类型改为部分
* fix(ocr): 修复ocrImage函数未使用await导致的问题
* fix(ocr): 修复迁移配置中ocr状态的初始化方式
将分散的属性赋值改为对象整体赋值,避免潜在的属性丢失问题
* chore: 移除不再使用的@types/tesseract.js依赖
* refactor(OCR设置): 添加错误边界处理并移除无用注释
在OCR设置组件中添加ErrorBoundary以处理潜在错误
移除OcrTesseractSettings中的TODO注释
* build: 添加 sharp 依赖以支持图片处理功能
* refactor(ocr): 添加OCR图像预处理功能并优化TesseractService
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* refactor(ocr): 移除独立的灰度处理模块并改进预处理流程
将灰度处理功能直接集成到OCR预处理中,不再需要单独的image模块
添加normalise和threshold处理以提升OCR识别效果
* improve image preprocess
---------
Co-authored-by: beyondkmp <beyondkmp@gmail.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
3d7a64a11d
commit
0af5a85f67
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,6 +79,7 @@
|
||||
"officeparser": "^4.2.0",
|
||||
"os-proxy-config": "^1.1.2",
|
||||
"selection-hook": "^1.0.11",
|
||||
"tesseract.js": "patch:tesseract.js@npm%3A6.0.1#~/.yarn/patches/tesseract.js-npm-6.0.1-2562a7e46d.patch",
|
||||
"turndown": "7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -257,6 +258,7 @@
|
||||
"remove-markdown": "^0.6.2",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.88.0",
|
||||
"sharp": "^0.34.3",
|
||||
"shiki": "^3.9.1",
|
||||
"strict-url-sanitise": "^0.0.1",
|
||||
"string-width": "^7.2.0",
|
||||
@ -296,7 +298,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": {
|
||||
|
||||
@ -281,5 +281,8 @@ export enum IpcChannel {
|
||||
TRACE_ADD_STREAM_MESSAGE = 'trace:addStreamMessage',
|
||||
|
||||
// CodeTools
|
||||
CodeTools_Run = 'code-tools:run'
|
||||
CodeTools_Run = 'code-tools:run',
|
||||
|
||||
// OCR
|
||||
OCR_ocr = 'ocr:ocr'
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import { openTraceWindow, setTraceWindowTitle } from './services/NodeTraceServic
|
||||
import NotificationService from './services/NotificationService'
|
||||
import * as NutstoreService from './services/NutstoreService'
|
||||
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||
import { ocrService } from './services/ocr/OcrService'
|
||||
import { proxyManager } from './services/ProxyManager'
|
||||
import { pythonService } from './services/PythonService'
|
||||
import { FileServiceManager } from './services/remotefile/FileServiceManager'
|
||||
@ -709,4 +710,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||
|
||||
// CodeTools
|
||||
ipcMain.handle(IpcChannel.CodeTools_Run, codeToolsService.run)
|
||||
|
||||
// OCR
|
||||
ipcMain.handle(IpcChannel.OCR_ocr, (_, ...args: Parameters<typeof ocrService.ocr>) => ocrService.ocr(...args))
|
||||
}
|
||||
|
||||
34
src/main/services/ocr/OcrService.ts
Normal file
34
src/main/services/ocr/OcrService.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { BuiltinOcrProviderIds, OcrHandler, OcrProvider, OcrResult, SupportedOcrFile } from '@types'
|
||||
|
||||
import { tesseractService } from './tesseract/TesseractService'
|
||||
|
||||
const logger = loggerService.withContext('OcrService')
|
||||
|
||||
export class OcrService {
|
||||
private registry: Map<string, OcrHandler> = new Map()
|
||||
|
||||
register(providerId: string, handler: OcrHandler): void {
|
||||
if (this.registry.has(providerId)) {
|
||||
logger.warn(`Provider ${providerId} has existing handler. Overwrited.`)
|
||||
}
|
||||
this.registry.set(providerId, handler)
|
||||
}
|
||||
|
||||
unregister(providerId: string): void {
|
||||
this.registry.delete(providerId)
|
||||
}
|
||||
|
||||
public async ocr(file: SupportedOcrFile, provider: OcrProvider): Promise<OcrResult> {
|
||||
const handler = this.registry.get(provider.id)
|
||||
if (!handler) {
|
||||
throw new Error(`Provider ${provider.id} is not registered`)
|
||||
}
|
||||
return handler(file)
|
||||
}
|
||||
}
|
||||
|
||||
export const ocrService = new OcrService()
|
||||
|
||||
// Register built-in providers
|
||||
ocrService.register(BuiltinOcrProviderIds.tesseract, tesseractService.ocr.bind(tesseractService))
|
||||
82
src/main/services/ocr/tesseract/TesseractService.ts
Normal file
82
src/main/services/ocr/tesseract/TesseractService.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { getIpCountry } from '@main/utils/ipService'
|
||||
import { loadOcrImage } from '@main/utils/ocr'
|
||||
import { MB } from '@shared/config/constant'
|
||||
import { ImageFileMetadata, isImageFile, OcrResult, SupportedOcrFile } from '@types'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import Tesseract, { createWorker, LanguageCode } from 'tesseract.js'
|
||||
|
||||
const logger = loggerService.withContext('TesseractService')
|
||||
|
||||
// config
|
||||
const MB_SIZE_THRESHOLD = 50
|
||||
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/'
|
||||
}
|
||||
|
||||
export class TesseractService {
|
||||
private worker: Tesseract.Worker | null = null
|
||||
|
||||
async getWorker(): Promise<Tesseract.Worker> {
|
||||
if (!this.worker) {
|
||||
// for now, only support limited languages
|
||||
this.worker = await createWorker(tesseractLangs, undefined, {
|
||||
langPath: await this._getLangPath(),
|
||||
cachePath: await this._getCacheDir(),
|
||||
gzip: false,
|
||||
logger: (m) => logger.debug('From worker', m)
|
||||
})
|
||||
}
|
||||
return this.worker
|
||||
}
|
||||
|
||||
async imageOcr(file: ImageFileMetadata): Promise<OcrResult> {
|
||||
const worker = await this.getWorker()
|
||||
const stat = await fs.promises.stat(file.path)
|
||||
if (stat.size > MB_SIZE_THRESHOLD * MB) {
|
||||
throw new Error(`This image is too large (max ${MB_SIZE_THRESHOLD}MB)`)
|
||||
}
|
||||
const buffer = await loadOcrImage(file)
|
||||
const result = await worker.recognize(buffer)
|
||||
return { text: result.data.text }
|
||||
}
|
||||
|
||||
async ocr(file: SupportedOcrFile): Promise<OcrResult> {
|
||||
if (!isImageFile(file)) {
|
||||
throw new Error('Only image files are supported currently')
|
||||
}
|
||||
return this.imageOcr(file)
|
||||
}
|
||||
|
||||
private async _getLangPath(): Promise<string> {
|
||||
const country = await getIpCountry()
|
||||
return country.toLowerCase() === 'cn' ? TesseractLangsDownloadUrl.CN : TesseractLangsDownloadUrl.GLOBAL
|
||||
}
|
||||
|
||||
private async _getCacheDir(): Promise<string> {
|
||||
const cacheDir = path.join(app.getPath('userData'), 'tesseract')
|
||||
// use access to check if the directory exists
|
||||
if (
|
||||
!(await fs.promises
|
||||
.access(cacheDir, fs.constants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false))
|
||||
) {
|
||||
await fs.promises.mkdir(cacheDir, { recursive: true })
|
||||
}
|
||||
return cacheDir
|
||||
}
|
||||
|
||||
async dispose(): Promise<void> {
|
||||
if (this.worker) {
|
||||
await this.worker.terminate()
|
||||
this.worker = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const tesseractService = new TesseractService()
|
||||
29
src/main/utils/ocr.ts
Normal file
29
src/main/utils/ocr.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ImageFileMetadata } from '@types'
|
||||
import { readFile } from 'fs/promises'
|
||||
import sharp from 'sharp'
|
||||
|
||||
const preprocessImage = async (buffer: Buffer) => {
|
||||
return await sharp(buffer)
|
||||
.grayscale() // 转为灰度
|
||||
.normalize()
|
||||
.sharpen()
|
||||
.threshold(100) // 可能需要根据具体图片调整
|
||||
.png({ quality: 100 })
|
||||
.toBuffer()
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载并预处理OCR图像
|
||||
* @param file - 图像文件元数据
|
||||
* @returns 预处理后的图像Buffer
|
||||
* @throws {Error} 当文件不存在或无法读取时抛出错误;当图像预处理失败时抛出错误
|
||||
*
|
||||
* 预处理步骤:
|
||||
* 1. 读取图像文件
|
||||
* 2. 转换为灰度图
|
||||
* 3. 后续可扩展其他预处理步骤
|
||||
*/
|
||||
export const loadOcrImage = async (file: ImageFileMetadata): Promise<Buffer> => {
|
||||
const buffer = await readFile(file.path)
|
||||
return await preprocessImage(buffer)
|
||||
}
|
||||
@ -17,9 +17,12 @@ import {
|
||||
MemoryConfig,
|
||||
MemoryListOptions,
|
||||
MemorySearchOptions,
|
||||
OcrProvider,
|
||||
OcrResult,
|
||||
Provider,
|
||||
S3Config,
|
||||
Shortcut,
|
||||
SupportedOcrFile,
|
||||
ThemeMode,
|
||||
WebDavConfig
|
||||
} from '@types'
|
||||
@ -406,6 +409,10 @@ const api = {
|
||||
env: Record<string, string>,
|
||||
options?: { autoUpdateToLatest?: boolean }
|
||||
) => ipcRenderer.invoke(IpcChannel.CodeTools_Run, cliTool, model, directory, env, options)
|
||||
},
|
||||
ocr: {
|
||||
ocr: (file: SupportedOcrFile, provider: OcrProvider): Promise<OcrResult> =>
|
||||
ipcRenderer.invoke(IpcChannel.OCR_ocr, file, provider)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
src/renderer/src/assets/images/providers/Tesseract.js.png
Normal file
BIN
src/renderer/src/assets/images/providers/Tesseract.js.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
32
src/renderer/src/config/ocr.ts
Normal file
32
src/renderer/src/config/ocr.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import {
|
||||
BuiltinOcrProvider,
|
||||
BuiltinOcrProviderId,
|
||||
ImageOcrProvider,
|
||||
OcrProviderCapability,
|
||||
OcrTesseractProvider
|
||||
} from '@renderer/types'
|
||||
|
||||
const tesseract: BuiltinOcrProvider & ImageOcrProvider & OcrTesseractProvider = {
|
||||
id: 'tesseract',
|
||||
name: 'Tesseract',
|
||||
capabilities: {
|
||||
image: true
|
||||
},
|
||||
config: {
|
||||
langs: {
|
||||
chi_sim: true,
|
||||
chi_tra: true,
|
||||
eng: true
|
||||
}
|
||||
}
|
||||
} as const satisfies OcrTesseractProvider
|
||||
|
||||
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
|
||||
} as const satisfies Record<OcrProviderCapability, BuiltinOcrProvider>
|
||||
@ -1,12 +0,0 @@
|
||||
import MacOSLogo from '@renderer/assets/images/providers/macos.svg'
|
||||
|
||||
export function getOcrProviderLogo(providerId: string) {
|
||||
switch (providerId) {
|
||||
case 'system':
|
||||
return MacOSLogo
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export const OCR_PROVIDER_CONFIG = {}
|
||||
54
src/renderer/src/hooks/useOcr.ts
Normal file
54
src/renderer/src/hooks/useOcr.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { loggerService } from '@logger'
|
||||
import * as OcrService from '@renderer/services/ocr/OcrService'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { ImageFileMetadata, isImageFile, SupportedOcrFile } from '@renderer/types'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { formatErrorMessage } from '@renderer/utils/error'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logger = loggerService.withContext('useOcr')
|
||||
|
||||
export const useOcr = () => {
|
||||
const { t } = useTranslation()
|
||||
const imageProvider = useAppSelector((state) => state.ocr.imageProvider)
|
||||
|
||||
/**
|
||||
* 对图片文件进行OCR识别
|
||||
* @param image 图片文件元数据
|
||||
* @returns OCR识别结果的Promise
|
||||
* @throws OCR失败时抛出错误
|
||||
*/
|
||||
const ocrImage = async (image: ImageFileMetadata) => {
|
||||
return OcrService.ocr(image, imageProvider)
|
||||
}
|
||||
|
||||
/**
|
||||
* 对支持的文件进行OCR识别.
|
||||
* @param file 支持OCR的文件
|
||||
* @returns OCR识别结果的Promise
|
||||
* @throws 当文件类型不支持或OCR失败时抛出错误
|
||||
*/
|
||||
const ocr = async (file: SupportedOcrFile) => {
|
||||
const key = uuid()
|
||||
window.message.loading({ content: t('ocr.processing'), key, duration: 0 })
|
||||
// await to keep show loading message
|
||||
try {
|
||||
if (isImageFile(file)) {
|
||||
return await ocrImage(file)
|
||||
} else {
|
||||
// @ts-expect-error all types should be covered
|
||||
throw new Error(t('ocr.file.not_supported', { type: file.type }))
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to ocr.', e as Error)
|
||||
window.message.error(t('ocr.error.unknown') + ': ' + formatErrorMessage(e))
|
||||
throw e
|
||||
} finally {
|
||||
window.message.destroy(key)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ocr
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "If the response is successful, then only messages exceeding 30 seconds will trigger a reminder"
|
||||
},
|
||||
"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...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Reverted to {{name}}, which may cause issues"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "The time in minutes to keep the connection alive, default is 5 minutes.",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "Settings",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"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",
|
||||
"title": "OCR service"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "Document Processing Provider",
|
||||
"provider_placeholder": "Choose a document processing provider",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "応答が成功した場合、30秒を超えるメッセージのみに通知を行います"
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "組み込みプロバイダーは削除できません",
|
||||
"existing": "プロバイダーはすでに存在します",
|
||||
"not_found": "OCRプロバイダーが存在しません",
|
||||
"update_failed": "更新構成に失敗しました"
|
||||
},
|
||||
"unknown": "OCR処理中にエラーが発生しました"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "サポートされていないファイルタイプ {{type}}"
|
||||
},
|
||||
"processing": "OCR処理中...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "{{name}} に戻されました。これにより問題が発生する可能性があります。"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "モデルがメモリに保持される時間(デフォルト:5分)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "設定",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"error": {
|
||||
"provider_not_found": "該提供者は存在しません"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "サポートされている言語",
|
||||
"temp_tooltip": "現在のところ、中国語と英語のみをサポートしています"
|
||||
},
|
||||
"title": "画像"
|
||||
},
|
||||
"image_provider": "OCRサービスプロバイダー",
|
||||
"title": "OCRサービス"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "プレプロセスプロバイダー",
|
||||
"provider_placeholder": "前処理プロバイダーを選択してください",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "Если ответ успешен, уведомление выдается только по сообщениям, превышающим 30 секунд"
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Не удается удалить встроенного поставщика",
|
||||
"existing": "Поставщик уже существует",
|
||||
"not_found": "Поставщик OCR отсутствует",
|
||||
"update_failed": "Обновление конфигурации не удалось"
|
||||
},
|
||||
"unknown": "Произошла ошибка в процессе распознавания текста"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Неподдерживаемый тип файла {{type}}"
|
||||
},
|
||||
"processing": "Обработка OCR...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Возвращено к {{name}}, это может вызвать проблемы"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "Время в минутах, в течение которого модель остается активной, по умолчанию 5 минут.",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "Настройки",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"error": {
|
||||
"provider_not_found": "Поставщик не существует"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Поддерживаемые языки",
|
||||
"temp_tooltip": "На данный момент поддерживаются только китайский и английский языки"
|
||||
},
|
||||
"title": "Изображение"
|
||||
},
|
||||
"image_provider": "Поставщик услуг OCR",
|
||||
"title": "OCR-сервис"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "Поставщик обработки документов",
|
||||
"provider_placeholder": "Выберите поставщика услуг обработки документов",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "如果响应成功,则只针对超过30秒的消息进行提醒"
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "不能删除内置提供商",
|
||||
"existing": "提供商已存在",
|
||||
"not_found": "OCR 提供商不存在",
|
||||
"update_failed": "更新配置失败"
|
||||
},
|
||||
"unknown": "OCR 过程发生错误"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "不支持的文件类型 {{type}}"
|
||||
},
|
||||
"processing": "OCR 处理中...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "已回退到 {{name}},这可能导致问题"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "对话后模型在内存中保持的时间(默认:5 分钟)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "设置",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"error": {
|
||||
"provider_not_found": "该提供商不存在"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "支持的语言",
|
||||
"temp_tooltip": "目前暂时只支持中文和英文"
|
||||
},
|
||||
"title": "图片"
|
||||
},
|
||||
"image_provider": "OCR 服务提供商",
|
||||
"title": "OCR 服务"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "文档处理服务商",
|
||||
"provider_placeholder": "选择一个文档处理服务商",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "如果回應成功,則只針對超過30秒的訊息發出提醒"
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "不能刪除內建提供者",
|
||||
"existing": "提供商已存在",
|
||||
"not_found": "OCR 提供商不存在",
|
||||
"update_failed": "更新配置失敗"
|
||||
},
|
||||
"unknown": "OCR過程發生錯誤"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "不支持的文件類型 {{type}}"
|
||||
},
|
||||
"processing": "OCR 處理中...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "已回退到 {{name}},這可能導致問題"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "對話後模型在記憶體中保持的時間(預設為 5 分鐘)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "設定",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"error": {
|
||||
"provider_not_found": "該提供商不存在"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "支援的語言",
|
||||
"temp_tooltip": "目前暫時只支援中文和英文"
|
||||
},
|
||||
"title": "圖片"
|
||||
},
|
||||
"image_provider": "OCR 服務提供商",
|
||||
"title": "OCR 服務"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "文件處理供應商",
|
||||
"provider_placeholder": "選擇一個文件處理供應商",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "Εάν η απάντηση είναι επιτυχής, η ειδοποίηση εμφανίζεται μόνο για μηνύματα που υπερβαίνουν τα 30 δευτερόλεπτα"
|
||||
},
|
||||
"ocr": {
|
||||
"error": {
|
||||
"provider": {
|
||||
"cannot_remove_builtin": "Δεν είναι δυνατή η διαγραφή του ενσωματωμένου παρόχου",
|
||||
"existing": "Ο πάροχος υπηρεσιών υπάρχει ήδη",
|
||||
"not_found": "Ο πάροχος OCR δεν υπάρχει",
|
||||
"update_failed": "Αποτυχία ενημέρωσης της διαμόρφωσης"
|
||||
},
|
||||
"unknown": "Η διαδικασία OCR εμφάνισε σφάλμα"
|
||||
},
|
||||
"file": {
|
||||
"not_supported": "Μη υποστηριζόμενος τύπος αρχείου {{type}}"
|
||||
},
|
||||
"processing": "Η επεξεργασία OCR βρίσκεται σε εξέλιξη...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Επαναφέρθηκε στο {{name}}, το οποίο μπορεί να προκαλέσει προβλήματα"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "Χρόνος που ο μοντέλος διατηρείται στη μνήμη μετά τη συζήτηση (προεπιλογή: 5 λεπτά)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "Ρυθμίσεις",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"error": {
|
||||
"provider_not_found": "Ο πάροχος δεν υπάρχει"
|
||||
},
|
||||
"tesseract": {
|
||||
"langs": "Υποστηριζόμενες γλώσσες",
|
||||
"temp_tooltip": "Προς το παρόν υποστηρίζονται μόνο η κινεζική και η αγγλική γλώσσα"
|
||||
},
|
||||
"title": "Εικόνα"
|
||||
},
|
||||
"image_provider": "Πάροχοι υπηρεσιών OCR",
|
||||
"title": "Υπηρεσία OCR"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "πάροχος υπηρεσιών προεπεξεργασίας εγγράφων",
|
||||
"provider_placeholder": "Επιλέξτε έναν πάροχο υπηρεσιών προεπεξεργασίας εγγράφων",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "Si la respuesta es exitosa, solo se enviará un recordatorio para mensajes que excedan los 30 segundos"
|
||||
},
|
||||
"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...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Se ha revertido a {{name}}, lo que podría causar problemas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "Tiempo que el modelo permanece en memoria después de la conversación (por defecto: 5 minutos)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "Configuración",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"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",
|
||||
"title": "Servicio OCR"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "Proveedor de servicios de preprocesamiento de documentos",
|
||||
"provider_placeholder": "Seleccionar un proveedor de servicios de preprocesamiento de documentos",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "Si la réponse est réussie, un rappel est envoyé uniquement pour les messages dépassant 30 secondes"
|
||||
},
|
||||
"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...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Revenu à {{name}}, ce qui pourrait entraîner des problèmes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "Le temps pendant lequel le modèle reste en mémoire après la conversation (par défaut : 5 minutes)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "Paramètres",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"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",
|
||||
"title": "Service OCR"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "fournisseur de services de prétraitement de documents",
|
||||
"provider_placeholder": "Choisissez un prestataire de traitement de documents",
|
||||
|
||||
@ -1574,6 +1574,26 @@
|
||||
},
|
||||
"tip": "Se a resposta for bem-sucedida, lembrete apenas para mensagens que excedam 30 segundos"
|
||||
},
|
||||
"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...",
|
||||
"warning": {
|
||||
"provider": {
|
||||
"fallback": "Revertido para {{name}}, o que pode causar problemas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ollama": {
|
||||
"keep_alive_time": {
|
||||
"description": "Tempo que o modelo permanece na memória após a conversa (padrão: 5 minutos)",
|
||||
@ -3498,6 +3518,20 @@
|
||||
},
|
||||
"title": "Configurações",
|
||||
"tool": {
|
||||
"ocr": {
|
||||
"image": {
|
||||
"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",
|
||||
"title": "Serviço OCR"
|
||||
},
|
||||
"preprocess": {
|
||||
"provider": "prestador de serviços de pré-processamento de documentos",
|
||||
"provider_placeholder": "Escolha um fornecedor de pré-processamento de documentos",
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { setImageOcrProvider } from '@renderer/store/ocr'
|
||||
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('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) {
|
||||
logger.error(`Failed to find image provider by id: ${id}`)
|
||||
window.message.error(t('settings.tool.ocr.image.error.provider_not_found'))
|
||||
return
|
||||
}
|
||||
|
||||
setProvider(provider)
|
||||
dispatch(setImageOcrProvider(provider))
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitle>{t('settings.tool.ocr.image_provider')}</SettingRowTitle>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<Select
|
||||
value={imageProvider.id}
|
||||
style={{ width: '200px' }}
|
||||
onChange={(id: string) => updateImageProvider(id)}
|
||||
options={imageProviders.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.name
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
</SettingRow>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OcrImageSettings
|
||||
@ -0,0 +1,52 @@
|
||||
// import { loggerService } from '@logger'
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
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' }} />
|
||||
<ErrorBoundary>{getProviderSettings()}</ErrorBoundary>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ProviderName = styled.span`
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
`
|
||||
const ProviderLogo = styled(Avatar)`
|
||||
border: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
export default OcrProviderSettings
|
||||
@ -0,0 +1,42 @@
|
||||
import { PictureOutlined } from '@ant-design/icons'
|
||||
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { OcrProvider } from '@renderer/types'
|
||||
import { Tabs, TabsProps } from 'antd'
|
||||
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<OcrProvider>(imageProvider) // since default to image provider
|
||||
|
||||
const tabs: TabsProps['items'] = [
|
||||
{
|
||||
key: 'image',
|
||||
label: t('settings.tool.ocr.image.title'),
|
||||
icon: <PictureOutlined />,
|
||||
children: <OcrImageSettings setProvider={setProvider} />
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SettingGroup theme={themeMode}>
|
||||
<SettingTitle>{t('settings.tool.ocr.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
<Tabs defaultActiveKey="image" items={tabs} />
|
||||
</SettingGroup>
|
||||
<SettingGroup theme={themeMode}>
|
||||
<OcrProviderSettings provider={provider} />
|
||||
</SettingGroup>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
export default OcrSettings
|
||||
@ -0,0 +1,51 @@
|
||||
// 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)
|
||||
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -5,8 +5,8 @@ import { Select } from 'antd'
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
import PreprocessProviderSettings from './PreprocessSettings'
|
||||
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||
import PreprocessProviderSettings from './PreprocessProviderSettings'
|
||||
|
||||
const PreprocessSettings: FC = () => {
|
||||
const { preprocessProviders } = usePreprocessProviders()
|
||||
@ -25,7 +25,7 @@ const PreprocessSettings: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingContainer theme={themeMode}>
|
||||
<>
|
||||
<SettingGroup theme={themeMode}>
|
||||
<SettingTitle>{t('settings.tool.preprocess.title')}</SettingTitle>
|
||||
<SettingDivider />
|
||||
@ -52,7 +52,7 @@ const PreprocessSettings: FC = () => {
|
||||
<PreprocessProviderSettings provider={selectedProvider} />
|
||||
</SettingGroup>
|
||||
)}
|
||||
</SettingContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default PreprocessSettings
|
||||
18
src/renderer/src/pages/settings/DocProcessSettings/index.tsx
Normal file
18
src/renderer/src/pages/settings/DocProcessSettings/index.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||
import { FC } from 'react'
|
||||
|
||||
import { SettingContainer } from '..'
|
||||
import OcrSettings from './OcrSettings'
|
||||
import PreprocessSettings from './PreprocessSettings'
|
||||
|
||||
const DocProcessSettings: FC = () => {
|
||||
const { theme: themeMode } = useTheme()
|
||||
|
||||
return (
|
||||
<SettingContainer theme={themeMode}>
|
||||
<OcrSettings />
|
||||
<PreprocessSettings />
|
||||
</SettingContainer>
|
||||
)
|
||||
}
|
||||
export default DocProcessSettings
|
||||
@ -26,10 +26,10 @@ import styled from 'styled-components'
|
||||
import AboutSettings from './AboutSettings'
|
||||
import DataSettings from './DataSettings/DataSettings'
|
||||
import DisplaySettings from './DisplaySettings/DisplaySettings'
|
||||
import DocProcessSettings from './DocProcessSettings'
|
||||
import GeneralSettings from './GeneralSettings'
|
||||
import MCPSettings from './MCPSettings'
|
||||
import MemorySettings from './MemorySettings'
|
||||
import PreprocessSettings from './PreprocessSettings'
|
||||
import ProvidersList from './ProviderSettings'
|
||||
import QuickAssistantSettings from './QuickAssistantSettings'
|
||||
import QuickPhraseSettings from './QuickPhraseSettings'
|
||||
@ -100,8 +100,8 @@ const SettingsPage: FC = () => {
|
||||
{t('memory.title')}
|
||||
</MenuItem>
|
||||
</MenuItemLink>
|
||||
<MenuItemLink to="/settings/preprocess">
|
||||
<MenuItem className={isRoute('/settings/preprocess')}>
|
||||
<MenuItemLink to="/settings/docprocess">
|
||||
<MenuItem className={isRoute('/settings/docprocess')}>
|
||||
<FileCode size={18} />
|
||||
{t('settings.tool.preprocess.title')}
|
||||
</MenuItem>
|
||||
@ -144,7 +144,7 @@ const SettingsPage: FC = () => {
|
||||
<Route path="provider" element={<ProvidersList />} />
|
||||
<Route path="model" element={<ModelSettings />} />
|
||||
<Route path="websearch" element={<WebSearchSettings />} />
|
||||
<Route path="preprocess" element={<PreprocessSettings />} />
|
||||
<Route path="docprocess" element={<DocProcessSettings />} />
|
||||
<Route path="quickphrase" element={<QuickPhraseSettings />} />
|
||||
<Route path="mcp/*" element={<MCPSettings />} />
|
||||
<Route path="memory" element={<MemorySettings />} />
|
||||
|
||||
23
src/renderer/src/services/ocr/OcrService.ts
Normal file
23
src/renderer/src/services/ocr/OcrService.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { isOcrApiProvider, OcrProvider, OcrResult, SupportedOcrFile } from '@renderer/types'
|
||||
|
||||
import { OcrApiClientFactory } from './clients/OcrApiClientFactory'
|
||||
|
||||
const logger = loggerService.withContext('renderer:OcrService')
|
||||
|
||||
/**
|
||||
* ocr a file
|
||||
* @param file any supported file
|
||||
* @param provider ocr provider
|
||||
* @returns ocr result
|
||||
* @throws {Error}
|
||||
*/
|
||||
export const ocr = async (file: SupportedOcrFile, provider: OcrProvider): Promise<OcrResult> => {
|
||||
logger.info(`ocr file ${file.path}`)
|
||||
if (isOcrApiProvider(provider)) {
|
||||
const client = OcrApiClientFactory.create(provider)
|
||||
return client.ocr(file)
|
||||
} else {
|
||||
return window.api.ocr.ocr(file, provider)
|
||||
}
|
||||
}
|
||||
28
src/renderer/src/services/ocr/clients/OcrApiClientFactory.ts
Normal file
28
src/renderer/src/services/ocr/clients/OcrApiClientFactory.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { OcrApiProvider } from '@renderer/types'
|
||||
|
||||
import { OcrBaseApiClient } from './OcrBaseApiClient'
|
||||
import { OcrExampleApiClient } from './OcrExampleApiClient'
|
||||
|
||||
const logger = loggerService.withContext('OcrApiClientFactory')
|
||||
|
||||
export class OcrApiClientFactory {
|
||||
/**
|
||||
* Create an ApiClient instance for the given provider
|
||||
* 为给定的提供者创建ApiClient实例
|
||||
*/
|
||||
static create(provider: OcrApiProvider): OcrBaseApiClient {
|
||||
logger.debug(`Creating ApiClient for provider:`, {
|
||||
id: provider.id,
|
||||
config: provider.config
|
||||
})
|
||||
|
||||
let instance: OcrBaseApiClient
|
||||
|
||||
// Extend other clients here
|
||||
// eslint-disable-next-line prefer-const
|
||||
instance = new OcrExampleApiClient(provider)
|
||||
|
||||
return instance
|
||||
}
|
||||
}
|
||||
43
src/renderer/src/services/ocr/clients/OcrBaseApiClient.ts
Normal file
43
src/renderer/src/services/ocr/clients/OcrBaseApiClient.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { OcrApiProvider, OcrHandler } from '@renderer/types'
|
||||
|
||||
export abstract class OcrBaseApiClient {
|
||||
public provider: OcrApiProvider
|
||||
protected host: string
|
||||
protected apiKey: string
|
||||
|
||||
constructor(provider: OcrApiProvider) {
|
||||
this.provider = provider
|
||||
this.host = this.getHost()
|
||||
this.apiKey = this.getApiKey()
|
||||
}
|
||||
|
||||
abstract ocr: OcrHandler
|
||||
|
||||
// copy from BaseApiClient
|
||||
public getHost(): string {
|
||||
return this.provider.config.api.apiHost
|
||||
}
|
||||
|
||||
// copy from BaseApiClient
|
||||
public getApiKey() {
|
||||
const keys = this.provider.config.api.apiKey.split(',').map((key) => key.trim())
|
||||
const keyName = `ocr_provider:${this.provider.id}:last_used_key`
|
||||
|
||||
if (keys.length === 1) {
|
||||
return keys[0]
|
||||
}
|
||||
|
||||
const lastUsedKey = window.keyv.get(keyName)
|
||||
if (!lastUsedKey) {
|
||||
window.keyv.set(keyName, keys[0])
|
||||
return keys[0]
|
||||
}
|
||||
|
||||
const currentIndex = keys.indexOf(lastUsedKey)
|
||||
const nextIndex = (currentIndex + 1) % keys.length
|
||||
const nextKey = keys[nextIndex]
|
||||
window.keyv.set(keyName, nextKey)
|
||||
|
||||
return nextKey
|
||||
}
|
||||
}
|
||||
15
src/renderer/src/services/ocr/clients/OcrExampleApiClient.ts
Normal file
15
src/renderer/src/services/ocr/clients/OcrExampleApiClient.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { OcrApiProvider, SupportedOcrFile } from '@renderer/types'
|
||||
|
||||
import { OcrBaseApiClient } from './OcrBaseApiClient'
|
||||
|
||||
export type OcrExampleProvider = OcrApiProvider
|
||||
|
||||
export class OcrExampleApiClient extends OcrBaseApiClient {
|
||||
constructor(provider: OcrApiProvider) {
|
||||
super(provider)
|
||||
}
|
||||
|
||||
public ocr = async (file: SupportedOcrFile) => {
|
||||
return { text: `Example output: ${file.path}` }
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ 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'
|
||||
@ -55,14 +56,15 @@ const rootReducer = combineReducers({
|
||||
messages: newMessagesReducer,
|
||||
messageBlocks: messageBlocksReducer,
|
||||
inputTools: inputToolsReducer,
|
||||
translate
|
||||
translate,
|
||||
ocr
|
||||
})
|
||||
|
||||
const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 136,
|
||||
version: 137,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -3,6 +3,7 @@ import { nanoid } from '@reduxjs/toolkit'
|
||||
import { DEFAULT_CONTEXTCOUNT, DEFAULT_TEMPERATURE, isMac } from '@renderer/config/constant'
|
||||
import { DEFAULT_MIN_APPS } from '@renderer/config/minapps'
|
||||
import { isFunctionCallingModel, isNotSupportedTextDelta, SYSTEM_MODELS } from '@renderer/config/models'
|
||||
import { BUILTIN_OCR_PROVIDERS, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr'
|
||||
import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
|
||||
import {
|
||||
isSupportArrayContentProvider,
|
||||
@ -2174,6 +2175,18 @@ const migrateConfig = {
|
||||
logger.error('migrate 136 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'137': (state: RootState) => {
|
||||
try {
|
||||
state.ocr = {
|
||||
providers: BUILTIN_OCR_PROVIDERS,
|
||||
imageProvider: DEFAULT_OCR_PROVIDER.image
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 137 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
61
src/renderer/src/store/ocr.ts
Normal file
61
src/renderer/src/store/ocr.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { BUILTIN_OCR_PROVIDERS, DEFAULT_OCR_PROVIDER } from '@renderer/config/ocr'
|
||||
import { ImageOcrProvider, OcrProvider, OcrProviderConfig } from '@renderer/types'
|
||||
|
||||
export interface OcrState {
|
||||
providers: OcrProvider[]
|
||||
imageProvider: ImageOcrProvider
|
||||
}
|
||||
|
||||
const initialState: OcrState = {
|
||||
providers: BUILTIN_OCR_PROVIDERS,
|
||||
imageProvider: DEFAULT_OCR_PROVIDER.image
|
||||
}
|
||||
|
||||
const ocrSlice = createSlice({
|
||||
name: 'ocr',
|
||||
initialState,
|
||||
reducers: {
|
||||
setOcrProviders(state, action: PayloadAction<OcrProvider[]>) {
|
||||
state.providers = action.payload
|
||||
},
|
||||
addOcrProvider(state, action: PayloadAction<OcrProvider>) {
|
||||
state.providers.push(action.payload)
|
||||
},
|
||||
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)
|
||||
if (index !== -1) {
|
||||
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,
|
||||
updateOcrProviderConfig,
|
||||
setImageOcrProvider
|
||||
} = ocrSlice.actions
|
||||
|
||||
export default ocrSlice.reducer
|
||||
@ -100,3 +100,16 @@ export enum FileTypes {
|
||||
DOCUMENT = 'document',
|
||||
OTHER = 'other'
|
||||
}
|
||||
|
||||
export type ImageFileMetadata = FileMetadata & {
|
||||
type: FileTypes.IMAGE
|
||||
}
|
||||
|
||||
/**
|
||||
* 类型守卫函数,用于检查一个 FileMetadata 是否为图片文件元数据
|
||||
* @param file - 要检查的文件元数据
|
||||
* @returns 如果文件是图片类型则返回 true
|
||||
*/
|
||||
export const isImageFile = (file: FileMetadata): file is ImageFileMetadata => {
|
||||
return file.type === FileTypes.IMAGE
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ export * from './file'
|
||||
import type { FileMetadata } from './file'
|
||||
import type { Message } from './newMessage'
|
||||
|
||||
export * from './ocr'
|
||||
|
||||
export type Assistant = {
|
||||
id: string
|
||||
name: string
|
||||
|
||||
142
src/renderer/src/types/ocr.ts
Normal file
142
src/renderer/src/types/ocr.ts
Normal file
@ -0,0 +1,142 @@
|
||||
import Tesseract from 'tesseract.js'
|
||||
|
||||
import { FileMetadata, ImageFileMetadata, isImageFile } from '.'
|
||||
|
||||
export const BuiltinOcrProviderIds = {
|
||||
tesseract: 'tesseract'
|
||||
} as const
|
||||
|
||||
export type BuiltinOcrProviderId = keyof typeof BuiltinOcrProviderIds
|
||||
|
||||
export const isBuiltinOcrProviderId = (id: string): id is BuiltinOcrProviderId => {
|
||||
return Object.hasOwn(BuiltinOcrProviderIds, id)
|
||||
}
|
||||
|
||||
// extensible
|
||||
export const OcrProviderCapabilities = {
|
||||
image: 'image'
|
||||
} as const
|
||||
|
||||
export type OcrProviderCapability = keyof typeof OcrProviderCapabilities
|
||||
|
||||
export const isOcrProviderCapability = (cap: string): cap is OcrProviderCapability => {
|
||||
return Object.hasOwn(OcrProviderCapabilities, cap)
|
||||
}
|
||||
|
||||
export type OcrProviderCapabilityRecord = Partial<Record<OcrProviderCapability, boolean>>
|
||||
|
||||
// OCR models and providers share the same type definition.
|
||||
// A provider can offer capabilities to process multiple file types,
|
||||
// while a model belonging to that provider may be limited to processing only one specific file type.
|
||||
export type OcrModelCapabilityRecord = OcrProviderCapabilityRecord
|
||||
|
||||
export interface OcrModel {
|
||||
id: string
|
||||
name: string
|
||||
providerId: string
|
||||
capabilities: OcrModelCapabilityRecord
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend this type to define provider-specefic config types.
|
||||
*/
|
||||
export type OcrProviderApiConfig = {
|
||||
apiKey: string
|
||||
apiHost: string
|
||||
apiVersion?: string
|
||||
}
|
||||
|
||||
export const isOcrProviderApiConfig = (config: unknown): config is OcrProviderApiConfig => {
|
||||
return (
|
||||
typeof config === 'object' &&
|
||||
config !== null &&
|
||||
'apiKey' in config &&
|
||||
typeof config.apiKey === 'string' &&
|
||||
'apiHost' in config &&
|
||||
typeof config.apiHost === 'string' &&
|
||||
(!('apiVersion' in config) || typeof config.apiVersion === 'string')
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* For future. Model based ocr, api based ocr. May different api client.
|
||||
*
|
||||
* Extend this type to define provider-specific config types.
|
||||
*/
|
||||
export type OcrProviderConfig = {
|
||||
/** Not used for now. Could safely remove. */
|
||||
api?: OcrProviderApiConfig
|
||||
/** Not used for now. Could safely remove. */
|
||||
models?: OcrModel[]
|
||||
/** Not used for now. Could safely remove. */
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export type OcrProvider = {
|
||||
id: string
|
||||
name: string
|
||||
capabilities: OcrProviderCapabilityRecord
|
||||
config?: OcrProviderConfig
|
||||
}
|
||||
|
||||
export type OcrApiProvider = OcrProvider & {
|
||||
config: OcrProviderConfig & {
|
||||
api: OcrProviderApiConfig
|
||||
}
|
||||
}
|
||||
|
||||
export const isOcrApiProvider = (p: OcrProvider): p is OcrApiProvider => {
|
||||
return !!(p.config && p.config.api && isOcrProviderApiConfig(p.config.api))
|
||||
}
|
||||
|
||||
export type BuiltinOcrProvider = OcrProvider & {
|
||||
id: BuiltinOcrProviderId
|
||||
}
|
||||
|
||||
export const isBuiltinOcrProvider = (p: OcrProvider): p is BuiltinOcrProvider => {
|
||||
return isBuiltinOcrProviderId(p.id)
|
||||
}
|
||||
|
||||
// Not sure compatiable api endpoint exists. May not support custom ocr provider
|
||||
export type CustomOcrProvider = OcrProvider & {
|
||||
id: Exclude<string, BuiltinOcrProviderId>
|
||||
}
|
||||
|
||||
export type ImageOcrProvider = OcrProvider & {
|
||||
capabilities: OcrProviderCapabilityRecord & {
|
||||
[OcrProviderCapabilities.image]: true
|
||||
}
|
||||
}
|
||||
|
||||
export const isImageOcrProvider = (p: OcrProvider): p is ImageOcrProvider => {
|
||||
return p.capabilities.image === true
|
||||
}
|
||||
|
||||
export type SupportedOcrFile = ImageFileMetadata
|
||||
|
||||
export const isSupportedOcrFile = (file: FileMetadata): file is SupportedOcrFile => {
|
||||
return isImageFile(file)
|
||||
}
|
||||
|
||||
export type OcrResult = {
|
||||
text: string
|
||||
}
|
||||
|
||||
export type OcrHandler = (file: SupportedOcrFile) => Promise<OcrResult>
|
||||
|
||||
export type OcrImageHandler = (file: ImageFileMetadata) => Promise<OcrResult>
|
||||
|
||||
// Tesseract Types
|
||||
export type OcrTesseractConfig = OcrProviderConfig & {
|
||||
langs: Partial<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
|
||||
12
src/renderer/src/utils/ocr.ts
Normal file
12
src/renderer/src/utils/ocr.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import TesseractLogo from '@renderer/assets/images/providers/Tesseract.js.png'
|
||||
import { isBuiltinOcrProviderId } from '@renderer/types'
|
||||
|
||||
export function getOcrProviderLogo(providerId: string) {
|
||||
if (isBuiltinOcrProviderId(providerId)) {
|
||||
switch (providerId) {
|
||||
case 'tesseract':
|
||||
return TesseractLogo
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
398
yarn.lock
398
yarn.lock
@ -2953,7 +2953,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/runtime@npm:^1.4.5":
|
||||
"@emnapi/runtime@npm:^1.4.4, @emnapi/runtime@npm:^1.4.5":
|
||||
version: 1.4.5
|
||||
resolution: "@emnapi/runtime@npm:1.4.5"
|
||||
dependencies:
|
||||
@ -3524,6 +3524,207 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-darwin-arm64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-darwin-arm64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-darwin-arm64":
|
||||
optional: true
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-darwin-x64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-darwin-x64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-darwin-x64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-darwin-x64":
|
||||
optional: true
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.0"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-darwin-x64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.0"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linux-arm64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.0"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linux-arm@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linux-arm@npm:1.2.0"
|
||||
conditions: os=linux & cpu=arm & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.0"
|
||||
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linux-s390x@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.0"
|
||||
conditions: os=linux & cpu=s390x & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linux-x64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linux-x64@npm:1.2.0"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.0"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64@npm:1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.0"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-arm64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linux-arm64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-arm64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-arm64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-arm@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linux-arm@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-arm": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-arm":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=arm & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-ppc64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linux-ppc64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-ppc64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=ppc64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-s390x@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linux-s390x@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-s390x": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-s390x":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=s390x & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linux-x64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linux-x64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linux-x64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linux-x64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linuxmusl-arm64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linuxmusl-arm64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-linuxmusl-x64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-linuxmusl-x64@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-libvips-linuxmusl-x64":
|
||||
optional: true
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-wasm32@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-wasm32@npm:0.34.3"
|
||||
dependencies:
|
||||
"@emnapi/runtime": "npm:^1.4.4"
|
||||
conditions: cpu=wasm32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-arm64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-win32-arm64@npm:0.34.3"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-ia32@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-win32-ia32@npm:0.34.3"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@img/sharp-win32-x64@npm:0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "@img/sharp-win32-x64@npm:0.34.3"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@isaacs/cliui@npm:^8.0.2":
|
||||
version: 8.0.2
|
||||
resolution: "@isaacs/cliui@npm:8.0.2"
|
||||
@ -8631,11 +8832,13 @@ __metadata:
|
||||
rollup-plugin-visualizer: "npm:^5.12.0"
|
||||
sass: "npm:^1.88.0"
|
||||
selection-hook: "npm:^1.0.11"
|
||||
sharp: "npm:^0.34.3"
|
||||
shiki: "npm:^3.9.1"
|
||||
strict-url-sanitise: "npm:^0.0.1"
|
||||
string-width: "npm:^7.2.0"
|
||||
styled-components: "npm:^6.1.11"
|
||||
tar: "npm:^7.4.3"
|
||||
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"
|
||||
@ -9371,6 +9574,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bmp-js@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "bmp-js@npm:0.1.0"
|
||||
checksum: 10c0/c651bd5936dcf8d67900050fac14dcbe30baf87c3d21c58f4934fcdf46172e152a87d8c0c3ca25caa2b4b2c7780ef3b5fcc6cd20afd8f0351856cadb1bef9694
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"body-parser@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "body-parser@npm:2.2.0"
|
||||
@ -10139,7 +10349,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-string@npm:^1.6.0":
|
||||
"color-string@npm:^1.6.0, color-string@npm:^1.9.0":
|
||||
version: 1.9.1
|
||||
resolution: "color-string@npm:1.9.1"
|
||||
dependencies:
|
||||
@ -10168,6 +10378,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color@npm:^4.2.3":
|
||||
version: 4.2.3
|
||||
resolution: "color@npm:4.2.3"
|
||||
dependencies:
|
||||
color-convert: "npm:^2.0.1"
|
||||
color-string: "npm:^1.9.0"
|
||||
checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "color@npm:5.0.0"
|
||||
@ -11280,7 +11500,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"detect-libc@npm:^2.0.3":
|
||||
"detect-libc@npm:^2.0.3, detect-libc@npm:^2.0.4":
|
||||
version: 2.0.4
|
||||
resolution: "detect-libc@npm:2.0.4"
|
||||
checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c
|
||||
@ -14050,6 +14270,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"idb-keyval@npm:^6.2.0":
|
||||
version: 6.2.2
|
||||
resolution: "idb-keyval@npm:6.2.2"
|
||||
checksum: 10c0/b52f0d2937cc2ec9f1da536b0b5c0875af3043ca210714beaffead4ec1f44f2ad322220305fd024596203855224d9e3523aed83e971dfb62ddc21b5b1721aeef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "ieee754@npm:1.2.1"
|
||||
@ -14441,6 +14668,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-url@npm:^1.2.4":
|
||||
version: 1.2.4
|
||||
resolution: "is-url@npm:1.2.4"
|
||||
checksum: 10c0/0157a79874f8f95fdd63540e3f38c8583c2ef572661cd0693cda80ae3e42dfe8e9a4a972ec1b827f861d9a9acf75b37f7d58a37f94a8a053259642912c252bc3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-wsl@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "is-wsl@npm:2.2.0"
|
||||
@ -17550,6 +17784,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"opencollective-postinstall@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "opencollective-postinstall@npm:2.0.3"
|
||||
bin:
|
||||
opencollective-postinstall: index.js
|
||||
checksum: 10c0/8a0104a218bc1afaae943f0af378461eeb2836f9848bad872bbd067ec5d1d9791636f307454ab77d0746f10341366f295384656a340ebdb87a2585058e8567e5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"option@npm:~0.2.1":
|
||||
version: 0.2.4
|
||||
resolution: "option@npm:0.2.4"
|
||||
@ -19454,6 +19697,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"regenerator-runtime@npm:^0.13.3":
|
||||
version: 0.13.11
|
||||
resolution: "regenerator-runtime@npm:0.13.11"
|
||||
checksum: 10c0/12b069dc774001fbb0014f6a28f11c09ebfe3c0d984d88c9bced77fdb6fedbacbca434d24da9ae9371bfbf23f754869307fb51a4c98a8b8b18e5ef748677ca24
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"regex-recursion@npm:^6.0.2":
|
||||
version: 6.0.2
|
||||
resolution: "regex-recursion@npm:6.0.2"
|
||||
@ -20145,6 +20395,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"semver@npm:^7.7.2":
|
||||
version: 7.7.2
|
||||
resolution: "semver@npm:7.7.2"
|
||||
bin:
|
||||
semver: bin/semver.js
|
||||
checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"send@npm:^1.1.0, send@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "send@npm:1.2.0"
|
||||
@ -20213,6 +20472,84 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"sharp@npm:^0.34.3":
|
||||
version: 0.34.3
|
||||
resolution: "sharp@npm:0.34.3"
|
||||
dependencies:
|
||||
"@img/sharp-darwin-arm64": "npm:0.34.3"
|
||||
"@img/sharp-darwin-x64": "npm:0.34.3"
|
||||
"@img/sharp-libvips-darwin-arm64": "npm:1.2.0"
|
||||
"@img/sharp-libvips-darwin-x64": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linux-arm": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linux-arm64": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linux-ppc64": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linux-s390x": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linux-x64": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.0"
|
||||
"@img/sharp-libvips-linuxmusl-x64": "npm:1.2.0"
|
||||
"@img/sharp-linux-arm": "npm:0.34.3"
|
||||
"@img/sharp-linux-arm64": "npm:0.34.3"
|
||||
"@img/sharp-linux-ppc64": "npm:0.34.3"
|
||||
"@img/sharp-linux-s390x": "npm:0.34.3"
|
||||
"@img/sharp-linux-x64": "npm:0.34.3"
|
||||
"@img/sharp-linuxmusl-arm64": "npm:0.34.3"
|
||||
"@img/sharp-linuxmusl-x64": "npm:0.34.3"
|
||||
"@img/sharp-wasm32": "npm:0.34.3"
|
||||
"@img/sharp-win32-arm64": "npm:0.34.3"
|
||||
"@img/sharp-win32-ia32": "npm:0.34.3"
|
||||
"@img/sharp-win32-x64": "npm:0.34.3"
|
||||
color: "npm:^4.2.3"
|
||||
detect-libc: "npm:^2.0.4"
|
||||
semver: "npm:^7.7.2"
|
||||
dependenciesMeta:
|
||||
"@img/sharp-darwin-arm64":
|
||||
optional: true
|
||||
"@img/sharp-darwin-x64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-darwin-arm64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-darwin-x64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-arm":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-arm64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-ppc64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-s390x":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linux-x64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linuxmusl-arm64":
|
||||
optional: true
|
||||
"@img/sharp-libvips-linuxmusl-x64":
|
||||
optional: true
|
||||
"@img/sharp-linux-arm":
|
||||
optional: true
|
||||
"@img/sharp-linux-arm64":
|
||||
optional: true
|
||||
"@img/sharp-linux-ppc64":
|
||||
optional: true
|
||||
"@img/sharp-linux-s390x":
|
||||
optional: true
|
||||
"@img/sharp-linux-x64":
|
||||
optional: true
|
||||
"@img/sharp-linuxmusl-arm64":
|
||||
optional: true
|
||||
"@img/sharp-linuxmusl-x64":
|
||||
optional: true
|
||||
"@img/sharp-wasm32":
|
||||
optional: true
|
||||
"@img/sharp-win32-arm64":
|
||||
optional: true
|
||||
"@img/sharp-win32-ia32":
|
||||
optional: true
|
||||
"@img/sharp-win32-x64":
|
||||
optional: true
|
||||
checksum: 10c0/df9e6645e3db6ed298a0ac956ba74e468c367fc038b547936fbdddc6a29fce9af40413acbef73b3716291530760f311a20e45c8983f20ee5ea69dd2f21464a2b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"shebang-command@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "shebang-command@npm:2.0.0"
|
||||
@ -21001,6 +21338,47 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tesseract.js-core@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "tesseract.js-core@npm:6.0.0"
|
||||
checksum: 10c0/c04be8bbaa296be658664496754f21e857bdffff84113f08adf02f03a1f84596d68b3542ed2fda4a6dc138abb84b09b30ab07c04ee5950879e780876d343955f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tesseract.js@npm:6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "tesseract.js@npm:6.0.1"
|
||||
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/1d73bb1fbc00c8629756d9594989d8bbfabda657a8cad84922ad68eb0f073148c82845bf71a882e5d2427a46edb5a470356864e60562c7a8442bddd70251435a
|
||||
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"
|
||||
@ -22173,6 +22551,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wasm-feature-detect@npm:^1.2.11":
|
||||
version: 1.8.0
|
||||
resolution: "wasm-feature-detect@npm:1.8.0"
|
||||
checksum: 10c0/2cb43e91bbf7aa7c121bc76b3133de3ab6dc4f482acc1d2dc46c528e8adb7a51c72df5c2aacf1d219f113c04efd1706f18274d5790542aa5dd49e0644e3ee665
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wcwidth@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "wcwidth@npm:1.0.1"
|
||||
@ -22678,6 +23063,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zlibjs@npm:^0.3.1":
|
||||
version: 0.3.1
|
||||
resolution: "zlibjs@npm:0.3.1"
|
||||
checksum: 10c0/2d110bfcb0f8b8dbf225423f6556da9c5bca95c8b849c1218983676158a24b5cd0350357e0c4d504e27f8c7e18d471d9712576f35114a81a51bcf83453f02beb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod-to-json-schema@npm:^3.22.3, zod-to-json-schema@npm:^3.22.4, zod-to-json-schema@npm:^3.22.5, zod-to-json-schema@npm:^3.24.1":
|
||||
version: 3.24.5
|
||||
resolution: "zod-to-json-schema@npm:3.24.5"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user