更新前端页交互模式
This commit is contained in:
218
web/main.py
218
web/main.py
@@ -5,6 +5,7 @@ import threading
|
||||
import glob
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Optional, Dict, List
|
||||
from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -18,8 +19,8 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from data_analysis_agent import DataAnalysisAgent
|
||||
from config.llm_config import LLMConfig
|
||||
from utils.create_session_dir import create_session_output_dir
|
||||
from merge_excel import merge_excel_files
|
||||
from sort_csv import sort_csv_by_time
|
||||
from config.llm_config import LLMConfig
|
||||
from utils.create_session_dir import create_session_output_dir
|
||||
|
||||
app = FastAPI(title="IOV Data Analysis Agent")
|
||||
|
||||
@@ -55,6 +56,7 @@ class SessionData:
|
||||
self.last_updated: str = ""
|
||||
self.user_requirement: str = ""
|
||||
self.file_list: List[str] = []
|
||||
self.reusable_script: Optional[str] = None # 新增:可复用脚本路径
|
||||
|
||||
|
||||
class SessionManager:
|
||||
@@ -70,7 +72,74 @@ class SessionManager:
|
||||
|
||||
|
||||
def get_session(self, session_id: str) -> Optional[SessionData]:
|
||||
return self.sessions.get(session_id)
|
||||
if session_id in self.sessions:
|
||||
return self.sessions[session_id]
|
||||
|
||||
# Fallback: Try to reconstruct from disk for history sessions
|
||||
output_dir = os.path.join("outputs", f"session_{session_id}")
|
||||
if os.path.exists(output_dir) and os.path.isdir(output_dir):
|
||||
return self._reconstruct_session(session_id, output_dir)
|
||||
|
||||
return None
|
||||
|
||||
def _reconstruct_session(self, session_id: str, output_dir: str) -> SessionData:
|
||||
"""从磁盘目录重建会话对象"""
|
||||
session = SessionData(session_id)
|
||||
session.output_dir = output_dir
|
||||
session.is_running = False
|
||||
session.current_round = session.max_rounds
|
||||
session.progress_percentage = 100.0
|
||||
session.status_message = "已完成 (历史记录)"
|
||||
|
||||
# Recover Log
|
||||
log_path = os.path.join(output_dir, "process.log")
|
||||
if os.path.exists(log_path):
|
||||
session.log_file = log_path
|
||||
|
||||
# Recover Report
|
||||
# 宽容查找:扫描所有 .md 文件,优先取包含 "report" 或 "报告" 的文件
|
||||
md_files = glob.glob(os.path.join(output_dir, "*.md"))
|
||||
if md_files:
|
||||
# 默认取第一个
|
||||
chosen = md_files[0]
|
||||
# 尝试找更好的匹配
|
||||
for md in md_files:
|
||||
fname = os.path.basename(md).lower()
|
||||
if "report" in fname or "报告" in fname:
|
||||
chosen = md
|
||||
break
|
||||
session.generated_report = chosen
|
||||
|
||||
# Recover Script (查找可能的脚本文件)
|
||||
possible_scripts = ["data_analysis_script.py", "script.py", "analysis_script.py"]
|
||||
for s in possible_scripts:
|
||||
p = os.path.join(output_dir, s)
|
||||
if os.path.exists(p):
|
||||
session.reusable_script = p
|
||||
break
|
||||
|
||||
# Recover Results (images etc)
|
||||
results_json = os.path.join(output_dir, "results.json")
|
||||
if os.path.exists(results_json):
|
||||
try:
|
||||
with open(results_json, "r") as f:
|
||||
session.analysis_results = json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Recover Metadata
|
||||
try:
|
||||
stat = os.stat(output_dir)
|
||||
dt = datetime.fromtimestamp(stat.st_ctime)
|
||||
session.created_at = dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
except:
|
||||
pass
|
||||
|
||||
# Cache it
|
||||
with self.lock:
|
||||
self.sessions[session_id] = session
|
||||
|
||||
return session
|
||||
|
||||
def list_sessions(self):
|
||||
return list(self.sessions.keys())
|
||||
@@ -99,7 +168,10 @@ class SessionManager:
|
||||
"max_rounds": session.max_rounds,
|
||||
"created_at": session.created_at,
|
||||
"last_updated": session.last_updated,
|
||||
"user_requirement": session.user_requirement[:100] + "..." if len(session.user_requirement) > 100 else session.user_requirement
|
||||
"created_at": session.created_at,
|
||||
"last_updated": session.last_updated,
|
||||
"user_requirement": session.user_requirement[:100] + "..." if len(session.user_requirement) > 100 else session.user_requirement,
|
||||
"script_path": session.reusable_script # 新增:返回脚本路径
|
||||
}
|
||||
return None
|
||||
|
||||
@@ -227,6 +299,7 @@ def run_analysis_task(session_id: str, files: list, user_requirement: str, is_fo
|
||||
|
||||
session.generated_report = result.get("report_file_path", None)
|
||||
session.analysis_results = result.get("analysis_results", [])
|
||||
session.reusable_script = result.get("reusable_script_path", None) # 新增:保存脚本路径
|
||||
|
||||
# Save results to json for persistence
|
||||
with open(os.path.join(session_output_dir, "results.json"), "w") as f:
|
||||
@@ -311,7 +384,8 @@ async def get_status(session_id: str = Query(..., description="Session ID")):
|
||||
"is_running": session.is_running,
|
||||
"log": log_content,
|
||||
"has_report": session.generated_report is not None,
|
||||
"report_path": session.generated_report
|
||||
"report_path": session.generated_report,
|
||||
"script_path": session.reusable_script # 新增:返回脚本路径
|
||||
}
|
||||
|
||||
@app.get("/api/export")
|
||||
@@ -453,71 +527,24 @@ async def export_report(session_id: str = Query(..., description="Session ID")):
|
||||
media_type='application/zip'
|
||||
)
|
||||
|
||||
@app.get("/api/download_script")
|
||||
async def download_script(session_id: str = Query(..., description="Session ID")):
|
||||
"""下载生成的Python脚本"""
|
||||
session = session_manager.get_session(session_id)
|
||||
if not session or not session.reusable_script:
|
||||
raise HTTPException(status_code=404, detail="Script not found")
|
||||
|
||||
if not os.path.exists(session.reusable_script):
|
||||
raise HTTPException(status_code=404, detail="Script file missing on server")
|
||||
|
||||
return FileResponse(
|
||||
path=session.reusable_script,
|
||||
filename=os.path.basename(session.reusable_script),
|
||||
media_type='text/x-python'
|
||||
)
|
||||
|
||||
# --- Tools API ---
|
||||
|
||||
class ToolRequest(BaseModel):
|
||||
source_dir: Optional[str] = "uploads"
|
||||
output_filename: Optional[str] = "merged_output.csv"
|
||||
target_file: Optional[str] = None
|
||||
|
||||
@app.post("/api/tools/merge")
|
||||
async def tool_merge_excel(req: ToolRequest):
|
||||
"""
|
||||
Trigger Excel Merge Tool
|
||||
"""
|
||||
try:
|
||||
source = req.source_dir
|
||||
output = req.output_filename
|
||||
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
await loop.run_in_executor(None, lambda: merge_excel_files(source, output))
|
||||
|
||||
output_abs = os.path.abspath(output)
|
||||
if os.path.exists(output_abs):
|
||||
return {"status": "success", "message": "Merge completed", "output_file": output_abs}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.post("/api/tools/sort")
|
||||
async def tool_sort_csv(req: ToolRequest):
|
||||
"""
|
||||
Trigger CSV Sort Tool
|
||||
"""
|
||||
try:
|
||||
target = req.target_file
|
||||
if not target:
|
||||
raise HTTPException(status_code=400, detail="Target file required")
|
||||
|
||||
import asyncio
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
await loop.run_in_executor(None, lambda: sort_csv_by_time(target))
|
||||
|
||||
return {"status": "success", "message": f"Sorted {target} by time"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
# --- Help API ---
|
||||
|
||||
@app.get("/api/help/troubleshooting")
|
||||
async def get_troubleshooting_guide():
|
||||
"""
|
||||
Returns the content of troubleshooting_guide.md
|
||||
"""
|
||||
guide_path = os.path.expanduser("~/.gemini/antigravity/brain/3ff617fe-5f27-4ab8-b61b-c634f2e75255/troubleshooting_guide.md")
|
||||
|
||||
if not os.path.exists(guide_path):
|
||||
return {"content": "# Troubleshooting Guide Not Found\n\nCould not locate the guide artifact."}
|
||||
|
||||
try:
|
||||
with open(guide_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
return {"content": content}
|
||||
except Exception as e:
|
||||
return {"content": f"# Error Loading Guide\n\n{e}"}
|
||||
|
||||
|
||||
# --- 新增API端点 ---
|
||||
@@ -553,6 +580,61 @@ async def delete_specific_session(session_id: str):
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
return {"status": "deleted", "session_id": session_id}
|
||||
|
||||
return {"status": "deleted", "session_id": session_id}
|
||||
|
||||
|
||||
# --- History API ---
|
||||
|
||||
@app.get("/api/history")
|
||||
async def get_history():
|
||||
"""
|
||||
Get list of past analysis sessions from outputs directory
|
||||
"""
|
||||
history = []
|
||||
output_base = "outputs"
|
||||
|
||||
if not os.path.exists(output_base):
|
||||
return {"history": []}
|
||||
|
||||
try:
|
||||
# Scan for session_* directories
|
||||
for entry in os.scandir(output_base):
|
||||
if entry.is_dir() and entry.name.startswith("session_"):
|
||||
# Extract timestamp from folder name: session_20250101_120000
|
||||
session_id = entry.name.replace("session_", "")
|
||||
|
||||
# Check creation time or extract from name
|
||||
try:
|
||||
# Try to parse timestamp from ID if it matches format
|
||||
# Format: YYYYMMDD_HHMMSS
|
||||
timestamp_str = session_id
|
||||
dt = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
|
||||
display_time = dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||
sort_key = dt.timestamp()
|
||||
except ValueError:
|
||||
# Fallback to file creation time
|
||||
sort_key = entry.stat().st_ctime
|
||||
display_time = datetime.fromtimestamp(sort_key).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
history.append({
|
||||
"id": session_id,
|
||||
"timestamp": display_time,
|
||||
"sort_key": sort_key,
|
||||
"name": f"Session {display_time}"
|
||||
})
|
||||
|
||||
# Sort by latest first
|
||||
history.sort(key=lambda x: x["sort_key"], reverse=True)
|
||||
|
||||
# Cleanup internal sort key
|
||||
for item in history:
|
||||
del item["sort_key"]
|
||||
|
||||
return {"history": history}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error scanning history: {e}")
|
||||
return {"history": []}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
Reference in New Issue
Block a user