| import json |
| import logging |
| from typing import Any, List, Optional |
|
|
| from fastapi import APIRouter, HTTPException, Request, UploadFile, File |
| from pydantic import BaseModel, Field |
|
|
| from .openai_parser import get_openai_parser |
|
|
| logger = logging.getLogger(__name__) |
|
|
| router = APIRouter() |
|
|
| plugin = None |
| core = None |
|
|
|
|
| def set_plugin_instance(plugin_instance): |
| """设置插件实例""" |
| global plugin |
| plugin = plugin_instance |
|
|
|
|
| def set_core_instance(core_instance): |
| """设置核心逻辑实例""" |
| global core |
| core = core_instance |
|
|
|
|
| class ExtractRequest(BaseModel): |
| """提取请求模型""" |
| file_path: Optional[str] = Field(default=None, description="JSON文件路径") |
| json_data: Optional[Any] = Field(default=None, description="JSON数据(直接传入)") |
| output_file: Optional[str] = Field(default=None, description="输出文件路径(可选)") |
|
|
|
|
| class ExtractResponse(BaseModel): |
| """提取响应模型""" |
| success: bool = Field(description="操作是否成功") |
| contents: List[str] = Field(default=[], description="提取的内容列表") |
| formatted_output: str = Field(default="", description="格式化后的输出") |
| error: Optional[str] = Field(default=None, description="错误信息") |
|
|
|
|
| class ConversationMessage(BaseModel): |
| """对话消息模型""" |
| role: str = Field(description="消息角色(system/user/assistant)") |
| content: str = Field(description="消息内容") |
|
|
|
|
| class ConversationExtractRequest(BaseModel): |
| """对话提取请求模型""" |
| file_path: Optional[str] = Field(default=None, description="JSON文件路径") |
| json_data: Optional[Any] = Field(default=None, description="JSON数据(直接传入)") |
|
|
|
|
| class ConversationExtractResponse(BaseModel): |
| """对话提取响应模型""" |
| success: bool = Field(description="操作是否成功") |
| is_conversation: bool = Field(description="是否为对话格式") |
| messages: List[ConversationMessage] = Field(default=[], description="对话消息列表") |
| formatted_output: str = Field(default="", description="格式化后的对话记录") |
| error: Optional[str] = Field(default=None, description="错误信息") |
|
|
|
|
| @router.get("/status") |
| async def get_status(): |
| """获取插件状态""" |
| if plugin is None: |
| return { |
| "name": "json", |
| "enabled": False, |
| "message": "插件未加载", |
| } |
| return plugin.get_status() |
|
|
|
|
| @router.post("/upload") |
| async def upload_json(file: UploadFile = File(...)): |
| """ |
| 上传 JSON/JSONL 文件并解析为对话视图模型 |
| |
| Returns: |
| 对话视图模型 { |
| "conversation_id": str, |
| "messages": List[dict], |
| "raw_warnings": List[str], |
| } |
| """ |
| if plugin is None or not plugin.enabled: |
| raise HTTPException(status_code=400, detail="插件未启用") |
|
|
| try: |
| content = await file.read() |
| text = content.decode("utf-8") |
|
|
| |
| filename = file.filename or "" |
| is_jsonl = filename.endswith(".jsonl") or filename.endswith(".jsonl") |
|
|
| parser = get_openai_parser() |
|
|
| if is_jsonl: |
| |
| result = parser.parse_jsonl(text) |
| else: |
| |
| try: |
| json_data = json.loads(text) |
| result = parser.parse_json(json_data) |
| except json.JSONDecodeError as e: |
| raise HTTPException(status_code=400, detail=f"JSON 解析失败: {e}") |
|
|
| return result |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"上传解析失败: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/conversation/parse") |
| async def parse_conversation(request: Request): |
| """ |
| 解析 JSON 数据为对话视图模型 |
| |
| 接受 JSON body,返回对话视图模型。 |
| """ |
| if plugin is None or not plugin.enabled: |
| raise HTTPException(status_code=400, detail="插件未启用") |
|
|
| try: |
| data = await request.json() |
| parser = get_openai_parser() |
| result = parser.parse_json(data) |
| return result |
|
|
| except Exception as e: |
| logger.error(f"解析对话失败: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/extract") |
| async def extract_content(request: ExtractRequest): |
| """ |
| 从JSON中提取content字段 |
| |
| 支持两种方式: |
| 1. 传入 file_path - 从文件读取JSON |
| 2. 传入 json_data - 直接处理JSON数据 |
| |
| 如果是OpenAI对话格式,会自动识别并按对话格式输出 |
| |
| Returns: |
| 提取结果 |
| """ |
| if plugin is None or not plugin.enabled: |
| raise HTTPException(status_code=400, detail="插件未启用") |
|
|
| if core is None: |
| raise HTTPException(status_code=500, detail="核心逻辑未初始化") |
|
|
| try: |
| |
| if request.file_path: |
| result = core.process_single_file( |
| request.file_path, |
| output_file=request.output_file |
| ) |
| contents = core.extract_content_from_file(request.file_path) |
| return ExtractResponse( |
| success=True, |
| contents=contents, |
| formatted_output=result |
| ) |
|
|
| |
| if request.json_data is not None: |
| |
| if core._is_conversation_format(request.json_data): |
| formatted_output = core._format_conversation(request.json_data) |
| messages = core._extract_conversation(request.json_data) |
| contents = [msg["content"] for msg in messages] |
| else: |
| contents = core.extract_content_from_json(request.json_data) |
| formatted_contents = [] |
| for i, content in enumerate(contents, 1): |
| formatted = core.format_content(content) |
| formatted_contents.append(f"=== Content {i} ===\n{formatted}") |
| formatted_output = "\n\n".join(formatted_contents) |
|
|
| return ExtractResponse( |
| success=True, |
| contents=contents, |
| formatted_output=formatted_output |
| ) |
|
|
| raise HTTPException( |
| status_code=400, |
| detail="请提供 file_path 或 json_data" |
| ) |
|
|
| except Exception as e: |
| logger.error(f"提取内容时出错: {str(e)}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/extract-conversation") |
| async def extract_conversation(request: ConversationExtractRequest): |
| """ |
| 从JSON中提取对话消息(结构化) |
| |
| 专门用于处理OpenAI格式的对话JSON,返回结构化的消息列表 |
| |
| Returns: |
| 结构化的对话消息 |
| """ |
| if plugin is None or not plugin.enabled: |
| raise HTTPException(status_code=400, detail="插件未启用") |
|
|
| if core is None: |
| raise HTTPException(status_code=500, detail="核心逻辑未初始化") |
|
|
| try: |
| json_data = None |
|
|
| |
| if request.file_path: |
| import os |
| if not os.path.exists(request.file_path): |
| raise HTTPException(status_code=400, detail=f"文件不存在: {request.file_path}") |
|
|
| messages = core.extract_conversation_from_file(request.file_path) |
| is_conversation = len(messages) > 0 |
|
|
| if is_conversation: |
| formatted_output = core.process_json_file(request.file_path) |
| else: |
| formatted_output = "该文件不是对话格式" |
|
|
| return ConversationExtractResponse( |
| success=True, |
| is_conversation=is_conversation, |
| messages=[ConversationMessage(**msg) for msg in messages], |
| formatted_output=formatted_output |
| ) |
|
|
| |
| if request.json_data is not None: |
| messages = core.extract_conversation_from_json(request.json_data) |
| is_conversation = len(messages) > 0 |
|
|
| if is_conversation: |
| formatted_output = core._format_conversation(request.json_data) |
| else: |
| formatted_output = "该数据不是对话格式" |
|
|
| return ConversationExtractResponse( |
| success=True, |
| is_conversation=is_conversation, |
| messages=[ConversationMessage(**msg) for msg in messages], |
| formatted_output=formatted_output |
| ) |
|
|
| raise HTTPException( |
| status_code=400, |
| detail="请提供 file_path 或 json_data" |
| ) |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"提取对话时出错: {str(e)}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/extract-batch") |
| async def extract_batch(request: Request): |
| """ |
| 批量提取目录中所有JSON文件的content字段 |
| |
| Returns: |
| 批量提取结果 |
| """ |
| if plugin is None or not plugin.enabled: |
| raise HTTPException(status_code=400, detail="插件未启用") |
|
|
| if core is None: |
| raise HTTPException(status_code=500, detail="核心逻辑未初始化") |
|
|
| try: |
| data = await request.json() |
| dir_path = data.get("dir_path") |
| output_file = data.get("output_file") |
|
|
| if not dir_path: |
| raise HTTPException(status_code=400, detail="请提供 dir_path") |
|
|
| result = core.process_directory(dir_path, output_file=output_file) |
|
|
| return { |
| "success": True, |
| "dir_path": dir_path, |
| "result": result |
| } |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"批量提取时出错: {str(e)}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|