mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
Compare commits
2 Commits
wayneshn-p
...
attachment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e1a3886431 | ||
|
|
f84bc0cbb0 |
13
.env.example
13
.env.example
@@ -52,6 +52,19 @@ STORAGE_S3_REGION=
|
||||
# Set to 'true' for MinIO and other non-AWS S3 services
|
||||
STORAGE_S3_FORCE_PATH_STYLE=false
|
||||
|
||||
# --- OCR Settings ---
|
||||
# Enable or disable Optical Character Recognition for attachments.
|
||||
# Default: false
|
||||
OCR_ENABLED=true
|
||||
# Comma-separated list of languages for OCR processing (e.g., eng,fra,deu,spa).
|
||||
# These must correspond to the .traineddata files mounted in the TESSERACT_PATH directory.
|
||||
# Default: "eng"
|
||||
OCR_LANGUAGES="eng"
|
||||
# The internal container path where Tesseract language data files (.traineddata) are located.
|
||||
# This path is the target for the volume mount specified in docker-compose.yml.
|
||||
# Default: "/opt/open-archiver/tessdata"
|
||||
TESSERACT_PATH="/opt/open-archiver/tessdata"
|
||||
|
||||
# --- Security & Authentication ---
|
||||
|
||||
# Rate Limiting
|
||||
|
||||
@@ -11,6 +11,9 @@ services:
|
||||
- .env
|
||||
volumes:
|
||||
- archiver-data:/var/data/open-archiver
|
||||
# (Optional) Mount a host directory containing Tesseract language files for OCR.
|
||||
# If you do not need OCR, you can safely comment out or remove the line below.
|
||||
- ${TESSERACT_PATH:-./tessdata}:/opt/open-archiver/tessdata:ro
|
||||
depends_on:
|
||||
- postgres
|
||||
- valkey
|
||||
|
||||
58
docs/services/indexing-service/ocr.md
Normal file
58
docs/services/indexing-service/ocr.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Attachment OCR
|
||||
|
||||
Open Archiver includes a powerful Optical Character Recognition (OCR) feature that allows it to extract text from images and scanned PDF documents during indexing. This makes the content of image-based attachments fully searchable.
|
||||
|
||||
## Overview
|
||||
|
||||
When enabled, the OCR service automatically processes common image formats and acts as a fallback for PDF files that do not contain selectable text. This is particularly useful for scanned documents, faxes, or photos of text.
|
||||
|
||||
## Enabling OCR
|
||||
|
||||
To enable the OCR feature, you must set the following environment variable in your `.env` file:
|
||||
|
||||
```ini
|
||||
OCR_ENABLED=true
|
||||
```
|
||||
|
||||
By default, this feature is disabled. If you do not need OCR, you can set this to `false` or omit the variable.
|
||||
|
||||
## Step-by-Step Language Configuration
|
||||
|
||||
The OCR service requires language data files to recognize text. You can add support for one or more languages by following these steps:
|
||||
|
||||
1. **Download Language Files**: Visit the official Tesseract `tessdata_fast` repository to find the available language files: [https://github.com/tesseract-ocr/tessdata_fast](https://github.com/tesseract-ocr/tessdata_fast). Download the `.traineddata` file for each language you need (e.g., `fra.traineddata` for French, `deu.traineddata` for German).
|
||||
|
||||
2. **Create a Directory on Host**: On your **host machine** (the machine running Docker), create a directory at any location to store your language files. For example, `/opt/openarchiver/tessdata`.
|
||||
|
||||
3. **Add Language Files**: Place the downloaded `.traineddata` files into the directory you just created.
|
||||
|
||||
4. **Configure Paths and Languages in `.env`**: Update your `.env` file with the following variables:
|
||||
- `TESSERACT_PATH`: Set this to the **full, absolute path** of the directory you created in Step 2.
|
||||
- `OCR_LANGUAGES`: Set this to a comma-separated list of the language codes you downloaded.
|
||||
|
||||
```ini
|
||||
# Example configuration in .env file
|
||||
TESSERACT_PATH="/opt/openarchiver/tessdata"
|
||||
OCR_LANGUAGES="eng,fra,deu"
|
||||
```
|
||||
|
||||
## Docker Compose Configuration
|
||||
|
||||
The system uses a Docker volume to make the language files on your host machine available to the application inside the container. The `docker-compose.yml` file is already configured to use the `TESSERACT_PATH` variable from your `.env` file.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
open-archiver:
|
||||
# ... other settings
|
||||
volumes:
|
||||
- archiver-data:/var/data/open-archiver
|
||||
# (Optional) Mount a host directory containing Tesseract language files for OCR.
|
||||
# If you do not need OCR, you can safely comment out or remove the line below.
|
||||
- ${TESSERACT_PATH:-./tessdata}:/opt/open-archiver/tessdata:ro
|
||||
```
|
||||
|
||||
This line connects the host path specified in `TESSERACT_PATH` (defaulting to `./tessdata` if not set) to the fixed `/opt/open-archiver/tessdata` path inside the container. If you have disabled OCR, you can comment out or remove the volume mount line.
|
||||
|
||||
## Performance Note
|
||||
|
||||
OCR is a CPU-intensive process. To ensure the main application remains responsive, all OCR operations are handled by background workers. The number of concurrent OCR processes is automatically scaled based on the number of available CPU cores on your system.
|
||||
@@ -42,6 +42,10 @@ You must change the following placeholder values to secure your instance:
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
### Attachment OCR Configuration
|
||||
|
||||
Open Archiver can extract text from images and scanned documents using Optical Character Recognition (OCR). For detailed instructions on how to enable and configure this feature, please see the [Attachment OCR Guide](../services/indexing-service/ocr.md).
|
||||
|
||||
### Storage Configuration
|
||||
|
||||
By default, the Docker Compose setup uses local filesystem storage, which is persisted using a Docker volume named `archiver-data`. This is suitable for most use cases.
|
||||
@@ -103,6 +107,14 @@ These variables are used by `docker-compose.yml` to configure the services.
|
||||
| `STORAGE_S3_REGION` | The region for S3-compatible storage (required if `STORAGE_TYPE` is `s3`). | |
|
||||
| `STORAGE_S3_FORCE_PATH_STYLE` | Force path-style addressing for S3 (optional). | `false` |
|
||||
|
||||
#### OCR Settings
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
| ---------------- | --------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `OCR_ENABLED` | Enable or disable Optical Character Recognition for attachments. | `false` |
|
||||
| `OCR_LANGUAGES` | A comma-separated list of languages for OCR processing (e.g., `eng,fra,deu`). | `eng` |
|
||||
| `TESSERACT_PATH` | The path on the host machine where Tesseract language data files (`.traineddata`) are stored. | `./tessdata` |
|
||||
|
||||
#### Security & Authentication
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"mammoth": "^1.9.1",
|
||||
"meilisearch": "^0.51.0",
|
||||
"multer": "^2.0.2",
|
||||
"pdf-to-png-converter": "^3.7.1",
|
||||
"pdf2json": "^3.1.6",
|
||||
"pg": "^8.16.3",
|
||||
"pino": "^9.7.0",
|
||||
@@ -58,6 +59,7 @@
|
||||
"pst-extractor": "^1.11.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"tesseract.js": "^6.0.1",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"xlsx": "^0.18.5",
|
||||
"yauzl": "^3.2.0",
|
||||
|
||||
@@ -6,4 +6,6 @@ export const app = {
|
||||
encryptionKey: process.env.ENCRYPTION_KEY,
|
||||
isDemo: process.env.IS_DEMO === 'true',
|
||||
syncFrequency: process.env.SYNC_FREQUENCY || '* * * * *', //default to 1 minute
|
||||
ocrEnabled: process.env.OCR_ENABLED === 'true',
|
||||
ocrLanguages: process.env.OCR_LANGUAGES || 'eng',
|
||||
};
|
||||
|
||||
@@ -1,38 +1,88 @@
|
||||
import PDFParser from 'pdf2json';
|
||||
import mammoth from 'mammoth';
|
||||
import xlsx from 'xlsx';
|
||||
import { ocrService } from '../services/OcrService';
|
||||
import { logger } from '../config/logger';
|
||||
import { config } from '../config';
|
||||
import { pdfToPng } from 'pdf-to-png-converter';
|
||||
|
||||
function extractTextFromPdf(buffer: Buffer): Promise<string> {
|
||||
interface PdfExtractResult {
|
||||
text: string;
|
||||
hasText: boolean;
|
||||
}
|
||||
|
||||
function extractTextFromPdf(buffer: Buffer): Promise<PdfExtractResult> {
|
||||
return new Promise((resolve) => {
|
||||
const pdfParser = new PDFParser(null, true);
|
||||
let completed = false;
|
||||
|
||||
const finish = (text: string) => {
|
||||
const finish = (result: PdfExtractResult) => {
|
||||
if (completed) return;
|
||||
completed = true;
|
||||
pdfParser.removeAllListeners();
|
||||
resolve(text);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
pdfParser.on('pdfParser_dataError', () => finish(''));
|
||||
pdfParser.on('pdfParser_dataReady', () => finish(pdfParser.getRawTextContent()));
|
||||
pdfParser.on('pdfParser_dataError', (err) => {
|
||||
logger.error({ err }, 'Error parsing PDF for text extraction');
|
||||
finish({ text: '', hasText: false });
|
||||
});
|
||||
|
||||
pdfParser.on('pdfParser_dataReady', (pdfData) => {
|
||||
let hasText = false;
|
||||
if (pdfData?.Pages) {
|
||||
for (const page of pdfData.Pages) {
|
||||
if (page.Texts && page.Texts.length > 0) {
|
||||
hasText = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const text = pdfParser.getRawTextContent();
|
||||
finish({ text, hasText });
|
||||
});
|
||||
|
||||
try {
|
||||
pdfParser.parseBuffer(buffer);
|
||||
} catch (err) {
|
||||
console.error('Error parsing PDF buffer', err);
|
||||
finish('');
|
||||
logger.error({ err }, 'Error parsing PDF buffer');
|
||||
finish({ text: '', hasText: false });
|
||||
}
|
||||
|
||||
// Prevent hanging if the parser never emits events
|
||||
setTimeout(() => finish(''), 10000);
|
||||
setTimeout(() => finish({ text: '', hasText: false }), 10000);
|
||||
});
|
||||
}
|
||||
|
||||
const OCR_SUPPORTED_MIME_TYPES = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/tiff',
|
||||
'image/bmp',
|
||||
'image/webp',
|
||||
'image/x-portable-bitmap',
|
||||
];
|
||||
|
||||
export async function extractText(buffer: Buffer, mimeType: string): Promise<string> {
|
||||
try {
|
||||
if (mimeType === 'application/pdf') {
|
||||
return await extractTextFromPdf(buffer);
|
||||
const pdfResult = await extractTextFromPdf(buffer);
|
||||
if (!pdfResult.hasText && config.app.ocrEnabled) {
|
||||
logger.info(
|
||||
{ mimeType },
|
||||
'PDF contains no selectable text. Attempting OCR fallback...'
|
||||
);
|
||||
const pngPages = await pdfToPng(buffer);
|
||||
let ocrText = '';
|
||||
for (const pngPage of pngPages) {
|
||||
ocrText += await ocrService.recognize(pngPage.content) + '\n';
|
||||
}
|
||||
return ocrText;
|
||||
}
|
||||
return pdfResult.text;
|
||||
}
|
||||
|
||||
if (OCR_SUPPORTED_MIME_TYPES.includes(mimeType) && config.app.ocrEnabled) {
|
||||
return await ocrService.recognize(buffer);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -61,10 +111,10 @@ export async function extractText(buffer: Buffer, mimeType: string): Promise<str
|
||||
return buffer.toString('utf-8');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error extracting text from attachment with MIME type ${mimeType}:`, error);
|
||||
return ''; // Return empty string on failure
|
||||
logger.error({ err: error, mimeType }, 'Error extracting text from attachment');
|
||||
return '';
|
||||
}
|
||||
|
||||
console.warn(`Unsupported MIME type for text extraction: ${mimeType}`);
|
||||
return ''; // Return empty string for unsupported types
|
||||
logger.warn({ mimeType }, 'Unsupported MIME type for text extraction');
|
||||
return '';
|
||||
}
|
||||
|
||||
71
packages/backend/src/services/OcrService.ts
Normal file
71
packages/backend/src/services/OcrService.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { createScheduler, createWorker, Scheduler } from 'tesseract.js';
|
||||
import { config } from '../config';
|
||||
import { logger } from '../config/logger';
|
||||
|
||||
class OcrService {
|
||||
private static instance: OcrService;
|
||||
private scheduler: Scheduler | null = null;
|
||||
private isInitialized = false;
|
||||
|
||||
private constructor() { }
|
||||
|
||||
public static getInstance(): OcrService {
|
||||
if (!OcrService.instance) {
|
||||
OcrService.instance = new OcrService();
|
||||
}
|
||||
return OcrService.instance;
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
if (this.isInitialized || !config.app.ocrEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info({ languages: config.app.ocrLanguages }, 'Initializing OCR Service...');
|
||||
this.scheduler = createScheduler();
|
||||
const languages = config.app.ocrLanguages.split(',');
|
||||
const numWorkers = Math.max(1, require('os').cpus().length - 1);
|
||||
|
||||
const workerPromises = Array.from({ length: numWorkers }).map(async () => {
|
||||
const worker = await createWorker(languages, 1, {
|
||||
cachePath: '/opt/open-archiver/tessdata',
|
||||
});
|
||||
this.scheduler!.addWorker(worker);
|
||||
});
|
||||
|
||||
await Promise.all(workerPromises);
|
||||
this.isInitialized = true;
|
||||
logger.info(
|
||||
`OCR Service initialized with ${numWorkers} workers for languages: [${languages.join(', ')}]`
|
||||
);
|
||||
}
|
||||
|
||||
public async recognize(buffer: Buffer): Promise<string> {
|
||||
if (!config.app.ocrEnabled) return '';
|
||||
if (!this.isInitialized) await this.initialize();
|
||||
if (!this.scheduler) {
|
||||
logger.error('OCR scheduler not available.');
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const {
|
||||
data: { text },
|
||||
} = await this.scheduler.addJob('recognize', buffer);
|
||||
return text;
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Error during OCR processing');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
public async terminate(): Promise<void> {
|
||||
if (this.scheduler && this.isInitialized) {
|
||||
logger.info('Terminating OCR Service...');
|
||||
await this.scheduler.terminate();
|
||||
this.scheduler = null;
|
||||
this.isInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ocrService = OcrService.getInstance();
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Worker } from 'bullmq';
|
||||
import { connection } from '../config/redis';
|
||||
import indexEmailProcessor from '../jobs/processors/index-email.processor';
|
||||
import { ocrService } from '../services/OcrService';
|
||||
import { logger } from '../config/logger';
|
||||
|
||||
const processor = async (job: any) => {
|
||||
switch (job.name) {
|
||||
@@ -22,7 +24,14 @@ const worker = new Worker('indexing', processor, {
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Indexing worker started');
|
||||
logger.info('Indexing worker started');
|
||||
|
||||
process.on('SIGINT', () => worker.close());
|
||||
process.on('SIGTERM', () => worker.close());
|
||||
const gracefulShutdown = async () => {
|
||||
logger.info('Shutting down indexing worker...');
|
||||
await worker.close();
|
||||
await ocrService.terminate();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', gracefulShutdown);
|
||||
process.on('SIGTERM', gracefulShutdown);
|
||||
|
||||
197
pnpm-lock.yaml
generated
197
pnpm-lock.yaml
generated
@@ -29,7 +29,7 @@ importers:
|
||||
version: 5.8.3
|
||||
vitepress:
|
||||
specifier: ^1.6.4
|
||||
version: 1.6.4(@algolia/client-search@5.34.1)(@types/node@24.0.13)(axios@1.10.0)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3)
|
||||
version: 1.6.4(@algolia/client-search@5.34.1)(@types/node@24.0.13)(axios@1.10.0)(idb-keyval@6.2.2)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3)
|
||||
|
||||
packages/backend:
|
||||
dependencies:
|
||||
@@ -123,6 +123,9 @@ importers:
|
||||
multer:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
pdf-to-png-converter:
|
||||
specifier: ^3.7.1
|
||||
version: 3.7.1
|
||||
pdf2json:
|
||||
specifier: ^3.1.6
|
||||
version: 3.1.6
|
||||
@@ -147,6 +150,9 @@ importers:
|
||||
sqlite3:
|
||||
specifier: ^5.1.7
|
||||
version: 5.1.7
|
||||
tesseract.js:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1(encoding@0.1.13)
|
||||
tsconfig-paths:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0
|
||||
@@ -1174,6 +1180,70 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.78':
|
||||
resolution: {integrity: sha512-N1ikxztjrRmh8xxlG5kYm1RuNr8ZW1EINEDQsLhhuy7t0pWI/e7SH91uFVLZKCMDyjel1tyWV93b5fdCAi7ggw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.78':
|
||||
resolution: {integrity: sha512-FA3aCU3G5yGc74BSmnLJTObnZRV+HW+JBTrsU+0WVVaNyVKlb5nMvYAQuieQlRVemsAA2ek2c6nYtHh6u6bwFw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.78':
|
||||
resolution: {integrity: sha512-xVij69o9t/frixCDEoyWoVDKgE3ksLGdmE2nvBWVGmoLu94MWUlv2y4Qzf5oozBmydG5Dcm4pRHFBM7YWa1i6g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.78':
|
||||
resolution: {integrity: sha512-aSEXrLcIpBtXpOSnLhTg4jPsjJEnK7Je9KqUdAWjc7T8O4iYlxWxrXFIF8rV8J79h5jNdScgZpAUWYnEcutR3g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.78':
|
||||
resolution: {integrity: sha512-dlEPRX1hLGKaY3UtGa1dtkA1uGgFITn2mDnfI6YsLlYyLJQNqHx87D1YTACI4zFCUuLr/EzQDzuX+vnp9YveVg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.78':
|
||||
resolution: {integrity: sha512-TsCfjOPZtm5Q/NO1EZHR5pwDPSPjPEttvnv44GL32Zn1uvudssjTLbvaG1jHq81Qxm16GTXEiYLmx4jOLZQYlg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.78':
|
||||
resolution: {integrity: sha512-+cpTTb0GDshEow/5Fy8TpNyzaPsYb3clQIjgWRmzRcuteLU+CHEU/vpYvAcSo7JxHYPJd8fjSr+qqh+nI5AtmA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.78':
|
||||
resolution: {integrity: sha512-wxRcvKfvYBgtrO0Uy8OmwvjlnTcHpY45LLwkwVNIWHPqHAsyoTyG/JBSfJ0p5tWRzMOPDCDqdhpIO4LOgXjeyg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.78':
|
||||
resolution: {integrity: sha512-vQFOGwC9QDP0kXlhb2LU1QRw/humXgcbVp8mXlyBqzc/a0eijlLF9wzyarHC1EywpymtS63TAj8PHZnhTYN6hg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.78':
|
||||
resolution: {integrity: sha512-/eKlTZBtGUgpRKalzOzRr6h7KVSuziESWXgBcBnXggZmimwIJWPJlEcbrx5Tcwj8rPuZiANXQOG9pPgy9Q4LTQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@napi-rs/canvas@0.1.78':
|
||||
resolution: {integrity: sha512-YaBHJvT+T1DoP16puvWM6w46Lq3VhwKIJ8th5m1iEJyGh7mibk5dT7flBvMQ1EH1LYmMzXJ+OUhu+8wQ9I6u7g==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@npmcli/fs@1.1.1':
|
||||
resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
|
||||
|
||||
@@ -2099,6 +2169,9 @@ packages:
|
||||
bluebird@3.4.7:
|
||||
resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
|
||||
|
||||
bmp-js@0.1.0:
|
||||
resolution: {integrity: sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==}
|
||||
|
||||
body-parser@1.19.0:
|
||||
resolution: {integrity: sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -3086,6 +3159,9 @@ packages:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
idb-keyval@6.2.2:
|
||||
resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==}
|
||||
|
||||
ieee754@1.2.1:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
|
||||
@@ -3188,6 +3264,9 @@ packages:
|
||||
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-url@1.2.4:
|
||||
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
||||
|
||||
is-what@4.1.16:
|
||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||
engines: {node: '>=12.13'}
|
||||
@@ -3716,6 +3795,10 @@ packages:
|
||||
oniguruma-to-es@3.1.1:
|
||||
resolution: {integrity: sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==}
|
||||
|
||||
opencollective-postinstall@2.0.3:
|
||||
resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==}
|
||||
hasBin: true
|
||||
|
||||
option@0.2.4:
|
||||
resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==}
|
||||
|
||||
@@ -3758,11 +3841,19 @@ packages:
|
||||
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
pdf-to-png-converter@3.7.1:
|
||||
resolution: {integrity: sha512-bbox+zXQ1FxhXCYwzIRikcfx4tgB6zl9gn3LYx7NyDIjnrtawZVKJ1No4/iz5PnxLTzEK8k6KYSxWFIphxgLbQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
pdf2json@3.1.6:
|
||||
resolution: {integrity: sha512-Nkwo9qeCvqVH0ZgYRUfPyj6o4o7StvNIxMFECeiz4y0uMOVyqc5Y9hjsdFVxdYCeiUjjXLQXA8KIz0iJL3HM0w==}
|
||||
engines: {node: '>=20.18.0'}
|
||||
hasBin: true
|
||||
|
||||
pdfjs-dist@5.4.149:
|
||||
resolution: {integrity: sha512-Xe8/1FMJEQPUVSti25AlDpwpUm2QAVmNOpFP0SIahaPIOKBKICaefbzogLdwey3XGGoaP4Lb9wqiw2e9Jqp0LA==}
|
||||
engines: {node: '>=20.16.0 || >=22.3.0'}
|
||||
|
||||
peberminta@0.9.0:
|
||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||
|
||||
@@ -4048,6 +4139,9 @@ packages:
|
||||
reflect-metadata@0.2.2:
|
||||
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
|
||||
|
||||
regenerator-runtime@0.13.11:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
|
||||
|
||||
@@ -4452,6 +4546,12 @@ packages:
|
||||
resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tesseract.js-core@6.0.0:
|
||||
resolution: {integrity: sha512-1Qncm/9oKM7xgrQXZXNB+NRh19qiXGhxlrR8EwFbK5SaUbPZnS5OMtP/ghtqfd23hsr1ZvZbZjeuAGcMxd/ooA==}
|
||||
|
||||
tesseract.js@6.0.1:
|
||||
resolution: {integrity: sha512-/sPvMvrCtgxnNRCjbTYbr7BRu0yfWDsMZQ2a/T5aN/L1t8wUQN6tTWv6p6FwzpoEBA0jrN2UD2SX4QQFRdoDbA==}
|
||||
|
||||
text-decoder@1.2.3:
|
||||
resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==}
|
||||
|
||||
@@ -4720,6 +4820,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
wasm-feature-detect@1.8.0:
|
||||
resolution: {integrity: sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==}
|
||||
|
||||
web-streams-polyfill@3.3.3:
|
||||
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -4804,6 +4907,9 @@ packages:
|
||||
resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
zlibjs@0.3.1:
|
||||
resolution: {integrity: sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==}
|
||||
|
||||
zod@4.1.5:
|
||||
resolution: {integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==}
|
||||
|
||||
@@ -5818,6 +5924,49 @@ snapshots:
|
||||
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-android-arm64@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-arm64@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-darwin-x64@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-gnu@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-arm64-musl@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-riscv64-gnu@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-gnu@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-linux-x64-musl@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas-win32-x64-msvc@0.1.78':
|
||||
optional: true
|
||||
|
||||
'@napi-rs/canvas@0.1.78':
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas-android-arm64': 0.1.78
|
||||
'@napi-rs/canvas-darwin-arm64': 0.1.78
|
||||
'@napi-rs/canvas-darwin-x64': 0.1.78
|
||||
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.78
|
||||
'@napi-rs/canvas-linux-arm64-gnu': 0.1.78
|
||||
'@napi-rs/canvas-linux-arm64-musl': 0.1.78
|
||||
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.78
|
||||
'@napi-rs/canvas-linux-x64-gnu': 0.1.78
|
||||
'@napi-rs/canvas-linux-x64-musl': 0.1.78
|
||||
'@napi-rs/canvas-win32-x64-msvc': 0.1.78
|
||||
|
||||
'@npmcli/fs@1.1.1':
|
||||
dependencies:
|
||||
'@gar/promisify': 1.1.3
|
||||
@@ -6686,7 +6835,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/integrations@12.8.2(axios@1.10.0)(focus-trap@7.6.5)(typescript@5.8.3)':
|
||||
'@vueuse/integrations@12.8.2(axios@1.10.0)(focus-trap@7.6.5)(idb-keyval@6.2.2)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@vueuse/core': 12.8.2(typescript@5.8.3)
|
||||
'@vueuse/shared': 12.8.2(typescript@5.8.3)
|
||||
@@ -6694,6 +6843,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
axios: 1.10.0
|
||||
focus-trap: 7.6.5
|
||||
idb-keyval: 6.2.2
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
@@ -6880,6 +7030,8 @@ snapshots:
|
||||
|
||||
bluebird@3.4.7: {}
|
||||
|
||||
bmp-js@0.1.0: {}
|
||||
|
||||
body-parser@1.19.0:
|
||||
dependencies:
|
||||
bytes: 3.1.0
|
||||
@@ -7978,6 +8130,8 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
idb-keyval@6.2.2: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
|
||||
imapflow@1.0.191:
|
||||
@@ -8078,6 +8232,8 @@ snapshots:
|
||||
|
||||
is-stream@2.0.1: {}
|
||||
|
||||
is-url@1.2.4: {}
|
||||
|
||||
is-what@4.1.16: {}
|
||||
|
||||
isarray@1.0.0: {}
|
||||
@@ -8643,6 +8799,8 @@ snapshots:
|
||||
regex: 6.0.1
|
||||
regex-recursion: 6.0.2
|
||||
|
||||
opencollective-postinstall@2.0.3: {}
|
||||
|
||||
option@0.2.4: {}
|
||||
|
||||
p-map@4.0.0:
|
||||
@@ -8676,8 +8834,17 @@ snapshots:
|
||||
|
||||
path-to-regexp@8.2.0: {}
|
||||
|
||||
pdf-to-png-converter@3.7.1:
|
||||
dependencies:
|
||||
'@napi-rs/canvas': 0.1.78
|
||||
pdfjs-dist: 5.4.149
|
||||
|
||||
pdf2json@3.1.6: {}
|
||||
|
||||
pdfjs-dist@5.4.149:
|
||||
optionalDependencies:
|
||||
'@napi-rs/canvas': 0.1.78
|
||||
|
||||
peberminta@0.9.0: {}
|
||||
|
||||
pend@1.2.0: {}
|
||||
@@ -8927,6 +9094,8 @@ snapshots:
|
||||
|
||||
reflect-metadata@0.2.2: {}
|
||||
|
||||
regenerator-runtime@0.13.11: {}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
@@ -9429,6 +9598,22 @@ snapshots:
|
||||
mkdirp: 3.0.1
|
||||
yallist: 5.0.0
|
||||
|
||||
tesseract.js-core@6.0.0: {}
|
||||
|
||||
tesseract.js@6.0.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
bmp-js: 0.1.0
|
||||
idb-keyval: 6.2.2
|
||||
is-url: 1.2.4
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
opencollective-postinstall: 2.0.3
|
||||
regenerator-runtime: 0.13.11
|
||||
tesseract.js-core: 6.0.0
|
||||
wasm-feature-detect: 1.8.0
|
||||
zlibjs: 0.3.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
text-decoder@1.2.3:
|
||||
dependencies:
|
||||
b4a: 1.6.7
|
||||
@@ -9629,7 +9814,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
vite: 6.3.5(@types/node@24.0.13)(jiti@2.4.2)(lightningcss@1.30.1)
|
||||
|
||||
vitepress@1.6.4(@algolia/client-search@5.34.1)(@types/node@24.0.13)(axios@1.10.0)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3):
|
||||
vitepress@1.6.4(@algolia/client-search@5.34.1)(@types/node@24.0.13)(axios@1.10.0)(idb-keyval@6.2.2)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@docsearch/css': 3.8.2
|
||||
'@docsearch/js': 3.8.2(@algolia/client-search@5.34.1)(search-insights@2.17.3)
|
||||
@@ -9642,7 +9827,7 @@ snapshots:
|
||||
'@vue/devtools-api': 7.7.7
|
||||
'@vue/shared': 3.5.18
|
||||
'@vueuse/core': 12.8.2(typescript@5.8.3)
|
||||
'@vueuse/integrations': 12.8.2(axios@1.10.0)(focus-trap@7.6.5)(typescript@5.8.3)
|
||||
'@vueuse/integrations': 12.8.2(axios@1.10.0)(focus-trap@7.6.5)(idb-keyval@6.2.2)(typescript@5.8.3)
|
||||
focus-trap: 7.6.5
|
||||
mark.js: 8.11.1
|
||||
minisearch: 7.1.2
|
||||
@@ -9688,6 +9873,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
wasm-feature-detect@1.8.0: {}
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
@@ -9771,6 +9958,8 @@ snapshots:
|
||||
compress-commons: 6.0.2
|
||||
readable-stream: 4.7.0
|
||||
|
||||
zlibjs@0.3.1: {}
|
||||
|
||||
zod@4.1.5: {}
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
Reference in New Issue
Block a user