Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| beb4fb6a76 | |||
| a371eada4a |
@@ -64,13 +64,36 @@ class OpenClawClient:
|
|||||||
raise Exception(f"CLI error: {result.stderr}")
|
raise Exception(f"CLI error: {result.stderr}")
|
||||||
|
|
||||||
# 解析 JSON 输出(跳过警告)
|
# 解析 JSON 输出(跳过警告)
|
||||||
lines = result.stdout.strip().split("\n")
|
# OpenClaw 输出格式化的多行 JSON,需要提取完整的 JSON 对象
|
||||||
json_lines = [l for l in lines if l.startswith("{") or l.startswith("[")]
|
output = result.stdout.strip()
|
||||||
|
|
||||||
if json_lines:
|
# 找到 JSON 开始位置
|
||||||
return json.loads(json_lines[-1])
|
json_start = -1
|
||||||
|
for i, char in enumerate(output):
|
||||||
|
if char == '{' or char == '[':
|
||||||
|
json_start = i
|
||||||
|
break
|
||||||
|
|
||||||
return {}
|
if json_start == -1:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# 提取 JSON 部分
|
||||||
|
json_str = output[json_start:]
|
||||||
|
|
||||||
|
try:
|
||||||
|
return json.loads(json_str)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# 如果失败,尝试其他方法
|
||||||
|
lines = output.split("\n")
|
||||||
|
json_lines = [l for l in lines if l.strip().startswith("{") or l.strip().startswith("[")]
|
||||||
|
if json_lines:
|
||||||
|
# 尝试解析每一行
|
||||||
|
for line in json_lines:
|
||||||
|
try:
|
||||||
|
return json.loads(line)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
return {}
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
raise Exception(f"Timeout running: {cmd}")
|
raise Exception(f"Timeout running: {cmd}")
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
|
|||||||
@@ -46,6 +46,74 @@ def get_client() -> OpenClawClient:
|
|||||||
|
|
||||||
# ============== API 路由 ==============
|
# ============== API 路由 ==============
|
||||||
|
|
||||||
|
@app.get("/api/config")
|
||||||
|
async def api_config():
|
||||||
|
"""获取 OpenClaw 配置信息"""
|
||||||
|
try:
|
||||||
|
client = get_client()
|
||||||
|
config = client.get_config()
|
||||||
|
|
||||||
|
# 提取关键信息
|
||||||
|
agents_config = config.get("agents", {})
|
||||||
|
defaults = agents_config.get("defaults", {})
|
||||||
|
|
||||||
|
# 查找所有智能体目录
|
||||||
|
agents_list = agents_config.get("list", [])
|
||||||
|
agent_dirs = []
|
||||||
|
for agent in agents_list:
|
||||||
|
if agent.get("agentDir"):
|
||||||
|
agent_dirs.append(agent.get("agentDir"))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"config_path": config.get("_path", "/home/openclaw/.openclaw/openclaw.json"),
|
||||||
|
"workspace_root": defaults.get("workspace", "/home/openclaw/.openclaw/workspace"),
|
||||||
|
"agents_dir": "/home/openclaw/.openclaw", # OpenClaw 根目录
|
||||||
|
"agents_count": len(agents_list),
|
||||||
|
"agent_dirs": agent_dirs,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/rescan")
|
||||||
|
async def api_rescan():
|
||||||
|
"""重新扫描智能体目录"""
|
||||||
|
try:
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 调用 openclaw gateway 命令重新加载配置
|
||||||
|
result = subprocess.run(
|
||||||
|
["openclaw", "gateway", "call", "agents.list", "--json"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise Exception(f"扫描失败: {result.stderr}")
|
||||||
|
|
||||||
|
# 解析结果
|
||||||
|
import json
|
||||||
|
output_lines = result.stdout.strip().split("\n")
|
||||||
|
json_line = [l for l in output_lines if l.startswith("{") or l.startswith("[")]
|
||||||
|
|
||||||
|
if json_line:
|
||||||
|
data = json.loads(json_line[-1])
|
||||||
|
agents = data.get("agents", [])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"agents_count": len(agents),
|
||||||
|
"message": f"扫描完成,找到 {len(agents)} 个智能体",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {"success": True, "agents_count": 0, "message": "扫描完成"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/health")
|
@app.get("/api/health")
|
||||||
async def api_health():
|
async def api_health():
|
||||||
"""健康检查"""
|
"""健康检查"""
|
||||||
|
|||||||
@@ -18,8 +18,15 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-bold text-white">🤖 OpenClaw Extra</h1>
|
<h1 class="text-3xl font-bold text-white">🤖 OpenClaw Extra</h1>
|
||||||
<p class="text-gray-400 mt-1">智能体管理面板</p>
|
<p class="text-gray-400 mt-1">智能体管理面板</p>
|
||||||
|
<div x-show="config" class="mt-2 text-sm">
|
||||||
|
<span class="text-gray-500">根目录:</span>
|
||||||
|
<span class="text-blue-400 font-mono" x-text="config?.agents_dir || '-'"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<nav class="flex gap-4">
|
<nav class="flex gap-4">
|
||||||
|
<button @click="rescan()" class="px-4 py-2 bg-green-600 hover:bg-green-700 rounded-lg transition">
|
||||||
|
🔄 重新扫描
|
||||||
|
</button>
|
||||||
<a href="/models" class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg transition">📊 模型列表</a>
|
<a href="/models" class="px-4 py-2 bg-gray-800 hover:bg-gray-700 rounded-lg transition">📊 模型列表</a>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,6 +38,13 @@
|
|||||||
<span x-text="error"></span>
|
<span x-text="error"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Rescan Message -->
|
||||||
|
<div x-show="rescanMessage" class="mb-4 p-4 bg-green-900/50 border border-green-700 rounded-lg">
|
||||||
|
<span class="text-green-300">✓ </span>
|
||||||
|
<span x-text="rescanMessage"></span>
|
||||||
|
<button @click="rescanMessage=''" class="ml-2 text-gray-400 hover:text-white">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
|
||||||
<div class="bg-gray-800 rounded-xl p-6">
|
<div class="bg-gray-800 rounded-xl p-6">
|
||||||
@@ -104,14 +118,19 @@
|
|||||||
loading: true,
|
loading: true,
|
||||||
error: null,
|
error: null,
|
||||||
gatewayOk: false,
|
gatewayOk: false,
|
||||||
|
config: null,
|
||||||
|
rescanMessage: '',
|
||||||
|
|
||||||
get totalSessions() {
|
get totalSessions() {
|
||||||
return this.agents.reduce((sum, a) => sum + a.sessions_count, 0)
|
return this.agents.reduce((sum, a) => sum + a.sessions_count, 0)
|
||||||
},
|
},
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.loadAgents()
|
await Promise.all([
|
||||||
await this.checkHealth()
|
this.loadAgents(),
|
||||||
|
this.loadConfig(),
|
||||||
|
this.checkHealth()
|
||||||
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadAgents() {
|
async loadAgents() {
|
||||||
@@ -126,6 +145,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async loadConfig() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/config')
|
||||||
|
this.config = await res.json()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Load config error:', e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async rescan() {
|
||||||
|
try {
|
||||||
|
this.rescanMessage = '正在扫描...'
|
||||||
|
const res = await fetch('/api/rescan', { method: 'POST' })
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.rescanMessage = data.message
|
||||||
|
// 重新加载智能体列表
|
||||||
|
await this.loadAgents()
|
||||||
|
} else {
|
||||||
|
this.error = data.message || '扫描失败'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.error = '扫描失败: ' + e.message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async checkHealth() {
|
async checkHealth() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/health')
|
const res = await fetch('/api/health')
|
||||||
|
|||||||
Reference in New Issue
Block a user