feat(ui): 添加AI协作页签
新增AI协作功能模块,并在主界面中添加了对应的页签。 更新了OpenBB集成文档的路径,将其从单独的Markdown文件迁移到目录结构中。 为项目添加了新的测试依赖,包括pytest相关工具、locust和memory-profiler等。
This commit is contained in:
509
app/tabs/ai_collaboration_tab.py
Normal file
509
app/tabs/ai_collaboration_tab.py
Normal file
@@ -0,0 +1,509 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
四AI团队协作Web界面
|
||||
基于Streamlit的实时协作监控和管理界面
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import asyncio
|
||||
import json
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime, timedelta
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目路径到sys.path
|
||||
project_root = Path(__file__).parent.parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from src.jixia.coordination.ai_team_collaboration import (
|
||||
AITeamCollaboration, AIRole, MessageType, CollaborationType, WorkPhase
|
||||
)
|
||||
|
||||
# 页面配置
|
||||
st.set_page_config(
|
||||
page_title="四AI团队协作中心",
|
||||
page_icon="🤖",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
# 初始化协作系统
|
||||
@st.cache_resource
|
||||
def init_collaboration_system():
|
||||
"""初始化协作系统"""
|
||||
return AITeamCollaboration()
|
||||
|
||||
def main():
|
||||
"""主界面"""
|
||||
st.title("🤖 四AI团队协作中心")
|
||||
st.markdown("### OpenBB集成项目实时协作监控")
|
||||
|
||||
# 初始化系统
|
||||
collab = init_collaboration_system()
|
||||
|
||||
# 侧边栏
|
||||
with st.sidebar:
|
||||
st.header("🎯 项目状态")
|
||||
|
||||
# 当前阶段
|
||||
current_phase = st.selectbox(
|
||||
"当前工作阶段",
|
||||
[phase.value for phase in WorkPhase],
|
||||
index=list(WorkPhase).index(collab.current_phase)
|
||||
)
|
||||
|
||||
if st.button("更新阶段"):
|
||||
new_phase = WorkPhase(current_phase)
|
||||
asyncio.run(collab.advance_phase(new_phase))
|
||||
st.success(f"阶段已更新为: {current_phase}")
|
||||
st.rerun()
|
||||
|
||||
st.divider()
|
||||
|
||||
# AI状态概览
|
||||
st.subheader("🤖 AI状态概览")
|
||||
for ai_role, status in collab.ai_status.items():
|
||||
status_color = {
|
||||
"ready": "🟢",
|
||||
"active": "🔵",
|
||||
"waiting": "🟡",
|
||||
"completed_handoff": "✅",
|
||||
"received_handoff": "📥"
|
||||
}.get(status["status"], "⚪")
|
||||
|
||||
st.write(f"{status_color} **{ai_role.value}**")
|
||||
st.write(f" 📋 {status['current_task']}")
|
||||
st.write(f" 🎯 {status['role']}")
|
||||
|
||||
# 主要内容区域
|
||||
tab1, tab2, tab3, tab4, tab5 = st.tabs([
|
||||
"📢 主协作频道", "📊 AI仪表板", "🔄 工作流管理", "📈 协作分析", "⚙️ 系统管理"
|
||||
])
|
||||
|
||||
with tab1:
|
||||
render_main_collaboration(collab)
|
||||
|
||||
with tab2:
|
||||
render_ai_dashboard(collab)
|
||||
|
||||
with tab3:
|
||||
render_workflow_management(collab)
|
||||
|
||||
with tab4:
|
||||
render_collaboration_analytics(collab)
|
||||
|
||||
with tab5:
|
||||
render_system_management(collab)
|
||||
|
||||
def render_main_collaboration(collab):
|
||||
"""渲染主协作频道"""
|
||||
st.header("📢 主协作频道")
|
||||
|
||||
# 频道选择
|
||||
channel_options = {
|
||||
channel.name: channel_id
|
||||
for channel_id, channel in collab.channels.items()
|
||||
}
|
||||
|
||||
selected_channel_name = st.selectbox(
|
||||
"选择频道",
|
||||
list(channel_options.keys()),
|
||||
index=0
|
||||
)
|
||||
|
||||
selected_channel_id = channel_options[selected_channel_name]
|
||||
channel = collab.channels[selected_channel_id]
|
||||
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
# 消息历史
|
||||
st.subheader(f"💬 {channel.name}")
|
||||
|
||||
if channel.message_history:
|
||||
for msg in channel.message_history[-10:]: # 显示最近10条消息
|
||||
sender_emoji = {
|
||||
AIRole.QWEN: "🏗️",
|
||||
AIRole.CLAUDE: "💻",
|
||||
AIRole.GEMINI: "🧪",
|
||||
AIRole.ROVODEV: "📚"
|
||||
}.get(msg.sender, "🤖")
|
||||
|
||||
with st.chat_message(msg.sender.value, avatar=sender_emoji):
|
||||
st.write(f"**{msg.message_type.value}** - {msg.timestamp.strftime('%H:%M')}")
|
||||
st.write(msg.content)
|
||||
|
||||
if msg.attachments:
|
||||
st.write("📎 附件:")
|
||||
for attachment in msg.attachments:
|
||||
st.write(f" • {attachment}")
|
||||
|
||||
if msg.tags:
|
||||
tag_html = " ".join([f"<span style='background-color: #e1f5fe; padding: 2px 6px; border-radius: 4px; font-size: 0.8em;'>{tag}</span>" for tag in msg.tags])
|
||||
st.markdown(tag_html, unsafe_allow_html=True)
|
||||
else:
|
||||
st.info("暂无消息")
|
||||
|
||||
with col2:
|
||||
# 频道信息
|
||||
st.subheader("ℹ️ 频道信息")
|
||||
st.write(f"**类型**: {channel.channel_type.value}")
|
||||
st.write(f"**参与者**: {len(channel.participants)}人")
|
||||
st.write(f"**主持人**: {channel.moderator.value}")
|
||||
st.write(f"**消息数**: {len(channel.message_history)}")
|
||||
st.write(f"**最后活动**: {channel.last_activity.strftime('%Y-%m-%d %H:%M')}")
|
||||
|
||||
# 参与者列表
|
||||
st.write("**参与者列表**:")
|
||||
for participant in channel.participants:
|
||||
role_emoji = {
|
||||
AIRole.QWEN: "🏗️",
|
||||
AIRole.CLAUDE: "💻",
|
||||
AIRole.GEMINI: "🧪",
|
||||
AIRole.ROVODEV: "📚"
|
||||
}.get(participant, "🤖")
|
||||
st.write(f"{role_emoji} {participant.value}")
|
||||
|
||||
# 发送消息区域
|
||||
st.divider()
|
||||
st.subheader("📝 发送消息")
|
||||
|
||||
col1, col2, col3 = st.columns([2, 1, 1])
|
||||
|
||||
with col1:
|
||||
message_content = st.text_area("消息内容", height=100)
|
||||
|
||||
with col2:
|
||||
sender = st.selectbox(
|
||||
"发送者",
|
||||
[role.value for role in AIRole]
|
||||
)
|
||||
|
||||
message_type = st.selectbox(
|
||||
"消息类型",
|
||||
[msg_type.value for msg_type in MessageType]
|
||||
)
|
||||
|
||||
with col3:
|
||||
receiver = st.selectbox(
|
||||
"接收者",
|
||||
["广播"] + [role.value for role in AIRole]
|
||||
)
|
||||
|
||||
priority = st.slider("优先级", 1, 5, 1)
|
||||
|
||||
if st.button("发送消息", type="primary"):
|
||||
if message_content:
|
||||
try:
|
||||
receiver_role = None if receiver == "广播" else AIRole(receiver)
|
||||
|
||||
asyncio.run(collab.send_message(
|
||||
sender=AIRole(sender),
|
||||
content=message_content,
|
||||
message_type=MessageType(message_type),
|
||||
channel_id=selected_channel_id,
|
||||
receiver=receiver_role,
|
||||
priority=priority
|
||||
))
|
||||
|
||||
st.success("消息发送成功!")
|
||||
st.rerun()
|
||||
except Exception as e:
|
||||
st.error(f"发送失败: {str(e)}")
|
||||
else:
|
||||
st.warning("请输入消息内容")
|
||||
|
||||
def render_ai_dashboard(collab):
|
||||
"""渲染AI仪表板"""
|
||||
st.header("📊 AI工作仪表板")
|
||||
|
||||
# AI选择
|
||||
selected_ai = st.selectbox(
|
||||
"选择AI",
|
||||
[role.value for role in AIRole]
|
||||
)
|
||||
|
||||
ai_role = AIRole(selected_ai)
|
||||
dashboard = collab.get_ai_dashboard(ai_role)
|
||||
|
||||
# 基本信息
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
|
||||
with col1:
|
||||
st.metric("当前状态", dashboard["status"]["status"])
|
||||
|
||||
with col2:
|
||||
st.metric("活跃频道", len(dashboard["active_channels"]))
|
||||
|
||||
with col3:
|
||||
st.metric("待处理任务", len(dashboard["pending_tasks"]))
|
||||
|
||||
with col4:
|
||||
st.metric("协作得分", dashboard["collaboration_stats"]["collaboration_score"])
|
||||
|
||||
# 详细信息
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
# 待处理任务
|
||||
st.subheader("📋 待处理任务")
|
||||
if dashboard["pending_tasks"]:
|
||||
for task in dashboard["pending_tasks"]:
|
||||
with st.expander(f"{task['type']} - 优先级 {task['priority']}"):
|
||||
st.write(f"**来自**: {task['from']}")
|
||||
st.write(f"**频道**: {task['channel']}")
|
||||
st.write(f"**创建时间**: {task['created']}")
|
||||
st.write(f"**描述**: {task['description']}")
|
||||
else:
|
||||
st.info("暂无待处理任务")
|
||||
|
||||
with col2:
|
||||
# 最近消息
|
||||
st.subheader("📨 最近消息")
|
||||
if dashboard["recent_messages"]:
|
||||
for msg in dashboard["recent_messages"][:5]:
|
||||
priority_color = {
|
||||
1: "🔵", 2: "🟢", 3: "🟡", 4: "🟠", 5: "🔴"
|
||||
}.get(msg["priority"], "⚪")
|
||||
|
||||
st.write(f"{priority_color} **{msg['sender']}** 在 **{msg['channel']}**")
|
||||
st.write(f" {msg['content']}")
|
||||
st.write(f" ⏰ {msg['timestamp']}")
|
||||
st.divider()
|
||||
else:
|
||||
st.info("暂无最近消息")
|
||||
|
||||
# 协作统计
|
||||
st.subheader("📈 协作统计")
|
||||
|
||||
stats = dashboard["collaboration_stats"]
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.metric("发送消息", stats["messages_sent"])
|
||||
|
||||
with col2:
|
||||
st.metric("接收消息", stats["messages_received"])
|
||||
|
||||
with col3:
|
||||
st.metric("总消息数", stats["total_messages"])
|
||||
|
||||
def render_workflow_management(collab):
|
||||
"""渲染工作流管理"""
|
||||
st.header("🔄 工作流管理")
|
||||
|
||||
# 工作流规则
|
||||
st.subheader("📜 工作流规则")
|
||||
|
||||
rules_data = []
|
||||
for rule_id, rule in collab.workflow_rules.items():
|
||||
rules_data.append({
|
||||
"规则ID": rule.id,
|
||||
"规则名称": rule.name,
|
||||
"触发阶段": rule.trigger_phase.value,
|
||||
"目标AI": rule.target_ai.value if rule.target_ai else "无",
|
||||
"状态": "✅ 激活" if rule.is_active else "❌ 禁用"
|
||||
})
|
||||
|
||||
if rules_data:
|
||||
st.dataframe(pd.DataFrame(rules_data), use_container_width=True)
|
||||
|
||||
# 手动工作交接
|
||||
st.divider()
|
||||
st.subheader("🤝 手动工作交接")
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
from_ai = st.selectbox("交接方", [role.value for role in AIRole])
|
||||
to_ai = st.selectbox("接收方", [role.value for role in AIRole])
|
||||
|
||||
with col2:
|
||||
task_desc = st.text_input("任务描述")
|
||||
deliverables = st.text_area("交付物列表 (每行一个)")
|
||||
|
||||
with col3:
|
||||
notes = st.text_area("备注")
|
||||
|
||||
if st.button("执行工作交接"):
|
||||
if task_desc and from_ai != to_ai:
|
||||
deliverable_list = [d.strip() for d in deliverables.split('\n') if d.strip()]
|
||||
|
||||
try:
|
||||
asyncio.run(collab.handoff_work(
|
||||
from_ai=AIRole(from_ai),
|
||||
to_ai=AIRole(to_ai),
|
||||
task_description=task_desc,
|
||||
deliverables=deliverable_list,
|
||||
notes=notes
|
||||
))
|
||||
st.success("工作交接完成!")
|
||||
st.rerun()
|
||||
except Exception as e:
|
||||
st.error(f"交接失败: {str(e)}")
|
||||
else:
|
||||
st.warning("请填写完整信息,且交接方和接收方不能相同")
|
||||
|
||||
def render_collaboration_analytics(collab):
|
||||
"""渲染协作分析"""
|
||||
st.header("📈 协作分析")
|
||||
|
||||
# 消息统计
|
||||
st.subheader("💬 消息统计")
|
||||
|
||||
# 收集所有消息数据
|
||||
message_data = []
|
||||
for channel_id, channel in collab.channels.items():
|
||||
for msg in channel.message_history:
|
||||
message_data.append({
|
||||
"频道": channel.name,
|
||||
"发送者": msg.sender.value,
|
||||
"消息类型": msg.message_type.value,
|
||||
"优先级": msg.priority,
|
||||
"时间": msg.timestamp,
|
||||
"日期": msg.timestamp.date(),
|
||||
"小时": msg.timestamp.hour
|
||||
})
|
||||
|
||||
if message_data:
|
||||
df = pd.DataFrame(message_data)
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
# 按AI发送者统计
|
||||
sender_counts = df.groupby("发送者").size().reset_index()
|
||||
sender_counts.columns = ["AI", "消息数量"]
|
||||
|
||||
fig = px.bar(sender_counts, x="AI", y="消息数量",
|
||||
title="各AI发送消息统计")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
with col2:
|
||||
# 按消息类型统计
|
||||
type_counts = df.groupby("消息类型").size().reset_index()
|
||||
type_counts.columns = ["消息类型", "数量"]
|
||||
|
||||
fig = px.pie(type_counts, values="数量", names="消息类型",
|
||||
title="消息类型分布")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# 时间线分析
|
||||
st.subheader("⏰ 活跃度时间线")
|
||||
|
||||
if len(df) > 1:
|
||||
daily_counts = df.groupby("日期").size().reset_index()
|
||||
daily_counts.columns = ["日期", "消息数量"]
|
||||
|
||||
fig = px.line(daily_counts, x="日期", y="消息数量",
|
||||
title="每日消息数量趋势")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
# 频道活跃度
|
||||
st.subheader("📢 频道活跃度")
|
||||
channel_counts = df.groupby("频道").size().reset_index()
|
||||
channel_counts.columns = ["频道", "消息数量"]
|
||||
channel_counts = channel_counts.sort_values("消息数量", ascending=True)
|
||||
|
||||
fig = px.bar(channel_counts, x="消息数量", y="频道",
|
||||
orientation='h', title="各频道消息数量")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
|
||||
else:
|
||||
st.info("暂无消息数据用于分析")
|
||||
|
||||
def render_system_management(collab):
|
||||
"""渲染系统管理"""
|
||||
st.header("⚙️ 系统管理")
|
||||
|
||||
# 系统状态
|
||||
st.subheader("🔍 系统状态")
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
st.metric("活跃频道", len([c for c in collab.channels.values() if c.is_active]))
|
||||
|
||||
with col2:
|
||||
total_messages = sum(len(c.message_history) for c in collab.channels.values())
|
||||
st.metric("总消息数", total_messages)
|
||||
|
||||
with col3:
|
||||
st.metric("工作流规则", len(collab.workflow_rules))
|
||||
|
||||
# 频道管理
|
||||
st.subheader("📢 频道管理")
|
||||
|
||||
for channel_id, channel in collab.channels.items():
|
||||
with st.expander(f"{channel.name} ({channel.channel_type.value})"):
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
st.write(f"**描述**: {channel.description}")
|
||||
st.write(f"**参与者**: {len(channel.participants)}人")
|
||||
st.write(f"**消息数**: {len(channel.message_history)}")
|
||||
st.write(f"**状态**: {'🟢 活跃' if channel.is_active else '🔴 禁用'}")
|
||||
|
||||
with col2:
|
||||
st.write("**参与者列表**:")
|
||||
for participant in channel.participants:
|
||||
st.write(f" • {participant.value}")
|
||||
|
||||
st.write(f"**主持人**: {channel.moderator.value}")
|
||||
st.write(f"**最后活动**: {channel.last_activity.strftime('%Y-%m-%d %H:%M')}")
|
||||
|
||||
# 数据导出
|
||||
st.divider()
|
||||
st.subheader("📤 数据导出")
|
||||
|
||||
if st.button("导出协作数据"):
|
||||
# 准备导出数据
|
||||
export_data = {
|
||||
"channels": {},
|
||||
"ai_status": {},
|
||||
"workflow_rules": {},
|
||||
"system_info": {
|
||||
"current_phase": collab.current_phase.value,
|
||||
"export_time": datetime.now().isoformat()
|
||||
}
|
||||
}
|
||||
|
||||
# 频道数据
|
||||
for channel_id, channel in collab.channels.items():
|
||||
export_data["channels"][channel_id] = {
|
||||
"name": channel.name,
|
||||
"type": channel.channel_type.value,
|
||||
"participants": [p.value for p in channel.participants],
|
||||
"message_count": len(channel.message_history),
|
||||
"last_activity": channel.last_activity.isoformat()
|
||||
}
|
||||
|
||||
# AI状态数据
|
||||
for ai_role, status in collab.ai_status.items():
|
||||
export_data["ai_status"][ai_role.value] = status
|
||||
|
||||
# 工作流规则
|
||||
for rule_id, rule in collab.workflow_rules.items():
|
||||
export_data["workflow_rules"][rule_id] = {
|
||||
"name": rule.name,
|
||||
"description": rule.description,
|
||||
"trigger_phase": rule.trigger_phase.value,
|
||||
"action": rule.action,
|
||||
"is_active": rule.is_active
|
||||
}
|
||||
|
||||
# 创建下载链接
|
||||
json_str = json.dumps(export_data, indent=2, ensure_ascii=False)
|
||||
st.download_button(
|
||||
label="下载协作数据 (JSON)",
|
||||
data=json_str,
|
||||
file_name=f"ai_collaboration_data_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
|
||||
mime="application/json"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user