mantisfetch 1.9.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- i18n.py +290 -0
- mantisfetch-1.9.7.dist-info/METADATA +783 -0
- mantisfetch-1.9.7.dist-info/RECORD +63 -0
- mantisfetch-1.9.7.dist-info/WHEEL +5 -0
- mantisfetch-1.9.7.dist-info/licenses/LICENSE +21 -0
- mantisfetch-1.9.7.dist-info/top_level.txt +8 -0
- mantisfetch_browser/__init__.py +4310 -0
- mantisfetch_browser/extract.py +449 -0
- mantisfetch_browser/models.py +412 -0
- mantisfetch_browser/negotiate.py +337 -0
- mantisfetch_browser/ranking.py +335 -0
- mantisfetch_browser/readability.js +2812 -0
- mantisfetch_browser/security.py +174 -0
- mantisfetch_browser/session.py +148 -0
- mantisfetch_browser/vision.py +178 -0
- mantisfetch_common/__init__.py +13 -0
- mantisfetch_common/actor.py +125 -0
- mantisfetch_common/allowed_hosts.py +66 -0
- mantisfetch_common/atomic.py +53 -0
- mantisfetch_common/doc_index_store.py +274 -0
- mantisfetch_common/http_headers.py +42 -0
- mantisfetch_common/metrics.py +76 -0
- mantisfetch_common/paths.py +42 -0
- mantisfetch_common/search_cache.py +97 -0
- mantisfetch_common/storage.py +88 -0
- mantisfetch_deliverables.py +171 -0
- mantisfetch_docreader/__init__.py +7549 -0
- mantisfetch_docreader/config_profiles/document/bid_cn.json +80 -0
- mantisfetch_docreader/config_profiles/document/contract_cn.json +170 -0
- mantisfetch_docreader/config_profiles/field/contract_cn.json +138 -0
- mantisfetch_docreader/images.py +207 -0
- mantisfetch_docreader/models.py +295 -0
- mantisfetch_docreader/ocr/__init__.py +1 -0
- mantisfetch_docreader/ocr/engines.py +396 -0
- mantisfetch_docreader/ocr/tables.py +467 -0
- mantisfetch_docreader/ocr_text.py +403 -0
- mantisfetch_docreader/paddle_ocr_worker.py +310 -0
- mantisfetch_docreader/pdf.py +755 -0
- mantisfetch_docreader/pdf_planning.py +350 -0
- mantisfetch_docreader/profiles.py +475 -0
- mantisfetch_docreader/regions.py +553 -0
- mantisfetch_docreader/sectioning.py +739 -0
- mantisfetch_docreader/storage.py +796 -0
- mantisfetch_docreader/summaries.py +481 -0
- mantisfetch_docreader/tabular.py +416 -0
- mantisfetch_docreader/text_utils.py +177 -0
- mantisfetch_docreader/word.py +415 -0
- mantisfetch_mcp/__init__.py +1655 -0
- mantisfetch_server.py +457 -0
- providers/__init__.py +231 -0
- providers/base.py +94 -0
- providers/errors.py +179 -0
- providers/failover.py +166 -0
- providers/gemini.py +273 -0
- providers/openai_compat.py +396 -0
- providers/search/__init__.py +288 -0
- providers/search/base.py +138 -0
- providers/search/bocha.py +115 -0
- providers/search/brave.py +92 -0
- providers/search/searxng.py +86 -0
- providers/search/tavily.py +98 -0
- providers/sentinel.py +64 -0
- providers/vendor_profiles.py +72 -0
i18n.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MantisFetch i18n — lightweight internationalization, zero dependencies.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from i18n import t, prompt, tmpl, get_locale, set_locale
|
|
6
|
+
|
|
7
|
+
# Messages
|
|
8
|
+
raise HTTPException(422, t("unsupported_format", fmt=".pptx"))
|
|
9
|
+
|
|
10
|
+
# Prompts (for LLM)
|
|
11
|
+
p = prompt("section_summary")
|
|
12
|
+
|
|
13
|
+
# Templates (for output files)
|
|
14
|
+
title = tmpl("digest_title", doc_id="DOC-001", filename="report.pdf")
|
|
15
|
+
|
|
16
|
+
Locale is controlled by LANG env var (default: "en").
|
|
17
|
+
Only "en" and "zh" are supported. Unrecognized values fall back to "en".
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
|
|
22
|
+
_locale: str = "en"
|
|
23
|
+
|
|
24
|
+
# ═══════════════════════════════════════════
|
|
25
|
+
# LLM Prompts
|
|
26
|
+
# ═══════════════════════════════════════════
|
|
27
|
+
|
|
28
|
+
PROMPTS = {
|
|
29
|
+
"zh": {
|
|
30
|
+
"ocr": (
|
|
31
|
+
"请提取这张图片中的所有文字内容。要求:\n"
|
|
32
|
+
"1. 保持原文的段落结构\n"
|
|
33
|
+
"2. 表格用 Markdown 表格格式输出\n"
|
|
34
|
+
"3. 忽略页眉页脚和页码\n"
|
|
35
|
+
"4. 如果有图表,用 [图表: 简要描述] 标记\n"
|
|
36
|
+
"5. 只输出提取的文字,不要加任何额外说明"
|
|
37
|
+
),
|
|
38
|
+
"section_summary": (
|
|
39
|
+
"请为以下文档章节生成简洁摘要。要求:\n"
|
|
40
|
+
"1. 用中文输出\n"
|
|
41
|
+
"2. 保留关键数据、结论和决策要点\n"
|
|
42
|
+
"3. 控制在 2-4 句话\n"
|
|
43
|
+
"4. 如果包含表格数据,提炼关键数字\n"
|
|
44
|
+
"5. 关键结论后标注页码引用,如 (p.12-13)\n"
|
|
45
|
+
"6. 只输出摘要内容"
|
|
46
|
+
),
|
|
47
|
+
"batch_summary": (
|
|
48
|
+
"请为以下 {n} 个章节分别生成简洁摘要。要求:\n"
|
|
49
|
+
"1. 用中文输出\n"
|
|
50
|
+
"2. 保留关键数据、结论和决策要点\n"
|
|
51
|
+
"3. 每个章节 2-4 句话\n"
|
|
52
|
+
"4. 关键结论后标注页码引用,如 (p.12)\n"
|
|
53
|
+
'5. **必须**严格输出 JSON 数组,格式如下(不要输出任何其他文字):\n'
|
|
54
|
+
"[\n"
|
|
55
|
+
' {{"index": 1, "summary": "章节1的摘要..."}},\n'
|
|
56
|
+
' {{"index": 2, "summary": "章节2的摘要..."}}\n'
|
|
57
|
+
"]"
|
|
58
|
+
),
|
|
59
|
+
"digest": (
|
|
60
|
+
"请为以下文档生成一段总体摘要。要求:\n"
|
|
61
|
+
"1. 用中文输出\n"
|
|
62
|
+
"2. 控制在 150 字以内(约 200 tokens)\n"
|
|
63
|
+
"3. 包含:文档主题、核心结论、关键数据(如有)\n"
|
|
64
|
+
"4. 只输出摘要内容,不要加标题或前缀"
|
|
65
|
+
),
|
|
66
|
+
"brief": (
|
|
67
|
+
"请为以下文档生成一份结构化简报。要求:\n"
|
|
68
|
+
"1. 用中文输出\n"
|
|
69
|
+
"2. 控制在 800 字以内(约 1500 tokens)\n"
|
|
70
|
+
"3. 包含:文档背景、各章节核心观点、关键数据、结论\n"
|
|
71
|
+
"4. 用 Markdown 格式,包含二级标题\n"
|
|
72
|
+
"5. 只输出简报内容"
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
"en": {
|
|
76
|
+
"ocr": (
|
|
77
|
+
"Extract all text content from this image. Requirements:\n"
|
|
78
|
+
"1. Preserve original paragraph structure\n"
|
|
79
|
+
"2. Output tables in Markdown table format\n"
|
|
80
|
+
"3. Ignore headers, footers, and page numbers\n"
|
|
81
|
+
"4. Mark charts/figures as [Figure: brief description]\n"
|
|
82
|
+
"5. Output only the extracted text, no additional commentary"
|
|
83
|
+
),
|
|
84
|
+
"section_summary": (
|
|
85
|
+
"Generate a concise summary for the following document section. Requirements:\n"
|
|
86
|
+
"1. Output in English\n"
|
|
87
|
+
"2. Preserve key data, conclusions, and decision points\n"
|
|
88
|
+
"3. Keep to 2-4 sentences\n"
|
|
89
|
+
"4. If table data is included, extract key figures\n"
|
|
90
|
+
"5. Add page references after key conclusions, e.g. (p.12-13)\n"
|
|
91
|
+
"6. Output only the summary"
|
|
92
|
+
),
|
|
93
|
+
"batch_summary": (
|
|
94
|
+
"Generate concise summaries for each of the following {n} sections. Requirements:\n"
|
|
95
|
+
"1. Output in English\n"
|
|
96
|
+
"2. Preserve key data, conclusions, and decision points\n"
|
|
97
|
+
"3. 2-4 sentences per section\n"
|
|
98
|
+
"4. Add page references after key conclusions, e.g. (p.12)\n"
|
|
99
|
+
"5. You MUST output a strict JSON array in the following format (no other text):\n"
|
|
100
|
+
"[\n"
|
|
101
|
+
' {{"index": 1, "summary": "Summary for section 1..."}},\n'
|
|
102
|
+
' {{"index": 2, "summary": "Summary for section 2..."}}\n'
|
|
103
|
+
"]"
|
|
104
|
+
),
|
|
105
|
+
"digest": (
|
|
106
|
+
"Generate an overall summary for the following document. Requirements:\n"
|
|
107
|
+
"1. Output in English\n"
|
|
108
|
+
"2. Keep under 150 words (~200 tokens)\n"
|
|
109
|
+
"3. Include: document topic, core conclusions, key data (if any)\n"
|
|
110
|
+
"4. Output only the summary, no title or prefix"
|
|
111
|
+
),
|
|
112
|
+
"brief": (
|
|
113
|
+
"Generate a structured briefing for the following document. Requirements:\n"
|
|
114
|
+
"1. Output in English\n"
|
|
115
|
+
"2. Keep under 800 words (~1500 tokens)\n"
|
|
116
|
+
"3. Include: document background, key points per chapter, key data, conclusions\n"
|
|
117
|
+
"4. Use Markdown format with level-2 headings\n"
|
|
118
|
+
"5. Output only the briefing"
|
|
119
|
+
),
|
|
120
|
+
},
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# ═══════════════════════════════════════════
|
|
124
|
+
# User-facing messages (API errors, fallbacks)
|
|
125
|
+
# ═══════════════════════════════════════════
|
|
126
|
+
|
|
127
|
+
MESSAGES = {
|
|
128
|
+
"zh": {
|
|
129
|
+
"unsupported_format": "不支持的格式: {fmt}",
|
|
130
|
+
"file_save_failed": "文件保存失败: {err}",
|
|
131
|
+
"parse_failed": "解析失败: {err}",
|
|
132
|
+
"write_failed": "写入失败: {err}",
|
|
133
|
+
"doc_not_found": "文档不存在: {doc_id}",
|
|
134
|
+
"digest_not_found": "digest 不存在: {doc_id}",
|
|
135
|
+
"brief_not_found": "brief 不存在: {doc_id}",
|
|
136
|
+
"full_not_found": "full 不存在: {doc_id}",
|
|
137
|
+
"section_not_found": "章节不存在: {sid}",
|
|
138
|
+
"tables_dir_not_found": "表格目录不存在: {doc_id}",
|
|
139
|
+
"table_not_found": "表格不存在: {table_id}",
|
|
140
|
+
"file_open_failed": "无法打开文件: {path}",
|
|
141
|
+
"office_converter_missing": "缺少 LibreOffice/soffice,无法转换老式 Office 文件",
|
|
142
|
+
"office_conversion_failed": "Office 文件转换失败: {src} -> .{dst}: {err}",
|
|
143
|
+
"gemini_not_installed": "请安装 google-genai: pip install google-genai",
|
|
144
|
+
"gemini_key_missing": "请设置 GEMINI_API_KEY 或 GOOGLE_API_KEY 环境变量",
|
|
145
|
+
"ocr_failed": "[OCR 失败: 第 {page} 页]",
|
|
146
|
+
"summary_failed": "[摘要生成失败]",
|
|
147
|
+
"summary_pending": "(摘要待生成)",
|
|
148
|
+
"summary_missing": "(摘要缺失)",
|
|
149
|
+
# Browser service
|
|
150
|
+
"table_prefix": "[表格]",
|
|
151
|
+
"table_truncated": "[... 共 {total} 行,已显示前 {shown} 行 ...]",
|
|
152
|
+
},
|
|
153
|
+
"en": {
|
|
154
|
+
"unsupported_format": "Unsupported format: {fmt}",
|
|
155
|
+
"file_save_failed": "File save failed: {err}",
|
|
156
|
+
"parse_failed": "Parse failed: {err}",
|
|
157
|
+
"write_failed": "Write failed: {err}",
|
|
158
|
+
"doc_not_found": "Document not found: {doc_id}",
|
|
159
|
+
"digest_not_found": "Digest not found: {doc_id}",
|
|
160
|
+
"brief_not_found": "Brief not found: {doc_id}",
|
|
161
|
+
"full_not_found": "Full text not found: {doc_id}",
|
|
162
|
+
"section_not_found": "Section not found: {sid}",
|
|
163
|
+
"tables_dir_not_found": "Tables directory not found: {doc_id}",
|
|
164
|
+
"table_not_found": "Table not found: {table_id}",
|
|
165
|
+
"file_open_failed": "Cannot open file: {path}",
|
|
166
|
+
"office_converter_missing": "LibreOffice/soffice is required to convert legacy Office files",
|
|
167
|
+
"office_conversion_failed": "Office conversion failed: {src} -> .{dst}: {err}",
|
|
168
|
+
"gemini_not_installed": "Please install google-genai: pip install google-genai",
|
|
169
|
+
"gemini_key_missing": "Please set GEMINI_API_KEY or GOOGLE_API_KEY environment variable",
|
|
170
|
+
"ocr_failed": "[OCR failed: page {page}]",
|
|
171
|
+
"summary_failed": "[Summary generation failed]",
|
|
172
|
+
"summary_pending": "(Summary pending)",
|
|
173
|
+
"summary_missing": "(Summary missing)",
|
|
174
|
+
# Browser service
|
|
175
|
+
"table_prefix": "[Table]",
|
|
176
|
+
"table_truncated": "[... {total} rows total, showing first {shown} ...]",
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# ═══════════════════════════════════════════
|
|
181
|
+
# Output file templates
|
|
182
|
+
# ═══════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
TEMPLATES = {
|
|
185
|
+
"zh": {
|
|
186
|
+
"digest_title": "# {doc_id}: {filename}",
|
|
187
|
+
"brief_header": (
|
|
188
|
+
"# {doc_id}: {filename} · 简报\n\n"
|
|
189
|
+
"**来源**: {filename} | **页数**: {pages} | "
|
|
190
|
+
"**章节**: {sections} | **OCR页**: {ocr_pages}\n\n---\n\n"
|
|
191
|
+
),
|
|
192
|
+
"section_header": (
|
|
193
|
+
"# {title}\n\n"
|
|
194
|
+
"**章节 {index}** | **SID**: {sid} | **页码**: {page_range}\n\n"
|
|
195
|
+
),
|
|
196
|
+
"section_summary_line": "**摘要**: {summary}\n\n---\n\n",
|
|
197
|
+
"default_section_title": "前言",
|
|
198
|
+
"full_document_title": "全文",
|
|
199
|
+
"summary_low_text_note": (
|
|
200
|
+
"**注意**: 文档可提取文本过少({chars} 字符),未生成模型摘要。"
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
"en": {
|
|
204
|
+
"digest_title": "# {doc_id}: {filename}",
|
|
205
|
+
"brief_header": (
|
|
206
|
+
"# {doc_id}: {filename} · Briefing\n\n"
|
|
207
|
+
"**Source**: {filename} | **Pages**: {pages} | "
|
|
208
|
+
"**Sections**: {sections} | **OCR pages**: {ocr_pages}\n\n---\n\n"
|
|
209
|
+
),
|
|
210
|
+
"section_header": (
|
|
211
|
+
"# {title}\n\n"
|
|
212
|
+
"**Section {index}** | **SID**: {sid} | **Pages**: {page_range}\n\n"
|
|
213
|
+
),
|
|
214
|
+
"section_summary_line": "**Summary**: {summary}\n\n---\n\n",
|
|
215
|
+
"default_section_title": "Preface",
|
|
216
|
+
"full_document_title": "Full document",
|
|
217
|
+
"summary_low_text_note": (
|
|
218
|
+
"**Note**: the document contains too little extractable text "
|
|
219
|
+
"({chars} chars) for a generated summary."
|
|
220
|
+
),
|
|
221
|
+
},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ═══════════════════════════════════════════
|
|
226
|
+
# Public API
|
|
227
|
+
# ═══════════════════════════════════════════
|
|
228
|
+
|
|
229
|
+
def get_locale() -> str:
|
|
230
|
+
return _locale
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def set_locale(locale: str) -> None:
|
|
234
|
+
global _locale
|
|
235
|
+
_locale = locale if locale in ("zh", "en") else "en"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def init_locale(env_var: str = "LANG") -> str:
|
|
239
|
+
"""Initialize locale from environment. Call once at startup."""
|
|
240
|
+
raw = os.environ.get(env_var, "en")
|
|
241
|
+
lang = raw[:2].lower()
|
|
242
|
+
set_locale(lang)
|
|
243
|
+
return _locale
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _normalize_locale(locale: str | None) -> str:
|
|
247
|
+
if not locale:
|
|
248
|
+
return _locale
|
|
249
|
+
value = locale.lower()
|
|
250
|
+
if value.startswith("zh"):
|
|
251
|
+
return "zh"
|
|
252
|
+
if value.startswith("en"):
|
|
253
|
+
return "en"
|
|
254
|
+
return _locale
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def t(key: str, **kwargs) -> str:
|
|
258
|
+
"""Translate a user-facing message."""
|
|
259
|
+
msg = MESSAGES.get(_locale, MESSAGES["en"]).get(key)
|
|
260
|
+
if msg is None:
|
|
261
|
+
msg = MESSAGES["en"].get(key, key)
|
|
262
|
+
return msg.format(**kwargs) if kwargs else msg
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def prompt_for_locale(locale: str | None, key: str, **kwargs) -> str:
|
|
266
|
+
"""Get an LLM prompt for an explicit locale."""
|
|
267
|
+
resolved = _normalize_locale(locale)
|
|
268
|
+
p = PROMPTS.get(resolved, PROMPTS["en"]).get(key)
|
|
269
|
+
if p is None:
|
|
270
|
+
p = PROMPTS["en"].get(key, "")
|
|
271
|
+
return p.format(**kwargs) if kwargs else p
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def prompt(key: str, **kwargs) -> str:
|
|
275
|
+
"""Get a localized LLM prompt."""
|
|
276
|
+
return prompt_for_locale(_locale, key, **kwargs)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def tmpl_for_locale(locale: str | None, key: str, **kwargs) -> str:
|
|
280
|
+
"""Get an output template for an explicit locale."""
|
|
281
|
+
resolved = _normalize_locale(locale)
|
|
282
|
+
tpl = TEMPLATES.get(resolved, TEMPLATES["en"]).get(key)
|
|
283
|
+
if tpl is None:
|
|
284
|
+
tpl = TEMPLATES["en"].get(key, "")
|
|
285
|
+
return tpl.format(**kwargs) if kwargs else tpl
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def tmpl(key: str, **kwargs) -> str:
|
|
289
|
+
"""Get a localized output template."""
|
|
290
|
+
return tmpl_for_locale(_locale, key, **kwargs)
|