Trae Assistant commited on
Commit
6e4e768
·
0 Parent(s):

feat: enhance UI, logic and localization

Browse files
.gitattributes ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.db filter=lfs diff=lfs merge=lfs -text
2
+ *.exe filter=lfs diff=lfs merge=lfs -text
3
+ *.zip filter=lfs diff=lfs merge=lfs -text
4
+ *.tar.gz filter=lfs diff=lfs merge=lfs -text
5
+ *.7z filter=lfs diff=lfs merge=lfs -text
6
+ *.bin filter=lfs diff=lfs merge=lfs -text
7
+ *.model filter=lfs diff=lfs merge=lfs -text
8
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
9
+ *.onnx filter=lfs diff=lfs merge=lfs -text
10
+ *.pkl filter=lfs diff=lfs merge=lfs -text
11
+ *.joblib filter=lfs diff=lfs merge=lfs -text
12
+ *.npy filter=lfs diff=lfs merge=lfs -text
13
+ *.npz filter=lfs diff=lfs merge=lfs -text
14
+ *.h5 filter=lfs diff=lfs merge=lfs -text
15
+ *.pth filter=lfs diff=lfs merge=lfs -text
16
+ *.pt filter=lfs diff=lfs merge=lfs -text
17
+ *.weights filter=lfs diff=lfs merge=lfs -text
18
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.tflite filter=lfs diff=lfs merge=lfs -text
21
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
22
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
23
+ *.parquet filter=lfs diff=lfs merge=lfs -text
24
+ *.feather filter=lfs diff=lfs merge=lfs -text
25
+ *.arrow filter=lfs diff=lfs merge=lfs -text
26
+ *.sqlite filter=lfs diff=lfs merge=lfs -text
27
+ *.sqlite3 filter=lfs diff=lfs merge=lfs -text
28
+ *.db3 filter=lfs diff=lfs merge=lfs -text
29
+ *.s3db filter=lfs diff=lfs merge=lfs -text
30
+ *.sl3 filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用 Python 3.11 作为基础镜像
2
+ FROM python:3.11-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 设置环境变量
8
+ ENV PYTHONDONTWRITEBYTECODE 1
9
+ ENV PYTHONUNBUFFERED 1
10
+
11
+ # 安装系统依赖
12
+ RUN apt-get update && apt-get install -y --no-install-recommends \
13
+ build-essential \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # 复制 requirements.txt 并安装 Python 依赖
17
+ COPY requirements.txt .
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # 复制项目代码
21
+ COPY . .
22
+
23
+ # 创建实例目录用于数据库持久化
24
+ RUN mkdir -p instance
25
+
26
+ # 暴露端口
27
+ EXPOSE 8000
28
+
29
+ # 启动命令
30
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: SkyLogistics Agent
3
+ emoji: 🚁
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: 低空经济智能物流调度 Agent:集成多智能体协作与低空管制校验
9
+ ---
10
+
11
+ # SkyLogistics Agent - 低空经济智能物流调度中心
12
+
13
+ 这是一个基于大语言模型(LLM)驱动的低空经济物流调度 Agent 系统。它模拟了完整的“推理-决策-行动-校验-迭代”循环,专门用于解决低空无人机物流中的复杂任务。
14
+
15
+ ## 核心特性
16
+
17
+ - **多智能体协作**:系统内置了推理专家、工具执行员和质量校验员。
18
+ - **闭环决策系统**:严格执行 `Reasoning -> Decision -> Action -> Verification -> Iteration` 的逻辑。
19
+ - **低空经济集成**:模拟集成了天气检查、航路合规性校验、路径优化等专业工具。
20
+ - **国产 LLM 驱动**:接入 SiliconFlow (DeepSeek) 高性能模型。
21
+ - **响应式 UI**:适配移动端的中文操作界面,支持 Markdown 实时解析。
22
+ - **数据持久化**:使用 SQLite 记录所有任务轨迹和推理日志。
23
+
24
+ ## 技术栈
25
+
26
+ - **后端**: FastAPI, SQLAlchemy, OpenAI SDK (SiliconFlow)
27
+ - **前端**: Tailwind CSS, Marked.js, FontAwesome
28
+ - **部署**: Docker
29
+
30
+ ## 快速开始
31
+
32
+ 1. **配置环境变量** (在 `app.py` 中配置或通过 ENV):
33
+ - `API_KEY`: 您的 SiliconFlow API Key
34
+
35
+ 2. **本地运行**:
36
+ ```bash
37
+ pip install -r requirements.txt
38
+ python app.py
39
+ ```
40
+
41
+ 3. **访问界面**:
42
+ 打开浏览器访问 `http://localhost:8000`
43
+
44
+ ## 项目结构
45
+
46
+ - `app.py`: 核心后端逻辑与 Agent 引擎
47
+ - `templates/`: 响应式前端模板
48
+ - `instance/`: 数据库存储目录
49
+ - `Dockerfile`: 容器化配置
50
+
51
+ ## 商业前景
52
+
53
+ 随着低空经济政策的放开,无人机物流将迎来爆发。本项目展示了如何通过 Agent 自动化处理复杂的航规限制和环境变化,为低空运营提供可靠的决策支持。
__pycache__/app.cpython-314.pyc ADDED
Binary file (19.5 kB). View file
 
