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.
@@ -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"]
@@ -1,62 +1,398 @@
1
- """根据LaTeX内容创建PDF文件的接口(未实现具体逻辑)"""
1
+ """根据用户输入请求创建PDF文件的接口"""
2
+ from dataclasses import dataclass
2
3
  from typing import Optional
3
4
  import os
4
5
  import uuid
5
- from .common import Logger, FileHandler
6
+ import httpx
7
+ from .common import Logger, FileHandler, BaseResult, BaseApiClient
6
8
  from .editor import Editor, EditResult, EditType
7
9
 
8
- async def create_pdf_from_latex(latex_code: str, output_path: Optional[str] = None, logger: Optional[Logger] = None, original_name: Optional[str] = None):
9
- """
10
- 根据LaTeX内容创建PDF文件。
11
-
12
- 参数:
13
- latex_code (str): LaTeX源内容。
14
- output_path (Optional[str]): 可选,输出PDF文件路径。
15
- logger (Optional[Logger]): 日志对象。
16
- original_name (Optional[str]): 可选,原始文件名。
17
- 返回:
18
- dict: 包含生成结果的信息(如成功与否、PDF路径、错误信息等)。
19
- """
20
- tex_path = None
21
- try:
22
- # 1. 保存latex_code为本地.tex文件
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
- temp_dir = "./tmp"
25
- os.makedirs(temp_dir, exist_ok=True)
26
- tex_filename = f"latex_{uuid.uuid4().hex}.tex"
27
- tex_path = os.path.join(temp_dir, tex_filename)
28
- with open(tex_path, "w", encoding="utf-8") as f:
29
- f.write(latex_code)
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 EditResult(
32
- error_message=f"保存LaTeX内容失败: {e}"
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
- # 2. 调用Editor.edit_pdf并传递oss://tex2pdf参数生成PDF
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
- # 构造Logger和FileHandler
38
- file_handler = FileHandler(logger)
39
- editor = Editor(logger, file_handler)
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
- # extra_params按txt转pdf方式
42
- extra_params = {"pages": '[{"url": "oss://tex2pdf", "oss_file": ""}]'}
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
- # original_name优先用传入的参数,否则用tex_filename
45
- result: EditResult = await editor.edit_pdf(tex_path, edit_type=EditType.EDIT, extra_params=extra_params, original_name=original_name or tex_filename)
46
- # 3. 返回结果
47
- return result
48
- except Exception as e:
49
- return EditResult(
50
- error_message=f"PDF生成失败: {e}"
51
- )
52
- finally:
53
- if tex_path and os.path.exists(tex_path):
54
- import threading
55
- def delayed_remove(path):
56
- try:
57
- os.remove(path)
58
- except Exception:
59
- pass
60
- timer = threading.Timer(300, delayed_remove, args=(tex_path,))
61
- timer.daemon = True
62
- timer.start()
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()
@@ -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="Create a PDF file from LaTeX source code string only. File upload is NOT supported. If you want to convert a TEX file to PDF, please use the convert_document tool instead. This tool only accepts pure LaTeX code as input.",
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
- "latex_code": {
1218
+ "prompt": {
1212
1219
  "type": "string",
1213
- "description": "The LaTeX source code string to be compiled into a PDF file. Only pure LaTeX code as a string is allowed; file upload, file path, or file content is NOT supported. If you have a TEX file, use the convert_document tool."
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": ["latex_code", "filename"]
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 create_pdf_from_latex
1575
- latex_code = arguments.get("latex_code")
1592
+ from .create_pdf import PDFCreator
1593
+ prompt = arguments.get("prompt")
1576
1594
  filename = arguments.get("filename")
1577
- if not latex_code:
1578
- error_msg = "latex_code参数不能为空"
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
- result = await create_pdf_from_latex(latex_code, logger=logger, original_name=filename)
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]
@@ -38,45 +38,8 @@ class Summarizer(BaseApiClient):
38
38
 
39
39
  data = extra_params.copy() if extra_params else {}
40
40
 
41
- await self.logger.log("info", f"正在提交{response_action}...{data}")
42
- # 检查是否为OSS路径
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"] = 4
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("info", f"正在提交{response_action}...{data}")
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("info", f"摘要任务2,task_id: {task_id}")
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("info", f"摘要完成。")
71
+ await self.logger.log("debug", f"摘要完成。")
109
72
  return SummarizeResult(
110
73
  success=True,
111
74
  file_path=file_path,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lightpdf-aipdf-mcp
3
- Version: 0.1.144
3
+ Version: 0.1.146
4
4
  Summary: MCP Server for LightPDF AI-PDF
5
5
  Author: LightPDF Team
6
6
  License: Proprietary
@@ -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,,