| 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" |
| ) |
|
|