lightpdf-aipdf-mcp 0.1.144__py3-none-any.whl → 0.1.146__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.
- lightpdf_aipdf_mcp/common.py +1 -0
- lightpdf_aipdf_mcp/create_pdf.py +386 -50
- lightpdf_aipdf_mcp/server.py +44 -10
- lightpdf_aipdf_mcp/summarizer.py +6 -43
- {lightpdf_aipdf_mcp-0.1.144.dist-info → lightpdf_aipdf_mcp-0.1.146.dist-info}/METADATA +1 -1
- lightpdf_aipdf_mcp-0.1.146.dist-info/RECORD +13 -0
- lightpdf_aipdf_mcp-0.1.144.dist-info/RECORD +0 -13
- {lightpdf_aipdf_mcp-0.1.144.dist-info → lightpdf_aipdf_mcp-0.1.146.dist-info}/WHEEL +0 -0
- {lightpdf_aipdf_mcp-0.1.144.dist-info → lightpdf_aipdf_mcp-0.1.146.dist-info}/entry_points.txt +0 -0
lightpdf_aipdf_mcp/common.py
CHANGED
@@ -249,4 +249,5 @@ class BaseApiClient:
|
|
249
249
|
if "data" not in result or "task_id" not in result["data"]:
|
250
250
|
await self.logger.error(f"无法获取任务ID。API响应:{json.dumps(result, ensure_ascii=False)}")
|
251
251
|
|
252
|
+
await self.logger.log("debug", f"API响应:{json.dumps(result, ensure_ascii=False)}")
|
252
253
|
return result["data"]["task_id"]
|
lightpdf_aipdf_mcp/create_pdf.py
CHANGED
@@ -1,62 +1,398 @@
|
|
1
|
-
"""
|
1
|
+
"""根据用户输入请求创建PDF文件的接口"""
|
2
|
+
from dataclasses import dataclass
|
2
3
|
from typing import Optional
|
3
4
|
import os
|
4
5
|
import uuid
|
5
|
-
|
6
|
+
import httpx
|
7
|
+
from .common import Logger, FileHandler, BaseResult, BaseApiClient
|
6
8
|
from .editor import Editor, EditResult, EditType
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
10
|
+
@dataclass
|
11
|
+
class CreatePdfResult(BaseResult):
|
12
|
+
"""PDF创建结果数据类"""
|
13
|
+
pass
|
14
|
+
|
15
|
+
class PDFCreator(BaseApiClient):
|
16
|
+
"""PDF文档创建器"""
|
17
|
+
def __init__(self, logger: Logger, file_handler: FileHandler):
|
18
|
+
super().__init__(logger, file_handler)
|
19
|
+
self.api_base_url = f"https://{self.api_endpoint}/tasks/llm/chats"
|
20
|
+
# 语言代码到语言名称的映射
|
21
|
+
self.language_map = {
|
22
|
+
"zh": "简体中文", "en": "English", "de": "Deutsch", "es": "Español",
|
23
|
+
"fr": "Français", "ja": "日本語", "pt": "Português", "zh-tw": "繁體中文",
|
24
|
+
"ar": "العربية", "cs": "Čeština", "da": "Dansk", "fi": "Suomi",
|
25
|
+
"el": "Ελληνικά", "hu": "Magyar", "it": "Italiano", "nl": "Nederlands",
|
26
|
+
"no": "Norsk", "pl": "Polski", "sv": "Svenska", "tr": "Türkçe"
|
27
|
+
}
|
28
|
+
|
29
|
+
async def create_pdf_from_prompt(
|
30
|
+
self,
|
31
|
+
prompt: str,
|
32
|
+
language: str,
|
33
|
+
enable_web_search: bool = False,
|
34
|
+
original_name: Optional[str] = None
|
35
|
+
) -> CreatePdfResult:
|
36
|
+
"""
|
37
|
+
根据用户输入请求创建PDF文件。
|
38
|
+
|
39
|
+
参数:
|
40
|
+
prompt (str): 用户的输入请求或提示词,仅支持文字描述,不支持文件附件等。
|
41
|
+
language (str): 生成PDF的语言,必需参数。
|
42
|
+
enable_web_search (bool): 是否启用联网搜索,默认False。
|
43
|
+
original_name (Optional[str]): 可选,原始文件名。
|
44
|
+
返回:
|
45
|
+
CreatePdfResult: 包含生成结果的信息。
|
46
|
+
"""
|
47
|
+
tex_path = None
|
23
48
|
try:
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
49
|
+
# 1. 根据prompt生成latex代码
|
50
|
+
latex_code = await self._generate_latex_code(prompt, language, enable_web_search)
|
51
|
+
if not latex_code:
|
52
|
+
return CreatePdfResult(
|
53
|
+
success=False,
|
54
|
+
file_path="",
|
55
|
+
error_message="生成的latex代码为空",
|
56
|
+
original_name=original_name
|
57
|
+
)
|
58
|
+
|
59
|
+
# 2. 保存latex_code为本地.tex文件
|
60
|
+
tex_path = await self._save_latex_to_file(latex_code)
|
61
|
+
|
62
|
+
# 3. 调用Editor.edit_pdf并传递oss://tex2pdf参数生成PDF
|
63
|
+
result = await self._convert_tex_to_pdf(tex_path, original_name)
|
64
|
+
|
65
|
+
# 转换EditResult到CreatePdfResult
|
66
|
+
return CreatePdfResult(
|
67
|
+
success=result.success,
|
68
|
+
file_path=result.file_path,
|
69
|
+
error_message=result.error_message,
|
70
|
+
download_url=result.download_url,
|
71
|
+
original_name=result.original_name,
|
72
|
+
task_id=result.task_id
|
73
|
+
)
|
74
|
+
|
30
75
|
except Exception as e:
|
31
|
-
return
|
32
|
-
|
76
|
+
return CreatePdfResult(
|
77
|
+
success=False,
|
78
|
+
file_path="",
|
79
|
+
error_message=f"PDF创建过程中发生错误: {e}",
|
80
|
+
original_name=original_name
|
33
81
|
)
|
82
|
+
finally:
|
83
|
+
# 清理临时文件
|
84
|
+
if tex_path and os.path.exists(tex_path):
|
85
|
+
self._schedule_file_cleanup(tex_path)
|
86
|
+
|
87
|
+
async def _generate_latex_code(self, prompt: str, language: str, enable_web_search: bool) -> str:
|
88
|
+
"""根据用户输入生成LaTeX代码"""
|
89
|
+
lang_str = self.language_map.get(language)
|
90
|
+
await self.logger.log("debug", f"开始为语言 {lang_str} 生成LaTeX代码,联网搜索: {enable_web_search}")
|
91
|
+
|
92
|
+
# 这里应该是实际的代码生成逻辑
|
93
|
+
# 暂时返回空字符串,需要后续实现
|
94
|
+
async with httpx.AsyncClient(timeout=3600.0) as client:
|
95
|
+
template_variables = {
|
96
|
+
"PROMPT": prompt
|
97
|
+
}
|
98
|
+
if lang_str:
|
99
|
+
template_variables["LANGUAGE"] = lang_str
|
100
|
+
|
101
|
+
headers = {"X-API-KEY": self.api_key}
|
102
|
+
data = {
|
103
|
+
"po": "lightpdf",
|
104
|
+
"response_type": 0,
|
105
|
+
"template_id": "48a62054-9cf0-483d-9787-b31afc32e079",
|
106
|
+
"template_variables": template_variables
|
107
|
+
}
|
108
|
+
if enable_web_search:
|
109
|
+
# 获取真实的用户信息
|
110
|
+
plugin_options = await self._get_real_user_info(language)
|
111
|
+
data["plugins"] = [
|
112
|
+
{
|
113
|
+
"function": {
|
114
|
+
"id": 1001,
|
115
|
+
"options": plugin_options
|
116
|
+
},
|
117
|
+
"callback": None
|
118
|
+
}
|
119
|
+
]
|
120
|
+
|
121
|
+
await self.logger.log("debug", f"正在提交生成LaTeX代码...{data}")
|
122
|
+
response = await client.post(self.api_base_url, json=data, headers=headers)
|
123
|
+
|
124
|
+
task_id = await self._handle_api_response(response, "生成LaTeX代码")
|
125
|
+
await self.logger.log("debug", f"生成LaTeX代码,task_id: {task_id}")
|
126
|
+
|
127
|
+
content = await self._wait_for_task(client, task_id, "生成LaTeX代码", is_raw=True)
|
34
128
|
|
35
|
-
|
129
|
+
return content.get("text", "")
|
130
|
+
|
131
|
+
async def _save_latex_to_file(self, latex_code: str) -> str:
|
132
|
+
"""保存LaTeX代码到临时文件"""
|
133
|
+
temp_dir = "./tmp"
|
134
|
+
os.makedirs(temp_dir, exist_ok=True)
|
135
|
+
tex_filename = f"latex_code_{uuid.uuid4().hex}.tex"
|
136
|
+
tex_path = os.path.join(temp_dir, tex_filename)
|
137
|
+
|
138
|
+
# 清理markdown代码块标记
|
139
|
+
cleaned_code = self._clean_latex_code(latex_code)
|
140
|
+
|
141
|
+
with open(tex_path, "w", encoding="utf-8") as f:
|
142
|
+
f.write(cleaned_code)
|
143
|
+
|
144
|
+
return tex_path
|
145
|
+
|
146
|
+
def _clean_latex_code(self, latex_code: str) -> str:
|
147
|
+
"""清理LaTeX代码中的markdown标记"""
|
148
|
+
if not latex_code:
|
149
|
+
return latex_code
|
150
|
+
|
151
|
+
# 去除首行和尾行的markdown代码块标记
|
152
|
+
lines = latex_code.strip().split('\n')
|
153
|
+
|
154
|
+
# 检查并移除首行的```latex标记
|
155
|
+
if lines and lines[0].strip().startswith('```'):
|
156
|
+
lines = lines[1:]
|
157
|
+
|
158
|
+
# 检查并移除尾行的```标记
|
159
|
+
if lines and lines[-1].strip() == '```':
|
160
|
+
lines = lines[:-1]
|
161
|
+
|
162
|
+
# 重新组合代码
|
163
|
+
cleaned_code = '\n'.join(lines)
|
164
|
+
|
165
|
+
return cleaned_code.strip()
|
166
|
+
|
167
|
+
async def _get_real_user_info(self, language: str) -> dict:
|
168
|
+
"""获取真实的用户信息"""
|
169
|
+
import platform
|
170
|
+
|
171
|
+
# 获取真实的用户代理字符串
|
172
|
+
system = platform.system()
|
173
|
+
version = platform.version()
|
174
|
+
architecture = platform.machine()
|
175
|
+
|
176
|
+
# 构建更真实的User-Agent
|
177
|
+
if system == "Darwin": # macOS
|
178
|
+
user_agent = f"Mozilla/5.0 (Macintosh; Intel Mac OS X {version.replace('.', '_')}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
179
|
+
elif system == "Windows":
|
180
|
+
user_agent = f"Mozilla/5.0 (Windows NT {version}; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
181
|
+
elif system == "Linux":
|
182
|
+
user_agent = f"Mozilla/5.0 (X11; Linux {architecture}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
183
|
+
else:
|
184
|
+
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
185
|
+
|
186
|
+
# 获取真实的IP地址
|
187
|
+
local_ip = self._get_local_ip()
|
188
|
+
|
189
|
+
# 根据语言参数动态生成accept_language
|
190
|
+
accept_language = self._get_accept_language(language)
|
191
|
+
|
192
|
+
return {
|
193
|
+
"user_agent": user_agent,
|
194
|
+
"accept_language": accept_language,
|
195
|
+
"ip": local_ip
|
196
|
+
}
|
197
|
+
|
198
|
+
def _get_accept_language(self, language: str) -> str:
|
199
|
+
"""根据语言代码生成对应的accept_language字符串"""
|
200
|
+
# 语言代码到HTTP Accept-Language的映射
|
201
|
+
language_codes = {
|
202
|
+
"zh": "zh-CN,zh;q=0.9,en;q=0.8",
|
203
|
+
"en": "en-US,en;q=0.9",
|
204
|
+
"de": "de-DE,de;q=0.9,en;q=0.8",
|
205
|
+
"es": "es-ES,es;q=0.9,en;q=0.8",
|
206
|
+
"fr": "fr-FR,fr;q=0.9,en;q=0.8",
|
207
|
+
"ja": "ja-JP,ja;q=0.9,en;q=0.8",
|
208
|
+
"pt": "pt-PT,pt;q=0.9,en;q=0.8",
|
209
|
+
"zh-tw": "zh-TW,zh;q=0.9,en;q=0.8",
|
210
|
+
"ar": "ar-SA,ar;q=0.9,en;q=0.8",
|
211
|
+
"cs": "cs-CZ,cs;q=0.9,en;q=0.8",
|
212
|
+
"da": "da-DK,da;q=0.9,en;q=0.8",
|
213
|
+
"fi": "fi-FI,fi;q=0.9,en;q=0.8",
|
214
|
+
"el": "el-GR,el;q=0.9,en;q=0.8",
|
215
|
+
"hu": "hu-HU,hu;q=0.9,en;q=0.8",
|
216
|
+
"it": "it-IT,it;q=0.9,en;q=0.8",
|
217
|
+
"nl": "nl-NL,nl;q=0.9,en;q=0.8",
|
218
|
+
"no": "no-NO,no;q=0.9,en;q=0.8",
|
219
|
+
"pl": "pl-PL,pl;q=0.9,en;q=0.8",
|
220
|
+
"sv": "sv-SE,sv;q=0.9,en;q=0.8",
|
221
|
+
"tr": "tr-TR,tr;q=0.9,en;q=0.8"
|
222
|
+
}
|
223
|
+
|
224
|
+
return language_codes.get(language, "en-US,en;q=0.9")
|
225
|
+
|
226
|
+
def _get_local_ip(self) -> str:
|
227
|
+
"""获取本地IP地址的稳定方法,优先获取物理网络接口IP"""
|
228
|
+
import socket
|
229
|
+
|
230
|
+
# 收集所有可能的IP地址
|
231
|
+
candidate_ips = []
|
232
|
+
|
233
|
+
# 方法1: 尝试连接外部DNS服务器
|
234
|
+
try:
|
235
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
236
|
+
# 使用多个DNS服务器进行尝试
|
237
|
+
for dns_server in ["8.8.8.8", "1.1.1.1", "208.67.222.222", "114.114.114.114", "101.101.101.101", "1.2.4.8"]:
|
238
|
+
try:
|
239
|
+
s.connect((dns_server, 80))
|
240
|
+
ip = s.getsockname()[0]
|
241
|
+
if self._is_valid_local_ip(ip):
|
242
|
+
candidate_ips.append(ip)
|
243
|
+
break # 找到第一个有效IP就停止
|
244
|
+
except Exception:
|
245
|
+
continue
|
246
|
+
except Exception:
|
247
|
+
pass
|
248
|
+
|
249
|
+
# 方法2: 获取本机所有网络接口
|
36
250
|
try:
|
37
|
-
|
38
|
-
|
39
|
-
|
251
|
+
hostname = socket.gethostname()
|
252
|
+
ip_list = socket.gethostbyname_ex(hostname)[2]
|
253
|
+
for ip in ip_list:
|
254
|
+
if self._is_valid_local_ip(ip) and ip not in candidate_ips:
|
255
|
+
candidate_ips.append(ip)
|
256
|
+
except Exception:
|
257
|
+
pass
|
258
|
+
|
259
|
+
# 方法3: 使用系统网络接口信息
|
260
|
+
try:
|
261
|
+
import platform
|
262
|
+
if platform.system() != "Windows":
|
263
|
+
# Unix/Linux/macOS系统:尝试使用更详细的网络接口信息
|
264
|
+
import subprocess
|
265
|
+
result = subprocess.run(['ip', 'route', 'get', '8.8.8.8'],
|
266
|
+
capture_output=True, text=True)
|
267
|
+
if result.returncode == 0:
|
268
|
+
for line in result.stdout.split('\n'):
|
269
|
+
if 'src' in line:
|
270
|
+
parts = line.split()
|
271
|
+
if 'src' in parts:
|
272
|
+
src_index = parts.index('src')
|
273
|
+
if src_index + 1 < len(parts):
|
274
|
+
ip = parts[src_index + 1]
|
275
|
+
if self._is_valid_local_ip(ip) and ip not in candidate_ips:
|
276
|
+
candidate_ips.append(ip)
|
277
|
+
break
|
278
|
+
except Exception:
|
279
|
+
pass
|
280
|
+
|
281
|
+
# 选择最优IP地址
|
282
|
+
if candidate_ips:
|
283
|
+
return self._select_best_ip(candidate_ips)
|
284
|
+
|
285
|
+
# 最后的后备方案
|
286
|
+
return "127.0.0.1"
|
287
|
+
|
288
|
+
def _select_best_ip(self, candidate_ips: list) -> str:
|
289
|
+
"""从候选IP中选择最佳的IP地址"""
|
290
|
+
import ipaddress
|
291
|
+
|
292
|
+
# 按优先级排序IP地址
|
293
|
+
def ip_priority(ip: str) -> int:
|
294
|
+
try:
|
295
|
+
ip_obj = ipaddress.ip_address(ip)
|
296
|
+
|
297
|
+
# 最高优先级:公网IP
|
298
|
+
if ip_obj.is_global:
|
299
|
+
return 1
|
300
|
+
|
301
|
+
# 高优先级:常见的家庭/办公网络
|
302
|
+
common_networks = [
|
303
|
+
ipaddress.ip_network('192.168.0.0/16'), # 家庭网络
|
304
|
+
ipaddress.ip_network('10.0.0.0/8'), # 企业网络
|
305
|
+
ipaddress.ip_network('172.16.0.0/12'), # 企业网络(但要避免虚拟网络段)
|
306
|
+
]
|
307
|
+
|
308
|
+
for network in common_networks:
|
309
|
+
if ip_obj in network:
|
310
|
+
# 进一步细分优先级
|
311
|
+
if ip.startswith('192.168.1.') or ip.startswith('192.168.0.'):
|
312
|
+
return 2 # 最常见的家庭网络
|
313
|
+
elif ip.startswith('192.168.'):
|
314
|
+
return 3 # 其他家庭网络
|
315
|
+
elif ip.startswith('10.'):
|
316
|
+
return 4 # 企业网络
|
317
|
+
else:
|
318
|
+
return 5 # 其他私有网络
|
319
|
+
|
320
|
+
# 较低优先级:其他私有IP
|
321
|
+
if ip_obj.is_private:
|
322
|
+
return 6
|
323
|
+
|
324
|
+
# 最低优先级:其他地址
|
325
|
+
return 7
|
326
|
+
|
327
|
+
except ValueError:
|
328
|
+
return 99 # 无效IP地址
|
329
|
+
|
330
|
+
# 按优先级排序并返回最佳IP
|
331
|
+
candidate_ips.sort(key=ip_priority)
|
332
|
+
return candidate_ips[0]
|
333
|
+
|
334
|
+
def _is_valid_local_ip(self, ip: str) -> bool:
|
335
|
+
"""验证IP地址是否为有效的本地IP,排除虚拟网络接口"""
|
336
|
+
import ipaddress
|
40
337
|
|
41
|
-
|
42
|
-
|
338
|
+
try:
|
339
|
+
ip_obj = ipaddress.ip_address(ip)
|
340
|
+
|
341
|
+
# 排除回环地址和链路本地地址
|
342
|
+
if ip_obj.is_loopback or ip_obj.is_link_local:
|
343
|
+
return False
|
344
|
+
|
345
|
+
# 排除常见的虚拟网络IP段
|
346
|
+
virtual_networks = [
|
347
|
+
# Docker默认网段
|
348
|
+
ipaddress.ip_network('172.17.0.0/16'),
|
349
|
+
ipaddress.ip_network('172.18.0.0/16'),
|
350
|
+
ipaddress.ip_network('172.19.0.0/16'),
|
351
|
+
ipaddress.ip_network('172.20.0.0/16'),
|
352
|
+
# VMware默认网段
|
353
|
+
ipaddress.ip_network('192.168.56.0/24'),
|
354
|
+
ipaddress.ip_network('192.168.57.0/24'),
|
355
|
+
# VirtualBox默认网段
|
356
|
+
ipaddress.ip_network('192.168.100.0/24'),
|
357
|
+
# Hyper-V默认网段
|
358
|
+
ipaddress.ip_network('172.16.0.0/12'),
|
359
|
+
# 其他常见虚拟网段
|
360
|
+
ipaddress.ip_network('10.0.75.0/24'), # Parallels
|
361
|
+
ipaddress.ip_network('169.254.0.0/16'), # APIPA
|
362
|
+
]
|
363
|
+
|
364
|
+
# 检查是否在虚拟网络范围内
|
365
|
+
for network in virtual_networks:
|
366
|
+
if ip_obj in network:
|
367
|
+
return False
|
368
|
+
|
369
|
+
return True
|
370
|
+
except ValueError:
|
371
|
+
return False
|
43
372
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
373
|
+
async def _convert_tex_to_pdf(self, tex_path: str, original_name: Optional[str]) -> EditResult:
|
374
|
+
"""将TEX文件转换为PDF"""
|
375
|
+
editor = Editor(self.logger, self.file_handler)
|
376
|
+
extra_params = {"pages": '[{"url": "oss://tex2pdf", "oss_file": ""}]'}
|
377
|
+
|
378
|
+
tex_filename = os.path.basename(tex_path)
|
379
|
+
return await editor.edit_pdf(
|
380
|
+
tex_path,
|
381
|
+
edit_type=EditType.EDIT,
|
382
|
+
extra_params=extra_params,
|
383
|
+
original_name=original_name or tex_filename
|
384
|
+
)
|
385
|
+
|
386
|
+
def _schedule_file_cleanup(self, file_path: str, delay: int = 300):
|
387
|
+
"""安排文件清理"""
|
388
|
+
import threading
|
389
|
+
|
390
|
+
def delayed_remove(path):
|
391
|
+
try:
|
392
|
+
os.remove(path)
|
393
|
+
except Exception:
|
394
|
+
pass
|
395
|
+
|
396
|
+
timer = threading.Timer(delay, delayed_remove, args=(file_path,))
|
397
|
+
timer.daemon = True
|
398
|
+
timer.start()
|
lightpdf_aipdf_mcp/server.py
CHANGED
@@ -526,6 +526,13 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
526
526
|
"type": "boolean",
|
527
527
|
"default": False,
|
528
528
|
"description": "Only effective when converting Excel to PDF. If true, each sheet will be forced to fit into a single PDF page (even if content overflows; no additional pages will be created). If false, each sheet may be split into multiple PDF pages if the content is too large."
|
529
|
+
},
|
530
|
+
"image_quality": {
|
531
|
+
"type": "integer",
|
532
|
+
"minimum": 0,
|
533
|
+
"maximum": 200,
|
534
|
+
"default": 100,
|
535
|
+
"description": "Image quality setting, 0-200. Only effective when converting PDF to image formats (jpg, jpeg, png). Higher values produce better quality but larger file sizes."
|
529
536
|
}
|
530
537
|
},
|
531
538
|
"required": ["files", "format"]
|
@@ -1204,20 +1211,30 @@ async def handle_list_tools() -> list[types.Tool]:
|
|
1204
1211
|
),
|
1205
1212
|
types.Tool(
|
1206
1213
|
name="create_pdf",
|
1207
|
-
description="
|
1214
|
+
description="Generate PDF documents from text-only instructions or descriptions. The tool creates PDFs based on written prompts such as 'create a business report', 'generate meeting minutes', etc. Only accepts plain text input - no file uploads or multimedia content supported.",
|
1208
1215
|
inputSchema={
|
1209
1216
|
"type": "object",
|
1210
1217
|
"properties": {
|
1211
|
-
"
|
1218
|
+
"prompt": {
|
1212
1219
|
"type": "string",
|
1213
|
-
"description": "
|
1220
|
+
"description": "A text-only description or instruction of what PDF content to generate (e.g., 'Create a business report about market analysis', 'Generate a technical documentation for API usage'). Must be plain text input only - no file uploads, attachments, images, or multimedia content are supported."
|
1214
1221
|
},
|
1215
1222
|
"filename": {
|
1216
1223
|
"type": "string",
|
1217
1224
|
"description": "The filename for the generated PDF"
|
1225
|
+
},
|
1226
|
+
"language": {
|
1227
|
+
"type": "string",
|
1228
|
+
"description": "The language for the generated PDF content.",
|
1229
|
+
"enum": ["zh", "en", "de", "es", "fr", "ja", "pt", "zh-tw", "ar", "cs", "da", "fi", "el", "hu", "it", "nl", "no", "pl", "sv", "tr"]
|
1230
|
+
},
|
1231
|
+
"enable_web_search": {
|
1232
|
+
"type": "boolean",
|
1233
|
+
"description": "Whether to enable web search to gather additional information for content generation",
|
1234
|
+
"default": False
|
1218
1235
|
}
|
1219
1236
|
},
|
1220
|
-
"required": ["
|
1237
|
+
"required": ["prompt", "filename", "language"]
|
1221
1238
|
}
|
1222
1239
|
),
|
1223
1240
|
types.Tool(
|
@@ -1367,7 +1384,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1367
1384
|
"convert_document": {
|
1368
1385
|
"format_key": "format", # 从arguments获取format
|
1369
1386
|
"is_edit_operation": False,
|
1370
|
-
"param_keys": ["merge_all", "one_page_per_sheet"]
|
1387
|
+
"param_keys": ["merge_all", "one_page_per_sheet", "image_quality"]
|
1371
1388
|
},
|
1372
1389
|
"remove_watermark": {
|
1373
1390
|
"format": "doc-repair", # 固定format
|
@@ -1475,6 +1492,7 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1475
1492
|
"format": "png", # 提取图片的默认格式
|
1476
1493
|
"page_size": "",
|
1477
1494
|
"resolution": 0,
|
1495
|
+
"image_quality": 100, # PDF转图片的图片质量默认值
|
1478
1496
|
}
|
1479
1497
|
|
1480
1498
|
if name in TOOL_CONFIG:
|
@@ -1571,19 +1589,35 @@ async def handle_call_tool(name: str, arguments: dict | None) -> list[types.Text
|
|
1571
1589
|
return [result]
|
1572
1590
|
|
1573
1591
|
elif name == "create_pdf":
|
1574
|
-
from .create_pdf import
|
1575
|
-
|
1592
|
+
from .create_pdf import PDFCreator
|
1593
|
+
prompt = arguments.get("prompt")
|
1576
1594
|
filename = arguments.get("filename")
|
1577
|
-
|
1578
|
-
|
1595
|
+
language = arguments.get("language")
|
1596
|
+
enable_web_search = arguments.get("enable_web_search", False)
|
1597
|
+
|
1598
|
+
if not prompt:
|
1599
|
+
error_msg = "prompt参数不能为空"
|
1579
1600
|
await logger.error(error_msg)
|
1580
1601
|
return [types.TextContent(type="text", text=error_msg)]
|
1581
1602
|
if not filename:
|
1582
1603
|
error_msg = "filename参数不能为空"
|
1583
1604
|
await logger.error(error_msg)
|
1584
1605
|
return [types.TextContent(type="text", text=error_msg)]
|
1606
|
+
if not language:
|
1607
|
+
error_msg = "language参数不能为空"
|
1608
|
+
await logger.error(error_msg)
|
1609
|
+
return [types.TextContent(type="text", text=error_msg)]
|
1585
1610
|
|
1586
|
-
|
1611
|
+
# 创建PDF创建器
|
1612
|
+
file_handler = FileHandler(logger)
|
1613
|
+
pdf_creator = PDFCreator(logger, file_handler)
|
1614
|
+
|
1615
|
+
result = await pdf_creator.create_pdf_from_prompt(
|
1616
|
+
prompt=prompt,
|
1617
|
+
language=language,
|
1618
|
+
enable_web_search=enable_web_search,
|
1619
|
+
original_name=filename
|
1620
|
+
)
|
1587
1621
|
# 构建结果报告
|
1588
1622
|
report_msg = generate_result_report(
|
1589
1623
|
[result]
|
lightpdf_aipdf_mcp/summarizer.py
CHANGED
@@ -38,45 +38,8 @@ class Summarizer(BaseApiClient):
|
|
38
38
|
|
39
39
|
data = extra_params.copy() if extra_params else {}
|
40
40
|
|
41
|
-
await self.
|
42
|
-
|
43
|
-
if self.file_handler.is_oss_id(file_path):
|
44
|
-
data = data.copy()
|
45
|
-
data["resource_id"] = file_path.split("oss_id://")[1]
|
46
|
-
headers["Content-Type"] = "application/json"
|
47
|
-
response = await client.post(
|
48
|
-
self.api_base_url,
|
49
|
-
json=data,
|
50
|
-
headers=headers
|
51
|
-
)
|
52
|
-
elif self.file_handler.is_url(file_path):
|
53
|
-
file_path_mod = file_path
|
54
|
-
if isinstance(file_path, str) and "arxiv.org/pdf/" in file_path:
|
55
|
-
from urllib.parse import urlparse, urlunparse
|
56
|
-
url_obj = urlparse(file_path)
|
57
|
-
if not url_obj.path.endswith(".pdf"):
|
58
|
-
new_path = url_obj.path + ".pdf"
|
59
|
-
file_path_mod = urlunparse(url_obj._replace(path=new_path))
|
60
|
-
data = data.copy()
|
61
|
-
data["url"] = file_path_mod
|
62
|
-
headers["Content-Type"] = "application/json"
|
63
|
-
response = await client.post(
|
64
|
-
self.api_base_url,
|
65
|
-
json=data,
|
66
|
-
headers=headers
|
67
|
-
)
|
68
|
-
else:
|
69
|
-
with open(file_path, "rb") as f:
|
70
|
-
files = {"file": f}
|
71
|
-
response = await client.post(
|
72
|
-
self.api_base_url,
|
73
|
-
files=files,
|
74
|
-
data=data,
|
75
|
-
headers=headers
|
76
|
-
)
|
77
|
-
|
78
|
-
task_id = await self._handle_api_response(response, response_action)
|
79
|
-
await self.logger.log("info", f"摘要任务1,task_id: {task_id}")
|
41
|
+
task_id = await self._create_task(client, file_path, data, response_action)
|
42
|
+
await self.logger.log("debug", f"摘要任务1,task_id: {task_id}")
|
80
43
|
|
81
44
|
file_hash = await self._wait_for_task(client, task_id, "摘要1")
|
82
45
|
|
@@ -86,12 +49,12 @@ class Summarizer(BaseApiClient):
|
|
86
49
|
|
87
50
|
data = extra_params.copy() if extra_params else {}
|
88
51
|
data["template_id"] = "63357fa3-ba37-47d5-b9c3-8b10ed0a59d6"
|
89
|
-
data["response_type"] =
|
52
|
+
data["response_type"] = 0
|
90
53
|
data["file_hash"] = file_hash
|
91
54
|
data["prompt"] = prompt
|
92
55
|
data["language"] = language
|
93
56
|
|
94
|
-
await self.logger.log("
|
57
|
+
await self.logger.log("debug", f"正在提交{response_action}...{data}")
|
95
58
|
response = await client.post(
|
96
59
|
self.api_base_url,
|
97
60
|
json=data,
|
@@ -99,13 +62,13 @@ class Summarizer(BaseApiClient):
|
|
99
62
|
)
|
100
63
|
|
101
64
|
task_id = await self._handle_api_response(response, response_action)
|
102
|
-
await self.logger.log("
|
65
|
+
await self.logger.log("debug", f"摘要任务2,task_id: {task_id}")
|
103
66
|
|
104
67
|
content = await self._wait_for_task(client, task_id, "摘要2", is_raw=True)
|
105
68
|
|
106
69
|
summary = content.get("answer", {}).get("text", "")
|
107
70
|
|
108
|
-
await self.logger.log("
|
71
|
+
await self.logger.log("debug", f"摘要完成。")
|
109
72
|
return SummarizeResult(
|
110
73
|
success=True,
|
111
74
|
file_path=file_path,
|
@@ -0,0 +1,13 @@
|
|
1
|
+
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
+
lightpdf_aipdf_mcp/common.py,sha256=VOipRuz2veRMhpvr0lJ2nZwuEZntx1MiRxDSNx0fSWs,9310
|
3
|
+
lightpdf_aipdf_mcp/converter.py,sha256=r8iO5R5vLNNKWdb6WSnwzTwwmp2TvgLXSIvvA4y___o,15336
|
4
|
+
lightpdf_aipdf_mcp/create_pdf.py,sha256=FKWttbR48foiwUwmSsCN6n6PfI25IpaXETPUscz9DjI,16073
|
5
|
+
lightpdf_aipdf_mcp/editor.py,sha256=BR-sWW9L7tybEPOhdc8W-uwdBoom19EPTmGDvy_2gMc,27941
|
6
|
+
lightpdf_aipdf_mcp/ocr.py,sha256=IyzxisA6qtXcGTHZofpUYXYDdcIjUaaHcVUKpM7DH9A,2832
|
7
|
+
lightpdf_aipdf_mcp/server.py,sha256=sEqgSxOZwa9RfbTKuUTZv9BHR_fWb_b6WYbQh6zGxGk,81258
|
8
|
+
lightpdf_aipdf_mcp/summarizer.py,sha256=UPAftDKjp2NFE2Wfoi2yAsGfaWqihu-c_W_BwfhVy0c,3671
|
9
|
+
lightpdf_aipdf_mcp/translator.py,sha256=nuZa4FpsA0xeRWAEGqSPIM55aJuazJX1m32uajowo7I,2778
|
10
|
+
lightpdf_aipdf_mcp-0.1.146.dist-info/METADATA,sha256=hUDDCP56u4c-P2hS0vDrZlVge9FS2pr6RJPze3LLp3s,8120
|
11
|
+
lightpdf_aipdf_mcp-0.1.146.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
+
lightpdf_aipdf_mcp-0.1.146.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
13
|
+
lightpdf_aipdf_mcp-0.1.146.dist-info/RECORD,,
|
@@ -1,13 +0,0 @@
|
|
1
|
-
lightpdf_aipdf_mcp/__init__.py,sha256=PPnAgpvJLYLVOTxnHDmJAulFnHJD6wuTwS6tRGjqq6s,141
|
2
|
-
lightpdf_aipdf_mcp/common.py,sha256=T4WKhtoWvDEosoU36ryZ-7IS62iNm_TL8H_jo6nFf7c,9214
|
3
|
-
lightpdf_aipdf_mcp/converter.py,sha256=r8iO5R5vLNNKWdb6WSnwzTwwmp2TvgLXSIvvA4y___o,15336
|
4
|
-
lightpdf_aipdf_mcp/create_pdf.py,sha256=oALIhOBo60D3Gu_li7d7FF0COhFfSTM-BJpc63r9iAs,2465
|
5
|
-
lightpdf_aipdf_mcp/editor.py,sha256=BR-sWW9L7tybEPOhdc8W-uwdBoom19EPTmGDvy_2gMc,27941
|
6
|
-
lightpdf_aipdf_mcp/ocr.py,sha256=IyzxisA6qtXcGTHZofpUYXYDdcIjUaaHcVUKpM7DH9A,2832
|
7
|
-
lightpdf_aipdf_mcp/server.py,sha256=5GYAkU2N4JFJV7D2lfQkNrHjUc1uS4jkfiAORs8ffug,79475
|
8
|
-
lightpdf_aipdf_mcp/summarizer.py,sha256=2QMMgo_xxlEDSd_STPh7-1lBc4VRsL4SPSTijJPyb3I,5456
|
9
|
-
lightpdf_aipdf_mcp/translator.py,sha256=nuZa4FpsA0xeRWAEGqSPIM55aJuazJX1m32uajowo7I,2778
|
10
|
-
lightpdf_aipdf_mcp-0.1.144.dist-info/METADATA,sha256=LNkXsClxPwKYAY1rfceqh94xBfJBnySOYM0sCQ7xdPk,8120
|
11
|
-
lightpdf_aipdf_mcp-0.1.144.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
12
|
-
lightpdf_aipdf_mcp-0.1.144.dist-info/entry_points.txt,sha256=X7TGUe52N4sYH-tYt0YUGApeJgw-efQlZA6uAZmlmr4,63
|
13
|
-
lightpdf_aipdf_mcp-0.1.144.dist-info/RECORD,,
|
File without changes
|
{lightpdf_aipdf_mcp-0.1.144.dist-info → lightpdf_aipdf_mcp-0.1.146.dist-info}/entry_points.txt
RENAMED
File without changes
|