509 lines
17 KiB
Python
509 lines
17 KiB
Python
#!/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() |