lightpdf-aipdf-mcp 0.1.31__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/__init__.py +8 -0
- lightpdf_aipdf_mcp/common.py +181 -0
- lightpdf_aipdf_mcp/converter.py +298 -0
- lightpdf_aipdf_mcp/editor.py +524 -0
- lightpdf_aipdf_mcp/server.py +888 -0
- lightpdf_aipdf_mcp-0.1.31.dist-info/METADATA +208 -0
- lightpdf_aipdf_mcp-0.1.31.dist-info/RECORD +9 -0
- lightpdf_aipdf_mcp-0.1.31.dist-info/WHEEL +4 -0
- lightpdf_aipdf_mcp-0.1.31.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
"""通用工具模块"""
|
2
|
+
import asyncio
|
3
|
+
import json
|
4
|
+
import os
|
5
|
+
import tempfile
|
6
|
+
import time
|
7
|
+
import urllib.parse
|
8
|
+
from dataclasses import dataclass, field
|
9
|
+
from typing import List, Optional, Dict, Any, Tuple
|
10
|
+
|
11
|
+
import httpx
|
12
|
+
from mcp.types import TextContent, LoggingMessageNotification, LoggingMessageNotificationParams, LoggingLevel
|
13
|
+
|
14
|
+
@dataclass
|
15
|
+
class BaseResult:
|
16
|
+
"""基础结果数据类"""
|
17
|
+
success: bool
|
18
|
+
file_path: str
|
19
|
+
error_message: Optional[str] = None
|
20
|
+
download_url: Optional[str] = None
|
21
|
+
|
22
|
+
class Logger:
|
23
|
+
"""日志记录器类"""
|
24
|
+
def __init__(self, context, collect_info: bool = True):
|
25
|
+
self.context = context
|
26
|
+
self.collect_info = collect_info
|
27
|
+
self._info_log = []
|
28
|
+
|
29
|
+
async def log(self, level: str, message: str, add_to_result: bool = True):
|
30
|
+
"""记录日志消息"""
|
31
|
+
if self.collect_info and add_to_result:
|
32
|
+
self._info_log.append(message)
|
33
|
+
|
34
|
+
level_map = {
|
35
|
+
"debug": LoggingLevel.Debug,
|
36
|
+
"info": LoggingLevel.Info,
|
37
|
+
"warning": LoggingLevel.Warning,
|
38
|
+
"error": LoggingLevel.Error
|
39
|
+
}
|
40
|
+
|
41
|
+
mcp_level = level_map.get(level.lower(), LoggingLevel.Info)
|
42
|
+
|
43
|
+
# 使用更简洁的send_log_message方法
|
44
|
+
await self.context.session.send_log_message(mcp_level, message)
|
45
|
+
|
46
|
+
async def error(self, message: str, error_class=RuntimeError):
|
47
|
+
"""记录错误并引发异常"""
|
48
|
+
await self.log("error", message)
|
49
|
+
raise error_class(message)
|
50
|
+
|
51
|
+
def get_result_info(self) -> List[str]:
|
52
|
+
"""获取收集的信息日志"""
|
53
|
+
return self._info_log
|
54
|
+
|
55
|
+
class FileHandler:
|
56
|
+
"""文件处理工具类"""
|
57
|
+
def __init__(self, logger: Logger):
|
58
|
+
self.logger = logger
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def is_url(path: str) -> bool:
|
62
|
+
"""检查路径是否为URL"""
|
63
|
+
return path.startswith(("http://", "https://"))
|
64
|
+
|
65
|
+
@staticmethod
|
66
|
+
def get_file_extension(file_path: str) -> str:
|
67
|
+
"""获取文件扩展名(小写)"""
|
68
|
+
if "?" in file_path: # 处理URL中的查询参数
|
69
|
+
file_path = file_path.split("?")[0]
|
70
|
+
return os.path.splitext(file_path)[1].lower()
|
71
|
+
|
72
|
+
@staticmethod
|
73
|
+
def get_input_format(file_path: str):
|
74
|
+
"""根据文件路径获取输入格式
|
75
|
+
|
76
|
+
此方法需要导入InputFormat和INPUT_EXTENSIONS,
|
77
|
+
但为避免循环导入,由调用者提供转换逻辑
|
78
|
+
"""
|
79
|
+
ext = FileHandler.get_file_extension(file_path)
|
80
|
+
return ext
|
81
|
+
|
82
|
+
@staticmethod
|
83
|
+
def get_available_output_formats(input_format):
|
84
|
+
"""获取指定输入格式支持的输出格式
|
85
|
+
|
86
|
+
此方法需要导入FORMAT_CONVERSION_MAP,
|
87
|
+
但为避免循环导入,由调用者提供转换逻辑
|
88
|
+
"""
|
89
|
+
# 实际实现在converter.py
|
90
|
+
return {}
|
91
|
+
|
92
|
+
async def validate_file_exists(self, file_path: str) -> Tuple[bool, bool]:
|
93
|
+
"""验证文件是否存在
|
94
|
+
|
95
|
+
Args:
|
96
|
+
file_path: 文件路径
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
tuple: (文件是否存在, 是否为URL)
|
100
|
+
"""
|
101
|
+
is_url = self.is_url(file_path)
|
102
|
+
if not is_url and not os.path.exists(file_path):
|
103
|
+
await self.logger.error(f"文件不存在:{file_path}", FileNotFoundError)
|
104
|
+
return False, is_url
|
105
|
+
return True, is_url
|
106
|
+
|
107
|
+
class BaseApiClient:
|
108
|
+
"""API客户端基类"""
|
109
|
+
def __init__(self, logger: Logger, file_handler: FileHandler):
|
110
|
+
self.logger = logger
|
111
|
+
self.file_handler = file_handler
|
112
|
+
self.api_key = os.getenv("API_KEY")
|
113
|
+
# 子类必须设置api_base_url
|
114
|
+
self.api_base_url = None
|
115
|
+
|
116
|
+
async def _wait_for_task(self, client: httpx.AsyncClient, task_id: str, operation_type: str = "处理") -> str:
|
117
|
+
"""等待任务完成并返回下载链接
|
118
|
+
|
119
|
+
Args:
|
120
|
+
client: HTTP客户端
|
121
|
+
task_id: 任务ID
|
122
|
+
operation_type: 操作类型描述,用于日志,默认为"处理"
|
123
|
+
|
124
|
+
Returns:
|
125
|
+
str: 下载链接
|
126
|
+
|
127
|
+
Raises:
|
128
|
+
RuntimeError: 如果任务失败或超时
|
129
|
+
"""
|
130
|
+
headers = {"X-API-KEY": self.api_key}
|
131
|
+
MAX_ATTEMPTS = 100
|
132
|
+
|
133
|
+
for attempt in range(MAX_ATTEMPTS):
|
134
|
+
await asyncio.sleep(3)
|
135
|
+
|
136
|
+
status_response = await client.get(
|
137
|
+
f"{self.api_base_url}/{task_id}",
|
138
|
+
headers=headers
|
139
|
+
)
|
140
|
+
|
141
|
+
if status_response.status_code != 200:
|
142
|
+
await self.logger.log("warning", f"获取任务状态失败。状态码: {status_response.status_code}")
|
143
|
+
continue
|
144
|
+
|
145
|
+
status_result = status_response.json()
|
146
|
+
state = status_result.get("data", {}).get("state")
|
147
|
+
progress = status_result.get("data", {}).get("progress", 0)
|
148
|
+
|
149
|
+
if state == 1: # 完成
|
150
|
+
download_url = status_result.get("data", {}).get("file")
|
151
|
+
if not download_url:
|
152
|
+
await self.logger.error(f"任务完成但未找到下载链接。任务状态:{json.dumps(status_result, ensure_ascii=False)}")
|
153
|
+
return download_url
|
154
|
+
elif state < 0: # 失败
|
155
|
+
await self.logger.error(f"任务失败: {json.dumps(status_result, ensure_ascii=False)}")
|
156
|
+
else: # 进行中
|
157
|
+
await self.logger.log("debug", f"{operation_type}进度: {progress}%", add_to_result=False)
|
158
|
+
|
159
|
+
await self.logger.error(f"超过最大尝试次数({MAX_ATTEMPTS}),任务未完成")
|
160
|
+
|
161
|
+
async def _handle_api_response(self, response: httpx.Response, error_prefix: str) -> str:
|
162
|
+
"""处理API响应并提取任务ID
|
163
|
+
|
164
|
+
Args:
|
165
|
+
response: API响应
|
166
|
+
error_prefix: 错误消息前缀
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
str: 任务ID
|
170
|
+
|
171
|
+
Raises:
|
172
|
+
RuntimeError: 如果响应无效或任务ID缺失
|
173
|
+
"""
|
174
|
+
if response.status_code != 200:
|
175
|
+
await self.logger.error(f"{error_prefix}失败。状态码: {response.status_code}\n响应: {response.text}")
|
176
|
+
|
177
|
+
result = response.json()
|
178
|
+
if "data" not in result or "task_id" not in result["data"]:
|
179
|
+
await self.logger.error(f"无法获取任务ID。API响应:{json.dumps(result, ensure_ascii=False)}")
|
180
|
+
|
181
|
+
return result["data"]["task_id"]
|
@@ -0,0 +1,298 @@
|
|
1
|
+
"""PDF文档转换模块"""
|
2
|
+
import os
|
3
|
+
import httpx
|
4
|
+
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from enum import Enum
|
7
|
+
from typing import List, Optional, Set
|
8
|
+
|
9
|
+
from .common import BaseResult, Logger, FileHandler, BaseApiClient
|
10
|
+
|
11
|
+
class InputFormat(str, Enum):
|
12
|
+
"""支持的输入文件格式"""
|
13
|
+
PDF = "pdf"
|
14
|
+
WORD = "docx"
|
15
|
+
EXCEL = "xlsx"
|
16
|
+
PPT = "pptx"
|
17
|
+
JPG = "jpg"
|
18
|
+
JPEG = "jpeg"
|
19
|
+
PNG = "png"
|
20
|
+
BMP = "bmp"
|
21
|
+
GIF = "gif"
|
22
|
+
CAD = "dwg"
|
23
|
+
CAJ = "caj"
|
24
|
+
OFD = "ofd"
|
25
|
+
|
26
|
+
class OutputFormat(str, Enum):
|
27
|
+
"""支持的输出文件格式"""
|
28
|
+
PDF = "pdf"
|
29
|
+
WORD = "docx"
|
30
|
+
EXCEL = "xlsx"
|
31
|
+
PPT = "pptx"
|
32
|
+
JPG = "jpg"
|
33
|
+
JPEG = "jpeg"
|
34
|
+
PNG = "png"
|
35
|
+
BMP = "bmp"
|
36
|
+
GIF = "gif"
|
37
|
+
HTML = "html"
|
38
|
+
TEXT = "txt"
|
39
|
+
|
40
|
+
# 文件扩展名到输入格式的映射
|
41
|
+
INPUT_EXTENSIONS = {
|
42
|
+
".pdf": InputFormat.PDF,
|
43
|
+
".docx": InputFormat.WORD,
|
44
|
+
".xlsx": InputFormat.EXCEL,
|
45
|
+
".pptx": InputFormat.PPT,
|
46
|
+
".jpg": InputFormat.JPG,
|
47
|
+
".jpeg": InputFormat.JPEG,
|
48
|
+
".png": InputFormat.PNG,
|
49
|
+
".bmp": InputFormat.BMP,
|
50
|
+
".gif": InputFormat.GIF,
|
51
|
+
".dwg": InputFormat.CAD,
|
52
|
+
".caj": InputFormat.CAJ,
|
53
|
+
".ofd": InputFormat.OFD,
|
54
|
+
}
|
55
|
+
|
56
|
+
# 输入格式到可用输出格式的映射
|
57
|
+
FORMAT_CONVERSION_MAP = {
|
58
|
+
InputFormat.PDF: {
|
59
|
+
OutputFormat.WORD, # PDF转Word
|
60
|
+
OutputFormat.EXCEL, # PDF转Excel
|
61
|
+
OutputFormat.PPT, # PDF转PPT
|
62
|
+
OutputFormat.JPG, # PDF转JPG
|
63
|
+
OutputFormat.JPEG, # PDF转JPEG
|
64
|
+
OutputFormat.PNG, # PDF转PNG
|
65
|
+
OutputFormat.BMP, # PDF转BMP
|
66
|
+
OutputFormat.GIF, # PDF转GIF
|
67
|
+
OutputFormat.HTML, # PDF转HTML
|
68
|
+
OutputFormat.TEXT, # PDF转文本
|
69
|
+
},
|
70
|
+
InputFormat.WORD: {OutputFormat.PDF}, # Word转PDF
|
71
|
+
InputFormat.EXCEL: {OutputFormat.PDF}, # Excel转PDF
|
72
|
+
InputFormat.PPT: {OutputFormat.PDF}, # PPT转PDF
|
73
|
+
InputFormat.JPG: {OutputFormat.PDF}, # JPG转PDF
|
74
|
+
InputFormat.JPEG: {OutputFormat.PDF}, # JPEG转PDF
|
75
|
+
InputFormat.PNG: {OutputFormat.PDF}, # PNG转PDF
|
76
|
+
InputFormat.BMP: {OutputFormat.PDF}, # BMP转PDF
|
77
|
+
InputFormat.GIF: {OutputFormat.PDF}, # GIF转PDF
|
78
|
+
InputFormat.CAD: {OutputFormat.PDF}, # CAD转PDF
|
79
|
+
InputFormat.CAJ: {OutputFormat.PDF}, # CAJ转PDF
|
80
|
+
InputFormat.OFD: {OutputFormat.PDF}, # OFD转PDF
|
81
|
+
}
|
82
|
+
|
83
|
+
# 扩展FileHandler类的方法
|
84
|
+
def get_input_format(file_path: str) -> Optional[InputFormat]:
|
85
|
+
"""根据文件路径获取输入格式"""
|
86
|
+
ext = FileHandler.get_file_extension(file_path)
|
87
|
+
return INPUT_EXTENSIONS.get(ext)
|
88
|
+
|
89
|
+
def get_available_output_formats(input_format: InputFormat) -> Set[OutputFormat]:
|
90
|
+
"""获取指定输入格式支持的输出格式"""
|
91
|
+
return FORMAT_CONVERSION_MAP.get(input_format, set())
|
92
|
+
|
93
|
+
# 为FileHandler类注入方法
|
94
|
+
FileHandler.get_input_format = staticmethod(get_input_format)
|
95
|
+
FileHandler.get_available_output_formats = staticmethod(get_available_output_formats)
|
96
|
+
|
97
|
+
@dataclass
|
98
|
+
class ConversionResult(BaseResult):
|
99
|
+
"""转换结果数据类"""
|
100
|
+
pass
|
101
|
+
|
102
|
+
class Converter(BaseApiClient):
|
103
|
+
"""PDF文档转换器"""
|
104
|
+
def __init__(self, logger: Logger, file_handler: FileHandler):
|
105
|
+
super().__init__(logger, file_handler)
|
106
|
+
self.api_base_url = "https://techsz.aoscdn.com/api/tasks/document/conversion"
|
107
|
+
|
108
|
+
async def add_page_numbers(self, file_path: str, start_num: int = 1, position: str = "5", margin: int = 30, password: str = None) -> ConversionResult:
|
109
|
+
"""为PDF文档添加页码
|
110
|
+
|
111
|
+
Args:
|
112
|
+
file_path: 要添加页码的PDF文件路径
|
113
|
+
start_num: 起始页码,整数类型,默认为1
|
114
|
+
position: 页码显示位置,字符串类型,可选值1-6(左上/上中/右上/左下/下中/右下),默认为5(下中)
|
115
|
+
margin: 页码显示的边距,整数类型,可选值10/30/60,默认为30
|
116
|
+
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
ConversionResult: 转换结果
|
120
|
+
"""
|
121
|
+
# 验证输入文件是否为PDF
|
122
|
+
input_format = self.file_handler.get_input_format(file_path)
|
123
|
+
if input_format != InputFormat.PDF:
|
124
|
+
await self.logger.error(f"添加页码功能仅支持PDF文件,当前文件格式为: {self.file_handler.get_file_extension(file_path)}")
|
125
|
+
|
126
|
+
# 验证参数
|
127
|
+
valid_positions = {"1", "2", "3", "4", "5", "6"}
|
128
|
+
valid_margins = {10, 30, 60}
|
129
|
+
|
130
|
+
# 验证position参数
|
131
|
+
if position not in valid_positions:
|
132
|
+
await self.logger.error(f"无效的页码位置值: {position}。有效值为: 1(左上), 2(上中), 3(右上), 4(左下), 5(下中), 6(右下)")
|
133
|
+
|
134
|
+
# 验证margin参数
|
135
|
+
if margin not in valid_margins:
|
136
|
+
await self.logger.error(f"无效的页码边距值: {margin}。有效值为: 10, 30, 60")
|
137
|
+
|
138
|
+
# 验证start_num是否为正整数
|
139
|
+
if not isinstance(start_num, int) or start_num < 1:
|
140
|
+
await self.logger.error(f"起始页码必须是正整数,当前值为: {start_num}")
|
141
|
+
|
142
|
+
# 构建API参数
|
143
|
+
extra_params = {
|
144
|
+
"start_num": start_num,
|
145
|
+
"position": position,
|
146
|
+
"margin": margin
|
147
|
+
}
|
148
|
+
|
149
|
+
# 记录操作描述
|
150
|
+
await self.logger.log("info", f"正在为PDF添加页码(起始页码: {start_num}, 位置: {position}, 边距: {margin})...")
|
151
|
+
|
152
|
+
# 调用convert_file方法处理API请求
|
153
|
+
return await self.convert_file(file_path, "number-pdf", extra_params, password)
|
154
|
+
|
155
|
+
async def convert_file(self, file_path: str, format: str, extra_params: dict = None, password: str = None) -> ConversionResult:
|
156
|
+
"""转换单个文件
|
157
|
+
|
158
|
+
Args:
|
159
|
+
file_path: 要转换的文件路径
|
160
|
+
format: 目标格式
|
161
|
+
extra_params: 额外的API参数,例如去除水印
|
162
|
+
password: 文档密码,如果文档受密码保护,则需要提供(可选)
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
ConversionResult: 转换结果
|
166
|
+
"""
|
167
|
+
if not self.api_key:
|
168
|
+
await self.logger.error("未找到API_KEY。请在客户端配置API_KEY环境变量。")
|
169
|
+
|
170
|
+
# 特殊格式:doc-repair用于去除水印,number-pdf用于添加页码,输出均为PDF
|
171
|
+
is_special_operation = format in ["doc-repair", "number-pdf"]
|
172
|
+
actual_output_format = "pdf" if is_special_operation else format
|
173
|
+
|
174
|
+
# 验证输入文件格式
|
175
|
+
input_format = self.file_handler.get_input_format(file_path)
|
176
|
+
if not input_format and not is_special_operation:
|
177
|
+
await self.logger.error(f"不支持的输入文件格式: {self.file_handler.get_file_extension(file_path)}")
|
178
|
+
|
179
|
+
# 如果是去除水印操作,检查是否PDF文件
|
180
|
+
if format == "doc-repair" and input_format != InputFormat.PDF:
|
181
|
+
await self.logger.error("去除水印功能仅支持PDF文件")
|
182
|
+
|
183
|
+
# 如果是添加页码操作,检查是否PDF文件
|
184
|
+
if format == "number-pdf" and input_format != InputFormat.PDF:
|
185
|
+
await self.logger.error("添加页码功能仅支持PDF文件")
|
186
|
+
|
187
|
+
# 验证输出格式(除去特殊操作外)
|
188
|
+
if not is_special_operation:
|
189
|
+
try:
|
190
|
+
output_format = OutputFormat(format)
|
191
|
+
except ValueError:
|
192
|
+
await self.logger.error(f"不支持的输出格式: {format}")
|
193
|
+
|
194
|
+
# 验证格式转换是否支持
|
195
|
+
if input_format: # 确保input_format有效
|
196
|
+
available_formats = self.file_handler.get_available_output_formats(input_format)
|
197
|
+
if output_format not in available_formats:
|
198
|
+
await self.logger.error(
|
199
|
+
f"不支持从 {input_format.value} 格式转换为 {output_format.value} 格式。"
|
200
|
+
f"支持的输出格式: {', '.join(f.value for f in available_formats)}"
|
201
|
+
)
|
202
|
+
else:
|
203
|
+
# 对于特殊操作,设置输出格式为PDF
|
204
|
+
output_format = OutputFormat.PDF
|
205
|
+
|
206
|
+
# 验证文件
|
207
|
+
file_exists_result = await self.file_handler.validate_file_exists(file_path)
|
208
|
+
if not file_exists_result[0]:
|
209
|
+
return ConversionResult(success=False, file_path=file_path, error_message="文件不存在")
|
210
|
+
is_url = file_exists_result[1]
|
211
|
+
|
212
|
+
# 操作描述
|
213
|
+
if format == "doc-repair":
|
214
|
+
operation_desc = "去除水印"
|
215
|
+
elif format == "number-pdf":
|
216
|
+
operation_desc = "添加页码"
|
217
|
+
else:
|
218
|
+
operation_desc = f"将 {input_format.value.upper()} 转换为 {output_format.value.upper()} 格式"
|
219
|
+
await self.logger.log("info", f"正在{operation_desc}...")
|
220
|
+
|
221
|
+
import httpx
|
222
|
+
async with httpx.AsyncClient(timeout=120.0) as client:
|
223
|
+
try:
|
224
|
+
# 初始化extra_params(如果为None)
|
225
|
+
if extra_params is None:
|
226
|
+
extra_params = {}
|
227
|
+
|
228
|
+
# 如果提供了密码,将其添加到extra_params
|
229
|
+
if password:
|
230
|
+
extra_params["password"] = password
|
231
|
+
|
232
|
+
# 创建转换任务
|
233
|
+
task_id = await self._create_task(client, file_path, format, is_url, extra_params)
|
234
|
+
|
235
|
+
# 等待任务完成
|
236
|
+
download_url = await self._wait_for_task(client, task_id, "转换")
|
237
|
+
|
238
|
+
# 记录完成信息
|
239
|
+
await self.logger.log("info", "转换完成。可通过下载链接获取结果文件。")
|
240
|
+
|
241
|
+
return ConversionResult(
|
242
|
+
success=True,
|
243
|
+
file_path=file_path,
|
244
|
+
error_message=None,
|
245
|
+
download_url=download_url
|
246
|
+
)
|
247
|
+
|
248
|
+
except Exception as e:
|
249
|
+
return ConversionResult(
|
250
|
+
success=False,
|
251
|
+
file_path=file_path,
|
252
|
+
error_message=str(e),
|
253
|
+
download_url=None
|
254
|
+
)
|
255
|
+
|
256
|
+
async def _create_task(self, client: httpx.AsyncClient, file_path: str, format: str, is_url: bool, extra_params: dict = None) -> str:
|
257
|
+
"""创建转换任务
|
258
|
+
|
259
|
+
Args:
|
260
|
+
client: HTTP客户端
|
261
|
+
file_path: 文件路径
|
262
|
+
format: 目标格式,特殊格式"doc-repair"用于去除水印,"number-pdf"用于添加页码
|
263
|
+
is_url: 是否URL路径
|
264
|
+
extra_params: 额外API参数(可选)
|
265
|
+
|
266
|
+
Returns:
|
267
|
+
str: 任务ID
|
268
|
+
"""
|
269
|
+
await self.logger.log("info", "正在提交转换任务...")
|
270
|
+
|
271
|
+
headers = {"X-API-KEY": self.api_key}
|
272
|
+
data = {"format": format}
|
273
|
+
|
274
|
+
# 添加额外参数
|
275
|
+
if extra_params:
|
276
|
+
data.update(extra_params)
|
277
|
+
|
278
|
+
if is_url:
|
279
|
+
# 使用JSON方式时添加Content-Type
|
280
|
+
headers["Content-Type"] = "application/json"
|
281
|
+
response = await client.post(
|
282
|
+
self.api_base_url,
|
283
|
+
json=data,
|
284
|
+
headers=headers
|
285
|
+
)
|
286
|
+
else:
|
287
|
+
# 对于文件上传,使用表单方式,不需要添加Content-Type
|
288
|
+
with open(file_path, "rb") as f:
|
289
|
+
files = {"file": f}
|
290
|
+
response = await client.post(
|
291
|
+
self.api_base_url,
|
292
|
+
files=files,
|
293
|
+
data=data,
|
294
|
+
headers=headers
|
295
|
+
)
|
296
|
+
|
297
|
+
# 使用基类的方法处理API响应
|
298
|
+
return await self._handle_api_response(response, "创建任务")
|