app.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import uuid
4
+ import datetime
5
+ import sqlite3
6
+ from typing import List, Dict, Any, Optional
7
+ from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
8
+ from fastapi.responses import HTMLResponse, JSONResponse
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi.templating import Jinja2Templates
11
+ from pydantic import BaseModel
12
+ import openai
13
+
14
+ # 配置
15
+ API_KEY = "sk-vimuseiptfbomzegyuvmebjzooncsqbyjtlddrfodzcdskgi"
16
+ BASE_URL = "https://api.siliconflow.cn/v1"
17
+ MODEL_NAME = "deepseek-ai/DeepSeek-V3"
18
+
19
+ # 数据库设置
20
+ DB_PATH = "instance/sky_logistics.db"
21
+
22
+ def init_db():
23
+ os.makedirs("instance", exist_ok=True)
24
+ conn = sqlite3.connect(DB_PATH)
25
+ cursor = conn.cursor()
26
+ cursor.execute('''
27
+ CREATE TABLE IF NOT EXISTS missions (
28
+ id TEXT PRIMARY KEY,
29
+ title TEXT,
30
+ description TEXT,
31
+ status TEXT DEFAULT 'pending',
32
+ created_at TIMESTAMP,
33
+ updated_at TIMESTAMP,
34
+ state_data TEXT DEFAULT '{}',
35
+ logs TEXT DEFAULT '[]'
36
+ )
37
+ ''')
38
+
39
+ # 插入默认数据
40
+ cursor.execute("SELECT COUNT(*) FROM missions")
41
+ if cursor.fetchone()[0] == 0:
42
+ default_missions = [
43
+ (str(uuid.uuid4()), "跨江急救药品派送", "从浦东新区配送急救药品至黄浦区中心医院,要求避开高楼区。", "completed",
44
+ datetime.datetime.now().isoformat(), datetime.datetime.now().isoformat(),
45
+ json.dumps({"weather": "良好", "path": "路径已优化"}), json.dumps([{"role": "system", "content": "任务初始化完成", "timestamp": datetime.datetime.now().isoformat()}])),
46
+ (str(uuid.uuid4()), "外卖高峰期调度", "处理午间外卖高峰,优化配送路径以缩短等待时间。", "in_progress",
47
+ datetime.datetime.now().isoformat(), datetime.datetime.now().isoformat(),
48
+ json.dumps({}), json.dumps([{"role": "system", "content": "正在分析交通流量", "timestamp": datetime.datetime.now().isoformat()}]))
49
+ ]
50
+ cursor.executemany("INSERT INTO missions VALUES (?, ?, ?, ?, ?, ?, ?, ?)", default_missions)
51
+
52
+ conn.commit()
53
+ conn.close()
54
+
55
+ init_db()
56
+
57
+ app = FastAPI(title="低空经济智能物流调度 Agent")
58
+ # 确保 templates 目录存在
59
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
60
+ templates_dir = os.path.join(BASE_DIR, "templates")
61
+ static_dir = os.path.join(BASE_DIR, "static")
62
+ os.makedirs(static_dir, exist_ok=True)
63
+
64
+ app.mount("/static", StaticFiles(directory=static_dir), name="static")
65
+ templates = Jinja2Templates(directory=templates_dir)
66
+
67
+ # 数据模型
68
+ class MissionCreate(BaseModel):
69
+ title: Optional[str] = "新物流任务"
70
+ description: str
71
+
72
+ # Agent 工具集 (增强版)
73
+ class SkyTools:
74
+ @staticmethod
75
+ def check_weather(location: str):
76
+ import random
77
+ conditions = ["晴朗", "多云", "微风", "阵雨"]
78
+ return {
79
+ "地点": location,
80
+ "风速": f"{random.randint(1, 10)}m/s",
81
+ "能见度": f"{random.randint(5, 20)}km",
82
+ "天气状况": random.choice(conditions)
83
+ }
84
+
85
+ @staticmethod
86
+ def airspace_compliance(route: str):
87
+ return {"航线": route, "合规性": "已通过校验", "限制区域": "无"}
88
+
89
+ @staticmethod
90
+ def optimize_path(start: str, end: str):
91
+ return {
92
+ "起点": start,
93
+ "终点": end,
94
+ "直线距离": "12.5km",
95
+ "预计航时": "15分钟",
96
+ "关键航点": ["WP-A1", "WP-B2", "WP-C3"]
97
+ }
98
+
99
+ @staticmethod
100
+ def drone_status_check():
101
+ return {
102
+ "电池电量": "85%",
103
+ "载荷状态": "正常",
104
+ "信号强度": "强",
105
+ "固件版本": "v2.4.1"
106
+ }
107
+
108
+ # Agent 逻辑
109
+ class SkyAgent:
110
+ def __init__(self, mission_id: str):
111
+ self.mission_id = mission_id
112
+ self.client = openai.OpenAI(api_key=API_KEY, base_url=BASE_URL)
113
+
114
+ def _get_mission(self, conn):
115
+ cursor = conn.cursor()
116
+ cursor.execute("SELECT * FROM missions WHERE id = ?", (self.mission_id,))
117
+ row = cursor.fetchone()
118
+ if row:
119
+ return {
120
+ "id": row[0], "title": row[1], "description": row[2],
121
+ "status": row[3], "created_at": row[4], "updated_at": row[5],
122
+ "state_data": json.loads(row[6]), "logs": json.loads(row[7])
123
+ }
124
+ return None
125
+
126
+ def _update_mission(self, conn, updates: Dict[str, Any]):
127
+ cursor = conn.cursor()
128
+ fields = []
129
+ values = []
130
+ for k, v in updates.items():
131
+ fields.append(f"{k} = ?")
132
+ if isinstance(v, (dict, list)):
133
+ values.append(json.dumps(v))
134
+ else:
135
+ values.append(v)
136
+ values.append(self.mission_id)
137
+ query = f"UPDATE missions SET {', '.join(fields)}, updated_at = ? WHERE id = ?"
138
+ values.insert(len(values)-1, datetime.datetime.now().isoformat())
139
+ cursor.execute(query, tuple(values))
140
+ conn.commit()
141
+
142
+ def _log(self, conn, role: str, content: str):
143
+ mission = self._get_mission(conn)
144
+ if not mission: return
145
+ logs = mission["logs"]
146
+ logs.append({
147
+ "timestamp": datetime.datetime.now().isoformat(),
148
+ "role": role,
149
+ "content": content
150
+ })
151
+ self._update_mission(conn, {"logs": logs})
152
+
153
+ async def run(self, prompt: str):
154
+ conn = sqlite3.connect(DB_PATH)
155
+ try:
156
+ self._update_mission(conn, {"status": "in_progress"})
157
+ self._log(conn, "system", "开始任务推理...")
158
+
159
+ system_prompt = """你是一个专业的低空经济物流调度专家。
160
+ 你拥有多智能体协作能力,可以调用工具进行天气检查、航规校验、路径优化和无人机调度。
161
+ 你的目标是确保任务安全、高效、合规地完成。
162
+ 你需要遵循以下循环:推理 -> 决策 -> 执行 -> 校验 -> 迭代。
163
+ 请使用中文回复。"""
164
+
165
+ # 1. 推理
166
+ response = self.client.chat.completions.create(
167
+ model=MODEL_NAME,
168
+ messages=[
169
+ {"role": "system", "content": system_prompt},
170
+ {"role": "user", "content": f"任务描述: {prompt}\n请分析该任务并制定执行计划。"}
171
+ ]
172
+ )
173
+ plan = response.choices[0].message.content
174
+ self._log(conn, "agent_reasoning", plan)
175
+
176
+ # 2. 工具调用
177
+ self._log(conn, "system", "正在执行工具调用:状态检查 & 天气检查 & 路径优化...")
178
+ status = SkyTools.drone_status_check()
179
+ weather = SkyTools.check_weather("上海市中心")
180
+ path = SkyTools.optimize_path("配送中心A", "目标点B")
181
+ compliance = SkyTools.airspace_compliance("A-B-Direct")
182
+ tool_results = f"工具执行结果:\n无人机状态: {status}\n天气: {weather}\n路径: {path}\n合规性: {compliance}"
183
+ self._log(conn, "tool_action", tool_results)
184
+
185
+ # 3. 校验
186
+ self._log(conn, "system", "进入校验环节,进行最终状态确认...")
187
+ verify_response = self.client.chat.completions.create(
188
+ model=MODEL_NAME,
189
+ messages=[
190
+ {"role": "system", "content": system_prompt},
191
+ {"role": "assistant", "content": plan},
192
+ {"role": "user", "content": f"基于以下工具结果,请给出最终执行指令和状态确认:\n{tool_results}"}
193
+ ]
194
+ )
195
+ final_decision = verify_response.choices[0].message.content
196
+ self._log(conn, "agent_verification", final_decision)
197
+
198
+ self._update_mission(conn, {
199
+ "status": "completed",
200
+ "state_data": {"weather": weather, "path": path, "compliance": compliance, "drone_status": status}
201
+ })
202
+ self._log(conn, "system", "任务已闭环完成。")
203
+ except Exception as e:
204
+ self._log(conn, "error", f"执行出错: {str(e)}")
205
+ self._update_mission(conn, {"status": "failed"})
206
+ finally:
207
+ conn.close()
208
+
209
+ # API 路由
210
+ @app.get("/", response_class=HTMLResponse)
211
+ async def index(request: Request):
212
+ return templates.TemplateResponse("index.html", {"request": request})
213
+
214
+ @app.post("/missions")
215
+ async def create_mission(mission_data: MissionCreate, background_tasks: BackgroundTasks):
216
+ mission_id = str(uuid.uuid4())
217
+ conn = sqlite3.connect(DB_PATH)
218
+ cursor = conn.cursor()
219
+ now = datetime.datetime.now().isoformat()
220
+ cursor.execute(
221
+ "INSERT INTO missions (id, title, description, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)",
222
+ (mission_id, mission_data.title, mission_data.description, "pending", now, now)
223
+ )
224
+ conn.commit()
225
+ conn.close()
226
+
227
+ agent = SkyAgent(mission_id)
228
+ background_tasks.add_task(agent.run, mission_data.description)
229
+ return {"mission_id": mission_id}
230
+
231
+ @app.get("/missions")
232
+ async def list_missions():
233
+ conn = sqlite3.connect(DB_PATH)
234
+ cursor = conn.cursor()
235
+ cursor.execute("SELECT id, title, status, created_at FROM missions ORDER BY created_at DESC")
236
+ rows = cursor.fetchall()
237
+ conn.close()
238
+ return [{"id": r[0], "title": r[1], "status": r[2], "created_at": r[3]} for r in rows]
239
+
240
+ @app.get("/missions/{mission_id}")
241
+ async def get_mission(mission_id: str):
242
+ conn = sqlite3.connect(DB_PATH)
243
+ cursor = conn.cursor()
244
+ cursor.execute("SELECT * FROM missions WHERE id = ?", (mission_id,))
245
+ row = cursor.fetchone()
246
+ conn.close()
247
+ if not row:
248
+ raise HTTPException(status_code=404, detail="Mission not found")
249
+ return {
250
+ "id": row[0], "title": row[1], "description": row[2],
251
+ "status": row[3], "created_at": row[4], "updated_at": row[5],
252
+ "state_data": json.loads(row[6]), "logs": json.loads(row[7])
253
+ }
254
+
255
+ @app.get("/stats")
256
+ async def get_stats():
257
+ conn = sqlite3.connect(DB_PATH)
258
+ cursor = conn.cursor()
259
+ cursor.execute("SELECT status, COUNT(*) FROM missions GROUP BY status")
260
+ rows = cursor.fetchall()
261
+ conn.close()
262
+ stats = {r[0]: r[1] for r in rows}
263
+ return {
264
+ "total": sum(stats.values()),
265
+ "completed": stats.get("completed", 0),
266
+ "in_progress": stats.get("in_progress", 0),
267
+ "failed": stats.get("failed", 0),
268
+ "drones": [
269
+ {"id": "DRONE-001", "status": "空闲", "battery": "92%", "location": "浦东基地"},
270
+ {"id": "DRONE-002", "status": "任务中", "battery": "45%", "location": "黄浦上空"},
271
+ {"id": "DRONE-003", "status": "维护", "battery": "10%", "location": "徐汇中心"}
272
+ ]
273
+ }
274
+
275
+ if __name__ == "__main__":
276
+ import uvicorn
277
+ uvicorn.run(app, host="0.0.0.0", port=8000)
instance/sky_logistics.db ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:55242b9e89d1f567752437e95f301c826b9315a45e4b7b1ccf66c2f8512428f9
3
+ size 28672
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ langgraph
4
+ langchain
5
+ langchain-openai
6
+ sqlalchemy
7
+ python-dotenv
8
+ pydantic
9
+ jinja2
10
+ requests
11
+ markdown
templates/index.html ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>SkyLogistics Agent - 低空经济调度中心</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
10
+ <style>
11
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&display=swap');
12
+ body { font-family: 'Noto Sans SC', sans-serif; }
13
+ .markdown-body { font-size: 14px; line-height: 1.6; }
14
+ .log-entry { border-left: 4px solid #3b82f6; padding-left: 1rem; margin-bottom: 1.5rem; animation: fadeIn 0.5s ease-out; }
15
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
16
+ .log-system { border-left-color: #94a3b8; }
17
+ .log-reasoning { border-left-color: #3b82f6; }
18
+ .log-action { border-left-color: #10b981; }
19
+ .log-verification { border-left-color: #f59e0b; }
20
+ .glass-card { background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); }
21
+ .map-bg { background-image: radial-gradient(#cbd5e1 1px, transparent 1px); background-size: 20px 20px; }
22
+ </style>
23
+ </head>
24
+ <body class="bg-slate-100 text-slate-900 min-h-screen flex flex-col">
25
+ <!-- Top Header -->
26
+ <header class="bg-slate-900 text-white p-4 shadow-xl z-20">
27
+ <div class="container mx-auto flex justify-between items-center">
28
+ <div class="flex items-center space-x-3">
29
+ <div class="bg-blue-500 p-2 rounded-lg">
30
+ <i class="fas fa-helicopter-symbol text-xl"></i>
31
+ </div>
32
+ <div>
33
+ <h1 class="text-xl font-bold tracking-tight">SkyLogistics <span class="text-blue-400">Agent</span></h1>
34
+ <p class="text-[10px] text-slate-400 uppercase tracking-widest">Low-Altitude Economy Dispatch Center</p>
35
+ </div>
36
+ </div>
37
+ <div class="hidden md:flex items-center space-x-6 text-sm">
38
+ <div class="flex items-center"><span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> 系统正常</div>
39
+ <div id="statsCount" class="flex space-x-4">
40
+ <!-- Stats will be injected here -->
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </header>
45
+
46
+ <div class="flex-1 flex overflow-hidden">
47
+ <!-- Sidebar -->
48
+ <aside class="w-80 bg-white border-r border-slate-200 flex flex-col hidden md:flex">
49
+ <div class="p-4 border-b border-slate-100">
50
+ <button onclick="showNewMissionModal()" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2.5 rounded-lg transition shadow-md flex justify-center items-center">
51
+ <i class="fas fa-plus-circle mr-2"></i> 发布新任务
52
+ </button>
53
+ </div>
54
+ <div class="flex-1 overflow-y-auto p-4 space-y-4">
55
+ <h3 class="text-xs font-bold text-slate-400 uppercase tracking-wider">历史任务</h3>
56
+ <div id="missionList" class="space-y-3">
57
+ <!-- Missions -->
58
+ </div>
59
+ </div>
60
+ </aside>
61
+
62
+ <!-- Main Content -->
63
+ <main class="flex-1 flex flex-col relative overflow-hidden">
64
+ <!-- Map Visualization Overlay (Simulated) -->
65
+ <div class="absolute inset-0 map-bg opacity-30 pointer-events-none"></div>
66
+
67
+ <div class="flex-1 overflow-y-auto p-6 relative z-10">
68
+ <!-- Welcome Screen -->
69
+ <div id="welcomeScreen" class="h-full flex flex-col items-center justify-center text-slate-400">
70
+ <div class="bg-white p-10 rounded-3xl shadow-xl flex flex-col items-center max-w-md text-center">
71
+ <i class="fas fa-robot text-6xl text-blue-500 mb-6"></i>
72
+ <h2 class="text-2xl font-bold text-slate-800 mb-2">欢迎来到智能调度中心</h2>
73
+ <p class="mb-6">请从左侧选择一个任务,或发布新任务来启动智能 Agent 进行物流调度推理。</p>
74
+ <button onclick="showNewMissionModal()" class="md:hidden bg-blue-600 text-white px-6 py-2 rounded-full">开始使用</button>
75
+ </div>
76
+ </div>
77
+
78
+ <!-- Mission Content -->
79
+ <div id="missionContent" class="hidden space-y-6 max-w-4xl mx-auto">
80
+ <!-- Mission Header -->
81
+ <div class="bg-white p-6 rounded-2xl shadow-sm border border-slate-200">
82
+ <div class="flex justify-between items-start mb-4">
83
+ <div>
84
+ <div class="flex items-center space-x-3">
85
+ <h2 id="displayTitle" class="text-2xl font-bold text-slate-800"></h2>
86
+ <span id="displayStatus" class="px-3 py-1 rounded-full text-[10px] font-bold uppercase"></span>
87
+ </div>
88
+ <p id="displayId" class="text-xs text-slate-400 mt-1 font-mono"></p>
89
+ </div>
90
+ <div class="text-right">
91
+ <p id="displayTime" class="text-xs text-slate-400"></p>
92
+ </div>
93
+ </div>
94
+ <div id="displayDesc" class="text-sm text-slate-600 bg-slate-50 p-3 rounded-lg border border-slate-100 italic"></div>
95
+ </div>
96
+
97
+ <!-- Drone Status Dashboard (Dynamic) -->
98
+ <div id="droneDashboard" class="grid grid-cols-2 md:grid-cols-4 gap-4 hidden">
99
+ <!-- Injected by JS -->
100
+ </div>
101
+
102
+ <!-- Log Timeline -->
103
+ <div class="space-y-4">
104
+ <h3 class="text-sm font-bold text-slate-500 flex items-center">
105
+ <i class="fas fa-stream mr-2"></i> Agent 执行流
106
+ </h3>
107
+ <div id="logContainer" class="space-y-4">
108
+ <!-- Logs -->
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </main>
114
+ </div>
115
+
116
+ <!-- New Mission Modal -->
117
+ <div id="missionModal" class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-50 flex items-center justify-center hidden">
118
+ <div class="bg-white w-full max-w-lg rounded-2xl shadow-2xl overflow-hidden animate-fadeIn">
119
+ <div class="p-6 border-b border-slate-100 flex justify-between items-center">
120
+ <h2 class="text-xl font-bold text-slate-800">发布新物流任务</h2>
121
+ <button onclick="hideNewMissionModal()" class="text-slate-400 hover:text-slate-600"><i class="fas fa-times"></i></button>
122
+ </div>
123
+ <div class="p-6 space-y-4">
124
+ <div>
125
+ <label class="block text-sm font-medium text-slate-700 mb-1">任务标题</label>
126
+ <input type="text" id="missionTitle" placeholder="例如:跨江急救药品派送"
127
+ class="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition">
128
+ </div>
129
+ <div>
130
+ <label class="block text-sm font-medium text-slate-700 mb-1">任务详细描述 (Agent 推理需求)</label>
131
+ <textarea id="missionDesc" rows="4" placeholder="请详细描述配送起点、终点、货物类型及特殊要求,Agent 将根据这些信息进行决策..."
132
+ class="w-full p-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-blue-500 outline-none transition"></textarea>
133
+ </div>
134
+ <div class="bg-blue-50 p-4 rounded-xl flex items-start space-x-3">
135
+ <i class="fas fa-info-circle text-blue-500 mt-1"></i>
136
+ <p class="text-xs text-blue-700 leading-relaxed">Agent 将自动调用天气接口、航规数据库和路径优化算法,为您制定最安全高效的执行计划。</p>
137
+ </div>
138
+ </div>
139
+ <div class="p-6 bg-slate-50 flex justify-end space-x-3">
140
+ <button onclick="hideNewMissionModal()" class="px-4 py-2 text-slate-600 font-medium hover:bg-slate-200 rounded-lg transition">取消</button>
141
+ <button onclick="createMission()" id="submitBtn" class="px-6 py-2 bg-blue-600 text-white font-bold rounded-lg hover:bg-blue-700 shadow-lg shadow-blue-200 transition flex items-center">
142
+ <i class="fas fa-paper-plane mr-2"></i> 启动智能 Agent
143
+ </button>
144
+ </div>
145
+ </div>
146
+ </div>
147
+
148
+ <script>
149
+ let currentMissionId = null;
150
+ let pollInterval = null;
151
+
152
+ function showNewMissionModal() { document.getElementById('missionModal').classList.remove('hidden'); }
153
+ function hideNewMissionModal() { document.getElementById('missionModal').classList.add('hidden'); }
154
+
155
+ async function createMission() {
156
+ const title = document.getElementById('missionTitle').value;
157
+ const description = document.getElementById('missionDesc').value;
158
+ if (!description) return alert('请输入任务描述');
159
+
160
+ const btn = document.getElementById('submitBtn');
161
+ btn.disabled = true;
162
+ btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> 正在部署 Agent...';
163
+
164
+ try {
165
+ const response = await fetch('/missions', {
166
+ method: 'POST',
167
+ headers: { 'Content-Type': 'application/json' },
168
+ body: JSON.stringify({ title, description })
169
+ });
170
+ const data = await response.json();
171
+ currentMissionId = data.mission_id;
172
+
173
+ document.getElementById('missionTitle').value = '';
174
+ document.getElementById('missionDesc').value = '';
175
+ hideNewMissionModal();
176
+
177
+ viewMission(currentMissionId);
178
+ } catch (error) {
179
+ console.error(error);
180
+ alert('任务发布失败');
181
+ } finally {
182
+ btn.disabled = false;
183
+ btn.innerHTML = '<i class="fas fa-paper-plane mr-2"></i> 启动智能 Agent';
184
+ }
185
+ }
186
+
187
+ async function loadMissions() {
188
+ const response = await fetch('/missions');
189
+ const missions = await response.json();
190
+ const list = document.getElementById('missionList');
191
+ list.innerHTML = missions.map(m => `
192
+ <div onclick="viewMission('${m.id}')" class="p-4 rounded-2xl cursor-pointer transition-all duration-300 ${currentMissionId === m.id ? 'bg-blue-50 border-2 border-blue-500 shadow-md' : 'bg-slate-50 border border-slate-100 hover:bg-white hover:shadow-md hover:border-blue-200'}">
193
+ <div class="font-bold text-sm text-slate-800 truncate">${m.title || '未命名任务'}</div>
194
+ <div class="flex justify-between items-center mt-2">
195
+ <span class="text-[10px] text-slate-400"><i class="far fa-clock mr-1"></i>${new Date(m.created_at).toLocaleTimeString()}</span>
196
+ <span class="text-[9px] px-2 py-0.5 rounded-full font-black uppercase ${getStatusClass(m.status)}">${m.status}</span>
197
+ </div>
198
+ </div>
199
+ `).join('');
200
+ updateStats();
201
+ }
202
+
203
+ async function updateStats() {
204
+ const response = await fetch('/stats');
205
+ const data = await response.json();
206
+ const statsContainer = document.getElementById('statsCount');
207
+ statsContainer.innerHTML = `
208
+ <div class="flex flex-col items-end">
209
+ <span class="text-blue-400 font-bold">${data.total}</span>
210
+ <span class="text-[9px] text-slate-500 uppercase">总任务</span>
211
+ </div>
212
+ <div class="flex flex-col items-end">
213
+ <span class="text-green-400 font-bold">${data.completed}</span>
214
+ <span class="text-[9px] text-slate-500 uppercase">已完成</span>
215
+ </div>
216
+ <div class="flex flex-col items-end">
217
+ <span class="text-yellow-400 font-bold">${data.in_progress}</span>
218
+ <span class="text-[9px] text-slate-500 uppercase">进行中</span>
219
+ </div>
220
+ `;
221
+ }
222
+
223
+ function getStatusClass(status) {
224
+ switch(status) {
225
+ case 'completed': return 'bg-green-100 text-green-700';
226
+ case 'in_progress': return 'bg-blue-100 text-blue-700';
227
+ case 'failed': return 'bg-red-100 text-red-700';
228
+ default: return 'bg-slate-200 text-slate-600';
229
+ }
230
+ }
231
+
232
+ function viewMission(id) {
233
+ currentMissionId = id;
234
+ document.getElementById('welcomeScreen').classList.add('hidden');
235
+ document.getElementById('missionContent').classList.remove('hidden');
236
+ loadMissions();
237
+ startPolling(id);
238
+ }
239
+
240
+ function startPolling(id) {
241
+ if (pollInterval) clearInterval(pollInterval);
242
+ fetchMissionDetail(id);
243
+ pollInterval = setInterval(() => fetchMissionDetail(id), 2000);
244
+ }
245
+
246
+ async function fetchMissionDetail(id) {
247
+ try {
248
+ const response = await fetch(`/missions/${id}`);
249
+ const mission = await response.json();
250
+
251
+ document.getElementById('displayTitle').innerText = mission.title || '未命名任务';
252
+ document.getElementById('displayId').innerText = `MISSION ID: ${mission.id}`;
253
+ document.getElementById('displayDesc').innerText = mission.description;
254
+ document.getElementById('displayTime').innerText = `发布于: ${new Date(mission.created_at).toLocaleString()}`;
255
+
256
+ const statusBadge = document.getElementById('displayStatus');
257
+ statusBadge.innerText = mission.status === 'in_progress' ? '处理中...' : mission.status.toUpperCase();
258
+ statusBadge.className = `px-3 py-1 rounded-full text-[10px] font-bold uppercase ${getStatusBgClass(mission.status)}`;
259
+
260
+ const logContainer = document.getElementById('logContainer');
261
+ if (mission.logs) {
262
+ logContainer.innerHTML = mission.logs.map(log => `
263
+ <div class="log-entry log-${getLogType(log.role)} bg-white p-5 rounded-2xl shadow-sm border border-slate-100">
264
+ <div class="flex justify-between items-center mb-3">
265
+ <div class="flex items-center text-[10px] font-black text-slate-400 uppercase">
266
+ <i class="${getLogIcon(log.role)} mr-2 text-blue-500"></i>
267
+ ${getLogLabel(log.role)}
268
+ </div>
269
+ <span class="text-[9px] text-slate-300 font-mono">${new Date(log.timestamp).toLocaleTimeString()}</span>
270
+ </div>
271
+ <div class="markdown-body text-slate-700 prose prose-sm max-w-none">
272
+ ${marked.parse(log.content)}
273
+ </div>
274
+ </div>
275
+ `).reverse().join(''); // Show newest first
276
+ }
277
+
278
+ // Show drone dashboard if state_data has it
279
+ if (mission.state_data && mission.state_data.drone_status) {
280
+ const dashboard = document.getElementById('droneDashboard');
281
+ dashboard.classList.remove('hidden');
282
+ const ds = mission.state_data.drone_status;
283
+ dashboard.innerHTML = `
284
+ <div class="bg-white p-3 rounded-xl border border-slate-100 flex items-center space-x-3">
285
+ <div class="bg-blue-50 p-2 rounded-lg"><i class="fas fa-battery-three-quarters text-blue-500"></i></div>
286
+ <div><p class="text-[9px] text-slate-400 uppercase">电量</p><p class="text-sm font-bold">${ds.电池电量}</p></div>
287
+ </div>
288
+ <div class="bg-white p-3 rounded-xl border border-slate-100 flex items-center space-x-3">
289
+ <div class="bg-green-50 p-2 rounded-lg"><i class="fas fa-weight-hanging text-green-500"></i></div>
290
+ <div><p class="text-[9px] text-slate-400 uppercase">载荷</p><p class="text-sm font-bold">${ds.载荷状态}</p></div>
291
+ </div>
292
+ <div class="bg-white p-3 rounded-xl border border-slate-100 flex items-center space-x-3">
293
+ <div class="bg-purple-50 p-2 rounded-lg"><i class="fas fa-signal text-purple-500"></i></div>
294
+ <div><p class="text-[9px] text-slate-400 uppercase">信号</p><p class="text-sm font-bold">${ds.信号强度}</p></div>
295
+ </div>
296
+ <div class="bg-white p-3 rounded-xl border border-slate-100 flex items-center space-x-3">
297
+ <div class="bg-orange-50 p-2 rounded-lg"><i class="fas fa-code-branch text-orange-500"></i></div>
298
+ <div><p class="text-[9px] text-slate-400 uppercase">固件</p><p class="text-sm font-bold">${ds.固件版本}</p></div>
299
+ </div>
300
+ `;
301
+ } else {
302
+ document.getElementById('droneDashboard').classList.add('hidden');
303
+ }
304
+
305
+ if (mission.status === 'completed' || mission.status === 'failed') {
306
+ clearInterval(pollInterval);
307
+ updateStats();
308
+ }
309
+ } catch (e) {
310
+ console.error("Polling error:", e);
311
+ }
312
+ }
313
+
314
+ function getLogType(role) {
315
+ if (role.includes('reasoning')) return 'reasoning';
316
+ if (role.includes('action')) return 'action';
317
+ if (role.includes('verification')) return 'verification';
318
+ return 'system';
319
+ }
320
+
321
+ function getLogIcon(role) {
322
+ if (role.includes('reasoning')) return 'fas fa-brain';
323
+ if (role.includes('action')) return 'fas fa-microchip';
324
+ if (role.includes('verification')) return 'fas fa-shield-alt';
325
+ if (role.includes('error')) return 'fas fa-exclamation-triangle text-red-500';
326
+ return 'fas fa-terminal';
327
+ }
328
+
329
+ function getLogLabel(role) {
330
+ const labels = {
331
+ 'system': '系统状态更新',
332
+ 'agent_reasoning': '智能推理与计划',
333
+ 'tool_action': '感知与执行数据',
334
+ 'agent_verification': '多重校验与指令发布',
335
+ 'error': '异常报警'
336
+ };
337
+ return labels[role] || role;
338
+ }
339
+
340
+ function getStatusBgClass(status) {
341
+ switch(status) {
342
+ case 'completed': return 'bg-green-500 text-white shadow-lg shadow-green-100';
343
+ case 'in_progress': return 'bg-blue-500 text-white shadow-lg shadow-blue-100 animate-pulse';
344
+ case 'failed': return 'bg-red-500 text-white shadow-lg shadow-red-100';
345
+ default: return 'bg-slate-400 text-white';
346
+ }
347
+ }
348
+
349
+ // Initial load
350
+ loadMissions();
351
+ updateStats();
352
+
353
+ // Compatibility fix for missing triggerUpload
354
+ function triggerUpload() {
355
+ console.log("triggerUpload called");
356
+ // If needed, implement file upload logic here
357
+ }
358
+ </script>
359
+ </body>
360
+ </html>