myagent-ai 1.19.5 → 1.19.7
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 +4 -4
- package/core/context_builder.py +49 -2
- package/core/vnc_manager.py +0 -0
- package/data/uploads/2026-04/32c9e9d3-8cf_test.pdf.pdf +0 -0
- package/package.json +1 -1
- package/requirements-optional.txt +0 -0
- package/skills/__init__.py +0 -0
- package/skills/base.py +0 -0
- package/skills/browser_skill.py +0 -0
- package/skills/chromedev_mcp.py +0 -0
- package/skills/docx_skill.py +0 -0
- package/skills/file_send.py +0 -0
- package/skills/file_skill.py +0 -0
- package/skills/frontend-dev/SKILL.md +0 -0
- package/skills/frontend-dev/references/asset-prompt-guide.md +0 -0
- package/skills/frontend-dev/references/env-setup.md +0 -0
- package/skills/frontend-dev/references/minimax-cli-reference.md +0 -0
- package/skills/frontend-dev/references/minimax-image-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-music-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-tts-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-video-guide.md +0 -0
- package/skills/frontend-dev/references/minimax-voice-catalog.md +0 -0
- package/skills/frontend-dev/references/motion-recipes.md +0 -0
- package/skills/frontend-dev/references/troubleshooting.md +0 -0
- package/skills/frontend-dev/scripts/minimax_image.py +0 -0
- package/skills/frontend-dev/scripts/minimax_music.py +0 -0
- package/skills/frontend-dev/scripts/minimax_tts.py +0 -0
- package/skills/frontend-dev/scripts/minimax_video.py +0 -0
- package/skills/frontend-dev/templates/generator_template.js +0 -0
- package/skills/frontend-dev/templates/viewer.html +0 -0
- package/skills/fullstack-dev/SKILL.md +0 -0
- package/skills/fullstack-dev/references/api-design.md +0 -0
- package/skills/fullstack-dev/references/auth-flow.md +0 -0
- package/skills/fullstack-dev/references/db-schema.md +0 -0
- package/skills/fullstack-dev/references/django-best-practices.md +0 -0
- package/skills/fullstack-dev/references/environment-management.md +0 -0
- package/skills/fullstack-dev/references/release-checklist.md +0 -0
- package/skills/fullstack-dev/references/technology-selection.md +0 -0
- package/skills/fullstack-dev/references/testing-strategy.md +0 -0
- package/skills/gui_skill.py +0 -0
- package/skills/pdf_skill.py +0 -0
- package/skills/ppt_skill.py +0 -0
- package/skills/registry.py +15 -2
- package/skills/search_skill.py +0 -0
- package/skills/system_skill.py +0 -0
- package/skills/xlsx_skill.py +0 -0
- 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>
|
package/core/context_builder.py
CHANGED
|
@@ -13,6 +13,7 @@ core/context_builder.py - 上下文构建器
|
|
|
13
13
|
<usersays> - 用户语音转文本输入(键盘输入时为空)
|
|
14
14
|
<task_plan> - 当前任务计划(Markdown 格式,上轮已完成时为空)
|
|
15
15
|
<tools> - 可用工具列表(名称、描述、参数格式)
|
|
16
|
+
<skill_prompts> - SKILL.md 格式技能的完整指令(Prompt 类型技能注入)
|
|
16
17
|
|
|
17
18
|
使用示例:
|
|
18
19
|
builder = ContextBuilder(memory_manager=mm, skill_registry=sr)
|
|
@@ -169,6 +170,7 @@ class ContextBuilder:
|
|
|
169
170
|
self._build_user_input(user_typed_text, user_voice_text),
|
|
170
171
|
self._build_task_plan(task_plan),
|
|
171
172
|
self._build_tools(self.skill_registry),
|
|
173
|
+
self._build_skill_prompts(self.skill_registry),
|
|
172
174
|
]
|
|
173
175
|
|
|
174
176
|
context_body = "\n".join(sections)
|
|
@@ -766,6 +768,51 @@ class ContextBuilder:
|
|
|
766
768
|
lines.append("</tools>")
|
|
767
769
|
return "\n".join(lines)
|
|
768
770
|
|
|
771
|
+
def _build_skill_prompts(
|
|
772
|
+
self,
|
|
773
|
+
skill_registry: Optional["SkillRegistry"],
|
|
774
|
+
) -> str:
|
|
775
|
+
"""
|
|
776
|
+
构建 <skill_prompts> 段落 —— SKILL.md 格式技能的完整指令内容。
|
|
777
|
+
|
|
778
|
+
对于 SKILL.md 目录格式的 Prompt 类型技能,将完整的 markdown body
|
|
779
|
+
注入到 LLM 上下文中,使 LLM 能按照技能指令执行任务。
|
|
780
|
+
普通 Python 技能(有 execute 实现)不在此处注入。
|
|
781
|
+
|
|
782
|
+
Returns:
|
|
783
|
+
<skill_prompts> XML 段落字符串,无 Prompt 技能时返回空字符串
|
|
784
|
+
"""
|
|
785
|
+
if not skill_registry:
|
|
786
|
+
return ""
|
|
787
|
+
|
|
788
|
+
# 收集所有 markdown 类型且未禁用的技能的 body
|
|
789
|
+
prompt_skills = []
|
|
790
|
+
try:
|
|
791
|
+
for skill in skill_registry._skills.values():
|
|
792
|
+
if skill_registry._is_disabled(skill.name):
|
|
793
|
+
continue
|
|
794
|
+
# 检查是否为 SKILL.md 格式技能
|
|
795
|
+
body = getattr(skill, 'get_body', None)
|
|
796
|
+
if body and callable(body):
|
|
797
|
+
content = body()
|
|
798
|
+
if content:
|
|
799
|
+
prompt_skills.append((skill.name, content))
|
|
800
|
+
except Exception as e:
|
|
801
|
+
logger.warning(f"获取 skill prompts 失败: {e}")
|
|
802
|
+
return ""
|
|
803
|
+
|
|
804
|
+
if not prompt_skills:
|
|
805
|
+
return ""
|
|
806
|
+
|
|
807
|
+
lines: List[str] = ["<skill_prompts>"]
|
|
808
|
+
for skill_name, content in prompt_skills:
|
|
809
|
+
safe_name = _xml_escape(skill_name)
|
|
810
|
+
lines.append(f"<{safe_name}>")
|
|
811
|
+
lines.append(content)
|
|
812
|
+
lines.append(f"</{safe_name}>")
|
|
813
|
+
lines.append("</skill_prompts>")
|
|
814
|
+
return "\n".join(lines)
|
|
815
|
+
|
|
769
816
|
# =========================================================================
|
|
770
817
|
# Token 预算管理
|
|
771
818
|
# =========================================================================
|
|
@@ -818,8 +865,8 @@ class ContextBuilder:
|
|
|
818
865
|
return re.sub(pattern, replacement, xml, count=1, flags=re.DOTALL)
|
|
819
866
|
|
|
820
867
|
# 按优先级从低到高裁剪(记忆最优先保留,知识库和任务计划优先裁剪)
|
|
821
|
-
# recent_summary
|
|
822
|
-
for tag in ['recent_summary', 'knowledge', 'task_plan', 'recall_memory', 'automemory']:
|
|
868
|
+
# recent_summary 是兜底机制,最先裁剪;skill_prompts 其次
|
|
869
|
+
for tag in ['recent_summary', 'skill_prompts', 'knowledge', 'task_plan', 'recall_memory', 'automemory']:
|
|
823
870
|
if estimated <= budget:
|
|
824
871
|
break
|
|
825
872
|
if f'<{tag}>' in context_xml:
|
package/core/vnc_manager.py
CHANGED
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
package/skills/__init__.py
CHANGED
|
File without changes
|
package/skills/base.py
CHANGED
|
File without changes
|
package/skills/browser_skill.py
CHANGED
|
File without changes
|
package/skills/chromedev_mcp.py
CHANGED
|
File without changes
|
package/skills/docx_skill.py
CHANGED
|
File without changes
|
package/skills/file_send.py
CHANGED
|
File without changes
|
package/skills/file_skill.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/skills/gui_skill.py
CHANGED
|
File without changes
|
package/skills/pdf_skill.py
CHANGED
|
File without changes
|
package/skills/ppt_skill.py
CHANGED
|
File without changes
|
package/skills/registry.py
CHANGED
|
@@ -215,6 +215,11 @@ def _load_skill_from_md(skill_dir: Path) -> Optional[Skill]:
|
|
|
215
215
|
for ref_file in sorted(ref_dir.glob("*.md")):
|
|
216
216
|
references.append(ref_file.name)
|
|
217
217
|
|
|
218
|
+
# 提取 SKILL.md body(YAML front matter 之后的内容)
|
|
219
|
+
body = ""
|
|
220
|
+
if len(parts) >= 3:
|
|
221
|
+
body = parts[2].strip()
|
|
222
|
+
|
|
218
223
|
class MarkdownSkill(Skill):
|
|
219
224
|
"""SKILL.md 格式的技能包装器"""
|
|
220
225
|
def __init__(self):
|
|
@@ -230,13 +235,21 @@ def _load_skill_from_md(skill_dir: Path) -> Optional[Skill]:
|
|
|
230
235
|
self._references = references
|
|
231
236
|
self._has_templates = (skill_dir / "templates").exists()
|
|
232
237
|
self._has_scripts = (skill_dir / "scripts").exists()
|
|
238
|
+
self._body = body
|
|
233
239
|
|
|
234
240
|
async def execute(self, **kwargs) -> SkillResult:
|
|
241
|
+
# SKILL.md 技能的指令已通过 <skill_prompts> 注入 system prompt
|
|
242
|
+
# 如果 LLM 仍然调用了此工具,返回确认信息
|
|
235
243
|
return SkillResult(
|
|
236
|
-
success=
|
|
237
|
-
|
|
244
|
+
success=True,
|
|
245
|
+
output=f"[{self.name}] 技能指令已激活,请按照 <skill_prompts> 中 {self.name} 的完整指令执行任务。",
|
|
246
|
+
message=f"技能 {self.name} 已激活",
|
|
238
247
|
)
|
|
239
248
|
|
|
249
|
+
def get_body(self) -> str:
|
|
250
|
+
"""返回 SKILL.md 的 body 内容"""
|
|
251
|
+
return self._body
|
|
252
|
+
|
|
240
253
|
def to_openclaw_format(self) -> Dict[str, Any]:
|
|
241
254
|
info = super().to_openclaw_format()
|
|
242
255
|
info["skill_type"] = "markdown"
|
package/skills/search_skill.py
CHANGED
|
File without changes
|
package/skills/system_skill.py
CHANGED
|
File without changes
|
package/skills/xlsx_skill.py
CHANGED
|
File without changes
|
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) ==========
|