🏗️ 项目重构:模块化清理完成
This commit is contained in:
1
modules/monitoring-dashboard/app/__init__.py
Normal file
1
modules/monitoring-dashboard/app/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# 应用模块
|
||||
228
modules/monitoring-dashboard/app/streamlit_app.py
Normal file
228
modules/monitoring-dashboard/app/streamlit_app.py
Normal file
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
炼妖壶 (Lianyaohu) - 稷下学宫AI辩论系统
|
||||
主Streamlit应用入口
|
||||
|
||||
重构版本:
|
||||
- 清晰的模块化结构
|
||||
- 统一的配置管理
|
||||
- 安全的密钥处理
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
def configure_page():
|
||||
"""配置页面基本设置"""
|
||||
st.set_page_config(
|
||||
page_title="炼妖壶 - 稷下学宫AI辩论系统",
|
||||
page_icon="🏛️",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
def show_header():
|
||||
"""显示页面头部"""
|
||||
st.title("🏛️ 炼妖壶 - 稷下学宫AI辩论系统")
|
||||
st.markdown("**基于中国哲学传统的多AI智能体辩论平台**")
|
||||
|
||||
# 显示系统状态
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("系统状态", "🟢 运行中")
|
||||
with col2:
|
||||
st.metric("AI模型", "OpenRouter")
|
||||
with col3:
|
||||
# 更新数据源展示,加入 OpenBB
|
||||
st.metric("数据源", "RapidAPI + OpenBB")
|
||||
|
||||
def show_sidebar():
|
||||
"""显示侧边栏"""
|
||||
with st.sidebar:
|
||||
st.markdown("## 🎛️ 控制面板")
|
||||
|
||||
# 系统信息
|
||||
st.markdown("### 📊 系统信息")
|
||||
st.info("**版本**: v2.0 (重构版)")
|
||||
st.info("**状态**: 迁移完成")
|
||||
|
||||
# 配置检查
|
||||
st.markdown("### 🔧 配置状态")
|
||||
try:
|
||||
from config.doppler_config import validate_config
|
||||
if validate_config():
|
||||
st.success("✅ 配置正常")
|
||||
else:
|
||||
st.error("❌ 配置异常")
|
||||
except Exception as e:
|
||||
st.warning(f"⚠️ 配置检查失败: {str(e)}")
|
||||
|
||||
# 快速操作
|
||||
st.markdown("### ⚡ 快速操作")
|
||||
if st.button("🧪 测试API连接"):
|
||||
test_api_connections()
|
||||
|
||||
if st.button("🏛️ 启动八仙论道"):
|
||||
start_jixia_debate()
|
||||
|
||||
def test_api_connections():
|
||||
"""测试API连接"""
|
||||
with st.spinner("正在测试API连接..."):
|
||||
try:
|
||||
from scripts.api_health_check import test_openrouter_api, test_rapidapi_connection
|
||||
|
||||
openrouter_ok = test_openrouter_api()
|
||||
rapidapi_ok = test_rapidapi_connection()
|
||||
|
||||
if openrouter_ok and rapidapi_ok:
|
||||
st.success("✅ 所有API连接正常")
|
||||
else:
|
||||
st.error("❌ 部分API连接失败")
|
||||
except Exception as e:
|
||||
st.error(f"❌ API测试异常: {str(e)}")
|
||||
|
||||
def start_jixia_debate():
|
||||
"""启动稷下学宫辩论"""
|
||||
with st.spinner("正在启动稷下学宫八仙论道..."):
|
||||
try:
|
||||
from config.settings import get_rapidapi_key
|
||||
from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine
|
||||
|
||||
api_key = get_rapidapi_key()
|
||||
engine = JixiaPerpetualEngine(api_key)
|
||||
|
||||
# 运行辩论
|
||||
results = engine.simulate_jixia_debate('TSLA')
|
||||
|
||||
st.success("✅ 八仙论道完成")
|
||||
st.json(results)
|
||||
except Exception as e:
|
||||
st.error(f"❌ 辩论启动失败: {str(e)}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
configure_page()
|
||||
show_header()
|
||||
show_sidebar()
|
||||
|
||||
# 主内容区域
|
||||
st.markdown("---")
|
||||
|
||||
# 选项卡(新增 OpenBB 数据页签和AI协作页签)
|
||||
tab1, tab2, tab3, tab4, tab5 = st.tabs(["🏛️ 稷下学宫", "🌍 天下体系", "📊 数据分析", "📈 OpenBB 数据", "🤖 AI协作"])
|
||||
|
||||
with tab1:
|
||||
st.markdown("### 🏛️ 稷下学宫 - 八仙论道")
|
||||
st.markdown("**多AI智能体辩论系统,基于中国传统八仙文化**")
|
||||
|
||||
# 辩论模式选择
|
||||
debate_mode = st.selectbox(
|
||||
"选择辩论模式",
|
||||
["ADK模式 (太上老君主持)", "传统模式 (RapidAPI数据)"]
|
||||
)
|
||||
|
||||
if debate_mode == "ADK模式 (太上老君主持)":
|
||||
from app.tabs.adk_debate_tab import render_adk_debate_tab
|
||||
render_adk_debate_tab()
|
||||
|
||||
else:
|
||||
# 传统模式
|
||||
col1, col2 = st.columns([2, 1])
|
||||
with col1:
|
||||
topic = st.text_input("辩论主题 (股票代码)", value="TSLA")
|
||||
with col2:
|
||||
if st.button("🎭 开始辩论", type="primary"):
|
||||
start_debate_session(topic)
|
||||
|
||||
# 显示辩论历史
|
||||
if 'debate_history' in st.session_state:
|
||||
st.markdown("#### 📜 辩论记录")
|
||||
for record in st.session_state.debate_history[-3:]: # 显示最近3次
|
||||
with st.expander(f"🎭 {record['topic']} - {record['time']}"):
|
||||
st.json(record['results'])
|
||||
|
||||
with tab2:
|
||||
st.markdown("### 🌍 天下体系分析")
|
||||
try:
|
||||
from app.tabs.tianxia_tab import render_tianxia_tab
|
||||
render_tianxia_tab()
|
||||
except Exception as e:
|
||||
st.error(f"❌ 天下体系模块加载失败: {str(e)}")
|
||||
|
||||
with tab3:
|
||||
st.markdown("### 📊 数据分析")
|
||||
st.info("🚧 数据分析模块开发中...")
|
||||
|
||||
# 显示系统统计
|
||||
try:
|
||||
from config.settings import get_rapidapi_key
|
||||
from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine
|
||||
|
||||
api_key = get_rapidapi_key()
|
||||
engine = JixiaPerpetualEngine(api_key)
|
||||
stats = engine.get_usage_stats()
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("API调用总数", stats['total_calls'])
|
||||
with col2:
|
||||
st.metric("活跃API数", f"{stats['active_apis']}/{stats['total_apis']}")
|
||||
with col3:
|
||||
st.metric("未使用API", stats['unused_count'])
|
||||
|
||||
except Exception as e:
|
||||
st.warning(f"⚠️ 无法加载统计数据: {str(e)}")
|
||||
|
||||
with tab5:
|
||||
st.markdown("### 🤖 AI协作")
|
||||
try:
|
||||
from app.tabs.ai_collaboration_tab import main as ai_collaboration_main
|
||||
ai_collaboration_main()
|
||||
except Exception as e:
|
||||
st.error(f"❌ AI协作模块加载失败: {str(e)}")
|
||||
|
||||
def start_debate_session(topic: str):
|
||||
"""启动辩论会话"""
|
||||
if not topic:
|
||||
st.error("请输入辩论主题")
|
||||
return
|
||||
|
||||
with st.spinner(f"🏛️ 八仙正在就 {topic} 展开论道..."):
|
||||
try:
|
||||
from config.settings import get_rapidapi_key
|
||||
from src.jixia.engines.perpetual_engine import JixiaPerpetualEngine
|
||||
from datetime import datetime
|
||||
|
||||
api_key = get_rapidapi_key()
|
||||
engine = JixiaPerpetualEngine(api_key)
|
||||
|
||||
# 运行辩论
|
||||
results = engine.simulate_jixia_debate(topic)
|
||||
|
||||
# 保存到会话状态
|
||||
if 'debate_history' not in st.session_state:
|
||||
st.session_state.debate_history = []
|
||||
|
||||
st.session_state.debate_history.append({
|
||||
'topic': topic,
|
||||
'time': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'results': {name: {'success': result.success, 'api_used': result.api_used}
|
||||
for name, result in results.items()}
|
||||
})
|
||||
|
||||
st.success(f"✅ 八仙论道完成!共有 {len(results)} 位仙人参与")
|
||||
|
||||
# 显示结果摘要
|
||||
successful_debates = sum(1 for result in results.values() if result.success)
|
||||
st.info(f"📊 成功获取数据: {successful_debates}/{len(results)} 位仙人")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"❌ 辩论启动失败: {str(e)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
205
modules/monitoring-dashboard/app/tabs/adk_debate_tab.py
Normal file
205
modules/monitoring-dashboard/app/tabs/adk_debate_tab.py
Normal file
@@ -0,0 +1,205 @@
|
||||
|
||||
import streamlit as st
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# Ensure the main project directory is in the Python path
|
||||
project_root = Path(__file__).parent.parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
try:
|
||||
from google.adk import Agent, Runner
|
||||
from google.adk.sessions import InMemorySessionService, Session
|
||||
from google.genai import types
|
||||
ADK_AVAILABLE = True
|
||||
except ImportError:
|
||||
ADK_AVAILABLE = False
|
||||
# 创建占位符类
|
||||
class Agent:
|
||||
pass
|
||||
class Runner:
|
||||
pass
|
||||
class InMemorySessionService:
|
||||
pass
|
||||
class Session:
|
||||
pass
|
||||
class types:
|
||||
class Content:
|
||||
pass
|
||||
class Part:
|
||||
pass
|
||||
|
||||
async def _get_llm_reply(runner: Runner, session: Session, prompt: str) -> str:
|
||||
"""Helper function to call a Runner and get a text reply."""
|
||||
content = types.Content(role='user', parts=[types.Part(text=prompt)])
|
||||
response = runner.run_async(
|
||||
user_id=session.user_id,
|
||||
session_id=session.id,
|
||||
new_message=content
|
||||
)
|
||||
|
||||
reply = ""
|
||||
async for event in response:
|
||||
if hasattr(event, 'content') and event.content and hasattr(event.content, 'parts'):
|
||||
for part in event.content.parts:
|
||||
if hasattr(part, 'text') and part.text:
|
||||
reply += str(part.text)
|
||||
elif hasattr(event, 'text') and event.text:
|
||||
reply += str(event.text)
|
||||
|
||||
return reply.strip()
|
||||
|
||||
async def run_adk_debate_streamlit(topic: str, participants: List[str], rounds: int):
|
||||
"""
|
||||
Runs the ADK turn-based debate and yields each statement for Streamlit display.
|
||||
"""
|
||||
try:
|
||||
yield "🚀 **启动ADK八仙轮流辩论 (太上老君主持)...**"
|
||||
|
||||
all_immortals = ["铁拐李", "吕洞宾", "何仙姑", "张果老", "蓝采和", "汉钟离", "韩湘子", "曹国舅"]
|
||||
if not participants:
|
||||
participants = all_immortals
|
||||
|
||||
character_configs = {
|
||||
"太上老君": {"name": "太上老君", "model": "gemini-1.5-pro", "instruction": "你是太上老君,天道化身,辩论的主持人。你的言辞沉稳、公正、充满智慧。你的任务是:1. 对辩论主题进行开场介绍。2. 在每轮开始时进行引导。3. 在辩论结束后,对所有观点进行全面、客观的总结。保持中立,不偏袒任何一方。"},
|
||||
"铁拐李": {"name": "铁拐李", "model": "gemini-1.5-flash", "instruction": "你是铁拐李,八仙中的逆向思维专家。你善于从批判和质疑的角度看问题,发言风格直接、犀利,但富有智慧。"},
|
||||
"吕洞宾": {"name": "吕洞宾", "model": "gemini-1.5-flash", "instruction": "你是吕洞宾,八仙中的理性分析者。你善于平衡各方观点,用理性和逻辑来分析问题,发言风格温和而深刻。"},
|
||||
"何仙姑": {"name": "何仙姑", "model": "gemini-1.5-flash", "instruction": "你是何仙姑,八仙中的风险控制专家。你总是从风险管理的角度思考问题,善于发现潜在危险,发言风格谨慎、细致。"},
|
||||
"张果老": {"name": "张果老", "model": "gemini-1.5-flash", "instruction": "你是张果老,八仙中的历史智慧者。你善于从历史数据中寻找规律和智慧,提供长期视角,发言风格沉稳、博学。"},
|
||||
"蓝采和": {"name": "蓝采和", "model": "gemini-1.5-flash", "instruction": "你是蓝采和,八仙中的创新思维者。你善于从新兴视角和非传统方法来看待问题,发言风格活泼、新颖。"},
|
||||
"汉钟离": {"name": "汉钟离", "model": "gemini-1.5-flash", "instruction": "你是汉钟离,八仙中的平衡协调者。你善于综合各方观点,寻求和谐统一的解决方案,发言风格平和、包容。"},
|
||||
"韩湘子": {"name": "韩湘子", "model": "gemini-1.5-flash", "instruction": "你是韩湘子,八仙中的艺术感知者。你善于从美学和感性的角度分析问题,发言风格优雅、感性。"},
|
||||
"曹国舅": {"name": "曹国舅", "model": "gemini-1.5-flash", "instruction": "你是曹国舅,八仙中的实务执行者。你关注实际操作和具体细节,发言风格务实、严谨。"}
|
||||
}
|
||||
|
||||
session_service = InMemorySessionService()
|
||||
session = await session_service.create_session(state={}, app_name="稷下学宫八仙论道系统-Streamlit", user_id="st_user")
|
||||
|
||||
runners: Dict[str, Runner] = {}
|
||||
for name, config in character_configs.items():
|
||||
if name == "太上老君" or name in participants:
|
||||
agent = Agent(name=config["name"], model=config["model"], instruction=config["instruction"])
|
||||
runners[name] = Runner(app_name="稷下学宫八仙论道系统-Streamlit", agent=agent, session_service=session_service)
|
||||
|
||||
host_runner = runners.get("太上老君")
|
||||
if not host_runner:
|
||||
yield "❌ **主持人太上老君初始化失败。**"
|
||||
return
|
||||
|
||||
yield f"🎯 **参与仙人**: {', '.join(participants)}"
|
||||
debate_history = []
|
||||
|
||||
# Opening statement
|
||||
opening_prompt = f"请为本次关于“{topic}”的辩论,发表一段公正、深刻的开场白,并宣布辩论开始。"
|
||||
opening_statement = await _get_llm_reply(host_runner, session, opening_prompt)
|
||||
yield f"👑 **太上老君**: {opening_statement}"
|
||||
|
||||
# Debate rounds
|
||||
for round_num in range(rounds):
|
||||
round_intro_prompt = f"请为第 {round_num + 1} 轮辩论说一段引导语。"
|
||||
round_intro = await _get_llm_reply(host_runner, session, round_intro_prompt)
|
||||
yield f"👑 **太上老君**: {round_intro}"
|
||||
|
||||
for name in participants:
|
||||
if name not in runners: continue
|
||||
|
||||
history_context = f"\n最近的论道内容:\n" + "\n".join([f"- {h}" for h in debate_history[-5:]]) if debate_history else ""
|
||||
prompt = f"论道主题: {topic}{history_context}\n\n请从你的角色特点出发,简洁地发表观点。"
|
||||
|
||||
reply = await _get_llm_reply(runners[name], session, prompt)
|
||||
yield f"🗣️ **{name}**: {reply}"
|
||||
|
||||
debate_history.append(f"{name}: {reply}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Summary
|
||||
summary_prompt = f"辩论已结束。以下是完整的辩论记录:\n\n{' '.join(debate_history)}\n\n请对本次辩论进行全面、公正、深刻的总结。"
|
||||
summary = await _get_llm_reply(host_runner, session, summary_prompt)
|
||||
yield f"👑 **太上老君**: {summary}"
|
||||
|
||||
for runner in runners.values():
|
||||
await runner.close()
|
||||
|
||||
yield "🎉 **ADK八仙轮流辩论完成!**"
|
||||
|
||||
except Exception as e:
|
||||
yield f"❌ **运行ADK八仙轮流辩论失败**: {e}"
|
||||
import traceback
|
||||
st.error(traceback.format_exc())
|
||||
|
||||
def render_adk_debate_tab():
|
||||
"""Renders the Streamlit UI for the ADK Debate tab."""
|
||||
|
||||
# 检查 ADK 是否可用
|
||||
if not ADK_AVAILABLE:
|
||||
st.error("🚫 Google ADK 模块未安装或不可用")
|
||||
st.info("📦 正在安装 Google ADK,请稍候...")
|
||||
st.info("💡 安装完成后请刷新页面")
|
||||
|
||||
with st.expander("📋 安装说明"):
|
||||
st.code("""
|
||||
# 安装 Google ADK
|
||||
pip install google-adk>=1.12.0
|
||||
|
||||
# 或从 GitHub 安装开发版
|
||||
pip install git+https://github.com/google/adk-python.git@main
|
||||
""")
|
||||
return
|
||||
st.markdown("### 🏛️ 八仙论道 (ADK版 - 太上老君主持)")
|
||||
|
||||
topic = st.text_input(
|
||||
"辩论主题",
|
||||
value="AI是否应该拥有创造力?",
|
||||
key="adk_topic_input"
|
||||
)
|
||||
|
||||
all_immortals = ["铁拐李", "吕洞宾", "何仙姑", "张果老", "蓝采和", "汉钟离", "韩湘子", "曹国舅"]
|
||||
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
rounds = st.number_input("辩论轮数", min_value=1, max_value=5, value=1, key="adk_rounds_input")
|
||||
with col2:
|
||||
participants = st.multiselect(
|
||||
"选择参与的仙人 (默认全选)",
|
||||
options=all_immortals,
|
||||
default=all_immortals,
|
||||
key="adk_participants_select"
|
||||
)
|
||||
|
||||
if st.button("🚀 开始论道", key="start_adk_debate_button", type="primary"):
|
||||
if not topic:
|
||||
st.error("请输入辩论主题。")
|
||||
return
|
||||
if not participants:
|
||||
st.error("请至少选择一位参与的仙人。")
|
||||
return
|
||||
|
||||
st.markdown("---")
|
||||
st.markdown("#### 📜 论道实录")
|
||||
|
||||
# Placeholder for real-time output
|
||||
output_container = st.empty()
|
||||
full_log = ""
|
||||
|
||||
# Run the async debate function
|
||||
try:
|
||||
# Get a new event loop for the thread
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# Create a coroutine object
|
||||
coro = run_adk_debate_streamlit(topic, participants, rounds)
|
||||
|
||||
# Run the coroutine until completion
|
||||
for message in loop.run_until_complete(async_generator_to_list(coro)):
|
||||
full_log += message + "\n\n"
|
||||
output_container.markdown(full_log)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"启动辩论时发生错误: {e}")
|
||||
|
||||
async def async_generator_to_list(async_gen):
|
||||
"""Helper to consume an async generator and return a list of its items."""
|
||||
return [item async for item in async_gen]
|
||||
509
modules/monitoring-dashboard/app/tabs/ai_collaboration_tab.py
Normal file
509
modules/monitoring-dashboard/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()
|
||||
184
modules/monitoring-dashboard/app/tabs/openbb_tab.py
Normal file
184
modules/monitoring-dashboard/app/tabs/openbb_tab.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
def _check_openbb_installed() -> bool:
|
||||
try:
|
||||
# OpenBB v4 推荐用法: from openbb import obb
|
||||
from openbb import obb # noqa: F401
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _load_price_data(symbol: str, days: int = 365) -> pd.DataFrame:
|
||||
"""Fetch OHLCV using OpenBB v4 when available; otherwise return demo/synthetic data."""
|
||||
end = datetime.utcnow().date()
|
||||
start = end - timedelta(days=days)
|
||||
|
||||
# 优先使用 OpenBB v4
|
||||
try:
|
||||
from openbb import obb
|
||||
|
||||
# 先尝试股票路由
|
||||
try:
|
||||
out = obb.equity.price.historical(
|
||||
symbol,
|
||||
start_date=str(start),
|
||||
end_date=str(end),
|
||||
)
|
||||
except Exception:
|
||||
out = None
|
||||
|
||||
# 若股票无数据,再尝试 ETF 路由
|
||||
if out is None or (hasattr(out, "is_empty") and out.is_empty):
|
||||
try:
|
||||
out = obb.etf.price.historical(
|
||||
symbol,
|
||||
start_date=str(start),
|
||||
end_date=str(end),
|
||||
)
|
||||
except Exception:
|
||||
out = None
|
||||
|
||||
if out is not None:
|
||||
if hasattr(out, "to_df"):
|
||||
df = out.to_df()
|
||||
elif hasattr(out, "to_dataframe"):
|
||||
df = out.to_dataframe()
|
||||
else:
|
||||
# 兜底: 有些 provider 返回可序列化对象
|
||||
df = pd.DataFrame(out) # type: ignore[arg-type]
|
||||
|
||||
# 规格化列名
|
||||
if not isinstance(df, pd.DataFrame) or df.empty:
|
||||
raise ValueError("OpenBB 返回空数据")
|
||||
|
||||
# 有的表以 index 为日期
|
||||
if 'date' in df.columns:
|
||||
df['Date'] = pd.to_datetime(df['date'])
|
||||
elif df.index.name in ('date', 'Date') or isinstance(df.index, pd.DatetimeIndex):
|
||||
df = df.copy()
|
||||
df['Date'] = pd.to_datetime(df.index)
|
||||
else:
|
||||
# 尝试查找常见日期列
|
||||
for cand in ['timestamp', 'time', 'datetime']:
|
||||
if cand in df.columns:
|
||||
df['Date'] = pd.to_datetime(df[cand])
|
||||
break
|
||||
|
||||
# 归一化收盘价列
|
||||
close_col = None
|
||||
for cand in ['adj_close', 'close', 'Close', 'price', 'close_price', 'c']:
|
||||
if cand in df.columns:
|
||||
close_col = cand
|
||||
break
|
||||
if close_col is None:
|
||||
raise ValueError("未找到收盘价列")
|
||||
df['Close'] = pd.to_numeric(df[close_col], errors='coerce')
|
||||
|
||||
# 仅保留需要列并清洗
|
||||
if 'Date' not in df.columns:
|
||||
raise ValueError("未找到日期列")
|
||||
df = df[['Date', 'Close']].dropna()
|
||||
df = df.sort_values('Date').reset_index(drop=True)
|
||||
# 限定时间窗口(有些 provider 可能返回更长区间)
|
||||
df = df[df['Date'].dt.date.between(start, end)]
|
||||
|
||||
if df.empty:
|
||||
raise ValueError("清洗后为空")
|
||||
return df
|
||||
|
||||
except Exception:
|
||||
# 如果 OpenBB 不可用或调用失败,进入本地演示/合成数据兜底
|
||||
pass
|
||||
|
||||
# Fallback to demo from examples/data
|
||||
try:
|
||||
from pathlib import Path
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
demo_map = {
|
||||
'AAPL': root / 'examples' / 'data' / 'demo_results_aapl.json',
|
||||
'MSFT': root / 'examples' / 'data' / 'demo_results_msft.json',
|
||||
'TSLA': root / 'examples' / 'data' / 'demo_results_tsla.json',
|
||||
}
|
||||
path = demo_map.get(symbol.upper())
|
||||
if path and path.exists():
|
||||
df = pd.read_json(path)
|
||||
if 'date' in df.columns:
|
||||
df['Date'] = pd.to_datetime(df['date'])
|
||||
if 'close' in df.columns:
|
||||
df['Close'] = df['close']
|
||||
df = df[['Date', 'Close']].dropna().sort_values('Date').reset_index(drop=True)
|
||||
# 裁剪到时间窗口
|
||||
df = df[df['Date'].dt.date.between(start, end)]
|
||||
return df
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Last resort: minimal synthetic data(避免 FutureWarning)
|
||||
dates = pd.date_range(end=end, periods=min(days, 180))
|
||||
return pd.DataFrame({
|
||||
'Date': dates,
|
||||
'Close': pd.Series(range(len(dates))).rolling(5).mean().bfill()
|
||||
})
|
||||
|
||||
|
||||
def _kpis_from_df(df: pd.DataFrame) -> dict:
|
||||
if df.empty or 'Close' not in df.columns:
|
||||
return {"最新价": "-", "近30日涨幅": "-", "最大回撤(近90日)": "-"}
|
||||
latest = float(df['Close'].iloc[-1])
|
||||
last_30 = df.tail(30)
|
||||
if len(last_30) > 1:
|
||||
pct_30 = (last_30['Close'].iloc[-1] / last_30['Close'].iloc[0] - 1) * 100
|
||||
else:
|
||||
pct_30 = 0.0
|
||||
# max drawdown over last 90 days
|
||||
lookback = df.tail(90)['Close']
|
||||
roll_max = lookback.cummax()
|
||||
drawdown = (lookback / roll_max - 1).min() * 100
|
||||
return {
|
||||
"最新价": f"{latest:,.2f}",
|
||||
"近30日涨幅": f"{pct_30:.2f}%",
|
||||
"最大回撤(近90日)": f"{drawdown:.2f}%",
|
||||
}
|
||||
|
||||
|
||||
def render_openbb_tab():
|
||||
st.write("使用 OpenBB(如可用)或演示数据展示市场概览。")
|
||||
|
||||
col_a, col_b = st.columns([2, 1])
|
||||
with col_b:
|
||||
symbol = st.text_input("股票/ETF 代码", value="AAPL")
|
||||
days = st.slider("时间窗口(天)", 90, 720, 365, step=30)
|
||||
obb_ready = _check_openbb_installed()
|
||||
if obb_ready:
|
||||
st.success("OpenBB 已安装 ✅")
|
||||
else:
|
||||
st.info("未检测到 OpenBB,将使用演示数据。可在 requirements.txt 中加入 openbb 后安装启用。")
|
||||
|
||||
with col_a:
|
||||
df = _load_price_data(symbol, days)
|
||||
if df is None or df.empty:
|
||||
st.warning("未获取到数据")
|
||||
return
|
||||
# 绘制收盘价
|
||||
if 'Date' in df.columns and 'Close' in df.columns:
|
||||
fig = px.line(df, x='Date', y='Close', title=f"{symbol.upper()} 收盘价")
|
||||
st.plotly_chart(fig, use_container_width=True)
|
||||
else:
|
||||
st.dataframe(df.head())
|
||||
|
||||
# KPI 卡片
|
||||
st.markdown("#### 关键指标")
|
||||
kpis = _kpis_from_df(df)
|
||||
k1, k2, k3 = st.columns(3)
|
||||
k1.metric("最新价", kpis["最新价"])
|
||||
k2.metric("近30日涨幅", kpis["近30日涨幅"])
|
||||
k3.metric("最大回撤(近90日)", kpis["最大回撤(近90日)"])
|
||||
|
||||
# 未来:基本面、新闻、情绪等组件占位
|
||||
with st.expander("🚧 更多组件(即将推出)"):
|
||||
st.write("基本面卡片、新闻与情绪、宏观指标、策略筛选等将逐步接入。")
|
||||
436
modules/monitoring-dashboard/app/tabs/tianxia_tab.py
Normal file
436
modules/monitoring-dashboard/app/tabs/tianxia_tab.py
Normal file
@@ -0,0 +1,436 @@
|
||||
"""
|
||||
天下体系 - 儒门天下观资本生态分析Tab
|
||||
基于"天命树"结构模型分析全球资本市场权力结构
|
||||
|
||||
重构版本:
|
||||
- 移除硬编码API密钥
|
||||
- 使用统一配置管理
|
||||
- 改进数据结构
|
||||
- 增强错误处理
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import pandas as pd
|
||||
import plotly.express as px
|
||||
from datetime import datetime
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, List, Any, Optional
|
||||
from dataclasses import dataclass
|
||||
|
||||
# 导入配置管理
|
||||
try:
|
||||
from config.settings import get_rapidapi_key
|
||||
except ImportError:
|
||||
# 如果配置模块不可用,使用环境变量
|
||||
import os
|
||||
def get_rapidapi_key():
|
||||
return os.getenv('RAPIDAPI_KEY', '')
|
||||
|
||||
@dataclass
|
||||
class StockEntity:
|
||||
"""股票实体数据类"""
|
||||
symbol: str
|
||||
name: str
|
||||
role: str
|
||||
dependency: Optional[str] = None
|
||||
serves: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
|
||||
@dataclass
|
||||
class EcosystemData:
|
||||
"""生态系统数据类"""
|
||||
tianzi: Dict[str, str]
|
||||
dafu: List[StockEntity]
|
||||
shi: List[StockEntity]
|
||||
jiajie: List[StockEntity]
|
||||
|
||||
class TianxiaAnalyzer:
|
||||
"""天下体系分析器 - 天命树结构分析"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化分析器"""
|
||||
try:
|
||||
self.rapidapi_key = get_rapidapi_key()
|
||||
except Exception:
|
||||
self.rapidapi_key = ""
|
||||
st.warning("⚠️ 未配置RapidAPI密钥,将使用模拟数据")
|
||||
|
||||
# 定义三大天命树生态系统
|
||||
self.ecosystems = self._initialize_ecosystems()
|
||||
|
||||
def _initialize_ecosystems(self) -> Dict[str, EcosystemData]:
|
||||
"""初始化生态系统数据"""
|
||||
return {
|
||||
'AI': EcosystemData(
|
||||
tianzi={'symbol': 'NVDA', 'name': 'NVIDIA', 'tianming': 'CUDA + GPU硬件,定义AI计算范式'},
|
||||
dafu=[
|
||||
StockEntity('TSM', 'TSMC', '芯片代工', '高端芯片唯一代工厂'),
|
||||
StockEntity('000660.SZ', 'SK Hynix', 'HBM内存', 'GPU性能关键'),
|
||||
StockEntity('MU', 'Micron', 'HBM内存', 'GPU性能关键'),
|
||||
StockEntity('SMCI', 'Supermicro', '服务器集成', 'GPU转化为计算能力')
|
||||
],
|
||||
shi=[
|
||||
StockEntity('ASML', 'ASML', '光刻设备', serves='TSMC'),
|
||||
StockEntity('AMAT', 'Applied Materials', '半导体设备', serves='TSMC')
|
||||
],
|
||||
jiajie=[
|
||||
StockEntity('AMD', 'AMD', '竞争对手', type='竞争天子'),
|
||||
StockEntity('GOOGL', 'Google', '云计算', type='云计算天子'),
|
||||
StockEntity('AMZN', 'Amazon', '云计算', type='云计算天子')
|
||||
]
|
||||
),
|
||||
'EV': EcosystemData(
|
||||
tianzi={'symbol': 'TSLA', 'name': 'Tesla', 'tianming': '软件定义汽车 + 超级充电网络'},
|
||||
dafu=[
|
||||
StockEntity('300750.SZ', 'CATL', '动力电池', '动力系统基石'),
|
||||
StockEntity('6752.T', 'Panasonic', '动力电池', '动力系统基石'),
|
||||
StockEntity('ALB', 'Albemarle', '锂矿', '源头命脉'),
|
||||
StockEntity('002460.SZ', 'Ganfeng Lithium', '锂矿', '源头命脉')
|
||||
],
|
||||
shi=[
|
||||
StockEntity('002497.SZ', 'Yahua Industrial', '氢氧化锂', serves='CATL'),
|
||||
StockEntity('002850.SZ', 'Kedali', '精密结构件', serves='CATL')
|
||||
],
|
||||
jiajie=[
|
||||
StockEntity('002594.SZ', 'BYD', '电动车', type='诸侯'),
|
||||
StockEntity('VWAGY', 'Volkswagen', '传统车企', type='诸侯'),
|
||||
StockEntity('F', 'Ford', '传统车企', type='诸侯')
|
||||
]
|
||||
),
|
||||
'Consumer_Electronics': EcosystemData(
|
||||
tianzi={'symbol': 'AAPL', 'name': 'Apple', 'tianming': 'iOS + App Store生态系统'},
|
||||
dafu=[
|
||||
StockEntity('2317.TW', 'Foxconn', '代工制造', '物理执行者'),
|
||||
StockEntity('TSM', 'TSMC', '芯片代工', '性能优势保障'),
|
||||
StockEntity('005930.KS', 'Samsung Display', '屏幕供应', '显示技术'),
|
||||
StockEntity('QCOM', 'Qualcomm', '基带芯片', '通信命脉')
|
||||
],
|
||||
shi=[
|
||||
StockEntity('002475.SZ', 'Luxshare', '精密制造', serves='Foxconn'),
|
||||
StockEntity('002241.SZ', 'Goertek', '声学器件', serves='Foxconn')
|
||||
],
|
||||
jiajie=[
|
||||
StockEntity('005930.KS', 'Samsung', '手机制造', type='亦敌亦友天子'),
|
||||
StockEntity('1810.HK', 'Xiaomi', '手机制造', type='诸侯'),
|
||||
StockEntity('NVDA', 'NVIDIA', 'AI芯片', type='跨生态天子')
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
def get_stock_data(self, symbol: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取股票数据
|
||||
|
||||
Args:
|
||||
symbol: 股票代码
|
||||
|
||||
Returns:
|
||||
股票数据字典
|
||||
"""
|
||||
# TODO: 实现真实API调用
|
||||
# 目前使用模拟数据
|
||||
try:
|
||||
return {
|
||||
'price': round(random.uniform(50, 500), 2),
|
||||
'change_pct': round(random.uniform(-5, 5), 2),
|
||||
'market_cap': f"{random.randint(100, 3000)}B",
|
||||
'volume': random.randint(1000000, 100000000)
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
'price': 'N/A',
|
||||
'change_pct': 0,
|
||||
'market_cap': 'N/A',
|
||||
'volume': 'N/A'
|
||||
}
|
||||
|
||||
def create_tianming_card(self, ecosystem_name: str, ecosystem_data: EcosystemData) -> None:
|
||||
"""
|
||||
创建天命卡片
|
||||
|
||||
Args:
|
||||
ecosystem_name: 生态系统名称
|
||||
ecosystem_data: 生态系统数据
|
||||
"""
|
||||
tianzi = ecosystem_data.tianzi
|
||||
stock_data = self.get_stock_data(tianzi['symbol'])
|
||||
|
||||
st.markdown(f"### 👑 {ecosystem_name} 天命树")
|
||||
|
||||
# 天子信息
|
||||
col1, col2, col3 = st.columns([1, 2, 1])
|
||||
|
||||
with col1:
|
||||
st.markdown("#### 🌟 天子")
|
||||
st.markdown(f"**{tianzi['name']}** ({tianzi['symbol']})")
|
||||
|
||||
with col2:
|
||||
st.markdown("#### 📜 天命")
|
||||
st.info(tianzi['tianming'])
|
||||
|
||||
with col3:
|
||||
st.metric(
|
||||
label="股价",
|
||||
value=f"${stock_data['price']}",
|
||||
delta=f"{stock_data['change_pct']:+.2f}%"
|
||||
)
|
||||
|
||||
# 大夫层级
|
||||
if ecosystem_data.dafu:
|
||||
st.markdown("#### 🏛️ 大夫 (核心依赖)")
|
||||
dafu_cols = st.columns(min(len(ecosystem_data.dafu), 4))
|
||||
|
||||
for i, dafu in enumerate(ecosystem_data.dafu):
|
||||
col_index = i % 4
|
||||
with dafu_cols[col_index]:
|
||||
data = self.get_stock_data(dafu.symbol)
|
||||
st.metric(
|
||||
label=f"{dafu.name}",
|
||||
value=f"${data['price']}",
|
||||
delta=f"{data['change_pct']:+.2f}%"
|
||||
)
|
||||
st.caption(f"**{dafu.role}**: {dafu.dependency}")
|
||||
|
||||
# 士层级
|
||||
if ecosystem_data.shi:
|
||||
st.markdown("#### ⚔️ 士 (专业供应商)")
|
||||
shi_cols = st.columns(min(len(ecosystem_data.shi), 3))
|
||||
|
||||
for i, shi in enumerate(ecosystem_data.shi):
|
||||
col_index = i % 3
|
||||
with shi_cols[col_index]:
|
||||
data = self.get_stock_data(shi.symbol)
|
||||
st.metric(
|
||||
label=f"{shi.name}",
|
||||
value=f"${data['price']}",
|
||||
delta=f"{data['change_pct']:+.2f}%"
|
||||
)
|
||||
st.caption(f"**{shi.role}** → 服务于{shi.serves}")
|
||||
|
||||
# 嫁接关系
|
||||
if ecosystem_data.jiajie:
|
||||
st.markdown("#### 🔗 嫁接关系 (跨生态链接)")
|
||||
jiajie_cols = st.columns(min(len(ecosystem_data.jiajie), 4))
|
||||
|
||||
for i, jiajie in enumerate(ecosystem_data.jiajie):
|
||||
col_index = i % 4
|
||||
with jiajie_cols[col_index]:
|
||||
data = self.get_stock_data(jiajie.symbol)
|
||||
st.metric(
|
||||
label=f"{jiajie.name}",
|
||||
value=f"${data['price']}",
|
||||
delta=f"{data['change_pct']:+.2f}%"
|
||||
)
|
||||
st.caption(f"**{jiajie.type}**")
|
||||
|
||||
st.markdown("---")
|
||||
|
||||
def create_tianming_tree_table(self) -> pd.DataFrame:
|
||||
"""
|
||||
创建天命树完整表格 - 用于投资组合去相关性分析
|
||||
|
||||
Returns:
|
||||
包含所有股票信息的DataFrame
|
||||
"""
|
||||
st.markdown("### 📋 天命树完整表格 - 投资组合去相关性分析")
|
||||
st.markdown("**核心理念**: 投资组合的本质是去相关性 - 从不同root下的不同spine下的不同leaf进行配置")
|
||||
|
||||
all_stocks = []
|
||||
|
||||
for eco_name, eco_data in self.ecosystems.items():
|
||||
# 天子
|
||||
tianzi = eco_data.tianzi
|
||||
stock_data = self.get_stock_data(tianzi['symbol'])
|
||||
all_stocks.append({
|
||||
'Root': eco_name,
|
||||
'Level': '👑 天子',
|
||||
'Symbol': tianzi['symbol'],
|
||||
'Company': tianzi['name'],
|
||||
'Role': '定义范式',
|
||||
'Dependency_Path': f"{eco_name}",
|
||||
'Price': stock_data['price'],
|
||||
'Change%': stock_data['change_pct'],
|
||||
'Market_Cap': stock_data['market_cap'],
|
||||
'Correlation_Risk': '极高 - 生态核心'
|
||||
})
|
||||
|
||||
# 大夫
|
||||
for dafu in eco_data.dafu:
|
||||
stock_data = self.get_stock_data(dafu.symbol)
|
||||
all_stocks.append({
|
||||
'Root': eco_name,
|
||||
'Level': '🏛️ 大夫',
|
||||
'Symbol': dafu.symbol,
|
||||
'Company': dafu.name,
|
||||
'Role': dafu.role,
|
||||
'Dependency_Path': f"{eco_name} → {tianzi['name']} → {dafu.name}",
|
||||
'Price': stock_data['price'],
|
||||
'Change%': stock_data['change_pct'],
|
||||
'Market_Cap': stock_data['market_cap'],
|
||||
'Correlation_Risk': '高 - 深度绑定天子'
|
||||
})
|
||||
|
||||
# 士
|
||||
for shi in eco_data.shi:
|
||||
stock_data = self.get_stock_data(shi.symbol)
|
||||
all_stocks.append({
|
||||
'Root': eco_name,
|
||||
'Level': '⚔️ 士',
|
||||
'Symbol': shi.symbol,
|
||||
'Company': shi.name,
|
||||
'Role': shi.role,
|
||||
'Dependency_Path': f"{eco_name} → {shi.serves} → {shi.name}",
|
||||
'Price': stock_data['price'],
|
||||
'Change%': stock_data['change_pct'],
|
||||
'Market_Cap': stock_data['market_cap'],
|
||||
'Correlation_Risk': '中 - 专业供应商'
|
||||
})
|
||||
|
||||
# 嫁接
|
||||
for jiajie in eco_data.jiajie:
|
||||
stock_data = self.get_stock_data(jiajie.symbol)
|
||||
all_stocks.append({
|
||||
'Root': '🔗 跨生态',
|
||||
'Level': '🔗 嫁接',
|
||||
'Symbol': jiajie.symbol,
|
||||
'Company': jiajie.name,
|
||||
'Role': jiajie.type or jiajie.role,
|
||||
'Dependency_Path': f"多生态嫁接 → {jiajie.name}",
|
||||
'Price': stock_data['price'],
|
||||
'Change%': stock_data['change_pct'],
|
||||
'Market_Cap': stock_data['market_cap'],
|
||||
'Correlation_Risk': '低 - 多元化依赖'
|
||||
})
|
||||
|
||||
df = pd.DataFrame(all_stocks)
|
||||
|
||||
# 显示表格
|
||||
st.dataframe(
|
||||
df,
|
||||
use_container_width=True,
|
||||
column_config={
|
||||
"Root": st.column_config.TextColumn("生态根节点", width="small"),
|
||||
"Level": st.column_config.TextColumn("层级", width="small"),
|
||||
"Symbol": st.column_config.TextColumn("代码", width="small"),
|
||||
"Company": st.column_config.TextColumn("公司", width="medium"),
|
||||
"Role": st.column_config.TextColumn("角色", width="medium"),
|
||||
"Dependency_Path": st.column_config.TextColumn("依赖路径", width="large"),
|
||||
"Price": st.column_config.NumberColumn("股价", format="$%.2f"),
|
||||
"Change%": st.column_config.NumberColumn("涨跌幅", format="%.2f%%"),
|
||||
"Market_Cap": st.column_config.TextColumn("市值", width="small"),
|
||||
"Correlation_Risk": st.column_config.TextColumn("相关性风险", width="medium")
|
||||
}
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
def render_tianxia_tab() -> None:
|
||||
"""渲染天下体系Tab"""
|
||||
|
||||
# 页面标题
|
||||
st.markdown("### 🏛️ 天下体系 - 儒门天下观资本生态分析")
|
||||
st.markdown("**基于'天命树'结构模型,穿透市场表象,绘制全球资本市场真实的权力结构**")
|
||||
st.markdown("---")
|
||||
|
||||
# 初始化分析器
|
||||
analyzer = TianxiaAnalyzer()
|
||||
|
||||
# 控制面板
|
||||
col1, col2, col3 = st.columns([1, 1, 2])
|
||||
with col1:
|
||||
auto_refresh = st.checkbox("🔄 自动刷新", value=False, key="tianxia_auto_refresh")
|
||||
with col2:
|
||||
if st.button("🏛️ 扫描天下", type="primary", key="tianxia_scan_btn"):
|
||||
st.session_state.trigger_tianxia_scan = True
|
||||
with col3:
|
||||
st.markdown("*正在分析全球资本生态权力结构...*")
|
||||
|
||||
# 理论介绍
|
||||
with st.expander("📚 天命树理论基础"):
|
||||
st.markdown("""
|
||||
### 🏛️ 儒门天下观核心思想
|
||||
|
||||
**两大哲学基石:**
|
||||
1. **结构非平权**: 资本宇宙本质是不平权的、层级森严的树状结构
|
||||
2. **天命与脉络**: 每个生态都有唯一的"根节点"(天子),拥有定义整个生态的"天命"
|
||||
|
||||
**四层架构:**
|
||||
- **👑 天子**: 定义范式的平台型公司 (如Apple, NVIDIA, Tesla)
|
||||
- **🏛️ 大夫**: 深度绑定天子的核心供应商 (如TSMC, CATL)
|
||||
- **⚔️ 士**: 专业供应商和服务商 (如ASML, Luxshare)
|
||||
- **🔗 嫁接**: 跨生态的策略性链接关系
|
||||
""")
|
||||
|
||||
# 自动刷新逻辑
|
||||
if auto_refresh:
|
||||
time.sleep(60)
|
||||
st.rerun()
|
||||
|
||||
# 触发扫描或显示数据
|
||||
if st.session_state.get('trigger_tianxia_scan', False) or 'tianxia_scan_time' not in st.session_state:
|
||||
with st.spinner("🏛️ 正在扫描天下体系..."):
|
||||
st.session_state.tianxia_scan_time = datetime.now()
|
||||
st.session_state.trigger_tianxia_scan = False
|
||||
|
||||
# 显示扫描时间
|
||||
if 'tianxia_scan_time' in st.session_state:
|
||||
st.info(f"📅 最后扫描时间: {st.session_state.tianxia_scan_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
|
||||
# 显示三大生态系统
|
||||
st.markdown("## 🌍 三大天命树生态系统")
|
||||
|
||||
# 分析模式选择
|
||||
analysis_mode = st.selectbox(
|
||||
"选择分析模式",
|
||||
["生态系统分析", "投资组合去相关性分析"],
|
||||
key="tianxia_analysis_mode"
|
||||
)
|
||||
|
||||
if analysis_mode == "生态系统分析":
|
||||
# 生态系统选择
|
||||
selected_ecosystem = st.selectbox(
|
||||
"选择要分析的生态系统",
|
||||
["全部", "AI", "EV", "Consumer_Electronics"],
|
||||
format_func=lambda x: {
|
||||
"全部": "🌍 全部生态系统",
|
||||
"AI": "🤖 AI人工智能生态",
|
||||
"EV": "⚡ 电动汽车生态",
|
||||
"Consumer_Electronics": "📱 消费电子生态"
|
||||
}[x],
|
||||
key="tianxia_ecosystem_select"
|
||||
)
|
||||
|
||||
if selected_ecosystem == "全部":
|
||||
# 显示所有生态系统
|
||||
for eco_name, eco_data in analyzer.ecosystems.items():
|
||||
analyzer.create_tianming_card(eco_name, eco_data)
|
||||
else:
|
||||
# 显示选定的生态系统
|
||||
analyzer.create_tianming_card(selected_ecosystem, analyzer.ecosystems[selected_ecosystem])
|
||||
|
||||
else: # 投资组合去相关性分析
|
||||
st.markdown("## 🎯 投资组合去相关性分析")
|
||||
st.info("**核心理念**: 真正的分散投资是从不同的root(天子)下的不同spine(大夫)下的不同leaf(士)进行配置")
|
||||
|
||||
# 创建完整天命树表格
|
||||
df = analyzer.create_tianming_tree_table()
|
||||
|
||||
# 页面底部说明
|
||||
st.markdown("---")
|
||||
st.markdown("""
|
||||
### 🎯 天下体系核心洞察
|
||||
|
||||
**权力结构分析**:
|
||||
- **AI生态**: NVIDIA通过CUDA平台统治AI计算,TSMC是关键"嫁接"节点
|
||||
- **电动车生态**: Tesla定义软件汽车范式,CATL掌握电池命脉
|
||||
- **消费电子生态**: Apple建立iOS护城河,供应链高度集中化
|
||||
|
||||
**投资策略启示**:
|
||||
1. **投资天子**: 寻找定义范式的平台型公司
|
||||
2. **关注大夫**: 深度绑定天子的核心供应商往往被低估
|
||||
3. **警惕嫁接**: 被多个天子"嫁接"的公司风险与机会并存
|
||||
4. **避开士层**: 缺乏议价能力的专业供应商投资价值有限
|
||||
|
||||
⚠️ **免责声明**: 天下体系分析仅供参考,投资有风险,决策需谨慎!
|
||||
""")
|
||||
283
modules/monitoring-dashboard/website/index.html
Normal file
283
modules/monitoring-dashboard/website/index.html
Normal file
@@ -0,0 +1,283 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>AI Agent协作框架 - 真实的多Agent协作系统</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="nav-container">
|
||||
<div class="nav-logo">
|
||||
<span class="logo-text">🤖 AI Agent协作</span>
|
||||
</div>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="#home">首页</a></li>
|
||||
<li><a href="#features">特性</a></li>
|
||||
<li><a href="#demo">演示</a></li>
|
||||
<li><a href="#docs">文档</a></li>
|
||||
<li><a href="#github" class="github-link">GitHub</a></li>
|
||||
</ul>
|
||||
<div class="hamburger">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero Section -->
|
||||
<section id="home" class="hero">
|
||||
<div class="hero-container">
|
||||
<div class="hero-content">
|
||||
<h1 class="hero-title">
|
||||
从模拟到真实<br>
|
||||
<span class="gradient-text">让每个AI Agent都拥有独立Git身份</span>
|
||||
</h1>
|
||||
<p class="hero-subtitle">
|
||||
不是让AI Agent假装协作,而是让每个Agent都有真实的Git身份:<br>
|
||||
独立的SSH密钥、GPG签名、用户名和邮箱,实现可追溯的团队协作历史
|
||||
</p>
|
||||
<div class="hero-buttons">
|
||||
<button class="btn btn-primary" onclick="startDemo()">
|
||||
🚀 立即体验
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="scrollToSection('features')">
|
||||
📖 了解更多
|
||||
</button>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
<div class="stat">
|
||||
<span class="stat-number">4</span>
|
||||
<span class="stat-label">预定义Agent</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-number">100%</span>
|
||||
<span class="stat-label">真实Git提交</span>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<span class="stat-number">一键</span>
|
||||
<span class="stat-label">快速启动</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-visual">
|
||||
<div class="terminal-window">
|
||||
<div class="terminal-header">
|
||||
<div class="terminal-buttons">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<div class="terminal-title">Agent协作演示</div>
|
||||
</div>
|
||||
<div class="terminal-content">
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">$</span>
|
||||
<span class="command">./agents/switch_agent.sh claude-ai</span>
|
||||
</div>
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">claude-ai@framework</span>
|
||||
<span class="command">git commit -m "设计系统架构"</span>
|
||||
</div>
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">$</span>
|
||||
<span class="command">./agents/switch_agent.sh gemini-dev</span>
|
||||
</div>
|
||||
<div class="terminal-line">
|
||||
<span class="prompt">gemini-dev@framework</span>
|
||||
<span class="command">git commit -m "实现核心功能"</span>
|
||||
</div>
|
||||
<div class="terminal-line success">
|
||||
✅ 真实的协作历史已创建
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features Section -->
|
||||
<section id="features" class="features">
|
||||
<div class="container">
|
||||
<h2 class="section-title">核心特性</h2>
|
||||
<div class="features-grid">
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🔐</div>
|
||||
<h3>真实身份系统</h3>
|
||||
<p>每个Agent拥有独立的SSH密钥、GPG签名和Git配置,确保每次提交都可追溯到具体Agent</p>
|
||||
<ul>
|
||||
<li>独立SSH密钥对</li>
|
||||
<li>GPG签名验证</li>
|
||||
<li>独立Git配置</li>
|
||||
<li>完整审计日志</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">🎭</div>
|
||||
<h3>专业化角色</h3>
|
||||
<p>预定义四种专业化Agent角色,每个都有明确的责任边界和专长领域</p>
|
||||
<div class="roles-list">
|
||||
<div class="role-item">
|
||||
<strong>claude-ai</strong> - 架构师
|
||||
</div>
|
||||
<div class="role-item">
|
||||
<strong>gemini-dev</strong> - 开发者
|
||||
</div>
|
||||
<div class="role-item">
|
||||
<strong>qwen-ops</strong> - 运维
|
||||
</div>
|
||||
<div class="role-item">
|
||||
<strong>llama-research</strong> - 研究员
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📊</div>
|
||||
<h3>实时协作监控</h3>
|
||||
<p>实时查看所有Agent的协作状态、提交统计和代码贡献分析</p>
|
||||
<div class="stats-preview">
|
||||
<div class="stat-bar">
|
||||
<span>claude-ai</span>
|
||||
<div class="bar"><div style="width: 25%"></div></div>
|
||||
<span>5次提交</span>
|
||||
</div>
|
||||
<div class="stat-bar">
|
||||
<span>gemini-dev</span>
|
||||
<div class="bar"><div style="width: 40%"></div></div>
|
||||
<span>8次提交</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Demo Section -->
|
||||
<section id="demo" class="demo">
|
||||
<div class="container">
|
||||
<h2 class="section-title">实时演示</h2>
|
||||
<div class="demo-container">
|
||||
<div class="demo-panel">
|
||||
<h3>Agent状态监控</h3>
|
||||
<div id="agent-status" class="agent-grid">
|
||||
<!-- 动态加载Agent状态 -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-panel">
|
||||
<h3>最近活动</h3>
|
||||
<div id="recent-activity" class="activity-feed">
|
||||
<!-- 动态加载活动数据 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-actions">
|
||||
<button class="btn btn-primary" onclick="runDemo()">
|
||||
🎬 运行演示
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="resetDemo()">
|
||||
🔄 重置演示
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Quick Start Section -->
|
||||
<section class="quick-start">
|
||||
<div class="container">
|
||||
<h2 class="section-title">快速开始</h2>
|
||||
<div class="install-options">
|
||||
<div class="install-card">
|
||||
<h3>🚀 一键安装</h3>
|
||||
<div class="code-block">
|
||||
<pre><code class="language-bash">curl -fsSL https://raw.githubusercontent.com/your-org/agent-collaboration-framework/main/install.sh | bash</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="install-card">
|
||||
<h3>📦 手动安装</h3>
|
||||
<div class="code-block">
|
||||
<pre><code class="language-bash">git clone https://github.com/your-org/agent-collaboration-framework.git
|
||||
cd agent-collaboration-framework
|
||||
./install.sh</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="install-card">
|
||||
<h3>🐳 Docker</h3>
|
||||
<div class="code-block">
|
||||
<pre><code class="language-bash">docker run -it -v $(pwd):/workspace agent-collaboration:latest</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Documentation Section -->
|
||||
<section id="docs" class="documentation">
|
||||
<div class="container">
|
||||
<h2 class="section-title">文档中心</h2>
|
||||
<div class="docs-grid">
|
||||
<div class="doc-card">
|
||||
<h3>📚 快速入门</h3>
|
||||
<p>从零开始了解AI Agent协作框架</p>
|
||||
<a href="docs/quickstart.html" class="doc-link">开始阅读 →</a>
|
||||
</div>
|
||||
<div class="doc-card">
|
||||
<h3>🏗️ 架构指南</h3>
|
||||
<p>深入理解系统架构和设计原理</p>
|
||||
<a href="docs/architecture.html" class="doc-link">查看详情 →</a>
|
||||
</div>
|
||||
<div class="doc-card">
|
||||
<h3>🔧 API文档</h3>
|
||||
<p>完整的API参考和开发指南</p>
|
||||
<a href="docs/api.html" class="doc-link">浏览API →</a>
|
||||
</div>
|
||||
<div class="doc-card">
|
||||
<h3>🎯 使用案例</h3>
|
||||
<p>真实场景下的使用示例和最佳实践</p>
|
||||
<a href="docs/examples.html" class="doc-link">查看案例 →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-section">
|
||||
<h4>AI Agent协作框架</h4>
|
||||
<p>让AI Agent拥有真实的Git身份,实现可追溯的团队协作</p>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>快速链接</h4>
|
||||
<ul>
|
||||
<li><a href="#home">首页</a></li>
|
||||
<li><a href="#features">特性</a></li>
|
||||
<li><a href="#demo">演示</a></li>
|
||||
<li><a href="#docs">文档</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="footer-section">
|
||||
<h4>社区</h4>
|
||||
<ul>
|
||||
<li><a href="https://github.com/your-org/agent-collaboration-framework">GitHub</a></li>
|
||||
<li><a href="https://github.com/your-org/agent-collaboration-framework/issues">问题反馈</a></li>
|
||||
<li><a href="https://github.com/your-org/agent-collaboration-framework/discussions">讨论</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2024 AI Agent协作框架. 开源项目,欢迎贡献</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
345
modules/monitoring-dashboard/website/script.js
Normal file
345
modules/monitoring-dashboard/website/script.js
Normal file
@@ -0,0 +1,345 @@
|
||||
// 全局变量
|
||||
let agents = [
|
||||
{ id: 'code-reviewer', name: 'Code Reviewer', status: 'active', commits: 12, tasks: 3 },
|
||||
{ id: 'test-runner', name: 'Test Runner', status: 'active', commits: 8, tasks: 5 },
|
||||
{ id: 'deploy-bot', name: 'Deploy Bot', status: 'inactive', commits: 3, tasks: 0 },
|
||||
{ id: 'doc-writer', name: 'Doc Writer', status: 'active', commits: 15, tasks: 2 }
|
||||
];
|
||||
|
||||
let activities = [
|
||||
{ type: 'commit', message: 'Code Reviewer pushed 3 new commits', time: '2 minutes ago' },
|
||||
{ type: 'test', message: 'Test Runner completed 5 test cases', time: '5 minutes ago' },
|
||||
{ type: 'deploy', message: 'Deploy Bot deployed to staging', time: '10 minutes ago' },
|
||||
{ type: 'docs', message: 'Doc Writer updated API documentation', time: '15 minutes ago' }
|
||||
];
|
||||
|
||||
// 初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeDemo();
|
||||
initializeTerminal();
|
||||
initializeStats();
|
||||
setupScrollEffects();
|
||||
});
|
||||
|
||||
// 演示区域初始化
|
||||
function initializeDemo() {
|
||||
updateAgentGrid();
|
||||
updateActivityFeed();
|
||||
|
||||
// 每3秒更新一次演示数据
|
||||
setInterval(updateDemoData, 3000);
|
||||
}
|
||||
|
||||
// 更新代理网格
|
||||
function updateAgentGrid() {
|
||||
const grid = document.getElementById('agent-grid');
|
||||
if (!grid) return;
|
||||
|
||||
grid.innerHTML = '';
|
||||
agents.forEach(agent => {
|
||||
const card = createAgentCard(agent);
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建代理卡片
|
||||
function createAgentCard(agent) {
|
||||
const card = document.createElement('div');
|
||||
card.className = `agent-card agent-status-${agent.status}`;
|
||||
|
||||
const statusIcon = agent.status === 'active' ? '🟢' : '🔴';
|
||||
|
||||
card.innerHTML = `
|
||||
<div>
|
||||
<strong>${agent.name}</strong>
|
||||
<div style="font-size: 0.9rem; color: #666;">
|
||||
${statusIcon} ${agent.status}
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: right; font-size: 0.9rem;">
|
||||
<div>📊 ${agent.commits} commits</div>
|
||||
<div>📝 ${agent.tasks} tasks</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// 更新活动流
|
||||
function updateActivityFeed() {
|
||||
const feed = document.getElementById('activity-feed');
|
||||
if (!feed) return;
|
||||
|
||||
feed.innerHTML = '';
|
||||
activities.forEach(activity => {
|
||||
const item = createActivityItem(activity);
|
||||
feed.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// 创建活动项
|
||||
function createActivityItem(activity) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'activity-item';
|
||||
|
||||
const icon = getActivityIcon(activity.type);
|
||||
|
||||
item.innerHTML = `
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span>${icon}</span>
|
||||
<span>${activity.message}</span>
|
||||
</div>
|
||||
<div style="font-size: 0.8rem; color: #666; margin-top: 0.5rem;">
|
||||
${activity.time}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
// 获取活动图标
|
||||
function getActivityIcon(type) {
|
||||
const icons = {
|
||||
'commit': '💻',
|
||||
'test': '🧪',
|
||||
'deploy': '🚀',
|
||||
'docs': '📚'
|
||||
};
|
||||
return icons[type] || '📝';
|
||||
}
|
||||
|
||||
// 更新演示数据
|
||||
function updateDemoData() {
|
||||
// 随机更新代理状态
|
||||
agents.forEach(agent => {
|
||||
if (Math.random() > 0.7) {
|
||||
agent.status = agent.status === 'active' ? 'inactive' : 'active';
|
||||
}
|
||||
if (Math.random() > 0.5) {
|
||||
agent.commits += Math.floor(Math.random() * 3);
|
||||
}
|
||||
});
|
||||
|
||||
// 添加新活动
|
||||
const newActivity = generateRandomActivity();
|
||||
activities.unshift(newActivity);
|
||||
if (activities.length > 5) {
|
||||
activities.pop();
|
||||
}
|
||||
|
||||
updateAgentGrid();
|
||||
updateActivityFeed();
|
||||
updateStats();
|
||||
}
|
||||
|
||||
// 生成随机活动
|
||||
function generateRandomActivity() {
|
||||
const types = ['commit', 'test', 'deploy', 'docs'];
|
||||
const messages = {
|
||||
'commit': ['Code Reviewer pushed new features', 'Test Runner fixed bugs', 'Deploy Bot merged PR'],
|
||||
'test': ['Test Runner passed all tests', 'Code Reviewer added unit tests', 'Doc Writer tested examples'],
|
||||
'deploy': ['Deploy Bot deployed to production', 'Code Reviewer released v2.0', 'Test Runner deployed hotfix'],
|
||||
'docs': ['Doc Writer updated README', 'Code Reviewer improved comments', 'Deploy Bot added changelog']
|
||||
};
|
||||
|
||||
const type = types[Math.floor(Math.random() * types.length)];
|
||||
const message = messages[type][Math.floor(Math.random() * messages[type].length)];
|
||||
|
||||
return {
|
||||
type: type,
|
||||
message: message,
|
||||
time: 'just now'
|
||||
};
|
||||
}
|
||||
|
||||
// 终端模拟
|
||||
function initializeTerminal() {
|
||||
const terminal = document.getElementById('terminal-content');
|
||||
if (!terminal) return;
|
||||
|
||||
const commands = [
|
||||
{ prompt: 'user@spore-colony:~$', command: 'spore init', output: '✓ Initializing Spore Colony...\n✓ Creating agent identities...\n✓ Setting up Git repositories...\n✓ Spore Colony initialized successfully!' },
|
||||
{ prompt: 'user@spore-colony:~$', command: 'spore agent list', output: '🟢 code-reviewer (Active)\n🟢 test-runner (Active)\n🔴 deploy-bot (Inactive)\n🟢 doc-writer (Active)' },
|
||||
{ prompt: 'user@spore-colony:~$', command: 'spore task create "Add new feature"', output: '✓ Task created and assigned to code-reviewer\n📊 Progress: 0% → 25% → 50% → 75% → 100%\n✅ Task completed successfully!' }
|
||||
];
|
||||
|
||||
let currentCommand = 0;
|
||||
|
||||
function typeCommand() {
|
||||
if (currentCommand >= commands.length) return;
|
||||
|
||||
const cmd = commands[currentCommand];
|
||||
const line = document.createElement('div');
|
||||
line.className = 'terminal-line';
|
||||
line.innerHTML = `<span class="prompt">${cmd.prompt}</span> <span class="command">${cmd.command}</span>`;
|
||||
terminal.appendChild(line);
|
||||
|
||||
setTimeout(() => {
|
||||
const outputLine = document.createElement('div');
|
||||
outputLine.className = 'terminal-line';
|
||||
outputLine.innerHTML = `<span class="success">${cmd.output}</span>`;
|
||||
terminal.appendChild(outputLine);
|
||||
|
||||
currentCommand++;
|
||||
if (currentCommand < commands.length) {
|
||||
setTimeout(typeCommand, 2000);
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
typeCommand();
|
||||
}
|
||||
|
||||
// 统计信息
|
||||
function initializeStats() {
|
||||
updateStats();
|
||||
}
|
||||
|
||||
function updateStats() {
|
||||
const totalCommits = agents.reduce((sum, agent) => sum + agent.commits, 0);
|
||||
const activeAgents = agents.filter(agent => agent.status === 'active').length;
|
||||
const totalTasks = agents.reduce((sum, agent) => sum + agent.tasks, 0);
|
||||
|
||||
animateCounter('total-commits', totalCommits);
|
||||
animateCounter('active-agents', activeAgents);
|
||||
animateCounter('total-tasks', totalTasks);
|
||||
|
||||
updateProgressBars();
|
||||
}
|
||||
|
||||
// 数字动画
|
||||
function animateCounter(elementId, targetValue) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const startValue = parseInt(element.textContent) || 0;
|
||||
const duration = 1000;
|
||||
const startTime = performance.now();
|
||||
|
||||
function updateCounter(currentTime) {
|
||||
const elapsed = currentTime - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
const currentValue = Math.floor(startValue + (targetValue - startValue) * progress);
|
||||
element.textContent = currentValue;
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(updateCounter);
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(updateCounter);
|
||||
}
|
||||
|
||||
// 进度条更新
|
||||
function updateProgressBars() {
|
||||
const progressBars = document.querySelectorAll('.bar div');
|
||||
progressBars.forEach(bar => {
|
||||
const width = Math.floor(Math.random() * 30) + 70; // 70-100%
|
||||
bar.style.width = width + '%';
|
||||
});
|
||||
}
|
||||
|
||||
// 滚动效果
|
||||
function setupScrollEffects() {
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('animate-in');
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// 观察所有需要动画的元素
|
||||
document.querySelectorAll('.feature-card, .demo-panel, .install-card, .doc-card').forEach(el => {
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
// 平滑滚动到锚点
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 响应式菜单
|
||||
function toggleMobileMenu() {
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
navMenu.classList.toggle('active');
|
||||
}
|
||||
|
||||
// 复制代码功能
|
||||
function copyToClipboard(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const text = element.textContent;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
// 显示复制成功提示
|
||||
const toast = document.createElement('div');
|
||||
toast.textContent = 'Copied to clipboard!';
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #27ca3f;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
z-index: 1000;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加CSS动画
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-in {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nav-menu.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: white;
|
||||
padding: 1rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
659
modules/monitoring-dashboard/website/styles.css
Normal file
659
modules/monitoring-dashboard/website/styles.css
Normal file
@@ -0,0 +1,659 @@
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-menu a:hover {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.github-link {
|
||||
background: #2563eb;
|
||||
color: white !important;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.github-link:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hamburger span {
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
background: #333;
|
||||
margin: 3px 0;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6rem 0 4rem;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hero-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 3.5rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 2rem;
|
||||
opacity: 0.9;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 1rem 2rem;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #ff5252;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: white;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: white;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Terminal Window */
|
||||
.terminal-window {
|
||||
background: #1a1a1a;
|
||||
border-radius: 1rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
background: #2d2d2d;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.terminal-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.terminal-buttons span {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.terminal-buttons span:nth-child(1) { background: #ff5f56; }
|
||||
.terminal-buttons span:nth-child(2) { background: #ffbd2e; }
|
||||
.terminal-buttons span:nth-child(3) { background: #27ca3f; }
|
||||
|
||||
.terminal-title {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.terminal-content {
|
||||
padding: 1.5rem;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.terminal-line {
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
color: #27ca3f;
|
||||
}
|
||||
|
||||
.command {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #27ca3f;
|
||||
}
|
||||
|
||||
/* Features Section */
|
||||
.features {
|
||||
padding: 6rem 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 3rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
padding: 2.5rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feature-card h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.feature-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feature-card ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.feature-card li {
|
||||
padding: 0.25rem 0;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.feature-card li:before {
|
||||
content: "✓";
|
||||
color: #27ca3f;
|
||||
font-weight: bold;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.roles-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.role-item {
|
||||
padding: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Demo Section */
|
||||
.demo {
|
||||
padding: 6rem 0;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.demo-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 3rem;
|
||||
}
|
||||
|
||||
.demo-panel {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-panel h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.agent-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.agent-card {
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agent-status-active {
|
||||
border-left: 4px solid #27ca3f;
|
||||
}
|
||||
|
||||
.agent-status-inactive {
|
||||
border-left: 4px solid #ff5f56;
|
||||
}
|
||||
|
||||
.activity-feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 0.5rem;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.demo-actions {
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Quick Start Section */
|
||||
.quick-start {
|
||||
padding: 6rem 0;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.install-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.install-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.install-card h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #1a1a1a;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block pre {
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Documentation Section */
|
||||
.documentation {
|
||||
padding: 6rem 0;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.docs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.doc-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.doc-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.doc-card h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.doc-card p {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.doc-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: #1a1a1a;
|
||||
color: white;
|
||||
padding: 3rem 0 1rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.footer-section h4 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.footer-section ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.footer-section ul li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.footer-section a {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-section a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid #333;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.hamburger {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-container {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.demo-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.install-options {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.docs-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.hero-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.hero-stats {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
animation: fadeInUp 0.6s ease forwards;
|
||||
}
|
||||
|
||||
.feature-card:nth-child(2) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
|
||||
.feature-card:nth-child(3) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
/* Stats Animation */
|
||||
.stats-preview {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.stat-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-bar span {
|
||||
font-size: 0.9rem;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bar div {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
transition: width 1s ease;
|
||||
}
|
||||
|
||||
/* Scrollbar Styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
Reference in New Issue
Block a user