myagent-ai 1.19.6 → 1.19.8
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/agents/main_agent.py +10 -10
- package/package.json +1 -1
- package/web/api_server.py +36 -4
- package/web/ui/index.html +175 -10
package/agents/main_agent.py
CHANGED
|
@@ -45,11 +45,11 @@ class MainAgent(BaseAgent):
|
|
|
45
45
|
<output>
|
|
46
46
|
<mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
|
|
47
47
|
<usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
|
|
48
|
-
<response><reply>展示给用户的文本,格式上尽量使用md
|
|
48
|
+
<response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接等。内容上,直接回应用户问题;告诉用户,为完成任务,准备如何开展工作;根据工具调用结果,展示任务进展;任务完成后的最终总结。注意:这是给用户展示信息的最重要标签,内容清晰明了,尽量不要跟上次回复重复。</reply><toolstocal>
|
|
49
49
|
<tool><beforecalltext>展示给用户的工具调用信息,方便用户了解调用目的。格式:先使用"接下来、下一步、接着、现在、然后、最后"等连接词,然后介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
|
|
50
50
|
</toolstocal>
|
|
51
51
|
</response>
|
|
52
|
-
<task_plan
|
|
52
|
+
<task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
|
|
53
53
|
<remember><type>填global或session,其中"global"为跨会话全局记忆,"session"为仅当前会话。</type><content>仅从最新用户输入,包括"userprint"或"usersays_correct"或工具调用结果,中提炼值得记忆的信息(如用户偏好、重要结论、错误经验、用户个人信息、对话要点、用户诉求、ai回复等)。因为对话默认不自动保存聊天记录,而是从记忆库搜索最相关的最新内容到"automemory"供决策,所以此次必须有所记忆,才能为后续多轮对话提供持续记忆基础。</content></remember>
|
|
54
54
|
<recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。也可直接调用"recall_memory"工具即时搜索。</recall>
|
|
55
55
|
<knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等,将被持久化存储,未来可通过 "get_knowledge"检索复用。如果本轮无需保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
|
|
@@ -79,11 +79,11 @@ class MainAgent(BaseAgent):
|
|
|
79
79
|
<output>
|
|
80
80
|
<mainsubject>当前对话的6字以内标题(每轮都需输出,系统会每3轮自动更新会话名称)</mainsubject>
|
|
81
81
|
<usersays_correct>通过修正识别错误、调整标点,结合上下文,将用户语音转写文本"usersays"修正为更准确的文本,但尽量少改动。如"usersays"为空,则此处为空。</usersays_correct>
|
|
82
|
-
<response><reply>展示给用户的文本,格式上尽量使用md
|
|
82
|
+
<response><reply>展示给用户的文本,格式上尽量使用md格式,直观形象展示,甚至可以包括超链接等。内容上,直接回应用户问题;告诉用户,为完成任务,准备如何开展工作;根据工具调用结果,展示任务进展;任务完成后的最终总结。注意:这是给用户展示信息的最重要标签,内容清晰明了,尽量不要跟上次回复重复。</reply><toolstocal>
|
|
83
83
|
<tool><beforecalltext>展示给用户的工具调用信息,方便用户了解调用目的。格式:先使用"接下来、下一步、接着、现在、然后、最后"等连接词,然后介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名,用于后台解析器解析调用工具</toolname><parms>调用工具的JSON格式参数对象,如格式: {"query": "搜索关键词", "num": 5}</parms><timeout>预估调用超时时限(秒),工具调用超时会立即回调大语言模型,方便调整工具使用</timeout></tool>
|
|
84
84
|
</toolstocal>
|
|
85
85
|
</response>
|
|
86
|
-
<task_plan
|
|
86
|
+
<task_plan>若"context"包含非空"task_plan",则更新它:若任务条数已超8,则精简为3条,若主题发生明显变化,重新设计任务列表。若"context"包含空"task_plan",则先评估任务复杂度,针对单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务,若预计操作步骤不超过2步,则此处输出为空,不创建任务列表;针对多文件修改、需要调研+实现+测试、涉及多个模块联动等复杂任务,如预计超过2步操作,则以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
|
|
87
87
|
<remember><type>填global或session,其中"global"为跨会话全局记忆,"session"为仅当前会话。</type><content>仅从最新用户输入,包括"userprint"或"usersays_correct"或工具调用结果,中提炼值得记忆的信息(如用户偏好、重要结论、错误经验、用户个人信息、对话要点、用户诉求、ai回复等)。因为对话默认不自动保存聊天记录,而是从记忆库搜索最相关的最新内容到"automemory"供决策,所以此次必须有所记忆,才能为后续多轮对话提供持续记忆基础。</content></remember>
|
|
88
88
|
<recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过"recall_memory"注入上下文。也可直接调用"recall_memory"工具即时搜索。</recall>
|
|
89
89
|
<knowledge>从本轮对话或工具执行结果中提炼值得长期保存到知识库的专业知识、事实、经验法则、技术要点等,将被持久化存储,未来可通过 "get_knowledge"检索复用。如果本轮无需保存的知识,则为空。格式要求:简洁明确,每条知识一行,用换行分隔。</knowledge>
|
|
@@ -1348,8 +1348,8 @@ class MainAgent(BaseAgent):
|
|
|
1348
1348
|
"v2_tool_result",
|
|
1349
1349
|
{"tool": {"toolname": tool_name}, "result": {
|
|
1350
1350
|
"success": tool_result.get("success", False),
|
|
1351
|
-
"output": truncate_str(tool_output_text,
|
|
1352
|
-
"error": truncate_str(tool_result.get("error", ""),
|
|
1351
|
+
"output": truncate_str(tool_output_text, 30000),
|
|
1352
|
+
"error": truncate_str(tool_result.get("error", ""), 30000),
|
|
1353
1353
|
"timed_out": tool_result.get("timed_out", False),
|
|
1354
1354
|
}},
|
|
1355
1355
|
stream_callback,
|
|
@@ -1359,7 +1359,7 @@ class MainAgent(BaseAgent):
|
|
|
1359
1359
|
"title": f"工具结果: {tool_name}",
|
|
1360
1360
|
"tool_name": tool_name,
|
|
1361
1361
|
"success": tool_result.get("success", False),
|
|
1362
|
-
"summary": truncate_str(tool_output_text,
|
|
1362
|
+
"summary": truncate_str(tool_output_text, 30000),
|
|
1363
1363
|
"result": tool_result,
|
|
1364
1364
|
})
|
|
1365
1365
|
|
|
@@ -1373,9 +1373,9 @@ class MainAgent(BaseAgent):
|
|
|
1373
1373
|
_HEAVY_TOOLS = ("web_search", "web_read", "url_read", "file_list",
|
|
1374
1374
|
"file_search", "browser_open", "process_list")
|
|
1375
1375
|
if tool_name in _HEAVY_TOOLS:
|
|
1376
|
-
_max_output =
|
|
1376
|
+
_max_output = 50000
|
|
1377
1377
|
else:
|
|
1378
|
-
_max_output =
|
|
1378
|
+
_max_output = 30000
|
|
1379
1379
|
# 超时工具明确标注状态,让 LLM 清楚知道哪个工具超时了
|
|
1380
1380
|
_status = "超时" if is_timeout else ('成功' if tool_result.get('success') else '失败')
|
|
1381
1381
|
tool_outputs_parts.append(
|
|
@@ -1410,7 +1410,7 @@ class MainAgent(BaseAgent):
|
|
|
1410
1410
|
self.memory.add_session(
|
|
1411
1411
|
session_id=context.session_id,
|
|
1412
1412
|
role="tool",
|
|
1413
|
-
content=f"[{tool_name}] {_status}\n{truncate_str(output_str,
|
|
1413
|
+
content=f"[{tool_name}] {_status}\n{truncate_str(output_str, 10000)}",
|
|
1414
1414
|
key="tool_result",
|
|
1415
1415
|
importance=0.4,
|
|
1416
1416
|
)
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -1920,13 +1920,45 @@ window.toggleFullscreen = function() {{
|
|
|
1920
1920
|
async def handle_status(self, request):
|
|
1921
1921
|
c = self.core
|
|
1922
1922
|
from core.version import get_version
|
|
1923
|
+
|
|
1924
|
+
# 模型库摘要
|
|
1925
|
+
models_summary = []
|
|
1926
|
+
for m in c.config.models_library:
|
|
1927
|
+
models_summary.append({
|
|
1928
|
+
"id": m.id, "name": m.name, "provider": m.provider,
|
|
1929
|
+
"model": m.model, "enabled": m.enabled,
|
|
1930
|
+
"input_modes": m.input_modes or ["text"],
|
|
1931
|
+
"is_global_fallback": getattr(m, 'is_global_fallback', True),
|
|
1932
|
+
})
|
|
1933
|
+
|
|
1934
|
+
# 全局 LLM 信息
|
|
1935
|
+
global_llm = {
|
|
1936
|
+
"provider": c.config.llm.provider,
|
|
1937
|
+
"model": c.config.llm.model,
|
|
1938
|
+
"input_modes": c.config.llm.input_modes or ["text"],
|
|
1939
|
+
"base_url": c.config.llm.base_url,
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
# 当前运行中的 agent 会话
|
|
1943
|
+
running_agents = []
|
|
1944
|
+
for sid, info in self._running_sessions.items():
|
|
1945
|
+
if info.get("running") and not info.get("done"):
|
|
1946
|
+
running_agents.append({
|
|
1947
|
+
"session_id": sid,
|
|
1948
|
+
"agent_path": info.get("agent_path", ""),
|
|
1949
|
+
"message": info.get("message", ""),
|
|
1950
|
+
"started_at": info.get("started_at"),
|
|
1951
|
+
})
|
|
1952
|
+
|
|
1923
1953
|
return web.json_response({
|
|
1924
|
-
"running": c._running,
|
|
1925
|
-
"
|
|
1954
|
+
"running": c._running,
|
|
1955
|
+
"version": get_version(),
|
|
1956
|
+
"global_llm": global_llm,
|
|
1957
|
+
"models": models_summary,
|
|
1958
|
+
"skills": len(c.skill_registry.list_skills()) if c.skill_registry else 0,
|
|
1926
1959
|
"memory": c.memory.get_stats() if c.memory else {},
|
|
1927
1960
|
"queue": c.task_queue.get_stats() if c.task_queue else {},
|
|
1928
|
-
"
|
|
1929
|
-
"version": get_version(),
|
|
1961
|
+
"running_agents": running_agents,
|
|
1930
1962
|
})
|
|
1931
1963
|
|
|
1932
1964
|
# ── Task List(纯内存 JSON 存储,exec 模式专用) ──
|
package/web/ui/index.html
CHANGED
|
@@ -58,6 +58,32 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
|
|
|
58
58
|
.stat{background:var(--surface2);padding:16px;border-radius:var(--radius)}
|
|
59
59
|
.stat .label{font-size:12px;color:var(--text2);margin-bottom:4px}
|
|
60
60
|
.stat .value{font-size:28px;font-weight:700}
|
|
61
|
+
/* ── Dashboard ── */
|
|
62
|
+
.dash-section{margin-bottom:24px}
|
|
63
|
+
.dash-section-title{font-size:14px;font-weight:600;color:var(--text2);margin-bottom:12px;display:flex;align-items:center;gap:8px}
|
|
64
|
+
.dash-section-title .icon{font-size:16px}
|
|
65
|
+
.dash-model-card{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius);padding:14px;display:flex;align-items:flex-start;gap:12px;transition:border-color .15s}
|
|
66
|
+
.dash-model-card:hover{border-color:var(--primary)}
|
|
67
|
+
.dash-model-card.active{border-color:var(--primary);box-shadow:0 0 0 1px var(--primary)}
|
|
68
|
+
.dash-model-icon{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
|
|
69
|
+
.dash-model-icon.text{background:rgba(99,102,241,.15);color:var(--primary)}
|
|
70
|
+
.dash-model-icon.multimodal{background:rgba(16,185,129,.15);color:#10b981}
|
|
71
|
+
.dash-model-info{flex:1;min-width:0}
|
|
72
|
+
.dash-model-name{font-size:14px;font-weight:600;color:var(--text);word-break:break-all}
|
|
73
|
+
.dash-model-provider{font-size:11px;color:var(--text3);margin-top:2px}
|
|
74
|
+
.dash-model-modes{display:flex;gap:4px;margin-top:6px;flex-wrap:wrap}
|
|
75
|
+
.dash-mode-tag{font-size:10px;padding:2px 8px;border-radius:10px;font-weight:500}
|
|
76
|
+
.dash-mode-tag.text{background:rgba(99,102,241,.1);color:var(--primary)}
|
|
77
|
+
.dash-mode-tag.image{background:rgba(16,185,129,.1);color:#10b981}
|
|
78
|
+
.dash-mode-tag.video{background:rgba(245,158,11,.1);color:#f59e0b}
|
|
79
|
+
.dash-mode-tag.audio{background:rgba(239,68,68,.1);color:#ef4444}
|
|
80
|
+
.dash-active-badge{font-size:10px;padding:2px 8px;border-radius:10px;background:var(--primary);color:#fff;font-weight:600;flex-shrink:0;align-self:flex-start}
|
|
81
|
+
.dash-running-item{background:var(--surface2);border:1px solid var(--border);border-left:3px solid var(--success);border-radius:var(--radius);padding:12px 14px;display:flex;align-items:center;gap:12px}
|
|
82
|
+
.dash-running-dot{width:8px;height:8px;border-radius:50%;background:var(--success);animation:pulse 2s infinite;flex-shrink:0}
|
|
83
|
+
.dash-running-agent{font-size:13px;font-weight:600;color:var(--text)}
|
|
84
|
+
.dash-running-task{font-size:12px;color:var(--text2);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:400px}
|
|
85
|
+
.dash-running-time{font-size:11px;color:var(--text3);flex-shrink:0}
|
|
86
|
+
.dash-empty{color:var(--text3);font-size:13px;padding:20px 0}
|
|
61
87
|
input,select,textarea{background:var(--surface2);border:1px solid var(--border);color:var(--text);padding:8px 12px;border-radius:6px;font-size:14px;width:100%;outline:none}
|
|
62
88
|
input:focus,select:focus,textarea:focus{border-color:var(--primary)}
|
|
63
89
|
textarea{font-family:'Cascadia Code',Consolas,monospace;resize:vertical;min-height:100px}
|
|
@@ -220,6 +246,11 @@ tr:hover{background:var(--surface2)}
|
|
|
220
246
|
.stat .label{font-size:11px}
|
|
221
247
|
.stat .value{font-size:20px}
|
|
222
248
|
.grid,.grid-2,.grid-3,.grid-4{grid-template-columns:repeat(2,1fr)!important}
|
|
249
|
+
/* 仪表盘移动端 */
|
|
250
|
+
.dash-model-card{padding:10px;gap:8px}
|
|
251
|
+
.dash-model-icon{width:32px;height:32px;font-size:15px}
|
|
252
|
+
.dash-model-name{font-size:13px}
|
|
253
|
+
.dash-running-task{max-width:200px}
|
|
223
254
|
.btn-sm{padding:6px 12px;font-size:12px;min-height:32px}
|
|
224
255
|
th,td{padding:6px 8px;font-size:12px;white-space:nowrap}
|
|
225
256
|
th{font-size:11px}
|
|
@@ -305,6 +336,8 @@ let allAgentsCache=[];
|
|
|
305
336
|
let allModelsCache=[];
|
|
306
337
|
let allDeptsCache=[];
|
|
307
338
|
|
|
339
|
+
function esc(s){if(!s)return'';return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
|
340
|
+
|
|
308
341
|
async function api(url,opts={}){
|
|
309
342
|
try{
|
|
310
343
|
const r=await fetch(API+url,{headers:{'Content-Type':'application/json'},...opts});
|
|
@@ -481,16 +514,148 @@ window.addEventListener('popstate',function(e){
|
|
|
481
514
|
|
|
482
515
|
// ========== Dashboard ==========
|
|
483
516
|
async function renderDashboard(){
|
|
484
|
-
const s=await api('/api/status');
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
517
|
+
const s = await api('/api/status');
|
|
518
|
+
const gl = s.global_llm || {};
|
|
519
|
+
const models = s.models || [];
|
|
520
|
+
const mem = s.memory || {};
|
|
521
|
+
const q = s.queue || {};
|
|
522
|
+
const runningAgents = s.running_agents || [];
|
|
523
|
+
|
|
524
|
+
// ── 辅助函数 ──
|
|
525
|
+
const modeLabel = m => ({text:'文本',image:'图片',video:'视频',audio:'音频'}[m] || m);
|
|
526
|
+
const modeIcon = m => ({text:'T',image:'🖼',video:'🎬',audio:'🎤'}[m] || m);
|
|
527
|
+
const providerLabel = p => ({openai:'OpenAI',anthropic:'Anthropic',ollama:'Ollama',zhipu:'智谱',custom:'自定义',gemini:'Google'}[p] || p);
|
|
528
|
+
const hasImage = modes => (modes || []).includes('image');
|
|
529
|
+
const isGlobalFallback = m => m.is_global_fallback && m.enabled;
|
|
530
|
+
|
|
531
|
+
// 找到全局默认模型(从模型库中 is_global_fallback 的启用模型)
|
|
532
|
+
const globalModel = models.find(m => isGlobalFallback(m)) || null;
|
|
533
|
+
|
|
534
|
+
// input_modes 标签 HTML
|
|
535
|
+
const modesHtml = (modes) => {
|
|
536
|
+
if (!modes || modes.length === 0) return '';
|
|
537
|
+
return '<div class="dash-model-modes">' + modes.map(m =>
|
|
538
|
+
'<span class="dash-mode-tag ' + m + '">' + modeLabel(m) + '</span>'
|
|
539
|
+
).join('') + '</div>';
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// 模型卡片 HTML
|
|
543
|
+
const modelCardHtml = (m, isActive) => {
|
|
544
|
+
const multimodal = hasImage(m.input_modes);
|
|
545
|
+
return '<div class="dash-model-card' + (isActive ? ' active' : '') + '">' +
|
|
546
|
+
'<div class="dash-model-icon ' + (multimodal ? 'multimodal' : 'text') + '">' +
|
|
547
|
+
(multimodal ? '🖼' : '💬') +
|
|
548
|
+
'</div>' +
|
|
549
|
+
'<div class="dash-model-info">' +
|
|
550
|
+
'<div class="dash-model-name">' + esc(m.name || m.model || m.id) + '</div>' +
|
|
551
|
+
'<div class="dash-model-provider">' + providerLabel(m.provider) + (m.model && m.model !== m.id ? ' · ' + esc(m.model) : '') + '</div>' +
|
|
552
|
+
modesHtml(m.input_modes) +
|
|
553
|
+
'</div>' +
|
|
554
|
+
(isActive ? '<span class="dash-active-badge">默认</span>' : '') +
|
|
555
|
+
'</div>';
|
|
556
|
+
};
|
|
557
|
+
|
|
558
|
+
// ── 顶部统计卡片 ──
|
|
559
|
+
var statsHtml = '<div class="grid" style="grid-template-columns:repeat(auto-fill,minmax(180px,1fr))">' +
|
|
560
|
+
'<div class="stat"><div class="label">版本</div><div class="value" style="font-size:18px">' + esc(s.version || '-') + '</div></div>' +
|
|
561
|
+
'<div class="stat"><div class="label">已注册技能</div><div class="value">' + (s.skills || 0) + '</div></div>' +
|
|
562
|
+
'<div class="stat"><div class="label">模型库</div><div class="value">' + models.length + '</div></div>' +
|
|
563
|
+
'<div class="stat"><div class="label">记忆条目</div><div class="value">' + (mem.total_count || 0) + '</div></div>' +
|
|
564
|
+
'<div class="stat"><div class="label">会话数</div><div class="value">' + (mem.session_count || 0) + '</div></div>' +
|
|
565
|
+
'<div class="stat"><div class="label">全局记忆</div><div class="value">' + (mem.global_count || 0) + '</div></div>' +
|
|
566
|
+
(q.total_submitted ? '<div class="stat"><div class="label">任务队列</div><div class="value">' + q.total_submitted + '</div></div>' : '') +
|
|
567
|
+
'</div>';
|
|
568
|
+
|
|
569
|
+
// ── 模型区域 ──
|
|
570
|
+
var modelSection = '';
|
|
571
|
+
if (models.length > 0) {
|
|
572
|
+
var enabledModels = models.filter(m => m.enabled);
|
|
573
|
+
var disabledModels = models.filter(m => !m.enabled);
|
|
574
|
+
|
|
575
|
+
modelSection = '<div class="dash-section">' +
|
|
576
|
+
'<div class="dash-section-title"><span class="icon">🤖</span> 模型配置</div>';
|
|
577
|
+
|
|
578
|
+
// 默认模型高亮展示
|
|
579
|
+
if (globalModel) {
|
|
580
|
+
modelSection += '<div style="margin-bottom:12px">' +
|
|
581
|
+
'<div style="font-size:12px;color:var(--text3);margin-bottom:8px">当前全局默认模型</div>' +
|
|
582
|
+
modelCardHtml(globalModel, true) +
|
|
583
|
+
'</div>';
|
|
584
|
+
} else {
|
|
585
|
+
// 没有在模型库中找到全局默认,用 config.llm 显示
|
|
586
|
+
modelSection += '<div style="margin-bottom:12px">' +
|
|
587
|
+
'<div style="font-size:12px;color:var(--text3);margin-bottom:8px">当前全局模型</div>' +
|
|
588
|
+
modelCardHtml({id:gl.model, name:gl.model, provider:gl.provider, model:gl.model, input_modes:gl.input_modes, enabled:true}, true) +
|
|
589
|
+
'</div>';
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// 按类型分类
|
|
593
|
+
var textModels = enabledModels.filter(m => !hasImage(m.input_modes));
|
|
594
|
+
var multiModels = enabledModels.filter(m => hasImage(m.input_modes));
|
|
595
|
+
|
|
596
|
+
// 多模态模型(支持图片的)
|
|
597
|
+
if (multiModels.length > 0) {
|
|
598
|
+
modelSection += '<div style="font-size:12px;color:var(--text3);margin-bottom:8px;margin-top:12px">多模态模型(支持图片输入)</div>' +
|
|
599
|
+
'<div class="grid" style="grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;margin-bottom:8px">' +
|
|
600
|
+
multiModels.map(m => modelCardHtml(m, false)).join('') +
|
|
601
|
+
'</div>';
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 纯文本模型
|
|
605
|
+
if (textModels.length > 0) {
|
|
606
|
+
modelSection += '<div style="font-size:12px;color:var(--text3);margin-bottom:8px;margin-top:12px">文本模型</div>' +
|
|
607
|
+
'<div class="grid" style="grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px;margin-bottom:8px">' +
|
|
608
|
+
textModels.map(m => modelCardHtml(m, false)).join('') +
|
|
609
|
+
'</div>';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 已禁用模型
|
|
613
|
+
if (disabledModels.length > 0) {
|
|
614
|
+
modelSection += '<div style="font-size:12px;color:var(--text3);margin-bottom:8px;margin-top:12px;opacity:.6">已禁用 (' + disabledModels.length + ')</div>' +
|
|
615
|
+
'<div class="grid" style="grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:10px">' +
|
|
616
|
+
disabledModels.map(m => {
|
|
617
|
+
var card = modelCardHtml(m, false);
|
|
618
|
+
return card.replace('dash-model-card"', 'dash-model-card" style="opacity:.5"');
|
|
619
|
+
}).join('') +
|
|
620
|
+
'</div>';
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
modelSection += '</div>';
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// ── 运行中的 Agent ──
|
|
627
|
+
var runningSection = '<div class="dash-section">' +
|
|
628
|
+
'<div class="dash-section-title"><span class="icon">⚡</span> 运行中的任务</div>';
|
|
629
|
+
|
|
630
|
+
if (runningAgents.length > 0) {
|
|
631
|
+
runningSection += '<div style="display:flex;flex-direction:column;gap:8px">';
|
|
632
|
+
for (var ra of runningAgents) {
|
|
633
|
+
var agentName = ra.agent_path || '未知';
|
|
634
|
+
var taskMsg = ra.message || '处理中...';
|
|
635
|
+
var timeStr = '';
|
|
636
|
+
if (ra.started_at) {
|
|
637
|
+
var elapsed = Math.round((Date.now() / 1000) - ra.started_at);
|
|
638
|
+
if (elapsed < 60) timeStr = elapsed + '秒前';
|
|
639
|
+
else if (elapsed < 3600) timeStr = Math.floor(elapsed / 60) + '分钟前';
|
|
640
|
+
else timeStr = Math.floor(elapsed / 3600) + '小时前';
|
|
641
|
+
}
|
|
642
|
+
runningSection += '<div class="dash-running-item">' +
|
|
643
|
+
'<div class="dash-running-dot"></div>' +
|
|
644
|
+
'<div style="flex:1;min-width:0">' +
|
|
645
|
+
'<div class="dash-running-agent">' + esc(agentName) + '</div>' +
|
|
646
|
+
'<div class="dash-running-task" title="' + esc(taskMsg) + '">' + esc(taskMsg) + '</div>' +
|
|
647
|
+
'</div>' +
|
|
648
|
+
(timeStr ? '<div class="dash-running-time">' + timeStr + '</div>' : '') +
|
|
649
|
+
'</div>';
|
|
650
|
+
}
|
|
651
|
+
runningSection += '</div>';
|
|
652
|
+
} else {
|
|
653
|
+
runningSection += '<div class="dash-empty">当前没有运行中的任务</div>';
|
|
654
|
+
}
|
|
655
|
+
runningSection += '</div>';
|
|
656
|
+
|
|
657
|
+
// ── 组装 ──
|
|
658
|
+
$('content').innerHTML = statsHtml + modelSection + runningSection;
|
|
494
659
|
}
|
|
495
660
|
|
|
496
661
|
// ========== Agents (FULL REWRITE) ==========
|