myagent-ai 1.15.20 → 1.15.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/deps_checker.py +14 -5
- package/package.json +1 -1
- package/skills/chromedev_mcp.py +3 -1
- package/skills/search_skill.py +38 -7
- package/web/ui/index.html +59 -34
package/core/deps_checker.py
CHANGED
|
@@ -23,6 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import importlib.util
|
|
25
25
|
import os
|
|
26
|
+
import re
|
|
26
27
|
import subprocess
|
|
27
28
|
import sys
|
|
28
29
|
import threading
|
|
@@ -213,7 +214,7 @@ def _pip_install(pip_names: List[str], category: str = "") -> Tuple[bool, str]:
|
|
|
213
214
|
|
|
214
215
|
|
|
215
216
|
def _check_version(import_name: str, min_version: str) -> bool:
|
|
216
|
-
"""
|
|
217
|
+
"""检查模块版本是否满足最低要求(使用简单的 tuple 比较,无需额外依赖)"""
|
|
217
218
|
if not min_version:
|
|
218
219
|
return True
|
|
219
220
|
try:
|
|
@@ -221,10 +222,18 @@ def _check_version(import_name: str, min_version: str) -> bool:
|
|
|
221
222
|
if spec is None:
|
|
222
223
|
return False
|
|
223
224
|
mod = importlib.import_module(import_name)
|
|
224
|
-
|
|
225
|
-
#
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
ver_str = getattr(mod, "__version__", "0.0.0")
|
|
226
|
+
# 简单版本比较:将版本字符串转为 tuple 进行比较
|
|
227
|
+
def _ver_tuple(v: str):
|
|
228
|
+
parts = []
|
|
229
|
+
for p in re.split(r'[.\-]', v)[:3]:
|
|
230
|
+
m = re.match(r'(\d+)', str(p))
|
|
231
|
+
parts.append(int(m.group(1)) if m else 0)
|
|
232
|
+
return tuple(parts)
|
|
233
|
+
try:
|
|
234
|
+
return _ver_tuple(ver_str) >= _ver_tuple(min_version)
|
|
235
|
+
except (ValueError, TypeError):
|
|
236
|
+
return True
|
|
228
237
|
except Exception:
|
|
229
238
|
return True # 无法获取版本时保守通过
|
|
230
239
|
|
package/package.json
CHANGED
package/skills/chromedev_mcp.py
CHANGED
|
@@ -128,11 +128,13 @@ class MCPClient:
|
|
|
128
128
|
|
|
129
129
|
try:
|
|
130
130
|
# 启动 MCP Server 子进程
|
|
131
|
+
# 注意: stderr 必须用 DEVNULL 或在后台线程中持续读取
|
|
132
|
+
# 否则 stderr 管道缓冲区满后子进程会阻塞,导致 JSON-RPC 超时
|
|
131
133
|
self._process = subprocess.Popen(
|
|
132
134
|
args,
|
|
133
135
|
stdin=subprocess.PIPE,
|
|
134
136
|
stdout=subprocess.PIPE,
|
|
135
|
-
stderr=subprocess.
|
|
137
|
+
stderr=subprocess.DEVNULL,
|
|
136
138
|
env={**os.environ, "NO_COLOR": "1"},
|
|
137
139
|
)
|
|
138
140
|
|
package/skills/search_skill.py
CHANGED
|
@@ -62,6 +62,27 @@ class WebSearchSkill(Skill):
|
|
|
62
62
|
"""
|
|
63
63
|
num = min(max(num, 1), 20)
|
|
64
64
|
|
|
65
|
+
# 确保搜索依赖已安装(requests, beautifulsoup4, lxml)
|
|
66
|
+
try:
|
|
67
|
+
import requests
|
|
68
|
+
from bs4 import BeautifulSoup
|
|
69
|
+
except ImportError as imp_err:
|
|
70
|
+
logger.warning(f"搜索依赖缺失: {imp_err},尝试自动安装...")
|
|
71
|
+
from core.deps_checker import ensure_skill_deps
|
|
72
|
+
if not ensure_skill_deps("search"):
|
|
73
|
+
return SkillResult(
|
|
74
|
+
success=False,
|
|
75
|
+
error=f"搜索依赖安装失败,请手动运行: pip install requests beautifulsoup4 lxml ({imp_err})",
|
|
76
|
+
)
|
|
77
|
+
try:
|
|
78
|
+
import requests # noqa: F811
|
|
79
|
+
from bs4 import BeautifulSoup # noqa: F811
|
|
80
|
+
except ImportError as imp_err:
|
|
81
|
+
return SkillResult(
|
|
82
|
+
success=False,
|
|
83
|
+
error=f"搜索依赖不可用,请手动运行: pip install requests beautifulsoup4 lxml ({imp_err})",
|
|
84
|
+
)
|
|
85
|
+
|
|
65
86
|
try:
|
|
66
87
|
# 尝试 DuckDuckGo HTML
|
|
67
88
|
results = await self._duckduckgo_html_search(query, num)
|
|
@@ -139,9 +160,6 @@ class WebSearchSkill(Skill):
|
|
|
139
160
|
break
|
|
140
161
|
|
|
141
162
|
return results
|
|
142
|
-
except ImportError:
|
|
143
|
-
logger.debug("beautifulsoup4 未安装")
|
|
144
|
-
return []
|
|
145
163
|
except Exception as e:
|
|
146
164
|
logger.warning(f"DuckDuckGo HTML 搜索失败: {e}")
|
|
147
165
|
return []
|
|
@@ -216,8 +234,6 @@ class WebSearchSkill(Skill):
|
|
|
216
234
|
break
|
|
217
235
|
|
|
218
236
|
return results
|
|
219
|
-
except ImportError:
|
|
220
|
-
return []
|
|
221
237
|
except Exception as e:
|
|
222
238
|
logger.warning(f"Bing 搜索失败: {e}")
|
|
223
239
|
return []
|
|
@@ -234,9 +250,18 @@ class WebReadSkill(Skill):
|
|
|
234
250
|
]
|
|
235
251
|
|
|
236
252
|
async def execute(self, url: str = "", extract_text: bool = True, **kwargs) -> SkillResult:
|
|
253
|
+
# 确保搜索依赖已安装
|
|
237
254
|
try:
|
|
238
255
|
import requests
|
|
239
256
|
from bs4 import BeautifulSoup
|
|
257
|
+
except ImportError as imp_err:
|
|
258
|
+
logger.warning(f"网页读取依赖缺失: {imp_err},尝试自动安装...")
|
|
259
|
+
from core.deps_checker import ensure_skill_deps
|
|
260
|
+
if not ensure_skill_deps("search"):
|
|
261
|
+
return SkillResult(success=False, error=f"依赖安装失败,请手动运行: pip install requests beautifulsoup4 lxml ({imp_err})")
|
|
262
|
+
try:
|
|
263
|
+
import requests # noqa: F811
|
|
264
|
+
from bs4 import BeautifulSoup # noqa: F811
|
|
240
265
|
|
|
241
266
|
loop = asyncio.get_event_loop()
|
|
242
267
|
|
|
@@ -293,8 +318,6 @@ class WebReadSkill(Skill):
|
|
|
293
318
|
},
|
|
294
319
|
message=f"已读取: {title} ({len(content)} 字符)",
|
|
295
320
|
)
|
|
296
|
-
except ImportError:
|
|
297
|
-
return SkillResult(success=False, error="请安装依赖: pip install requests beautifulsoup4")
|
|
298
321
|
except Exception as e:
|
|
299
322
|
return SkillResult(success=False, error=f"网页读取失败: {e}")
|
|
300
323
|
|
|
@@ -314,8 +337,16 @@ class URLReadSkill(Skill):
|
|
|
314
337
|
|
|
315
338
|
async def execute(self, url: str = "", method: str = "GET",
|
|
316
339
|
headers: dict = None, body: str = "", **kwargs) -> SkillResult:
|
|
340
|
+
# 确保依赖已安装
|
|
317
341
|
try:
|
|
318
342
|
import requests
|
|
343
|
+
except ImportError as imp_err:
|
|
344
|
+
logger.warning(f"URL读取依赖缺失: {imp_err},尝试自动安装...")
|
|
345
|
+
from core.deps_checker import ensure_skill_deps
|
|
346
|
+
if not ensure_skill_deps("search"):
|
|
347
|
+
return SkillResult(success=False, error=f"依赖安装失败,请手动运行: pip install requests ({imp_err})")
|
|
348
|
+
try:
|
|
349
|
+
import requests # noqa: F811
|
|
319
350
|
|
|
320
351
|
headers = headers or {}
|
|
321
352
|
loop = asyncio.get_event_loop()
|
package/web/ui/index.html
CHANGED
|
@@ -52,6 +52,9 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
|
|
|
52
52
|
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;margin-bottom:16px}
|
|
53
53
|
.card h3{font-size:15px;margin-bottom:12px;color:var(--text2);text-transform:uppercase;letter-spacing:.5px}
|
|
54
54
|
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
|
55
|
+
.grid-2{grid-template-columns:repeat(2,1fr)}
|
|
56
|
+
.grid-3{grid-template-columns:repeat(3,1fr)}
|
|
57
|
+
.grid-4{grid-template-columns:repeat(4,1fr)}
|
|
55
58
|
.stat{background:var(--surface2);padding:16px;border-radius:var(--radius)}
|
|
56
59
|
.stat .label{font-size:12px;color:var(--text2);margin-bottom:4px}
|
|
57
60
|
.stat .value{font-size:28px;font-weight:700}
|
|
@@ -184,7 +187,17 @@ tr:hover{background:var(--surface2)}
|
|
|
184
187
|
.content{padding:16px}
|
|
185
188
|
.grid{grid-template-columns:1fr}
|
|
186
189
|
.form-row{grid-template-columns:1fr}
|
|
187
|
-
.table-wrap{overflow-x:auto}
|
|
190
|
+
.table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
|
|
191
|
+
.card{padding:12px;margin-bottom:12px}
|
|
192
|
+
.card h3{font-size:14px;margin-bottom:8px}
|
|
193
|
+
.stat{padding:12px}
|
|
194
|
+
.stat .label{font-size:11px}
|
|
195
|
+
.stat .value{font-size:20px}
|
|
196
|
+
.grid,.grid-2,.grid-3,.grid-4{grid-template-columns:repeat(2,1fr)!important}
|
|
197
|
+
.btn-sm{padding:6px 12px;font-size:12px;min-height:32px}
|
|
198
|
+
th,td{padding:6px 8px;font-size:12px;white-space:nowrap}
|
|
199
|
+
th{font-size:11px}
|
|
200
|
+
.badge{padding:2px 6px;font-size:10px}
|
|
188
201
|
.modal{width:95%;max-height:90vh;padding:16px}
|
|
189
202
|
.modal-wide{max-width:95%}
|
|
190
203
|
.tabs{gap:0;overflow-x:auto}
|
|
@@ -192,6 +205,18 @@ tr:hover{background:var(--surface2)}
|
|
|
192
205
|
.agent-card{flex-direction:column;align-items:flex-start}
|
|
193
206
|
.agent-card .flex.flex-col{flex-direction:row;gap:4px}
|
|
194
207
|
}
|
|
208
|
+
@media(max-width:480px){
|
|
209
|
+
.content{padding:12px}
|
|
210
|
+
.card{padding:10px}
|
|
211
|
+
.grid,.grid-2,.grid-3,.grid-4{grid-template-columns:1fr!important}
|
|
212
|
+
.header h2{font-size:15px}
|
|
213
|
+
.btn-sm{padding:8px 12px;font-size:12px;min-height:36px}
|
|
214
|
+
.stat .value{font-size:18px}
|
|
215
|
+
.stat .label{font-size:10px}
|
|
216
|
+
.modal{width:98%;padding:12px}
|
|
217
|
+
.form-group{margin-bottom:10px}
|
|
218
|
+
.form-group label{font-size:12px}
|
|
219
|
+
}
|
|
195
220
|
</style>
|
|
196
221
|
</head>
|
|
197
222
|
<body>
|
|
@@ -635,9 +660,9 @@ async function loadAgentKB(){
|
|
|
635
660
|
<h4 style="font-size:14px;color:var(--text2)">知识库文件 (${files.length})</h4>
|
|
636
661
|
<button class="btn btn-sm btn-primary" onclick="uploadAgentKB('${escHtml(path)}',false)">上传文件</button> <button class="btn btn-sm btn-secondary" onclick="uploadAgentKB('${escHtml(path)}',true)">📁 上传文件夹</button></div>`;
|
|
637
662
|
if(files.length===0){html+='<div class="empty">暂无知识库文件</div>';}
|
|
638
|
-
else{html+='<table><tr><th>文件名</th><th>大小</th><th></th></tr>';
|
|
663
|
+
else{html+='<div class="table-wrap"><table><tr><th>文件名</th><th>大小</th><th></th></tr>';
|
|
639
664
|
for(const f of files){html+=`<tr><td>${escHtml(f.name||f.filename||'')}</td><td>${f.size||'-'}</td><td><button class="btn btn-sm btn-danger" onclick="deleteAgentKB('${escHtml(path)}','${escHtml(f.name||f.filename||'')}')">删除</button></td></tr>`}
|
|
640
|
-
html+='</table>';}
|
|
665
|
+
html+='</table></div>';}
|
|
641
666
|
$('kbContent').innerHTML=html;
|
|
642
667
|
}
|
|
643
668
|
|
|
@@ -672,9 +697,9 @@ async function loadAgentSessions(){
|
|
|
672
697
|
const sessions=Array.isArray(data)?data:(data?.sessions||[]);
|
|
673
698
|
let html=`<div class="flex justify-between items-center mb-16"><h4 style="font-size:14px;color:var(--text2)">会话 (${sessions.length})</h4></div>`;
|
|
674
699
|
if(sessions.length===0){html+='<div class="empty">暂无会话</div>';}
|
|
675
|
-
else{html+='<table><tr><th>会话</th><th>消息数</th><th>最后活动</th><th></th></tr>';
|
|
700
|
+
else{html+='<div class="table-wrap"><table><tr><th>会话</th><th>消息数</th><th>最后活动</th><th></th></tr>';
|
|
676
701
|
for(const s of sessions){const dn=s.display_name||s.id;html+=`<tr><td style="max-width:200px;overflow:hidden;text-overflow:ellipsis" title="${escHtml(s.id)}">${escHtml(dn.length>25?dn.slice(0,25)+'...':dn)}</td><td>${s.messages||0}</td><td>${fmtTimeAgo(s.last)}</td><td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}','${escHtml(path)}')">切入</button> <button class="btn btn-sm btn-ghost" onclick="viewSessionMsgs('${escHtml(s.id)}')">查看</button></td></tr>`}
|
|
677
|
-
html+='</table>';}
|
|
702
|
+
html+='</table></div>';}
|
|
678
703
|
$('sessionsContent').innerHTML=html;
|
|
679
704
|
}
|
|
680
705
|
|
|
@@ -745,7 +770,7 @@ async function loadAgentPerms(){
|
|
|
745
770
|
html+='<div class="card" style="margin-bottom:16px">';
|
|
746
771
|
html+='<h3 style="font-size:14px;color:var(--text2);margin-bottom:8px">🔑 功能权限</h3>';
|
|
747
772
|
html+='<p style="color:var(--text2);font-size:12px;margin-bottom:12px">精细控制 Agent 的各项能力。未设置的项目将使用全局默认值。</p>';
|
|
748
|
-
html+='<div class="grid" style="
|
|
773
|
+
html+='<div class="grid grid-3" style="gap:12px">';
|
|
749
774
|
for(const p of perms){
|
|
750
775
|
const label=labels[p]||p;
|
|
751
776
|
const defVal=defaults[p]!==false;
|
|
@@ -891,7 +916,7 @@ async function doDeletePlatform(name){
|
|
|
891
916
|
// ========== Sessions ==========
|
|
892
917
|
async function renderSessions(){
|
|
893
918
|
const ss=await api('/api/sessions');
|
|
894
|
-
let html='<table><tr><th>会话</th><th>Agent</th><th>消息数</th><th>最后活动</th><th>操作</th></tr>';
|
|
919
|
+
let html='<div class="table-wrap"><table><tr><th>会话</th><th>Agent</th><th>消息数</th><th>最后活动</th><th>操作</th></tr>';
|
|
895
920
|
for(const s of (ss||[])){
|
|
896
921
|
// 从 session_id 提取 agent 名 (格式: agent_web_timestamp)
|
|
897
922
|
const parts=(s.id||'').split('_web_');
|
|
@@ -901,7 +926,7 @@ async function renderSessions(){
|
|
|
901
926
|
<td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}','${escHtml(agentName)}')">切入</button>
|
|
902
927
|
<button class="btn btn-sm btn-ghost" onclick="viewSession('${s.id}')">查看</button>
|
|
903
928
|
<button class="btn btn-sm btn-danger" onclick="clearSession('${s.id}')">清除</button></td></tr>`;}
|
|
904
|
-
html+='</table>';if(!ss||!ss.length)html='<div class="empty">暂无会话</div>';
|
|
929
|
+
html+='</table></div>';if(!ss||!ss.length)html='<div class="empty">暂无会话</div>';
|
|
905
930
|
$('content').innerHTML=html;
|
|
906
931
|
}
|
|
907
932
|
async function viewSession(sid){
|
|
@@ -975,7 +1000,7 @@ async function renderMemory(){
|
|
|
975
1000
|
tabHtml+=`<button class="btn ${active}" onclick="_memCategory='${c.k}';renderMemory()">${c.l} (${count})</button>`;
|
|
976
1001
|
}
|
|
977
1002
|
tabHtml+='</div>';
|
|
978
|
-
let html=`<div class="grid" style="
|
|
1003
|
+
let html=`<div class="grid grid-3" style="margin-bottom:16px">
|
|
979
1004
|
<div class="stat"><div class="label">总计</div><div class="value">${stats.total_count||0}</div></div>
|
|
980
1005
|
<div class="stat"><div class="label">全局记忆</div><div class="value">${stats.global_count||0}</div></div>
|
|
981
1006
|
<div class="stat"><div class="label">会话记忆</div><div class="value">${stats.session_count||0}</div></div></div>`;
|
|
@@ -989,7 +1014,7 @@ async function renderMemory(){
|
|
|
989
1014
|
thHtml+='<th>会话</th>';
|
|
990
1015
|
if(!isSession)thHtml+='<th>重要性</th>';
|
|
991
1016
|
thHtml+='<th></th></tr>';
|
|
992
|
-
html+='<table>'+thHtml;
|
|
1017
|
+
html+='<div class="table-wrap"><table>'+thHtml;
|
|
993
1018
|
for(const e of lt){
|
|
994
1019
|
const content=(e.content||e.summary||'')||(e.role==='user'?'[用户消息]':e.role==='assistant'?'[助手回复]':'[系统]');
|
|
995
1020
|
|
|
@@ -1002,7 +1027,7 @@ async function renderMemory(){
|
|
|
1002
1027
|
if(!isSession)html+='<td>'+(e.importance!=null?e.importance.toFixed(2):'')+'</td>';
|
|
1003
1028
|
html+='<td><button class="btn btn-sm btn-danger" onclick="deleteMemory(\''+e.id+'\')">删除</button></td></tr>';
|
|
1004
1029
|
}
|
|
1005
|
-
html+='</table>';
|
|
1030
|
+
html+='</table></div>';
|
|
1006
1031
|
}else{
|
|
1007
1032
|
html+='<div class="empty">暂无'+(_memCategory==='session'?'会话':'全局')+'记忆</div>';
|
|
1008
1033
|
}
|
|
@@ -1013,12 +1038,12 @@ async function searchMemory(){
|
|
|
1013
1038
|
const r=await api('/api/memory/search?q='+encodeURIComponent(q));
|
|
1014
1039
|
let html='<h3>搜索结果: '+(r.length||0)+' 条</h3>';
|
|
1015
1040
|
if(r&&r.length){
|
|
1016
|
-
html+='<table><tr><th>Key</th><th>内容</th><th>分类</th><th>角色</th><th>会话</th></tr>';
|
|
1041
|
+
html+='<div class="table-wrap"><table><tr><th>Key</th><th>内容</th><th>分类</th><th>角色</th><th>会话</th></tr>';
|
|
1017
1042
|
for(const e of r){
|
|
1018
1043
|
const content=(e.content||'').slice(0,300);
|
|
1019
1044
|
html+=`<tr><td style="white-space:nowrap">${escHtml(e.key||'')}</td><td style="max-width:600px;word-break:break-word;font-size:13px">${escHtml(content)}</td><td>${e.category||''}</td><td>${escHtml(e.role||'')}</td><td style="font-size:12px;color:var(--text3)">${escHtml((e.session_id||'').split('_web_')[0])}</td></tr>`;
|
|
1020
1045
|
}
|
|
1021
|
-
html+='</table>';
|
|
1046
|
+
html+='</table></div>';
|
|
1022
1047
|
}else{
|
|
1023
1048
|
html+='<div class="empty">未找到匹配的记忆</div>';
|
|
1024
1049
|
}
|
|
@@ -1040,7 +1065,7 @@ async function renderPermissions(){
|
|
|
1040
1065
|
|
|
1041
1066
|
// 全局默认权限
|
|
1042
1067
|
let html='<div class="card"><h3>全局默认权限</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">新 Agent 将继承这些默认权限设置</p>';
|
|
1043
|
-
html+='<div class="grid" style="
|
|
1068
|
+
html+='<div class="grid grid-3" style="gap:12px">';
|
|
1044
1069
|
for(const p of perms){
|
|
1045
1070
|
const label=labels[p]||p;
|
|
1046
1071
|
const val=defaults[p]!==false?'checked':'';
|
|
@@ -1051,7 +1076,7 @@ async function renderPermissions(){
|
|
|
1051
1076
|
// Agent 权限覆盖
|
|
1052
1077
|
const agentKeys=Object.keys(agents);
|
|
1053
1078
|
if(agentKeys.length>0){
|
|
1054
|
-
html+='<div class="card"><h3>Agent 权限覆盖</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">以下 Agent 使用自定义权限(覆盖默认值)</p><table><tr><th>Agent</th>';
|
|
1079
|
+
html+='<div class="card"><h3>Agent 权限覆盖</h3><p style="color:var(--text2);font-size:13px;margin-bottom:12px">以下 Agent 使用自定义权限(覆盖默认值)</p><div class="table-wrap"><table><tr><th>Agent</th>';
|
|
1055
1080
|
for(const p of perms){html+=`<th>${labels[p]||p}</th>`;}
|
|
1056
1081
|
html+='<th>操作</th></tr>';
|
|
1057
1082
|
for(const name of agentKeys){
|
|
@@ -1063,7 +1088,7 @@ async function renderPermissions(){
|
|
|
1063
1088
|
}
|
|
1064
1089
|
html+=`<td><button class="btn btn-sm btn-ghost" onclick="editAgentPerms('${escHtml(name)}')">编辑</button> <button class="btn btn-sm btn-danger" onclick="resetAgentPerms('${escHtml(name)}')">重置</button></td></tr>`;
|
|
1065
1090
|
}
|
|
1066
|
-
html+='</table></div>';
|
|
1091
|
+
html+='</table></div></div>';
|
|
1067
1092
|
}
|
|
1068
1093
|
|
|
1069
1094
|
$('content').innerHTML=html;
|
|
@@ -1114,7 +1139,7 @@ async function editAgentPerms(name){
|
|
|
1114
1139
|
|
|
1115
1140
|
// 功能权限
|
|
1116
1141
|
html+='<div style="font-size:13px;color:var(--text2);margin-bottom:8px;font-weight:600">🔑 功能权限</div>';
|
|
1117
|
-
html+='<div class="grid" style="
|
|
1142
|
+
html+='<div class="grid grid-3" style="gap:12px">';
|
|
1118
1143
|
for(const p of perms){
|
|
1119
1144
|
const label=labels[p]||p;
|
|
1120
1145
|
const defVal=defaults[p]!==false;
|
|
@@ -1174,7 +1199,7 @@ async function renderLLM(){
|
|
|
1174
1199
|
allModelsCache=Array.isArray(models)?models:[];
|
|
1175
1200
|
let html='';
|
|
1176
1201
|
// 用量统计
|
|
1177
|
-
html+=`<div class="card"><h3>用量统计</h3><div class="grid
|
|
1202
|
+
html+=`<div class="card"><h3>用量统计</h3><div class="grid grid-4">
|
|
1178
1203
|
<div class="stat"><div class="label">调用次数</div><div class="value">${u.call_count||0}</div></div>
|
|
1179
1204
|
<div class="stat"><div class="label">Prompt Tokens</div><div class="value">${u.total_prompt_tokens||0}</div></div>
|
|
1180
1205
|
<div class="stat"><div class="label">Completion Tokens</div><div class="value">${u.total_completion_tokens||0}</div></div>
|
|
@@ -1191,7 +1216,7 @@ async function renderLLM(){
|
|
|
1191
1216
|
if(!modelList.length){
|
|
1192
1217
|
html+='<div class="empty">暂无自定义模型,点击上方按钮添加。</div>';
|
|
1193
1218
|
}else{
|
|
1194
|
-
html+='<table style="font-size:12px"><tr><th>ID</th><th>名称</th><th>Provider</th><th>模型</th><th>上下文</th><th>输入</th><th>推理</th><th>兜底</th><th>状态</th><th>操作</th></tr>';
|
|
1219
|
+
html+='<div class="table-wrap"><table style="font-size:12px"><tr><th>ID</th><th>名称</th><th>Provider</th><th>模型</th><th>上下文</th><th>输入</th><th>推理</th><th>兜底</th><th>状态</th><th>操作</th></tr>';
|
|
1195
1220
|
const providerColors={openai:'badge-green',anthropic:'badge-yellow',ollama:'badge-purple',zhipu:'badge-blue',custom:'badge-red',deepseek:'badge-blue',moonshot:'badge-purple',qwen:'badge-yellow',modelscope:'badge-purple'};
|
|
1196
1221
|
for(const m of modelList){
|
|
1197
1222
|
const badgeClass=providerColors[m.provider]||'badge-green';
|
|
@@ -1211,7 +1236,7 @@ async function renderLLM(){
|
|
|
1211
1236
|
<button class="btn btn-sm btn-success" onclick="testModel(encodeURIComponent('${escHtml(m.id)}'))">测试</button>
|
|
1212
1237
|
<button class="btn btn-sm btn-danger" onclick="deleteModel('${escHtml(m.id)}','${escHtml(String(m.name||'').replace(/'/g,"\\'"))}')">删除</button></td></tr>`;
|
|
1213
1238
|
}
|
|
1214
|
-
html+='</table>';
|
|
1239
|
+
html+='</table></div>';
|
|
1215
1240
|
}
|
|
1216
1241
|
html+='</div>';
|
|
1217
1242
|
$('content').innerHTML=html;
|
|
@@ -1343,12 +1368,12 @@ async function renderExecutor(){
|
|
|
1343
1368
|
<input type="radio" name="execMode" value="sandbox" ${isSandbox?'checked':''} onchange="switchMode('sandbox')" ${!dockerOk?'disabled':''}>
|
|
1344
1369
|
<div><strong>📦 沙盒执行 (Docker)</strong><br><span style="font-size:12px;color:var(--text2)">在隔离容器中运行,更安全${!dockerOk?' (Docker 不可用)':''}</span></div></label></div>
|
|
1345
1370
|
<div style="font-size:13px;color:var(--text2)">当前模式: <span class="badge ${isSandbox?'badge-yellow':'badge-green'}">${isSandbox?'沙盒 (Docker)':'本机'}</span> Docker 状态: <span class="badge ${dockerOk?'badge-green':'badge-red'}">${dockerOk?'可用':'不可用'}</span> 累计执行: <span class="tag">${e.execution_count||0} 次</span></div></div>`;
|
|
1346
|
-
html+=`<div class="card"><h3>沙盒设置</h3><div class="
|
|
1371
|
+
html+=`<div class="card"><h3>沙盒设置</h3><div class="form-row">
|
|
1347
1372
|
<div class="form-group"><label>Docker 镜像</label><input id="sbImage" value="${e.sandbox_image||'python:3.12-slim'}"></div>
|
|
1348
1373
|
<div class="form-group"><label>内存限制</label><input id="sbMemory" value="${e.sandbox_memory||'512m'}" placeholder="512m"></div>
|
|
1349
1374
|
<div class="form-group"><label>网络访问</label><select id="sbNetwork"><option ${!e.sandbox_network?'selected':''} value="false">禁止 (更安全)</option><option ${e.sandbox_network?'selected':''} value="true">允许</option></select></div></div>
|
|
1350
1375
|
<div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveExecutor()">保存设置</button></div></div>`;
|
|
1351
|
-
html+=`<div class="card"><h3>执行参数</h3><div class="
|
|
1376
|
+
html+=`<div class="card"><h3>执行参数</h3><div class="form-row">
|
|
1352
1377
|
<div class="form-group"><label>超时时间 (秒)</label><input id="exTimeout" type="number" value="${e.timeout||300}"></div>
|
|
1353
1378
|
<div class="form-group"><label>自动重试</label><input id="exRetries" type="number" value="2"></div>
|
|
1354
1379
|
<div class="form-group"><label>自动修复</label><select id="exAutoFix"><option ${e.auto_fix?'selected':''} value="true">开启</option><option ${!e.auto_fix?'selected':''} value="false">关闭</option></select></div></div></div>`;
|
|
@@ -1420,9 +1445,9 @@ async function viewSkillDetail(name){
|
|
|
1420
1445
|
</div>
|
|
1421
1446
|
<div class="form-group"><label>危险操作</label><div>${s.dangerous?'<span class="badge badge-red">是</span>':'<span class="badge badge-green">否</span>'}</div></div>
|
|
1422
1447
|
<div class="form-group"><label>参数 (${(s.parameters||[]).length})</label>
|
|
1423
|
-
<table><tr><th>名称</th><th>类型</th><th>必需</th><th>描述</th></tr>
|
|
1448
|
+
<div class="table-wrap"><table><tr><th>名称</th><th>类型</th><th>必需</th><th>描述</th></tr>
|
|
1424
1449
|
${(s.parameters||[]).map(p=>`<tr><td><code>${escHtml(p.name)}</code></td><td>${p.type||'string'}</td><td>${p.required?'✅':'❌'}</td><td>${escHtml(p.description||'')}</td></tr>`).join('')}
|
|
1425
|
-
</table></div>`;
|
|
1450
|
+
</table></div></div>`;
|
|
1426
1451
|
$('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()">${html}<div class="flex gap-8 mt-16"><button class="btn btn-ghost" onclick="closeModal()">关闭</button></div></div></div>`;
|
|
1427
1452
|
}
|
|
1428
1453
|
|
|
@@ -1433,12 +1458,12 @@ async function renderFiles(){
|
|
|
1433
1458
|
<span style="font-size:14px;color:var(--text2)">工作目录: ${wd.path}</span>
|
|
1434
1459
|
<button class="btn btn-sm btn-ghost" onclick="changeWorkdir()">更改</button>
|
|
1435
1460
|
<button class="btn btn-sm btn-ghost" onclick="renderFiles()">刷新</button></div>`;
|
|
1436
|
-
html+='<table><tr><th>名称</th><th>类型</th><th>大小</th></tr>';
|
|
1461
|
+
html+='<div class="table-wrap"><table><tr><th>名称</th><th>类型</th><th>大小</th></tr>';
|
|
1437
1462
|
for(const f of (files||[])){
|
|
1438
1463
|
const icon=f.type==='dir'?'📁':'📄';const size=f.type==='file'?(f.size>1024?(f.size/1024).toFixed(1)+'KB':f.size+'B'):'-';
|
|
1439
1464
|
html+=`<tr><td>${icon} ${escHtml(f.name)}</td><td>${f.type}</td><td>${size}</td></tr>`;}
|
|
1440
1465
|
if(!files||!files.length)html+='<tr><td colspan="3" class="empty">目录为空</td></tr>';
|
|
1441
|
-
html+='</table>';$('content').innerHTML=html;
|
|
1466
|
+
html+='</table></div>';$('content').innerHTML=html;
|
|
1442
1467
|
}
|
|
1443
1468
|
async function changeWorkdir(){const p=prompt('新路径:');if(!p)return;await api('/api/workdir',{method:'PUT',body:JSON.stringify({path:p})});renderFiles();}
|
|
1444
1469
|
|
|
@@ -1471,7 +1496,7 @@ async function renderTasks(){
|
|
|
1471
1496
|
const tasks=r.tasks||[];
|
|
1472
1497
|
const statusCounts={pending:0,running:0,completed:0,failed:0};
|
|
1473
1498
|
for(const t of tasks){statusCounts[t.status]=(statusCounts[t.status]||0)+1;}
|
|
1474
|
-
let html=`<div class="grid" style="
|
|
1499
|
+
let html=`<div class="grid grid-4" style="margin-bottom:16px">
|
|
1475
1500
|
<div class="stat"><div class="label">待处理</div><div class="value">${statusCounts.pending}</div></div>
|
|
1476
1501
|
<div class="stat"><div class="label">运行中</div><div class="value">${statusCounts.running}</div></div>
|
|
1477
1502
|
<div class="stat"><div class="label">已完成</div><div class="value" style="color:var(--success)">${statusCounts.completed}</div></div>
|
|
@@ -1489,7 +1514,7 @@ async function renderTasks(){
|
|
|
1489
1514
|
if(tasks.length===0){html+='<div class="empty">暂无任务记录</div>';}
|
|
1490
1515
|
else{
|
|
1491
1516
|
html+='<div style="max-height:calc(100vh - 340px);overflow-y:auto" id="taskList">';
|
|
1492
|
-
html+='<table><tr><th>任务ID</th><th>描述</th><th>群聊</th><th>状态</th><th>时间</th><th>操作</th></tr>';
|
|
1517
|
+
html+='<div class="table-wrap"><table><tr><th>任务ID</th><th>描述</th><th>群聊</th><th>状态</th><th>时间</th><th>操作</th></tr>';
|
|
1493
1518
|
for(const t of tasks){
|
|
1494
1519
|
const statusBadge=t.status==='completed'?'<span class="badge badge-green">已完成</span>':
|
|
1495
1520
|
t.status==='running'?'<span class="badge badge-blue">运行中</span>':
|
|
@@ -1511,7 +1536,7 @@ async function renderTasks(){
|
|
|
1511
1536
|
<button class="btn btn-sm btn-danger" onclick="deleteTask('${escHtml(t.task_id)}')">删除</button>
|
|
1512
1537
|
</td></tr>`;
|
|
1513
1538
|
}
|
|
1514
|
-
html+='</table></div>';
|
|
1539
|
+
html+='</table></div></div>';
|
|
1515
1540
|
}
|
|
1516
1541
|
$('content').innerHTML=html;
|
|
1517
1542
|
}
|
|
@@ -1554,7 +1579,7 @@ async function renderOrganization(){
|
|
|
1554
1579
|
</div>
|
|
1555
1580
|
<div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="saveOrgConfig()">保存配置</button></div></div>`;
|
|
1556
1581
|
html+=`<div class="card"><h3>组织信息</h3>
|
|
1557
|
-
<div class="
|
|
1582
|
+
<div class="form-row">
|
|
1558
1583
|
<div class="form-group"><label>组织名称</label><input id="orgName" value="${escHtml(inf.name||'')}" placeholder="我的组织"></div>
|
|
1559
1584
|
<div class="form-group"><label>组织描述</label><input id="orgDesc" value="${escHtml(inf.description||'')}" placeholder="组织简介"></div>
|
|
1560
1585
|
<div class="form-group"><label>联系方式</label><input id="orgContact" value="${escHtml(inf.contact||'')}" placeholder="联系邮箱或电话"></div>
|
|
@@ -1581,13 +1606,13 @@ async function loadOrgKnowledge(){
|
|
|
1581
1606
|
const files=await api('/api/organization/knowledge');
|
|
1582
1607
|
const el=document.getElementById('orgKBList');if(!el)return;
|
|
1583
1608
|
if(!files||!files.length){el.innerHTML='<div class="empty">暂无知识库文件</div>';return}
|
|
1584
|
-
let html='<table><tr><th>文件名</th><th>大小</th><th>操作</th></tr>';
|
|
1609
|
+
let html='<div class="table-wrap"><table><tr><th>文件名</th><th>大小</th><th>操作</th></tr>';
|
|
1585
1610
|
for(const f of files){
|
|
1586
1611
|
html+=`<tr><td>${escHtml(f.name||f.path)}</td><td>${f.size||'-'}</td>
|
|
1587
1612
|
<td><button class="btn btn-sm btn-ghost" onclick="viewOrgKBFile('${escHtml(f.path||f.name)}')">查看</button>
|
|
1588
1613
|
<button class="btn btn-sm btn-danger" onclick="deleteOrgKBFile('${escHtml(f.path||f.name)}')">删除</button></td></tr>`;
|
|
1589
1614
|
}
|
|
1590
|
-
html+='</table>';el.innerHTML=html;
|
|
1615
|
+
html+='</table></div>';el.innerHTML=html;
|
|
1591
1616
|
}
|
|
1592
1617
|
function uploadOrgKnowledge(folderMode){
|
|
1593
1618
|
const input=document.createElement('input');input.type='file';input.multiple=true;
|
|
@@ -1773,13 +1798,13 @@ async function loadDeptKB(path){
|
|
|
1773
1798
|
let html='<h4 style="margin-bottom:8px">部门知识库</h4>';
|
|
1774
1799
|
html+='<div style="margin-bottom:8px"><button class="btn btn-sm btn-primary" onclick="uploadDeptKB(\''+escHtml(path)+'\',false)">上传文件</button> <button class="btn btn-sm btn-secondary" onclick="uploadDeptKB(\''+escHtml(path)+'\',true)">📁 上传文件夹</button></div>';
|
|
1775
1800
|
if(!files||!files.length){html+='<div class="empty" style="margin-top:12px">暂无知识库文件</div>';el.innerHTML=html;return}
|
|
1776
|
-
html+='<table><tr><th>文件</th><th>操作</th></tr>';
|
|
1801
|
+
html+='<div class="table-wrap"><table><tr><th>文件</th><th>操作</th></tr>';
|
|
1777
1802
|
for(const f of files){
|
|
1778
1803
|
html+=`<tr><td>${escHtml(f.name||f.path)}</td>
|
|
1779
1804
|
<td><button class="btn btn-sm btn-ghost" onclick="viewDeptKBFile('${escHtml(path)}','${escHtml(f.path||f.name)}')">查看</button>
|
|
1780
1805
|
<button class="btn btn-sm btn-danger" onclick="deleteDeptKBFile('${escHtml(path)}','${escHtml(f.path||f.name)}')">删除</button></td></tr>`;
|
|
1781
1806
|
}
|
|
1782
|
-
html+='</table>';el.innerHTML=html;
|
|
1807
|
+
html+='</table></div>';el.innerHTML=html;
|
|
1783
1808
|
}
|
|
1784
1809
|
async function uploadDeptKB(path,folderMode){
|
|
1785
1810
|
const input=document.createElement('input');input.type='file';input.multiple=true;
|