feat: 优化agent插件及webUI对话页面

This commit is contained in:
Saboteur7
2025-05-22 17:31:32 +08:00
parent 8e6afa5614
commit 70d7e52df0
10 changed files with 602 additions and 100 deletions

3
.gitignore vendored
View File

@@ -15,6 +15,8 @@ plugins.json
itchat.pkl
*.log
logs/
workspace
config.yaml
user_datas.pkl
chatgpt_tool_hub/
plugins/**/
@@ -31,4 +33,5 @@ plugins/banwords/lib/__pycache__
!plugins/role
!plugins/keyword
!plugins/linkai
!plugins/agent
client_config.json

View File

@@ -6,7 +6,7 @@
<title>AI Assistant</title>
<link rel="icon" href="assets/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
@@ -14,6 +14,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/java.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/cpp.min.js"></script>
<script src="assets/axios.min.js"></script>
<style>
:root {
--primary-color: #10a37f;
@@ -200,7 +201,7 @@
display: flex;
justify-content: flex-end;
margin: 10px 0;
padding: 0 15px;
padding: 0 0;
}
.user-container .message-container {
@@ -267,68 +268,139 @@
.message {
padding: 12px 16px;
border-radius: 10px;
margin-top: 0; /* 移除顶部外边距 */
margin-top: 0;
margin-bottom: 8px;
word-wrap: break-word;
overflow-wrap: break-word;
line-height: 1.5;
}
.message p {
margin-top: 0.5em;
margin-bottom: 0.5em;
margin: 0.8em 0;
line-height: 1.6;
}
.message p:empty {
margin: 0;
line-height: 0.7em;
.message p:first-child {
margin-top: 0;
}
.message p:empty::before {
content: "";
display: inline-block;
height: 0.7em;
.message p:last-child {
margin-bottom: 0;
}
.message h1, .message h2, .message h3, .message h4, .message h5, .message h6 {
margin-top: 1.5em;
margin-bottom: 0.75em;
font-weight: 600;
line-height: 1.25;
}
.message h1 {
font-size: 1.5em;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.3em;
}
.message h2 {
font-size: 1.3em;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.3em;
}
.message h3 {
font-size: 1.15em;
}
.message h4 {
font-size: 1em;
}
.message ul, .message ol {
margin: 0.8em 0;
padding-left: 2em;
}
.message li {
margin: 0.3em 0;
}
.message li > p {
margin: 0.3em 0;
}
.message pre {
background-color: #f0f0f0;
padding: 10px;
border-radius: 4px;
padding: 12px;
border-radius: 6px;
overflow-x: auto;
margin: 1em 0;
}
.message code {
font-family: monospace;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
background-color: rgba(0, 0, 0, 0.05);
padding: 2px 4px;
border-radius: 3px;
font-size: 0.9em;
}
.message pre code {
background-color: transparent;
padding: 0;
border-radius: 0;
font-size: 0.9em;
line-height: 1.5;
}
.message blockquote {
border-left: 4px solid #ddd;
padding-left: 10px;
padding: 0 0 0 1em;
margin: 1em 0;
color: #777;
margin: 10px 0;
}
.message blockquote > :first-child {
margin-top: 0;
}
.message blockquote > :last-child {
margin-bottom: 0;
}
.message table {
border-collapse: collapse;
width: 100%;
margin: 10px 0;
margin: 1em 0;
overflow-x: auto;
display: block;
}
.message th, .message td {
border: 1px solid #ddd;
padding: 8px;
padding: 8px 12px;
text-align: left;
}
.message th {
background-color: #f2f2f2;
font-weight: 600;
}
.message tr:nth-child(even) {
background-color: #f9f9f9;
}
.message hr {
height: 1px;
background-color: var(--border-color);
border: none;
margin: 1.5em 0;
}
.message img {
max-width: 100%;
height: auto;
margin: 1em 0;
}
.timestamp {
@@ -487,7 +559,7 @@
}
.message-container {
padding: 10px;
padding: 10px 0 10px 0;
}
.bot-container {
@@ -621,28 +693,10 @@
font-weight: bold;
}
/* 基础消息样式 */
.message {
border-radius: 10px;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* 用户消息样式 */
.user-container .message {
background-color: var(--bot-msg-bg);
border-radius: 10px;
margin-bottom: 8px;
padding: 12px 16px;
}
/* 机器人消息样式 */
.bot-container .message {
background-color: var(--bot-msg-bg);
border-radius: 10px 10px 10px 0;
margin-bottom: 8px;
padding: 0px 16px 12px 16px;
margin-top: 0;
/* 确保代码块内的文本不会溢出 */
.hljs {
white-space: pre-wrap;
word-break: break-all;
}
</style>
</head>
@@ -728,6 +782,19 @@
let userId = 'user_' + Math.random().toString(36).substring(2, 10);
let currentSessionId = 'default_session'; // 使用固定会话ID
// 添加一个变量来跟踪输入法状态
let isComposing = false;
// 监听输入法组合状态开始
input.addEventListener('compositionstart', function() {
isComposing = true;
});
// 监听输入法组合状态结束
input.addEventListener('compositionend', function() {
isComposing = false;
});
// 自动调整文本区域高度
input.addEventListener('input', function() {
this.style.height = 'auto';
@@ -780,15 +847,19 @@
event.preventDefault();
}
// Enter 键发送消息
else if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey) {
// Enter 键发送消息,但只在不是输入法组合状态时
else if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !isComposing) {
sendMessage();
event.preventDefault();
}
});
// 在发送消息函数前添加调试代码
console.log('Axios loaded:', typeof axios !== 'undefined');
// 发送消息函数
function sendMessage() {
console.log('Send message function called');
const userMessage = input.value.trim();
if (userMessage) {
// 隐藏欢迎屏幕
@@ -811,33 +882,26 @@
sendButton.disabled = true;
// 发送到服务器并等待响应
fetch('/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
axios({
method: 'post',
url: '/message',
data: {
user_id: userId,
message: userMessage,
timestamp: timestamp.toISOString(),
session_id: currentSessionId
})
},
timeout: 120000 // 120秒超时
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to send message');
}
return response.json();
})
.then(data => {
// 移除加载消息
if (loadingContainer.parentNode) {
messagesDiv.removeChild(loadingContainer);
}
// 添加AI回复
if (data.reply) {
addBotMessage(data.reply, new Date());
if (response.data.reply) {
addBotMessage(response.data.reply, new Date());
}
})
.catch(error => {
@@ -847,7 +911,11 @@
messagesDiv.removeChild(loadingContainer);
}
// 显示错误消息
addBotMessage("抱歉,发生了错误,请稍后再试。", new Date());
if (error.code === 'ECONNABORTED') {
addBotMessage("请求超时,请再试一次吧。", new Date());
} else {
addBotMessage("抱歉,发生了错误,请稍后再试。", new Date());
}
});
}
}
@@ -882,35 +950,30 @@
return botContainer;
}
// 格式化消息内容处理Markdown和代码高亮
// 替换 formatMessage 函数,使用 markdown-it 替代 marked
function formatMessage(content) {
// 配置 marked 以使用 highlight.js
marked.setOptions({
highlight: function(code, language) {
if (language && hljs.getLanguage(language)) {
try {
return hljs.highlight(code, { language: language }).value;
} catch (e) {
console.error('Error highlighting code:', e);
return code;
}
}
return code;
},
breaks: true, // 启用换行符转换为 <br>
gfm: true, // 启用 GitHub 风格的 Markdown
headerIds: true, // 为标题生成ID
mangle: false, // 不转义内联HTML
sanitize: false, // 不净化输出
smartLists: true, // 使用更智能的列表行为
smartypants: false, // 不使用更智能的标点符号
xhtml: false // 不使用自闭合标签
});
try {
// 使用 marked 解析 Markdown
const parsed = marked.parse(content);
return parsed;
// 初始化 markdown-it 实例
const md = window.markdownit({
html: false, // 禁用 HTML 标签
xhtmlOut: false, // 使用 '/' 关闭单标签
breaks: true, // 将换行符转换为 <br>
linkify: true, // 自动将 URL 转换为链接
typographer: true, // 启用一些语言中性的替换和引号美化
highlight: function(str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value;
} catch (e) {
console.error('Error highlighting code:', e);
}
}
return hljs.highlightAuto(str).value;
}
});
// 渲染 Markdown
return md.render(content);
} catch (e) {
console.error('Error parsing markdown:', e);
// 如果解析失败,至少确保换行符正确显示
@@ -918,17 +981,30 @@
}
}
// 添加消息后应用代码高亮
// 更新 applyHighlighting 函数
function applyHighlighting() {
try {
document.querySelectorAll('pre code').forEach((block) => {
// 手动应用高亮
const language = block.className.replace('language-', '');
// 确保代码块有正确的类
if (!block.classList.contains('hljs')) {
block.classList.add('hljs');
}
// 尝试获取语言
let language = '';
block.classList.forEach(cls => {
if (cls.startsWith('language-')) {
language = cls.replace('language-', '');
}
});
// 应用高亮
if (language && hljs.getLanguage(language)) {
try {
hljs.highlightBlock(block);
} catch (e) {
console.error('Error highlighting block:', e);
console.error('Error highlighting specific language:', e);
hljs.highlightAuto(block);
}
} else {
hljs.highlightAuto(block);

2
channel/web/static/axios.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -126,7 +126,7 @@ class WebChannel(ChatChannel):
# 等待响应最多等待30秒
try:
response = response_queue.get(timeout=30)
response = response_queue.get(timeout=120)
return json.dumps({"status": "success", "reply": response["content"]})
except Empty:
return json.dumps({"status": "error", "message": "Response timeout"})
@@ -151,13 +151,27 @@ class WebChannel(ChatChannel):
logger.info(f"Created static directory: {static_dir}")
urls = (
'/', 'RootHandler', # 添加根路径处理器
'/message', 'MessageHandler',
'/chat', 'ChatHandler',
'/assets/(.*)', 'AssetsHandler', # 匹配 /assets/任何路径
)
port = conf().get("web_port", 9899)
app = web.application(urls, globals(), autoreload=False)
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
# 禁用web.py的默认日志输出
import io
from contextlib import redirect_stdout
# 临时重定向标准输出捕获web.py的启动消息
with redirect_stdout(io.StringIO()):
web.httpserver.runsimple(app.wsgifunc(), ("0.0.0.0", port))
class RootHandler:
def GET(self):
# 重定向到/chat
raise web.seeother('/chat')
class MessageHandler:
@@ -185,11 +199,6 @@ class AssetsHandler:
current_dir = os.path.dirname(os.path.abspath(__file__))
static_dir = os.path.join(current_dir, 'static')
# 打印调试信息
logger.info(f"Current directory: {current_dir}")
logger.info(f"Static directory: {static_dir}")
logger.info(f"Requested file: {file_path}")
full_path = os.path.normpath(os.path.join(static_dir, file_path))
# 安全检查确保请求的文件在static目录内

77
plugins/agent/README.md Normal file
View File

@@ -0,0 +1,77 @@
# AgentMesh Plugin
这个插件集成了 AgentMesh 多智能体框架,允许用户通过简单的命令使用多智能体团队来完成各种任务。
## 功能介绍
AgentMesh 是一个开源的多智能体平台,提供开箱即用的 Agent 开发框架、多 Agent 间的协同策略、任务规划和自主决策能力。通过这个插件,你可以:
- 使用预配置的智能体团队处理复杂任务
- 利用多智能体协作能力解决问题
- 访问各种工具,如搜索引擎、浏览器、文件系统等
## 安装
1. 确保已安装 AgentMesh SDK
```bash
pip install agentmesh-sdk>=0.1.0
```
2. 如需使用浏览器工具,还需安装:
```bash
pip install browser-use>=0.1.40
playwright install
```
## 配置
插件从项目根目录的 `config.yaml` 文件中读取配置。请确保该文件包含正确的团队配置。
配置示例:
```yaml
teams:
general_team:
description: "通用智能体团队,擅长于搜索、研究和执行各种任务"
model: "gpt-4o"
max_steps: 20
agents:
- name: "通用助手"
description: "全能的通用智能体"
system_prompt: "你是全能的通用智能体,可以帮助用户解决工作、生活、学习上的任何问题,以及使用工具解决各类复杂问题"
tools: ["google_search", "calculator", "current_time"]
```
## 使用方法
使用 `$agent` 前缀触发插件,支持以下命令:
- `$agent teams` - 列出可用的团队
- `$agent use [team_name] [task]` - 使用特定团队执行任务
- `$agent [task]` - 使用默认团队执行任务
### 示例
```
$agent teams
$agent use general_team 帮我分析多智能体技术发展趋势
$agent 帮我查看当前文件夹路径
```
## 工具支持
AgentMesh 支持多种工具,包括但不限于:
- `calculator`: 数学计算工具
- `current_time`: 获取当前时间
- `browser`: 浏览器操作工具
- `google_search`: 搜索引擎
- `file_save`: 文件保存工具
- `terminal`: 终端命令执行工具
## 注意事项
1. 确保 `config.yaml` 文件中包含正确的团队配置
2. 如果需要使用浏览器工具,请确保安装了相关依赖

View File

@@ -0,0 +1,3 @@
from .agent import AgentPlugin
__all__ = ["AgentPlugin"]

283
plugins/agent/agent.py Normal file
View File

@@ -0,0 +1,283 @@
import os
import yaml
from typing import Dict, List, Optional
from agentmesh import AgentTeam, Agent, LLMModel
from agentmesh.models import ClaudeModel
from agentmesh.tools import ToolManager
from config import conf
import plugins
from plugins import Plugin, Event, EventContext, EventAction
from bridge.context import ContextType
from bridge.reply import Reply, ReplyType
from common.log import logger
@plugins.register(
name="agent",
desc="Use AgentMesh framework to process tasks with multi-agent teams",
version="0.1.0",
author="Saboteur7",
desire_priority=1,
)
class AgentPlugin(Plugin):
"""Plugin for integrating AgentMesh framework."""
def __init__(self):
super().__init__()
self.handlers[Event.ON_HANDLE_CONTEXT] = self.on_handle_context
self.name = "agent"
self.description = "Use AgentMesh framework to process tasks with multi-agent teams"
self.config = self._load_config()
self.tool_manager = ToolManager()
self.tool_manager.load_tools(config_dict=self.config.get("tools"))
logger.info("[agent] inited")
def _load_config(self) -> Dict:
"""Load configuration from config.yaml file."""
config_path = os.path.join(self.path, "config.yaml")
if not os.path.exists(config_path):
logger.warning(f"Config file not found at {config_path}")
return {}
with open(config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
def get_help_text(self, verbose=False, **kwargs):
"""Return help message for the agent plugin."""
help_text = "AgentMesh插件: 使用多智能体团队处理任务和回答问题,支持多种工具和智能体协作能力。"
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
if not verbose:
return help_text
teams = self.get_available_teams()
teams_str = ", ".join(teams) if teams else "未配置任何团队"
help_text += "\n\n使用说明:\n"
help_text += f"{trigger_prefix}agent teams - 列出可用的团队\n"
help_text += f"{trigger_prefix}agent use [team_name] [task] - 使用特定团队执行任务\n"
help_text += f"{trigger_prefix}agent [task] - 使用默认团队执行任务\n\n"
help_text += f"可用团队: {teams_str}\n\n"
help_text += f"示例:\n"
help_text += f"{trigger_prefix}agent use general_team 帮我分析多智能体技术发展趋势\n"
help_text += f"{trigger_prefix}agent 帮我查看当前文件夹路径"
return help_text
def get_available_teams(self) -> List[str]:
"""Get list of available teams from configuration."""
teams_config = self.config.get("teams", {})
return list(teams_config.keys())
def create_team_from_config(self, team_name: str) -> Optional[AgentTeam]:
"""Create a team from configuration."""
# Get teams configuration
teams_config = self.config.get("teams", {})
# Check if the specified team exists
if team_name not in teams_config:
logger.error(f"Team '{team_name}' not found in configuration.")
available_teams = list(teams_config.keys())
logger.info(f"Available teams: {', '.join(available_teams)}")
return None
# Get team configuration
team_config = teams_config[team_name]
# Get team's model
team_model_name = team_config.get("model", "gpt-4.1-mini")
team_model = self.create_llm_model(team_model_name)
# Get team's max_steps (default to 20 if not specified)
team_max_steps = team_config.get("max_steps", 20)
# Create team with the model
team = AgentTeam(
name=team_name,
description=team_config.get("description", ""),
rule=team_config.get("rule", ""),
model=team_model,
max_steps=team_max_steps
)
# Create and add agents to the team
agents_config = team_config.get("agents", [])
for agent_config in agents_config:
# Check if agent has a specific model
if agent_config.get("model"):
agent_model = self.create_llm_model(agent_config.get("model"))
else:
agent_model = team_model
# Get agent's max_steps
agent_max_steps = agent_config.get("max_steps")
agent = Agent(
name=agent_config.get("name", ""),
system_prompt=agent_config.get("system_prompt", ""),
model=agent_model, # Use agent's model if specified, otherwise will use team's model
description=agent_config.get("description", ""),
max_steps=agent_max_steps
)
# Add tools to the agent if specified
tool_names = agent_config.get("tools", [])
for tool_name in tool_names:
tool = self.tool_manager.create_tool(tool_name)
if tool:
agent.add_tool(tool)
else:
if tool_name == "browser":
logger.warning(
"Tool 'Browser' loaded failed, "
"please install the required dependency with: \n"
"'pip install browser-use>=0.1.40' or 'pip install agentmesh-sdk[full]'\n"
)
else:
logger.warning(f"Tool '{tool_name}' not found for agent '{agent.name}'\n")
# Add agent to team
team.add(agent)
return team
def on_handle_context(self, e_context: EventContext):
"""Handle the message context."""
if e_context['context'].type != ContextType.TEXT:
return
content = e_context['context'].content
trigger_prefix = conf().get("plugin_trigger_prefix", "$")
if not content.startswith(f"{trigger_prefix}agent "):
e_context.action = EventAction.CONTINUE
return
if not self.config:
reply = Reply()
reply.type = ReplyType.ERROR
reply.content = "未找到插件配置,请在 plugins/agent 目录下创建 config.yaml 配置文件,可根据 config-template.yml 模板文件复制"
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
# Extract the actual task
task = content[len(f"{trigger_prefix}agent "):].strip()
# If task is empty, return help message
if not task:
reply = Reply()
reply.type = ReplyType.TEXT
reply.content = self.get_help_text(verbose=True)
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
# Check if task is asking for available teams
if task.lower() in ["teams", "list teams", "show teams"]:
teams = self.get_available_teams()
reply = Reply()
reply.type = ReplyType.TEXT
if not teams:
reply.content = "未配置任何团队。请检查 config.yaml 文件。"
else:
reply.content = f"可用团队: {', '.join(teams)}"
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
# Check if task specifies a team
team_name = None
if task.startswith("use "):
parts = task[4:].split(" ", 1)
if len(parts) > 0:
team_name = parts[0]
if len(parts) > 1:
task = parts[1].strip()
else:
reply = Reply()
reply.type = ReplyType.TEXT
reply.content = f"已选择团队 '{team_name}'。请输入您想执行的任务。"
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
if not team_name:
team_name = self.config.get("team")
# If no team specified, use default or first available
if not team_name:
teams = self.configself.get_available_teams()
if not teams:
reply = Reply()
reply.type = ReplyType.TEXT
reply.content = "未配置任何团队。请检查 config.yaml 文件。"
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
team_name = teams[0]
# Create team
team = self.create_team_from_config(team_name)
if not team:
reply = Reply()
reply.type = ReplyType.TEXT
reply.content = f"创建团队 '{team_name}' 失败。请检查配置。"
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
# Run the task
try:
logger.info(f"[agent] Running task '{task}' with team '{team_name}', team_model={team.model.model}")
result = team.run_async(task=task)
for agent_result in result:
res_text = f"🤖 {agent_result.get('agent_name')}\n\n{agent_result.get('final_answer')}"
_send_text(e_context, content=res_text)
reply = Reply()
reply.type = ReplyType.TEXT
reply.content = ""
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
except Exception as e:
logger.exception(f"Error running task with team '{team_name}'")
reply = Reply()
reply.type = ReplyType.ERROR
reply.content = f"执行任务时出错: {str(e)}"
e_context['reply'] = reply
e_context.action = EventAction.BREAK_PASS
return
def create_llm_model(self, model_name) -> LLMModel:
if conf().get("use_linkai"):
api_base = "https://api.link-ai.tech/v1"
api_key = conf().get("linkai_api_key")
elif model_name.startswith(("gpt", "text-davinci", "o1", "o3")):
api_base = conf().get("open_ai_api_base") or "https://api.openai.com/v1"
api_key = conf().get("open_ai_api_key")
elif model_name.startswith("claude"):
return ClaudeModel(model=model_name, api_key=conf().get("claude_api_key"))
elif model_name.startswith("moonshot"):
api_base = "https://api.moonshot.cn/v1"
api_key = conf().get("moonshot_api_key")
elif model_name.startswith("qwen"):
api_base = "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key = conf().get("dashscope_api_key")
else:
api_base = conf().get("open_ai_api_base") or "https://api.openai.com/v1"
api_key = conf().get("open_ai_api_key")
llm_model = LLMModel(model=model_name, api_key=api_key, api_base=api_base)
return llm_model
def _send_text(e_context: EventContext, content: str):
reply = Reply(ReplyType.TEXT, content)
channel = e_context["channel"]
channel.send(reply, e_context["context"])

View File

@@ -0,0 +1,49 @@
# 选中的Agent Team
team: general_team
tools:
google_search:
# get your apikey from https://serper.dev/
api_key: "e7a21d840d6bb0ba832d850bb5aa4dee337415c4"
# Team config
teams:
general_team:
model: "qwen-plus"
description: "A versatile research and information agent team"
max_steps: 5
agents:
- name: "通用智能助手"
description: "Universal assistant specializing in research, information synthesis, and task execution"
system_prompt: "You are a versatile assistant who answers questions and completes tasks using available tools. Reply in a clearly structured, attractive and easy to read format."
tools:
- time
- calculator
- google_search
- browser
- terminal
software_team:
model: "gpt-4.1-mini"
description: "A software development team with product manager, developer and tester."
rule: "A normal R&D process should be that Product Manager writes PRD, Developer writes code based on PRD, and Finally, Tester performs testing."
max_steps: 10
agents:
- name: "Product-Manager"
description: "Responsible for product requirements and documentation"
system_prompt: "You are an experienced product manager who creates concise PRDs, focusing on user needs and feature specifications. You always format your responses in Markdown."
tools:
- time
- file_save
- name: "Developer"
description: "Implements code based on PRD"
system_prompt: "You are a skilled developer. When developing web application, you creates single-page website based on user needs, you deliver HTML files with embedded JavaScript and CSS that are visually appealing, responsive, and user-friendly, featuring a grand layout and beautiful background. The HTML, CSS, and JavaScript code should be well-structured and effectively organized."
tools:
- file_save
- name: "Tester"
description: "Tests code and verifies functionality"
system_prompt: "You are a tester who validates code against requirements. For HTML applications, use browser tools to test functionality. For Python or other client-side applications, use the terminal tool to run and test. You only need to test a few core cases."
tools:
- file_save
- browser
- terminal

View File

@@ -155,7 +155,7 @@ def get_help_text(isadmin, isgroup):
for plugin in plugins:
if plugins[plugin].enabled and not plugins[plugin].hidden:
namecn = plugins[plugin].namecn
help_text += "\n%s:" % namecn
help_text += "\n%s: " % namecn
help_text += PluginManager().instances[plugin].get_help_text(verbose=False).strip()
if ADMIN_COMMANDS and isadmin:

View File

@@ -8,4 +8,4 @@ Pillow
pre-commit
web.py
linkai>=0.0.6.0
agentmesh-sdk>=0.1.2