message / plugins /doc /api.py
hunian
refactor(plugins): 插件短名并统一 MCP tool 为 {plugin}-{tool}
cc826a1
Raw
History Blame Contribute Delete
4.73 kB
import logging
import re
import tempfile
import zipfile
from pathlib import Path
from typing import List
from fastapi import APIRouter, File, Form, HTTPException, UploadFile
from fastapi.responses import FileResponse
logger = logging.getLogger(__name__)
router = APIRouter()
plugin = None
def set_plugin_instance(plugin_instance):
global plugin
plugin = plugin_instance
@router.get("/status")
async def get_status():
if plugin is None:
return {
"name": "doc",
"enabled": False,
"message": "插件未加载",
}
return plugin.get_status()
@router.post("/merge")
async def merge_documents(
files: List[UploadFile] = File(...),
order: str = Form(None),
output_format: str = Form("md"),
):
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
if output_format not in {"md", "txt"}:
raise HTTPException(status_code=400, detail="格式参数错误,仅支持 md 或 txt")
if not files:
raise HTTPException(status_code=400, detail="至少需要上传一个文件")
allowed_extensions = {".md", ".txt"}
contents = []
for file in files:
ext = Path(file.filename).suffix.lower()
if ext not in allowed_extensions:
raise HTTPException(
status_code=400, detail=f"不支持的文件格式: {file.filename}"
)
content = await file.read()
try:
decoded_content = content.decode("utf-8")
contents.append(decoded_content)
except UnicodeDecodeError:
raise HTTPException(
status_code=400, detail=f"文件编码不支持: {file.filename}"
)
if order:
try:
order_list = [int(x) for x in order.split(",")]
reordered_contents = [
contents[i] for i in order_list if 0 <= i < len(contents)
]
if not reordered_contents:
raise HTTPException(status_code=400, detail="顺序参数无效")
contents = reordered_contents
except (ValueError, IndexError):
raise HTTPException(
status_code=400, detail="顺序参数格式错误,应为逗号分隔的数字"
)
merged_content = "\n\n".join([item.strip() for item in contents])
file_suffix = f".{output_format}"
temp_file = tempfile.NamedTemporaryFile(
mode="w", delete=False, suffix=file_suffix, encoding="utf-8"
)
temp_file.write(merged_content)
temp_file.close()
media_type = "text/markdown" if output_format == "md" else "text/plain"
filename = f"merged_document.{output_format}"
return FileResponse(temp_file.name, media_type=media_type, filename=filename)
@router.post("/split")
async def split_document(file: UploadFile = File(...), output_format: str = Form("md")):
if plugin is None or not plugin.enabled:
raise HTTPException(status_code=400, detail="插件未启用")
if output_format not in {"md", "txt"}:
raise HTTPException(status_code=400, detail="格式参数错误,仅支持 md 或 txt")
ext = Path(file.filename).suffix.lower()
if ext not in {".md", ".txt"}:
raise HTTPException(
status_code=400, detail="不支持的文件格式,仅支持 .md 和 .txt"
)
content_bytes = await file.read()
try:
content = content_bytes.decode("utf-8")
except UnicodeDecodeError:
raise HTTPException(status_code=400, detail="文件编码不支持,请使用 UTF-8 编码")
pattern = re.compile(r"^第\d+章", re.MULTILINE)
matches = list(pattern.finditer(content))
if not matches:
raise HTTPException(status_code=400, detail="未找到 '第X章' 格式的章节标记")
temp_dir = tempfile.mkdtemp()
zip_path = Path(temp_dir) / "split_documents.zip"
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zipf:
for i, match in enumerate(matches):
start_pos = match.start()
end_pos = matches[i + 1].start() if i + 1 < len(matches) else len(content)
chapter_content = content[start_pos:end_pos].strip()
first_line = chapter_content.split("\n", 1)[0].strip()
sanitized = re.sub(r'[<>:"/\\|?*]', "_", first_line)
filename = f"{sanitized}.{output_format}"
temp_file = Path(temp_dir) / filename
with open(temp_file, "w", encoding="utf-8") as f:
f.write(chapter_content)
zipf.write(temp_file, filename)
return FileResponse(
zip_path, media_type="application/zip", filename="split_documents.zip"
)