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: # JSONL 格式 result = parser.parse_jsonl(text) else: # JSON 格式 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 ) # 直接处理JSON数据 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 ) # 直接处理JSON数据 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))