更新前端页交互模式

This commit is contained in:
2026-01-31 20:27:17 +08:00
parent 5eb13324c2
commit e9644360ce
7 changed files with 1050 additions and 937 deletions

View File

@@ -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