Spaces:
Sleeping
Sleeping
Trae Assistant
commited on
Commit
·
6ff4e1a
0
Parent(s):
Enhance UI, add file upload, localize to Chinese, and optimize agent logic
Browse files- Dockerfile +25 -0
- README.md +56 -0
- app.py +79 -0
- core/__pycache__/agent.cpython-314.pyc +0 -0
- core/__pycache__/database.cpython-314.pyc +0 -0
- core/__pycache__/tools.cpython-314.pyc +0 -0
- core/agent.py +99 -0
- core/database.py +76 -0
- core/tools.py +53 -0
- requirements.txt +9 -0
- stellar.db +0 -0
- templates/index.html +512 -0
Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 使用轻量级 Python 基础镜像
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# 设置工作目录
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# 安装系统依赖
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
build-essential \
|
| 10 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 11 |
+
|
| 12 |
+
# 复制依赖文件
|
| 13 |
+
COPY requirements.txt .
|
| 14 |
+
|
| 15 |
+
# 安装 Python 依赖
|
| 16 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 17 |
+
|
| 18 |
+
# 复制项目代码
|
| 19 |
+
COPY . .
|
| 20 |
+
|
| 21 |
+
# 暴露端口
|
| 22 |
+
EXPOSE 7860
|
| 23 |
+
|
| 24 |
+
# 启动命令
|
| 25 |
+
CMD ["python", "app.py"]
|
README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Stellar Compute Agent
|
| 3 |
+
emoji: 🛰️
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
short_description: 星际算力调度智能体 - 低轨卫星边缘计算与任务自动化管理系统
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# 星际算力调度智能体 (Stellar Compute Agent)
|
| 12 |
+
|
| 13 |
+
## 项目简介
|
| 14 |
+
星际算力调度智能体是一个面向未来“太空算力”趋势的自动化管理系统。它模拟并实现了低轨卫星(LEO)星座的边缘计算资源调度、空间环境监测、任务自动化执行及闭环验证。
|
| 15 |
+
|
| 16 |
+
该项目采用 **LangGraph** 构建核心决策大脑,通过 **ReAct** (Reasoning and Acting) 模式实现多智能体协同,能够根据复杂的自然语言指令,自主调用卫星状态查询、算力分配、轨道天气检查等工具,确保太空任务的高效与安全。
|
| 17 |
+
|
| 18 |
+
## 核心特性
|
| 19 |
+
- **🤖 闭环决策系统**:集成“推理/决策 + 工具行动 + 状态/记忆 + 校验/验收 + 迭代/回放”的完整循环。
|
| 20 |
+
- **🛰️ 太空算力管理**:模拟卫星星座的实时电量、负载及轨道位置管理。
|
| 21 |
+
- **⛈️ 空间天气感知**:动态响应太阳风暴、空间碎片等异常环境,调整调度策略。
|
| 22 |
+
- **📱 响应式 UI**:适配移动端,提供现代化的“任务控制中心”交互体验。
|
| 23 |
+
- **🧠 强大的 AI 驱动**:接入硅基流动 (SiliconFlow) 的高性能大模型(如 DeepSeek-V3)。
|
| 24 |
+
- **💾 数据持久化**:所有任务轨迹、卫星状态及审计日志均保存在 SQLite 数据库中。
|
| 25 |
+
|
| 26 |
+
## 快速启动
|
| 27 |
+
|
| 28 |
+
### 1. 环境准备
|
| 29 |
+
确保已安装 Python 3.9+,并获取 SiliconFlow API Key。
|
| 30 |
+
|
| 31 |
+
### 2. 安装依赖
|
| 32 |
+
```bash
|
| 33 |
+
pip install -r requirements.txt
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
### 3. 运行项目
|
| 37 |
+
```bash
|
| 38 |
+
python app.py
|
| 39 |
+
```
|
| 40 |
+
访问 `http://localhost:8001` 即可进入控制台。
|
| 41 |
+
|
| 42 |
+
### 4. Docker 运行
|
| 43 |
+
```bash
|
| 44 |
+
docker build -t stellar-compute-agent .
|
| 45 |
+
docker run -p 8001:8001 stellar-compute-agent
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## 技术架构
|
| 49 |
+
- **后端**: FastAPI, Uvicorn
|
| 50 |
+
- **Agent 框架**: LangChain, LangGraph (StateGraph)
|
| 51 |
+
- **大模型**: DeepSeek-V3 (via SiliconFlow)
|
| 52 |
+
- **前端**: HTML5, Tailwind CSS, Marked.js (Markdown 解析)
|
| 53 |
+
- **存储**: SQLite3
|
| 54 |
+
|
| 55 |
+
## 开发者
|
| 56 |
+
由 AI 助手自动构建,专注于未来趋势与生产力工具。
|
app.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import uuid
|
| 3 |
+
import asyncio
|
| 4 |
+
import shutil
|
| 5 |
+
from typing import List
|
| 6 |
+
from fastapi import FastAPI, Request, Form, UploadFile, File, HTTPException
|
| 7 |
+
from fastapi.responses import HTMLResponse, JSONResponse
|
| 8 |
+
from fastapi.staticfiles import StaticFiles
|
| 9 |
+
from fastapi.templating import Jinja2Templates
|
| 10 |
+
from core.agent import StellarAgent
|
| 11 |
+
from core.database import StellarDB
|
| 12 |
+
|
| 13 |
+
app = FastAPI(title="Stellar Compute Agent")
|
| 14 |
+
db = StellarDB()
|
| 15 |
+
agent = StellarAgent()
|
| 16 |
+
|
| 17 |
+
# 挂载静态文件
|
| 18 |
+
os.makedirs("static", exist_ok=True)
|
| 19 |
+
os.makedirs("uploads", exist_ok=True)
|
| 20 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 21 |
+
templates = Jinja2Templates(directory="templates")
|
| 22 |
+
|
| 23 |
+
@app.get("/", response_class=HTMLResponse)
|
| 24 |
+
async def index(request: Request):
|
| 25 |
+
try:
|
| 26 |
+
sats = db.get_satellites()
|
| 27 |
+
tasks = db.get_tasks()
|
| 28 |
+
return templates.TemplateResponse("index.html", {
|
| 29 |
+
"request": request,
|
| 30 |
+
"sats": sats,
|
| 31 |
+
"tasks": tasks
|
| 32 |
+
})
|
| 33 |
+
except Exception as e:
|
| 34 |
+
return HTMLResponse(content=f"<html><body><h1>Internal Server Error</h1><p>{str(e)}</p></body></html>", status_code=500)
|
| 35 |
+
|
| 36 |
+
@app.post("/mission")
|
| 37 |
+
async def create_mission(prompt: str = Form(...)):
|
| 38 |
+
try:
|
| 39 |
+
task_id = str(uuid.uuid4())[:8]
|
| 40 |
+
# 在后台运行任务
|
| 41 |
+
result, logs = await agent.run_mission(task_id, prompt)
|
| 42 |
+
return {"task_id": task_id, "result": result, "logs": logs}
|
| 43 |
+
except Exception as e:
|
| 44 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 45 |
+
|
| 46 |
+
@app.post("/upload")
|
| 47 |
+
async def upload_file(file: UploadFile = File(...)):
|
| 48 |
+
try:
|
| 49 |
+
file_path = os.path.join("uploads", file.filename)
|
| 50 |
+
with open(file_path, "wb") as buffer:
|
| 51 |
+
shutil.copyfileobj(file.file, buffer)
|
| 52 |
+
return {"filename": file.filename, "status": "success", "path": file_path}
|
| 53 |
+
except Exception as e:
|
| 54 |
+
return JSONResponse(status_code=500, content={"message": f"上传失败: {str(e)}"})
|
| 55 |
+
|
| 56 |
+
@app.get("/api/tasks")
|
| 57 |
+
async def get_tasks():
|
| 58 |
+
return db.get_tasks()
|
| 59 |
+
|
| 60 |
+
@app.get("/api/satellites")
|
| 61 |
+
async def get_satellites():
|
| 62 |
+
return db.get_satellites()
|
| 63 |
+
|
| 64 |
+
@app.get("/api/stats")
|
| 65 |
+
async def get_stats():
|
| 66 |
+
sats = db.get_satellites()
|
| 67 |
+
labels = [s['name'] for s in sats]
|
| 68 |
+
loads = [s['compute_load'] for s in sats]
|
| 69 |
+
batteries = [s['battery'] for s in sats]
|
| 70 |
+
return {
|
| 71 |
+
"labels": labels,
|
| 72 |
+
"loads": loads,
|
| 73 |
+
"batteries": batteries
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
if __name__ == "__main__":
|
| 77 |
+
import uvicorn
|
| 78 |
+
port = int(os.environ.get("PORT", 7860))
|
| 79 |
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
core/__pycache__/agent.cpython-314.pyc
ADDED
|
Binary file (6.35 kB). View file
|
|
|
core/__pycache__/database.cpython-314.pyc
ADDED
|
Binary file (5.77 kB). View file
|
|
|
core/__pycache__/tools.cpython-314.pyc
ADDED
|
Binary file (3.14 kB). View file
|
|
|
core/agent.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
from typing import Annotated, TypedDict, List, Union
|
| 4 |
+
from langchain_openai import ChatOpenAI
|
| 5 |
+
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
|
| 6 |
+
from langgraph.graph import StateGraph, END
|
| 7 |
+
from langgraph.prebuilt import ToolNode
|
| 8 |
+
from core.tools import AVAILABLE_TOOLS, query_satellite_status, allocate_compute_task, check_orbital_weather, verify_mission_result
|
| 9 |
+
from langchain_core.utils.function_calling import convert_to_openai_function
|
| 10 |
+
from core.database import StellarDB
|
| 11 |
+
|
| 12 |
+
# 配置 SiliconFlow API
|
| 13 |
+
os.environ["OPENAI_API_KEY"] = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi"
|
| 14 |
+
os.environ["OPENAI_API_BASE"] = "https://api.siliconflow.cn/v1"
|
| 15 |
+
|
| 16 |
+
db = StellarDB()
|
| 17 |
+
|
| 18 |
+
class AgentState(TypedDict):
|
| 19 |
+
messages: Annotated[List[BaseMessage], lambda x, y: x + y]
|
| 20 |
+
task_id: str
|
| 21 |
+
|
| 22 |
+
# 初始化模型
|
| 23 |
+
llm = ChatOpenAI(
|
| 24 |
+
model="deepseek-ai/DeepSeek-V3",
|
| 25 |
+
temperature=0.7,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# 绑定工具
|
| 29 |
+
tools = [query_satellite_status, allocate_compute_task, check_orbital_weather, verify_mission_result]
|
| 30 |
+
llm_with_tools = llm.bind_tools(tools)
|
| 31 |
+
|
| 32 |
+
def call_model(state: AgentState):
|
| 33 |
+
messages = state["messages"]
|
| 34 |
+
response = llm_with_tools.invoke(messages)
|
| 35 |
+
return {"messages": [response]}
|
| 36 |
+
|
| 37 |
+
def should_continue(state: AgentState):
|
| 38 |
+
messages = state["messages"]
|
| 39 |
+
last_message = messages[-1]
|
| 40 |
+
if last_message.tool_calls:
|
| 41 |
+
return "tools"
|
| 42 |
+
return END
|
| 43 |
+
|
| 44 |
+
# 构建图
|
| 45 |
+
workflow = StateGraph(AgentState)
|
| 46 |
+
|
| 47 |
+
workflow.add_node("agent", call_model)
|
| 48 |
+
workflow.add_node("tools", ToolNode(tools))
|
| 49 |
+
|
| 50 |
+
workflow.set_entry_point("agent")
|
| 51 |
+
workflow.add_conditional_edges(
|
| 52 |
+
"agent",
|
| 53 |
+
should_continue,
|
| 54 |
+
)
|
| 55 |
+
workflow.add_edge("tools", "agent")
|
| 56 |
+
|
| 57 |
+
app_graph = workflow.compile()
|
| 58 |
+
|
| 59 |
+
class StellarAgent:
|
| 60 |
+
def __init__(self):
|
| 61 |
+
self.graph = app_graph
|
| 62 |
+
|
| 63 |
+
async def run_mission(self, task_id: str, prompt: str):
|
| 64 |
+
# 初始系统消息
|
| 65 |
+
system_msg = (
|
| 66 |
+
"你是一个名为 Stellar-Commander 的太空算力调度智能体。你的任务是管理低轨卫星星座的计算任务分配、"
|
| 67 |
+
"监控空间环境以及验证任务结果。你可以使用工具来查询状态、分配任务、检查天气和验证结果。"
|
| 68 |
+
"请始终用中文回答。你的回答应该专业、高效,并始终以保障卫星安全和最大化算力产出为目标。"
|
| 69 |
+
"在执行任务前,先通过工具了解现状。如果有附件信息,请一并考虑。"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
inputs = {
|
| 73 |
+
"messages": [
|
| 74 |
+
{"role": "system", "content": system_msg},
|
| 75 |
+
{"role": "human", "content": prompt}
|
| 76 |
+
],
|
| 77 |
+
"task_id": task_id
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
full_log = [f"System: 正在初始化任务序列 #{task_id}...", f"System: 接收到指令: {prompt}"]
|
| 81 |
+
final_result = ""
|
| 82 |
+
|
| 83 |
+
async for event in self.graph.astream(inputs, config={"configurable": {"thread_id": task_id}}):
|
| 84 |
+
for value in event.values():
|
| 85 |
+
if "messages" in value:
|
| 86 |
+
msg = value["messages"][-1]
|
| 87 |
+
if isinstance(msg, AIMessage):
|
| 88 |
+
if msg.content:
|
| 89 |
+
full_log.append(f"Commander: {msg.content}")
|
| 90 |
+
final_result = msg.content
|
| 91 |
+
if msg.tool_calls:
|
| 92 |
+
for tc in msg.tool_calls:
|
| 93 |
+
full_log.append(f"Decision: 调用工具 {tc['name']} 进行分析,参数: {json.dumps(tc['args'], ensure_ascii=False)}")
|
| 94 |
+
elif isinstance(msg, ToolMessage):
|
| 95 |
+
full_log.append(f"Action Result: 工具反馈数据 -> {msg.content}")
|
| 96 |
+
|
| 97 |
+
# 持久化
|
| 98 |
+
db.save_task(task_id, prompt, "Completed", final_result, "\n".join(full_log))
|
| 99 |
+
return final_result, full_log
|
core/database.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sqlite3
|
| 2 |
+
import json
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
|
| 5 |
+
class StellarDB:
|
| 6 |
+
def __init__(self, db_path="stellar.db"):
|
| 7 |
+
self.db_path = db_path
|
| 8 |
+
self._init_db()
|
| 9 |
+
|
| 10 |
+
def _init_db(self):
|
| 11 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 12 |
+
cursor = conn.cursor()
|
| 13 |
+
# 卫星状态表
|
| 14 |
+
cursor.execute('''
|
| 15 |
+
CREATE TABLE IF NOT EXISTS satellites (
|
| 16 |
+
id TEXT PRIMARY KEY,
|
| 17 |
+
name TEXT,
|
| 18 |
+
status TEXT,
|
| 19 |
+
battery INTEGER,
|
| 20 |
+
compute_load INTEGER,
|
| 21 |
+
last_updated TIMESTAMP
|
| 22 |
+
)
|
| 23 |
+
''')
|
| 24 |
+
# 任务记录表
|
| 25 |
+
cursor.execute('''
|
| 26 |
+
CREATE TABLE IF NOT EXISTS tasks (
|
| 27 |
+
id TEXT PRIMARY KEY,
|
| 28 |
+
description TEXT,
|
| 29 |
+
status TEXT,
|
| 30 |
+
result TEXT,
|
| 31 |
+
agent_log TEXT,
|
| 32 |
+
created_at TIMESTAMP
|
| 33 |
+
)
|
| 34 |
+
''')
|
| 35 |
+
# 初始卫星数据
|
| 36 |
+
satellites = [
|
| 37 |
+
('SAT-001', 'Stellar-Alpha (领航者)', 'Orbiting', 85, 10, datetime.now()),
|
| 38 |
+
('SAT-002', 'Stellar-Beta (守望者)', 'Orbiting', 92, 5, datetime.now()),
|
| 39 |
+
('SAT-003', 'Stellar-Gamma (开拓者)', 'Maintenance', 45, 0, datetime.now()),
|
| 40 |
+
('SAT-004', 'Stellar-Delta (中继站)', 'Orbiting', 78, 25, datetime.now()),
|
| 41 |
+
('SAT-005', 'Stellar-Epsilon (监测站)', 'Orbiting', 60, 40, datetime.now()),
|
| 42 |
+
]
|
| 43 |
+
cursor.executemany('INSERT OR IGNORE INTO satellites VALUES (?,?,?,?,?,?)', satellites)
|
| 44 |
+
conn.commit()
|
| 45 |
+
|
| 46 |
+
def get_satellites(self):
|
| 47 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 48 |
+
conn.row_factory = sqlite3.Row
|
| 49 |
+
cursor = conn.cursor()
|
| 50 |
+
cursor.execute('SELECT * FROM satellites')
|
| 51 |
+
return [dict(row) for row in cursor.fetchall()]
|
| 52 |
+
|
| 53 |
+
def update_satellite(self, sat_id, status=None, battery=None, compute_load=None):
|
| 54 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 55 |
+
cursor = conn.cursor()
|
| 56 |
+
if status: cursor.execute('UPDATE satellites SET status = ? WHERE id = ?', (status, sat_id))
|
| 57 |
+
if battery is not None: cursor.execute('UPDATE satellites SET battery = ? WHERE id = ?', (battery, sat_id))
|
| 58 |
+
if compute_load is not None: cursor.execute('UPDATE satellites SET compute_load = ? WHERE id = ?', (compute_load, sat_id))
|
| 59 |
+
cursor.execute('UPDATE satellites SET last_updated = ? WHERE id = ?', (datetime.now(), sat_id))
|
| 60 |
+
conn.commit()
|
| 61 |
+
|
| 62 |
+
def save_task(self, task_id, description, status, result="", agent_log=""):
|
| 63 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 64 |
+
cursor = conn.cursor()
|
| 65 |
+
cursor.execute('''
|
| 66 |
+
INSERT OR REPLACE INTO tasks (id, description, status, result, agent_log, created_at)
|
| 67 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
| 68 |
+
''', (task_id, description, status, result, agent_log, datetime.now()))
|
| 69 |
+
conn.commit()
|
| 70 |
+
|
| 71 |
+
def get_tasks(self):
|
| 72 |
+
with sqlite3.connect(self.db_path) as conn:
|
| 73 |
+
conn.row_factory = sqlite3.Row
|
| 74 |
+
cursor = conn.cursor()
|
| 75 |
+
cursor.execute('SELECT * FROM tasks ORDER BY created_at DESC')
|
| 76 |
+
return [dict(row) for row in cursor.fetchall()]
|
core/tools.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from core.database import StellarDB
|
| 2 |
+
import random
|
| 3 |
+
|
| 4 |
+
db = StellarDB()
|
| 5 |
+
|
| 6 |
+
def query_satellite_status():
|
| 7 |
+
"""查询所有卫星的当前状态、电量和计算负载。"""
|
| 8 |
+
sats = db.get_satellites()
|
| 9 |
+
status_str = "当前卫星星座状态:\n"
|
| 10 |
+
for s in sats:
|
| 11 |
+
status_str += f"- {s['name']} ({s['id']}): 状态={s['status']}, 电量={s['battery']}%, 负载={s['compute_load']}%\n"
|
| 12 |
+
return status_str
|
| 13 |
+
|
| 14 |
+
def allocate_compute_task(task_description, target_sat_id):
|
| 15 |
+
"""将计算任务分配给指定的卫星。"""
|
| 16 |
+
sats = db.get_satellites()
|
| 17 |
+
target = next((s for s in sats if s['id'] == target_sat_id), None)
|
| 18 |
+
|
| 19 |
+
if not target:
|
| 20 |
+
return f"错误: 找不到卫星 {target_sat_id}"
|
| 21 |
+
|
| 22 |
+
if target['status'] != 'Orbiting':
|
| 23 |
+
return f"错误: 卫星 {target_sat_id} 当前处于 {target['status']} 状态,无法处理任务。"
|
| 24 |
+
|
| 25 |
+
if target['battery'] < 20:
|
| 26 |
+
return f"警告: 卫星 {target_sat_id} 电量过低 ({target['battery']}%),任务分配失败。"
|
| 27 |
+
|
| 28 |
+
# 模拟分配逻辑
|
| 29 |
+
new_load = min(100, target['compute_load'] + 25)
|
| 30 |
+
db.update_satellite(target_sat_id, compute_load=new_load)
|
| 31 |
+
|
| 32 |
+
return f"成功: 任务 '{task_description}' 已分配给 {target_sat_id}。当前负载增加至 {new_load}%。"
|
| 33 |
+
|
| 34 |
+
def check_orbital_weather():
|
| 35 |
+
"""检查当前的轨道空间天气,如太阳活动、碎片风险等。"""
|
| 36 |
+
weathers = ["正常", "强太阳风暴", "空间碎片预警", "电离层干扰"]
|
| 37 |
+
current = random.choice(weathers)
|
| 38 |
+
if current == "正常":
|
| 39 |
+
return "空间天气状况良好,适合进行高强度计算任务。"
|
| 40 |
+
else:
|
| 41 |
+
return f"警告: 当前空间天气为 '{current}',建议推迟非必要任务或降低负载。"
|
| 42 |
+
|
| 43 |
+
def verify_mission_result(task_id):
|
| 44 |
+
"""校验任务执行结果。"""
|
| 45 |
+
return f"任务 {task_id} 结果校验完成: 100% 匹配预期数据模型。"
|
| 46 |
+
|
| 47 |
+
# 工具映射表
|
| 48 |
+
AVAILABLE_TOOLS = {
|
| 49 |
+
"query_satellite_status": query_satellite_status,
|
| 50 |
+
"allocate_compute_task": allocate_compute_task,
|
| 51 |
+
"check_orbital_weather": check_orbital_weather,
|
| 52 |
+
"verify_mission_result": verify_mission_result
|
| 53 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
langchain
|
| 4 |
+
langchain-openai
|
| 5 |
+
langgraph
|
| 6 |
+
pydantic
|
| 7 |
+
jinja2
|
| 8 |
+
python-multipart
|
| 9 |
+
requests
|
stellar.db
ADDED
|
Binary file (20.5 kB). View file
|
|
|
templates/index.html
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="zh-CN">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Stellar Compute - 太空算力调度智能体</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 10 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 11 |
+
<style>
|
| 12 |
+
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
| 13 |
+
body { font-family: 'Space Grotesk', 'PingFang SC', 'Microsoft YaHei', sans-serif; background-color: #050505; color: #e5e5e5; }
|
| 14 |
+
.glass { background: rgba(255, 255, 255, 0.05); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); }
|
| 15 |
+
.accent-blue { color: #3b82f6; }
|
| 16 |
+
.bg-accent-blue { background-color: #3b82f6; }
|
| 17 |
+
.prose { color: #d1d5db; }
|
| 18 |
+
.prose h1, .prose h2, .prose h3 { color: #fff; margin-top: 1em; margin-bottom: 0.5em; }
|
| 19 |
+
.prose p { margin-bottom: 1em; line-height: 1.6; }
|
| 20 |
+
.status-dot { height: 8px; width: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; }
|
| 21 |
+
.orbiting { background-color: #10b981; box-shadow: 0 0 8px #10b981; }
|
| 22 |
+
.maintenance { background-color: #f59e0b; box-shadow: 0 0 8px #f59e0b; }
|
| 23 |
+
|
| 24 |
+
/* 隐藏滚动条但保留功能 */
|
| 25 |
+
.no-scrollbar::-webkit-scrollbar { display: none; }
|
| 26 |
+
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
| 27 |
+
</style>
|
| 28 |
+
</head>
|
| 29 |
+
<body class="min-h-screen">
|
| 30 |
+
<!-- 导航栏 -->
|
| 31 |
+
<nav class="p-4 border-b border-white/10 glass sticky top-0 z-50">
|
| 32 |
+
<div class="max-w-7xl mx-auto flex justify-between items-center">
|
| 33 |
+
<div class="flex items-center space-x-3">
|
| 34 |
+
<i class="fas fa-satellite-dish text-2xl text-blue-500"></i>
|
| 35 |
+
<span class="text-xl font-bold tracking-tighter">STELLAR COMPUTE</span>
|
| 36 |
+
<span class="text-xs bg-blue-500/20 text-blue-400 px-2 py-0.5 rounded border border-blue-500/30">V2.0 升级版</span>
|
| 37 |
+
</div>
|
| 38 |
+
<div class="hidden md:flex space-x-8 text-sm font-medium">
|
| 39 |
+
<a href="#" class="text-blue-400 border-b-2 border-blue-400 pb-1">控制台</a>
|
| 40 |
+
<a href="#" class="hover:text-blue-400 transition">星座网络</a>
|
| 41 |
+
<a href="#" class="hover:text-blue-400 transition">算力市场</a>
|
| 42 |
+
<a href="#" class="hover:text-blue-400 transition">任务审计</a>
|
| 43 |
+
</div>
|
| 44 |
+
<div class="flex items-center space-x-4">
|
| 45 |
+
<span id="system-time" class="text-xs font-mono text-gray-500">2026-02-14 12:00:00 UTC</span>
|
| 46 |
+
<button class="text-xl"><i class="fas fa-user-circle"></i></button>
|
| 47 |
+
</div>
|
| 48 |
+
</div>
|
| 49 |
+
</nav>
|
| 50 |
+
|
| 51 |
+
<main class="max-w-7xl mx-auto p-4 md:p-6 grid grid-cols-1 lg:grid-cols-4 gap-6">
|
| 52 |
+
|
| 53 |
+
<!-- 左侧:状态与图表 -->
|
| 54 |
+
<div class="lg:col-span-1 space-y-6">
|
| 55 |
+
<div class="glass p-5 rounded-2xl">
|
| 56 |
+
<h2 class="text-sm font-bold mb-4 flex items-center text-gray-400 uppercase tracking-widest">
|
| 57 |
+
<i class="fas fa-chart-line mr-2 text-blue-500"></i> 星座负载概览
|
| 58 |
+
</h2>
|
| 59 |
+
<div class="h-48 w-full">
|
| 60 |
+
<canvas id="loadChart"></canvas>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div class="glass p-5 rounded-2xl">
|
| 65 |
+
<h2 class="text-sm font-bold mb-4 flex items-center text-gray-400 uppercase tracking-widest">
|
| 66 |
+
<i class="fas fa-microchip mr-2 text-blue-500"></i> 卫星节点状态
|
| 67 |
+
</h2>
|
| 68 |
+
<div id="satellite-list" class="space-y-4 max-h-[400px] overflow-y-auto no-scrollbar">
|
| 69 |
+
{% for sat in sats %}
|
| 70 |
+
<div class="p-3 rounded-xl bg-white/5 border border-white/10 hover:border-blue-500/30 transition-all">
|
| 71 |
+
<div class="flex justify-between items-start mb-2">
|
| 72 |
+
<div>
|
| 73 |
+
<h3 class="font-medium text-sm">{{ sat.name }}</h3>
|
| 74 |
+
<p class="text-[10px] text-gray-500 font-mono">{{ sat.id }}</p>
|
| 75 |
+
</div>
|
| 76 |
+
<span class="text-[10px] px-2 py-0.5 rounded-full {% if sat.status == 'Orbiting' %}bg-green-500/20 text-green-400 border border-green-500/30{% else %}bg-yellow-500/20 text-yellow-400 border border-yellow-500/30{% endif %}">
|
| 77 |
+
{{ "运行中" if sat.status == 'Orbiting' else "维护中" }}
|
| 78 |
+
</span>
|
| 79 |
+
</div>
|
| 80 |
+
<div class="space-y-2">
|
| 81 |
+
<div class="flex justify-between text-[10px]">
|
| 82 |
+
<span class="text-gray-400">电量</span>
|
| 83 |
+
<span>{{ sat.battery }}%</span>
|
| 84 |
+
</div>
|
| 85 |
+
<div class="w-full bg-white/10 rounded-full h-1">
|
| 86 |
+
<div class="bg-green-500 h-1 rounded-full transition-all duration-1000" style="width: {{ sat.battery }}%"></div>
|
| 87 |
+
</div>
|
| 88 |
+
<div class="flex justify-between text-[10px] pt-1">
|
| 89 |
+
<span class="text-gray-400">算力负载</span>
|
| 90 |
+
<span>{{ sat.compute_load }}%</span>
|
| 91 |
+
</div>
|
| 92 |
+
<div class="w-full bg-white/10 rounded-full h-1">
|
| 93 |
+
<div class="bg-blue-500 h-1 rounded-full transition-all duration-1000" style="width: {{ sat.compute_load }}%"></div>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
{% endfor %}
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</div>
|
| 101 |
+
|
| 102 |
+
<!-- 中间:控制台 -->
|
| 103 |
+
<div class="lg:col-span-2 space-y-6">
|
| 104 |
+
<div class="glass p-6 rounded-2xl relative overflow-hidden">
|
| 105 |
+
<div class="absolute top-0 right-0 p-4">
|
| 106 |
+
<i class="fas fa-shield-alt text-blue-500/20 text-6xl"></i>
|
| 107 |
+
</div>
|
| 108 |
+
<h2 class="text-lg font-bold mb-4 flex items-center">
|
| 109 |
+
<i class="fas fa-terminal mr-2 text-blue-500"></i> 任务控制台 (Mission Control)
|
| 110 |
+
</h2>
|
| 111 |
+
<form id="mission-form" class="space-y-4">
|
| 112 |
+
<div class="relative">
|
| 113 |
+
<textarea
|
| 114 |
+
name="prompt"
|
| 115 |
+
id="mission-input"
|
| 116 |
+
class="w-full bg-white/5 border border-white/10 rounded-xl p-4 text-white focus:outline-none focus:border-blue-500 transition h-40 resize-none text-sm leading-relaxed"
|
| 117 |
+
placeholder="请输入您的指令...
|
| 118 |
+
例如:'分析 SAT-001 的能耗趋势,并根据当前轨道天气,将非核心计算任务迁移至电力充沛的节点。'"
|
| 119 |
+
></textarea>
|
| 120 |
+
<div class="absolute bottom-3 right-3 flex space-x-2">
|
| 121 |
+
<button type="button" onclick="triggerUpload()" class="p-2 rounded-lg bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white transition" title="上传附件">
|
| 122 |
+
<i class="fas fa-paperclip"></i>
|
| 123 |
+
</button>
|
| 124 |
+
<input type="file" id="file-upload" class="hidden" onchange="handleFileSelected(this)">
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
<div id="upload-status" class="hidden text-xs text-blue-400 bg-blue-500/10 p-2 rounded-lg border border-blue-500/20">
|
| 128 |
+
<i class="fas fa-file-alt mr-1"></i> <span id="filename-display"></span> 上传成功
|
| 129 |
+
</div>
|
| 130 |
+
<button type="submit" id="submit-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-xl transition shadow-lg shadow-blue-500/20 flex items-center justify-center space-x-3">
|
| 131 |
+
<i class="fas fa-bolt"></i>
|
| 132 |
+
<span>启动智能调度流</span>
|
| 133 |
+
</button>
|
| 134 |
+
</form>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
<!-- 输出区域 -->
|
| 138 |
+
<div id="output-container" class="glass p-6 rounded-2xl hidden transition-all duration-500">
|
| 139 |
+
<div class="flex justify-between items-center mb-6">
|
| 140 |
+
<h2 class="text-lg font-bold flex items-center">
|
| 141 |
+
<i class="fas fa-brain mr-2 text-blue-500"></i> 智能体推理与决策轨迹
|
| 142 |
+
</h2>
|
| 143 |
+
<span id="task-status-badge" class="px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider">等待中</span>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
<div class="space-y-6">
|
| 147 |
+
<div id="agent-logs" class="font-mono text-xs space-y-3 max-h-80 overflow-y-auto p-4 bg-black/40 rounded-xl border border-white/5 no-scrollbar">
|
| 148 |
+
<!-- 日志将在此处动态生成 -->
|
| 149 |
+
</div>
|
| 150 |
+
|
| 151 |
+
<div class="border-t border-white/10 pt-6">
|
| 152 |
+
<div class="flex items-center mb-4">
|
| 153 |
+
<div class="h-1 w-8 bg-blue-500 rounded-full mr-3"></div>
|
| 154 |
+
<h3 class="text-md font-bold">最终执行指令</h3>
|
| 155 |
+
</div>
|
| 156 |
+
<div id="final-result" class="prose prose-invert max-w-none text-sm bg-white/5 p-5 rounded-xl border border-white/10">
|
| 157 |
+
<!-- 结果将在此处动态生成 -->
|
| 158 |
+
</div>
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
</div>
|
| 163 |
+
|
| 164 |
+
<!-- ���侧:任务审计 -->
|
| 165 |
+
<div class="lg:col-span-1 space-y-6">
|
| 166 |
+
<div class="glass p-5 rounded-2xl">
|
| 167 |
+
<h2 class="text-sm font-bold mb-4 flex items-center text-gray-400 uppercase tracking-widest">
|
| 168 |
+
<i class="fas fa-history mr-2 text-blue-500"></i> 历史任务记录
|
| 169 |
+
</h2>
|
| 170 |
+
<div id="task-history" class="space-y-3 overflow-y-auto max-h-[600px] no-scrollbar">
|
| 171 |
+
{% for task in tasks %}
|
| 172 |
+
<div class="p-3 rounded-xl bg-white/5 border border-white/5 text-sm cursor-pointer hover:bg-white/10 hover:border-white/20 transition-all group" onclick="showTaskDetail('{{ task.id }}')">
|
| 173 |
+
<div class="flex justify-between mb-1">
|
| 174 |
+
<span class="font-mono text-[10px] text-blue-400">#{{ task.id }}</span>
|
| 175 |
+
<span class="text-[9px] text-gray-500">{{ task.created_at.strftime('%H:%M:%S') if task.created_at else '' }}</span>
|
| 176 |
+
</div>
|
| 177 |
+
<p class="truncate text-gray-300 group-hover:text-white transition-colors">{{ task.description }}</p>
|
| 178 |
+
<div class="mt-2 flex items-center text-[9px] text-gray-500">
|
| 179 |
+
<span class="status-dot orbiting"></span> 已完成
|
| 180 |
+
</div>
|
| 181 |
+
</div>
|
| 182 |
+
{% else %}
|
| 183 |
+
<div class="text-center py-10 text-gray-600 text-xs">
|
| 184 |
+
<i class="fas fa-inbox text-2xl mb-2 opacity-20"></i>
|
| 185 |
+
<p>暂无任务记录</p>
|
| 186 |
+
</div>
|
| 187 |
+
{% endfor %}
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
</div>
|
| 191 |
+
</main>
|
| 192 |
+
|
| 193 |
+
<!-- 任务详情弹窗 -->
|
| 194 |
+
<div id="detail-modal" class="fixed inset-0 bg-black/90 backdrop-blur-md z-[100] hidden items-center justify-center p-4">
|
| 195 |
+
<div class="glass max-w-4xl w-full max-h-[85vh] rounded-3xl overflow-hidden flex flex-col shadow-2xl border-white/20">
|
| 196 |
+
<div class="p-6 border-b border-white/10 flex justify-between items-center bg-white/5">
|
| 197 |
+
<div class="flex items-center space-x-3">
|
| 198 |
+
<i class="fas fa-file-invoice text-blue-500"></i>
|
| 199 |
+
<h3 class="text-xl font-bold">任务全周期审计报告</h3>
|
| 200 |
+
</div>
|
| 201 |
+
<button onclick="closeModal()" class="w-10 h-10 rounded-full flex items-center justify-center bg-white/5 hover:bg-white/10 text-gray-400 hover:text-white transition">
|
| 202 |
+
<i class="fas fa-times"></i>
|
| 203 |
+
</button>
|
| 204 |
+
</div>
|
| 205 |
+
<div id="modal-content" class="p-8 overflow-y-auto space-y-8 no-scrollbar">
|
| 206 |
+
<!-- 弹窗内容动态生成 -->
|
| 207 |
+
</div>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
|
| 211 |
+
<script>
|
| 212 |
+
// 初始化图表
|
| 213 |
+
let loadChart;
|
| 214 |
+
function initChart(data) {
|
| 215 |
+
const ctx = document.getElementById('loadChart').getContext('2d');
|
| 216 |
+
loadChart = new Chart(ctx, {
|
| 217 |
+
type: 'bar',
|
| 218 |
+
data: {
|
| 219 |
+
labels: data.labels,
|
| 220 |
+
datasets: [
|
| 221 |
+
{
|
| 222 |
+
label: '负载 (%)',
|
| 223 |
+
data: data.loads,
|
| 224 |
+
backgroundColor: 'rgba(59, 130, 246, 0.5)',
|
| 225 |
+
borderColor: '#3b82f6',
|
| 226 |
+
borderWidth: 1,
|
| 227 |
+
borderRadius: 4
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
label: '电量 (%)',
|
| 231 |
+
data: data.batteries,
|
| 232 |
+
type: 'line',
|
| 233 |
+
borderColor: '#10b981',
|
| 234 |
+
backgroundColor: '#10b981',
|
| 235 |
+
borderWidth: 2,
|
| 236 |
+
pointRadius: 3,
|
| 237 |
+
fill: false
|
| 238 |
+
}
|
| 239 |
+
]
|
| 240 |
+
},
|
| 241 |
+
options: {
|
| 242 |
+
responsive: true,
|
| 243 |
+
maintainAspectRatio: false,
|
| 244 |
+
scales: {
|
| 245 |
+
y: {
|
| 246 |
+
beginAtZero: true,
|
| 247 |
+
max: 100,
|
| 248 |
+
grid: { color: 'rgba(255,255,255,0.05)' },
|
| 249 |
+
ticks: { color: '#666', font: { size: 10 } }
|
| 250 |
+
},
|
| 251 |
+
x: {
|
| 252 |
+
grid: { display: false },
|
| 253 |
+
ticks: { color: '#666', font: { size: 10 } }
|
| 254 |
+
}
|
| 255 |
+
},
|
| 256 |
+
plugins: {
|
| 257 |
+
legend: {
|
| 258 |
+
display: true,
|
| 259 |
+
position: 'bottom',
|
| 260 |
+
labels: { color: '#999', boxWidth: 10, font: { size: 10 } }
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
});
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
// 定时更新系统时间
|
| 268 |
+
setInterval(() => {
|
| 269 |
+
const now = new Date();
|
| 270 |
+
document.getElementById('system-time').innerText = now.toISOString().replace('T', ' ').substring(0, 19) + ' UTC';
|
| 271 |
+
}, 1000);
|
| 272 |
+
|
| 273 |
+
// 表单提交
|
| 274 |
+
const form = document.getElementById('mission-form');
|
| 275 |
+
const submitBtn = document.getElementById('submit-btn');
|
| 276 |
+
const outputContainer = document.getElementById('output-container');
|
| 277 |
+
const agentLogs = document.getElementById('agent-logs');
|
| 278 |
+
const finalResult = document.getElementById('final-result');
|
| 279 |
+
const taskStatusBadge = document.getElementById('task-status-badge');
|
| 280 |
+
|
| 281 |
+
form.onsubmit = async (e) => {
|
| 282 |
+
e.preventDefault();
|
| 283 |
+
const formData = new FormData(form);
|
| 284 |
+
if (!formData.get('prompt')) return;
|
| 285 |
+
|
| 286 |
+
// UI 重置
|
| 287 |
+
submitBtn.disabled = true;
|
| 288 |
+
submitBtn.innerHTML = '<i class="fas fa-satellite animate-pulse"></i> <span>正在建立星地链路...</span>';
|
| 289 |
+
outputContainer.classList.remove('hidden');
|
| 290 |
+
agentLogs.innerHTML = '<div class="text-blue-400/60">[SYSTEM] 链路已建立,开始上传指令...</div>';
|
| 291 |
+
finalResult.innerHTML = '<div class="flex items-center space-x-2 text-gray-500"><div class="w-2 h-2 bg-gray-500 rounded-full animate-bounce"></div><span>计算中...</span></div>';
|
| 292 |
+
taskStatusBadge.className = 'px-3 py-1 rounded-full text-[10px] font-bold bg-yellow-500/20 text-yellow-400 animate-pulse';
|
| 293 |
+
taskStatusBadge.innerText = '执行中';
|
| 294 |
+
|
| 295 |
+
try {
|
| 296 |
+
const response = await fetch('/mission', {
|
| 297 |
+
method: 'POST',
|
| 298 |
+
body: formData
|
| 299 |
+
});
|
| 300 |
+
const data = await response.json();
|
| 301 |
+
|
| 302 |
+
// 渲染日志
|
| 303 |
+
agentLogs.innerHTML = '';
|
| 304 |
+
data.logs.forEach((log, index) => {
|
| 305 |
+
setTimeout(() => {
|
| 306 |
+
const div = document.createElement('div');
|
| 307 |
+
div.className = 'opacity-0 transform translate-x-2 transition-all duration-300';
|
| 308 |
+
|
| 309 |
+
if (log.includes('Decision:')) {
|
| 310 |
+
div.innerHTML = `<span class="text-blue-400 font-bold">◈ 决策:</span> <span class="text-blue-100">${log.split('Decision:')[1]}</span>`;
|
| 311 |
+
} else if (log.includes('Action Result:')) {
|
| 312 |
+
div.innerHTML = `<span class="text-green-400 font-bold">✓ 响应:</span> <span class="text-green-100">${log.split('Action Result:')[1]}</span>`;
|
| 313 |
+
} else if (log.includes('Commander:')) {
|
| 314 |
+
div.innerHTML = `<span class="text-purple-400 font-bold">⌘ 指挥:</span> <span class="text-purple-100">${log.split('Commander:')[1]}</span>`;
|
| 315 |
+
} else {
|
| 316 |
+
div.innerHTML = `<span class="text-gray-500"># ${log}</span>`;
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
agentLogs.appendChild(div);
|
| 320 |
+
setTimeout(() => {
|
| 321 |
+
div.classList.remove('opacity-0', 'translate-x-2');
|
| 322 |
+
agentLogs.scrollTop = agentLogs.scrollHeight;
|
| 323 |
+
}, 50);
|
| 324 |
+
}, index * 100);
|
| 325 |
+
});
|
| 326 |
+
|
| 327 |
+
// 渲染 Markdown 结果
|
| 328 |
+
setTimeout(() => {
|
| 329 |
+
finalResult.innerHTML = marked.parse(data.result);
|
| 330 |
+
taskStatusBadge.className = 'px-3 py-1 rounded-full text-[10px] font-bold bg-green-500/20 text-green-400';
|
| 331 |
+
taskStatusBadge.innerText = '任务完成';
|
| 332 |
+
updateStatus();
|
| 333 |
+
}, data.logs.length * 100 + 500);
|
| 334 |
+
|
| 335 |
+
} catch (err) {
|
| 336 |
+
console.error(err);
|
| 337 |
+
taskStatusBadge.className = 'px-3 py-1 rounded-full text-[10px] font-bold bg-red-500/20 text-red-400';
|
| 338 |
+
taskStatusBadge.innerText = '链路中断';
|
| 339 |
+
finalResult.innerHTML = '<span class="text-red-400">错误: 无法连接到星际计算引擎。请检查您的 API 密钥或网络连接。</span>';
|
| 340 |
+
} finally {
|
| 341 |
+
submitBtn.disabled = false;
|
| 342 |
+
submitBtn.innerHTML = '<i class="fas fa-bolt"></i> <span>启动智能调度流</span>';
|
| 343 |
+
}
|
| 344 |
+
};
|
| 345 |
+
|
| 346 |
+
// 文件上传逻辑
|
| 347 |
+
function triggerUpload() {
|
| 348 |
+
document.getElementById('file-upload').click();
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
async function handleFileSelected(input) {
|
| 352 |
+
if (!input.files || input.files.length === 0) return;
|
| 353 |
+
const file = input.files[0];
|
| 354 |
+
const statusDiv = document.getElementById('upload-status');
|
| 355 |
+
const filenameSpan = document.getElementById('filename-display');
|
| 356 |
+
|
| 357 |
+
statusDiv.classList.remove('hidden');
|
| 358 |
+
statusDiv.innerHTML = `<i class="fas fa-spinner animate-spin mr-1"></i> 正在上传 ${file.name}...`;
|
| 359 |
+
|
| 360 |
+
const formData = new FormData();
|
| 361 |
+
formData.append('file', file);
|
| 362 |
+
|
| 363 |
+
try {
|
| 364 |
+
const res = await fetch('/upload', { method: 'POST', body: formData });
|
| 365 |
+
const data = await res.json();
|
| 366 |
+
if (data.status === 'success') {
|
| 367 |
+
statusDiv.innerHTML = `<i class="fas fa-check-circle text-green-400 mr-1"></i> ${file.name} 已挂载到当前任务上下文`;
|
| 368 |
+
statusDiv.classList.add('bg-green-500/10', 'border-green-500/20', 'text-green-400');
|
| 369 |
+
// 将文件名添加到输入框
|
| 370 |
+
const missionInput = document.getElementById('mission-input');
|
| 371 |
+
missionInput.value += `\n[附件: ${file.name}]`;
|
| 372 |
+
} else {
|
| 373 |
+
throw new Error(data.message);
|
| 374 |
+
}
|
| 375 |
+
} catch (err) {
|
| 376 |
+
statusDiv.innerHTML = `<i class="fas fa-exclamation-triangle text-red-400 mr-1"></i> 上传失败: ${err.message}`;
|
| 377 |
+
statusDiv.classList.add('bg-red-500/10', 'border-red-500/20', 'text-red-400');
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
// 更新状态
|
| 382 |
+
async function updateStatus() {
|
| 383 |
+
try {
|
| 384 |
+
const [satRes, statsRes] = await Promise.all([
|
| 385 |
+
fetch('/api/satellites'),
|
| 386 |
+
fetch('/api/stats')
|
| 387 |
+
]);
|
| 388 |
+
|
| 389 |
+
const sats = await satRes.json();
|
| 390 |
+
const stats = await statsRes.json();
|
| 391 |
+
|
| 392 |
+
// 更新列表
|
| 393 |
+
const list = document.getElementById('satellite-list');
|
| 394 |
+
list.innerHTML = sats.map(sat => `
|
| 395 |
+
<div class="p-3 rounded-xl bg-white/5 border border-white/10 hover:border-blue-500/30 transition-all">
|
| 396 |
+
<div class="flex justify-between items-start mb-2">
|
| 397 |
+
<div>
|
| 398 |
+
<h3 class="font-medium text-sm">${sat.name}</h3>
|
| 399 |
+
<p class="text-[10px] text-gray-500 font-mono">${sat.id}</p>
|
| 400 |
+
</div>
|
| 401 |
+
<span class="text-[10px] px-2 py-0.5 rounded-full ${sat.status === 'Orbiting' ? 'bg-green-500/20 text-green-400 border border-green-500/30' : 'bg-yellow-500/20 text-yellow-400 border border-yellow-500/30'}">
|
| 402 |
+
${sat.status === 'Orbiting' ? '运行中' : '维护中'}
|
| 403 |
+
</span>
|
| 404 |
+
</div>
|
| 405 |
+
<div class="space-y-2">
|
| 406 |
+
<div class="flex justify-between text-[10px]">
|
| 407 |
+
<span class="text-gray-400">电量</span>
|
| 408 |
+
<span>${sat.battery}%</span>
|
| 409 |
+
</div>
|
| 410 |
+
<div class="w-full bg-white/10 rounded-full h-1">
|
| 411 |
+
<div class="bg-green-500 h-1 rounded-full transition-all duration-1000" style="width: ${sat.battery}%"></div>
|
| 412 |
+
</div>
|
| 413 |
+
<div class="flex justify-between text-[10px] pt-1">
|
| 414 |
+
<span class="text-gray-400">算力负载</span>
|
| 415 |
+
<span>${sat.compute_load}%</span>
|
| 416 |
+
</div>
|
| 417 |
+
<div class="w-full bg-white/10 rounded-full h-1">
|
| 418 |
+
<div class="bg-blue-500 h-1 rounded-full transition-all duration-1000" style="width: ${sat.compute_load}%"></div>
|
| 419 |
+
</div>
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
`).join('');
|
| 423 |
+
|
| 424 |
+
// 更新图表
|
| 425 |
+
if (loadChart) {
|
| 426 |
+
loadChart.data.labels = stats.labels;
|
| 427 |
+
loadChart.data.datasets[0].data = stats.loads;
|
| 428 |
+
loadChart.data.datasets[1].data = stats.batteries;
|
| 429 |
+
loadChart.update();
|
| 430 |
+
}
|
| 431 |
+
} catch (err) {
|
| 432 |
+
console.error("更新状态失败:", err);
|
| 433 |
+
}
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
// 显示详情
|
| 437 |
+
async function showTaskDetail(id) {
|
| 438 |
+
try {
|
| 439 |
+
const tasksRes = await fetch('/api/tasks');
|
| 440 |
+
const tasks = await tasksRes.json();
|
| 441 |
+
const task = tasks.find(t => t.id === id);
|
| 442 |
+
if (!task) return;
|
| 443 |
+
|
| 444 |
+
const modal = document.getElementById('detail-modal');
|
| 445 |
+
const content = document.getElementById('modal-content');
|
| 446 |
+
|
| 447 |
+
content.innerHTML = `
|
| 448 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
| 449 |
+
<div class="space-y-6">
|
| 450 |
+
<div>
|
| 451 |
+
<h4 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-3 flex items-center">
|
| 452 |
+
<span class="w-1.5 h-1.5 bg-blue-500 rounded-full mr-2"></span> 原始任务指令
|
| 453 |
+
</h4>
|
| 454 |
+
<div class="bg-white/5 p-5 rounded-2xl border border-white/10 text-sm leading-relaxed text-blue-100">
|
| 455 |
+
${task.description}
|
| 456 |
+
</div>
|
| 457 |
+
</div>
|
| 458 |
+
<div>
|
| 459 |
+
<h4 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-3 flex items-center">
|
| 460 |
+
<span class="w-1.5 h-1.5 bg-green-500 rounded-full mr-2"></span> 最终决策结果
|
| 461 |
+
</h4>
|
| 462 |
+
<div class="prose prose-invert max-w-none text-sm bg-white/5 p-5 rounded-2xl border border-white/10">
|
| 463 |
+
${marked.parse(task.result)}
|
| 464 |
+
</div>
|
| 465 |
+
</div>
|
| 466 |
+
</div>
|
| 467 |
+
<div>
|
| 468 |
+
<h4 class="text-xs font-bold text-gray-500 uppercase tracking-widest mb-3 flex items-center">
|
| 469 |
+
<span class="w-1.5 h-1.5 bg-purple-500 rounded-full mr-2"></span> 智能体执行日志 (审计跟踪)
|
| 470 |
+
</h4>
|
| 471 |
+
<div class="bg-black/40 p-5 rounded-2xl border border-white/10 text-[11px] font-mono text-blue-300 h-full overflow-y-auto max-h-[500px] leading-relaxed no-scrollbar">
|
| 472 |
+
${task.agent_log.split('\n').map(line => `<div class="mb-2 border-l border-white/10 pl-3">${line}</div>`).join('')}
|
| 473 |
+
</div>
|
| 474 |
+
</div>
|
| 475 |
+
</div>
|
| 476 |
+
`;
|
| 477 |
+
|
| 478 |
+
modal.classList.remove('hidden');
|
| 479 |
+
modal.classList.add('flex');
|
| 480 |
+
document.body.style.overflow = 'hidden';
|
| 481 |
+
} catch (err) {
|
| 482 |
+
console.error("加载详情失败:", err);
|
| 483 |
+
}
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
function closeModal() {
|
| 487 |
+
const modal = document.getElementById('detail-modal');
|
| 488 |
+
modal.classList.add('hidden');
|
| 489 |
+
modal.classList.remove('flex');
|
| 490 |
+
document.body.style.overflow = 'auto';
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
// 初始化
|
| 494 |
+
window.onload = async () => {
|
| 495 |
+
const res = await fetch('/api/stats');
|
| 496 |
+
const data = await res.json();
|
| 497 |
+
initChart(data);
|
| 498 |
+
|
| 499 |
+
// 初始动画效果
|
| 500 |
+
document.querySelectorAll('.glass').forEach((el, i) => {
|
| 501 |
+
el.style.opacity = '0';
|
| 502 |
+
el.style.transform = 'translateY(10px)';
|
| 503 |
+
setTimeout(() => {
|
| 504 |
+
el.style.transition = 'all 0.5s ease';
|
| 505 |
+
el.style.opacity = '1';
|
| 506 |
+
el.style.transform = 'translateY(0)';
|
| 507 |
+
}, i * 100);
|
| 508 |
+
});
|
| 509 |
+
};
|
| 510 |
+
</script>
|
| 511 |
+
</body>
|
| 512 |
+
</html>
|