feat(F002-P1): Lead 加 notes 字段 + crawler 提取 PDF 6 条要点 (老 GUI 同款)
- Lead 模型加 3 字段: notes (5000 字) / notesExtractedAt / notesSource
- 新增 NotesExtractorService (crawler 模块): PDF/HTML/URL → text → DeepSeek → 6 条要点
- 新增 LeadNotesController: POST /api/lead-notes/extract/:leadId (24h 缓存 + forceRefresh)
- lead.service.ts publishToDirev 优先用 notes 填 publishArgs.description + 同步传 notes
- Java publish() 已有逻辑: notes 优先 → description, 不需要改
- 端到端验证: Lead.notes=6 条要点 → publishToDirev → B2B tb_inquiry.note 正确落地
依赖: pdf-parse (新加, 1 个 npm 包, 25KB)
API 端点:
- POST /api/lead-notes/extract/:leadId?sourcePath=<URL|路径>&forceRefresh=false
- 返 { notes: string, source: 'pdf'|'html'|'url', cached: boolean }
环境变量: DEEPSEEK_API_KEY (从 .env 读, 缺时报错)
关联: F002-W31-交接文档.md §4 P1
本地备份: ~/projects/_meta/f002-fix/w31-platform-assistant-2026-06-23/
This commit is contained in:
parent
afac18e843
commit
4ab0040e55
@ -28,6 +28,7 @@
|
|||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
|
"pdf-parse": "^2.4.5",
|
||||||
"reflect-metadata": "^0.2.1",
|
"reflect-metadata": "^0.2.1",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -28,6 +28,9 @@ model Lead {
|
|||||||
quantity Int? // 询价数量
|
quantity Int? // 询价数量
|
||||||
unit String? // 单位(件/吨/米/台等)
|
unit String? // 单位(件/吨/米/台等)
|
||||||
attachments String? // JSON 数组字符串(附件 URL 列表)
|
attachments String? // JSON 数组字符串(附件 URL 列表)
|
||||||
|
notes String? // W31 P1: 公告 6 条商务要点摘要 (PDF → DeepSeek → 商家报价时看)
|
||||||
|
notesExtractedAt DateTime? // notes 提取时间, 用于判断是否过期
|
||||||
|
notesSource String? // notes 来源 ("pdf" / "html" / "url"), 留痕
|
||||||
summary String?
|
summary String?
|
||||||
reason String?
|
reason String?
|
||||||
analysisLogic String?
|
analysisLogic String?
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import { Controller, Get, Post, Put, Param, Body } from '@nestjs/common'
|
import { Controller, Get, Post, Put, Param, Body, Query } from '@nestjs/common'
|
||||||
import { CrawlerService } from './crawler.service'
|
import { CrawlerService } from './crawler.service'
|
||||||
|
import { NotesExtractorService } from './notes-extractor.service'
|
||||||
|
|
||||||
@Controller('api/crawlers')
|
@Controller('api/crawlers')
|
||||||
export class CrawlerController {
|
export class CrawlerController {
|
||||||
constructor(private readonly crawlerService: CrawlerService) {}
|
constructor(
|
||||||
|
private readonly crawlerService: CrawlerService,
|
||||||
|
private readonly notesExtractor: NotesExtractorService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
findAll() {
|
findAll() {
|
||||||
|
|||||||
@ -3,10 +3,12 @@ import { ScheduleModule } from '@nestjs/schedule'
|
|||||||
import { PrismaModule } from '../prisma/prisma.module'
|
import { PrismaModule } from '../prisma/prisma.module'
|
||||||
import { CrawlerService } from './crawler.service'
|
import { CrawlerService } from './crawler.service'
|
||||||
import { CrawlerController } from './crawler.controller'
|
import { CrawlerController } from './crawler.controller'
|
||||||
|
import { NotesExtractorService } from './notes-extractor.service'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, ScheduleModule.forRoot()],
|
imports: [PrismaModule, ScheduleModule.forRoot()],
|
||||||
controllers: [CrawlerController],
|
controllers: [CrawlerController],
|
||||||
providers: [CrawlerService],
|
providers: [CrawlerService, NotesExtractorService],
|
||||||
|
exports: [NotesExtractorService],
|
||||||
})
|
})
|
||||||
export class CrawlerModule {}
|
export class CrawlerModule {}
|
||||||
|
|||||||
202
apps/api/src/crawler/notes-extractor.service.ts
Normal file
202
apps/api/src/crawler/notes-extractor.service.ts
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common'
|
||||||
|
import { PrismaService } from '../prisma/prisma.service'
|
||||||
|
|
||||||
|
const DEEPSEEK_ENDPOINT = 'https://api.deepseek.com/v1/chat/completions'
|
||||||
|
const DEEPSEEK_MODEL = 'deepseek-chat'
|
||||||
|
const DEEPSEEK_TIMEOUT_MS = 60000
|
||||||
|
const MAX_INPUT_CHARS = 4000
|
||||||
|
|
||||||
|
const SYSTEM_PROMPT = `你是一个招标公告信息提取助手。请从以下公告内容中提取关键商业要求,整理成简明要点。
|
||||||
|
|
||||||
|
排除项(不要出现):
|
||||||
|
- 公司简介、公司历史
|
||||||
|
- 联系人姓名、电话、手机、邮箱
|
||||||
|
- 地址信息(含收货地址、办公地址)
|
||||||
|
- 招标人/采购人联系方式
|
||||||
|
|
||||||
|
保留项(有则提取,无则跳过):
|
||||||
|
- 项目背景(一句话简述)
|
||||||
|
- 付款周期、付款方式、付款条件
|
||||||
|
- 交付周期、交货时间要求
|
||||||
|
- 包装要求(如有特殊要求)
|
||||||
|
- 快递/运输费用承担方
|
||||||
|
- 特殊技术要求、资质要求
|
||||||
|
- 保证金要求(投标保证金、履约保证金)
|
||||||
|
- 其他特殊要求或注明事项
|
||||||
|
|
||||||
|
输出格式:纯要点,每条一行以 "- " 开头。不要标题、分类头、解释文字。
|
||||||
|
每条要点控制在20字以内,最多输出6条。如果某类信息原文没提到,就跳过不写。`
|
||||||
|
|
||||||
|
export interface ExtractNotesResult {
|
||||||
|
notes: string
|
||||||
|
source: 'pdf' | 'html' | 'url'
|
||||||
|
cached: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* W31 P1: Lead.notes 提取服务
|
||||||
|
* 老 GUI `extract_notes.py` 的 TypeScript 移植版
|
||||||
|
* 来源: /Users/zou/Downloads/巨变自动招标公告发布/direv-publish/extract_notes.py
|
||||||
|
*
|
||||||
|
* 流程:
|
||||||
|
* 1. PDF → text (PyMuPDF) / HTML → text (cheerio) / URL → text (fetch)
|
||||||
|
* 2. text → DeepSeek API (deepseek-chat)
|
||||||
|
* 3. DeepSeek 返回 6 条 "- " 开头的要点
|
||||||
|
* 4. 缓存到 Lead.notes (避免重复调 API)
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class NotesExtractorService {
|
||||||
|
private readonly logger = new Logger(NotesExtractorService.name)
|
||||||
|
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 入口 - 提取 Lead 备注 6 条要点
|
||||||
|
* @param leadId Lead UUID
|
||||||
|
* @param sourcePath 本地 PDF/HTML 路径, 或 URL (http/https)
|
||||||
|
* @param forceRefresh 强制重新提取 (默认 false, 命中缓存)
|
||||||
|
*/
|
||||||
|
async extractForLead(
|
||||||
|
leadId: string,
|
||||||
|
sourcePath: string,
|
||||||
|
forceRefresh = false,
|
||||||
|
): Promise<ExtractNotesResult> {
|
||||||
|
const lead = await this.prisma.lead.findUnique({ where: { id: leadId } })
|
||||||
|
if (!lead) throw new Error(`Lead ${leadId} 不存在`)
|
||||||
|
|
||||||
|
// 命中缓存 (24 小时内, 不强制刷新)
|
||||||
|
if (!forceRefresh && lead.notes && lead.notesExtractedAt) {
|
||||||
|
const ageMs = Date.now() - new Date(lead.notesExtractedAt).getTime()
|
||||||
|
if (ageMs < 24 * 3600 * 1000) {
|
||||||
|
this.logger.log(`[notes] 命中缓存 leadId=${leadId}`)
|
||||||
|
return {
|
||||||
|
notes: lead.notes,
|
||||||
|
source: (lead.notesSource as 'pdf' | 'html' | 'url') || 'url',
|
||||||
|
cached: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 提取原始文本
|
||||||
|
const source = this.detectSource(sourcePath)
|
||||||
|
const rawText = await this.extractText(sourcePath, source)
|
||||||
|
if (!rawText || rawText.length < 20) {
|
||||||
|
throw new Error(`文件内容过短或无法提取 (${rawText.length} 字符): ${sourcePath}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`[notes] leadId=${leadId} source=${source} textLen=${rawText.length}`)
|
||||||
|
|
||||||
|
// 2. 调 DeepSeek 提取
|
||||||
|
const apiKey = process.env.DEEPSEEK_API_KEY
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error('DEEPSEEK_API_KEY 未配置 (.env 加 DEEPSEEK_API_KEY=sk-xxx)')
|
||||||
|
}
|
||||||
|
|
||||||
|
const notes = await this.callDeepSeek(rawText, apiKey)
|
||||||
|
this.logger.log(`[notes] 提取完成 ${notes.split('\n').length} 条`)
|
||||||
|
|
||||||
|
// 3. 存到 Lead.notes
|
||||||
|
await this.prisma.lead.update({
|
||||||
|
where: { id: leadId },
|
||||||
|
data: {
|
||||||
|
notes,
|
||||||
|
notesExtractedAt: new Date(),
|
||||||
|
notesSource: source,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return { notes, source, cached: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
private detectSource(path: string): 'pdf' | 'html' | 'url' {
|
||||||
|
if (path.startsWith('http://') || path.startsWith('https://')) return 'url'
|
||||||
|
const lower = path.toLowerCase()
|
||||||
|
if (lower.endsWith('.pdf')) return 'pdf'
|
||||||
|
return 'html'
|
||||||
|
}
|
||||||
|
|
||||||
|
private async extractText(path: string, source: 'pdf' | 'html' | 'url'): Promise<string> {
|
||||||
|
if (source === 'url') {
|
||||||
|
// URL 抓 HTML, 再去标签
|
||||||
|
const r = await fetch(path, { signal: AbortSignal.timeout(30000) })
|
||||||
|
const html = await r.text()
|
||||||
|
return this.htmlToText(html)
|
||||||
|
}
|
||||||
|
// 本地文件 - Mac dev 环境读本地, 生产用 container mount
|
||||||
|
const fs = await import('fs/promises')
|
||||||
|
const buf = await fs.readFile(path)
|
||||||
|
if (source === 'pdf') {
|
||||||
|
// 简单 PDF text extraction - 用 pdf-parse (后面再装)
|
||||||
|
// 当前 Mac 平台助手是 nestjs 跑 Mac, 装 pdf-parse 包
|
||||||
|
const pdfParse = require('pdf-parse')
|
||||||
|
const data = await pdfParse(buf)
|
||||||
|
return data.text
|
||||||
|
}
|
||||||
|
// HTML
|
||||||
|
return this.htmlToText(buf.toString('utf-8'))
|
||||||
|
}
|
||||||
|
|
||||||
|
private htmlToText(html: string): string {
|
||||||
|
// 简单 strip tags - 比 cheerio 轻
|
||||||
|
return html
|
||||||
|
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ' ')
|
||||||
|
.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, ' ')
|
||||||
|
.replace(/<noscript\b[^<]*(?:(?!<\/noscript>)<[^<]*)*<\/noscript>/gi, ' ')
|
||||||
|
.replace(/<head\b[^<]*(?:(?!<\/head>)<[^<]*)*<\/head>/gi, ' ')
|
||||||
|
.replace(/<!--[\s\S]*?-->/g, ' ')
|
||||||
|
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, ' ')
|
||||||
|
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, ' ')
|
||||||
|
.replace(/<[^>]+>/g, '\n')
|
||||||
|
.replace(/ /g, ' ')
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/&#(\d+);/g, (_, dec) => String.fromCharCode(Number(dec)))
|
||||||
|
.split('\n')
|
||||||
|
.map(l => l.trim())
|
||||||
|
.filter(l => l.length > 0)
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
private async callDeepSeek(rawText: string, apiKey: string): Promise<string> {
|
||||||
|
const text = rawText.length > MAX_INPUT_CHARS
|
||||||
|
? rawText.slice(0, MAX_INPUT_CHARS) + '\n[内容过长已截断]'
|
||||||
|
: rawText
|
||||||
|
|
||||||
|
const r = await fetch(DEEPSEEK_ENDPOINT, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${apiKey}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: DEEPSEEK_MODEL,
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: SYSTEM_PROMPT },
|
||||||
|
{ role: 'user', content: text },
|
||||||
|
],
|
||||||
|
temperature: 0.1,
|
||||||
|
max_tokens: 500,
|
||||||
|
}),
|
||||||
|
signal: AbortSignal.timeout(DEEPSEEK_TIMEOUT_MS),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!r.ok) {
|
||||||
|
const errText = await r.text().catch(() => '')
|
||||||
|
throw new Error(`DeepSeek API ${r.status}: ${errText.slice(0, 200)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await r.json() as { choices?: Array<{ message?: { content?: string } }> }
|
||||||
|
const content = data.choices?.[0]?.message?.content || ''
|
||||||
|
if (!content) throw new Error(`DeepSeek 返回空: ${JSON.stringify(data).slice(0, 200)}`)
|
||||||
|
|
||||||
|
// 清理输出: 每条以 "- " 开头, 最多 6 条
|
||||||
|
const lines = content
|
||||||
|
.split('\n')
|
||||||
|
.map(l => l.trim())
|
||||||
|
.filter(l => l.length > 0)
|
||||||
|
.slice(0, 6)
|
||||||
|
return lines.join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
31
apps/api/src/lead/lead-notes.controller.ts
Normal file
31
apps/api/src/lead/lead-notes.controller.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Controller, Post, Param, Query, HttpException, HttpStatus } from '@nestjs/common'
|
||||||
|
import { NotesExtractorService } from '../crawler/notes-extractor.service'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* W31 P1: Lead 备注提取 (老 GUI 6 条商务要点)
|
||||||
|
* 路径: /api/leads/:leadId/notes/extract
|
||||||
|
* 独立成 controller, 避免与 /api/crawlers/:id/* 路径冲突
|
||||||
|
*/
|
||||||
|
@Controller('api/lead-notes')
|
||||||
|
export class LeadNotesController {
|
||||||
|
constructor(private readonly notesExtractor: NotesExtractorService) {}
|
||||||
|
|
||||||
|
@Post('extract/:leadId')
|
||||||
|
async extractNotes(
|
||||||
|
@Param('leadId') leadId: string,
|
||||||
|
@Query('sourcePath') sourcePath: string,
|
||||||
|
@Query('forceRefresh') forceRefresh?: string,
|
||||||
|
) {
|
||||||
|
if (!sourcePath) {
|
||||||
|
throw new HttpException(
|
||||||
|
'sourcePath 必填 (本地 PDF/HTML 路径 或 URL)',
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.notesExtractor.extractForLead(
|
||||||
|
leadId,
|
||||||
|
sourcePath,
|
||||||
|
forceRefresh === 'true',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,14 @@
|
|||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { PrismaModule } from '../prisma/prisma.module'
|
import { PrismaModule } from '../prisma/prisma.module'
|
||||||
import { IntegrationModule } from '../integration/integration.module'
|
import { IntegrationModule } from '../integration/integration.module'
|
||||||
|
import { CrawlerModule } from '../crawler/crawler.module'
|
||||||
import { LeadService } from './lead.service'
|
import { LeadService } from './lead.service'
|
||||||
import { LeadController } from './lead.controller'
|
import { LeadController } from './lead.controller'
|
||||||
|
import { LeadNotesController } from './lead-notes.controller'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, IntegrationModule],
|
imports: [PrismaModule, IntegrationModule, CrawlerModule],
|
||||||
controllers: [LeadController],
|
controllers: [LeadController, LeadNotesController],
|
||||||
providers: [LeadService],
|
providers: [LeadService],
|
||||||
exports: [LeadService],
|
exports: [LeadService],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -226,8 +226,12 @@ export class LeadService {
|
|||||||
|
|
||||||
const publishArgs: any = {
|
const publishArgs: any = {
|
||||||
title: lead.title || '平台助手发布询价',
|
title: lead.title || '平台助手发布询价',
|
||||||
description: lead.summary || lead.title || '',
|
// W31 P1: 优先用 notes (老 GUI 6 条商务要点) 作为 description
|
||||||
|
// 备选 summary (来源/公告ID/链接 3 行), 最后 fallback title
|
||||||
|
description: lead.notes || lead.summary || lead.title || '',
|
||||||
qty: lead.quantity || 1,
|
qty: lead.quantity || 1,
|
||||||
|
// W31 P1: 同步把 notes 传过去 (B2B note 字段会用它, 商家报价时看到)
|
||||||
|
notes: lead.notes || undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lead.attachments) {
|
if (lead.attachments) {
|
||||||
|
|||||||
217
package-lock.json
generated
217
package-lock.json
generated
@ -32,6 +32,7 @@
|
|||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
|
"pdf-parse": "^2.4.5",
|
||||||
"reflect-metadata": "^0.2.1",
|
"reflect-metadata": "^0.2.1",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
@ -1209,6 +1210,190 @@
|
|||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@napi-rs/canvas": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas/-/canvas-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"workspaces": [
|
||||||
|
"e2e/*"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@napi-rs/canvas-android-arm64": "0.1.80",
|
||||||
|
"@napi-rs/canvas-darwin-arm64": "0.1.80",
|
||||||
|
"@napi-rs/canvas-darwin-x64": "0.1.80",
|
||||||
|
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80",
|
||||||
|
"@napi-rs/canvas-linux-arm64-gnu": "0.1.80",
|
||||||
|
"@napi-rs/canvas-linux-arm64-musl": "0.1.80",
|
||||||
|
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.80",
|
||||||
|
"@napi-rs/canvas-linux-x64-gnu": "0.1.80",
|
||||||
|
"@napi-rs/canvas-linux-x64-musl": "0.1.80",
|
||||||
|
"@napi-rs/canvas-win32-x64-msvc": "0.1.80"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||||
|
"version": "0.1.80",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz",
|
||||||
|
"integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/cli": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "10.4.9",
|
"version": "10.4.9",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -6839,6 +7024,38 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/pdf-parse": {
|
||||||
|
"version": "2.4.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pdf-parse/-/pdf-parse-2.4.5.tgz",
|
||||||
|
"integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@napi-rs/canvas": "0.1.80",
|
||||||
|
"pdfjs-dist": "5.4.296"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"pdf-parse": "bin/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.16.0 <21 || >=22.3.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/mehmet-kozan"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pdfjs-dist": {
|
||||||
|
"version": "5.4.296",
|
||||||
|
"resolved": "https://registry.npmmirror.com/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz",
|
||||||
|
"integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.16.0 || >=22.3.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@napi-rs/canvas": "^0.1.80"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user