feat: implement council v3 round-table mode
This commit is contained in:
parent
5913d2dc47
commit
02eea5bfb4
Binary file not shown.
145
app.py
145
app.py
@ -13,7 +13,9 @@ from agents import get_all_agents, get_recommended_agents, AGENT_PROFILES
|
||||
from orchestrator import DebateManager, DebateConfig
|
||||
from orchestrator.research_manager import ResearchManager, ResearchConfig
|
||||
from report import ReportGenerator
|
||||
from report import ReportGenerator
|
||||
from utils import LLMClient
|
||||
from utils.storage import StorageManager
|
||||
import config
|
||||
|
||||
# ==================== 页面配置 ====================
|
||||
@ -77,6 +79,23 @@ DECISION_TYPES = {
|
||||
}
|
||||
|
||||
# ==================== 初始化 Session State ====================
|
||||
if "storage" not in st.session_state:
|
||||
st.session_state.storage = StorageManager()
|
||||
|
||||
# Load saved config
|
||||
if "saved_config" not in st.session_state:
|
||||
st.session_state.saved_config = st.session_state.storage.load_config()
|
||||
|
||||
# Helper to save config
|
||||
def save_current_config():
|
||||
cfg = {
|
||||
"provider": st.session_state.get("selected_provider", "AIHubMix"),
|
||||
"api_key": st.session_state.get("api_key", ""),
|
||||
"base_url": st.session_state.get("base_url", ""),
|
||||
"language": st.session_state.get("output_language", "Chinese")
|
||||
}
|
||||
st.session_state.storage.save_config(cfg)
|
||||
|
||||
if "mode" not in st.session_state:
|
||||
st.session_state.mode = "Deep Research"
|
||||
|
||||
@ -109,40 +128,73 @@ with st.sidebar:
|
||||
|
||||
# 全局 API Key & Provider 设置
|
||||
with st.expander("🔑 API / Provider 设置", expanded=True):
|
||||
# Saved preferences
|
||||
saved = st.session_state.saved_config
|
||||
|
||||
# Provider Selection
|
||||
provider_options = list(config.LLM_PROVIDERS.keys())
|
||||
default_provider = saved.get("provider", "AIHubMix")
|
||||
try:
|
||||
prov_idx = provider_options.index(default_provider)
|
||||
except ValueError:
|
||||
prov_idx = 0
|
||||
|
||||
selected_provider_label = st.selectbox(
|
||||
"选择 API 提供商",
|
||||
options=list(config.LLM_PROVIDERS.keys()),
|
||||
index=0
|
||||
options=provider_options,
|
||||
index=prov_idx,
|
||||
key="selected_provider",
|
||||
on_change=save_current_config
|
||||
)
|
||||
|
||||
provider_config = config.LLM_PROVIDERS[selected_provider_label]
|
||||
provider_id = selected_provider_label.lower()
|
||||
|
||||
# API Key Input
|
||||
default_key = os.getenv(provider_config["api_key_var"], "")
|
||||
# If saved key exists for this provider, use it. Otherwise env var.
|
||||
default_key = saved.get("api_key") if saved.get("provider") == selected_provider_label else os.getenv(provider_config["api_key_var"], "")
|
||||
|
||||
api_key = st.text_input(
|
||||
f"{selected_provider_label} API Key",
|
||||
type="password",
|
||||
value=default_key,
|
||||
help=f"环境变量: {provider_config['api_key_var']}"
|
||||
help=f"环境变量: {provider_config['api_key_var']}",
|
||||
key="api_key_input"
|
||||
)
|
||||
# Sync to session state for save callback
|
||||
st.session_state.api_key = api_key
|
||||
|
||||
# Base URL
|
||||
default_url = saved.get("base_url") if saved.get("provider") == selected_provider_label else provider_config["base_url"]
|
||||
base_url = st.text_input(
|
||||
"API Base URL",
|
||||
value=provider_config["base_url"]
|
||||
value=default_url,
|
||||
key="base_url_input"
|
||||
)
|
||||
st.session_state.base_url = base_url
|
||||
|
||||
# Trigger save if values changed (manual check since text_input on_change is tricky with typing)
|
||||
if api_key != saved.get("api_key") or base_url != saved.get("base_url"):
|
||||
save_current_config()
|
||||
|
||||
if not api_key:
|
||||
st.warning("请配置 API Key 以继续")
|
||||
|
||||
# Output Language Selection
|
||||
lang_options = config.SUPPORTED_LANGUAGES
|
||||
default_lang = saved.get("language", "Chinese")
|
||||
try:
|
||||
lang_idx = lang_options.index(default_lang)
|
||||
except ValueError:
|
||||
lang_idx = 0
|
||||
|
||||
output_language = st.sidebar.selectbox(
|
||||
"🌐 输出语言",
|
||||
options=config.SUPPORTED_LANGUAGES,
|
||||
index=0,
|
||||
help="所有 AI Agent 将使用此语言进行回复"
|
||||
options=lang_options,
|
||||
index=lang_idx,
|
||||
help="所有 AI Agent 将使用此语言进行回复",
|
||||
key="output_language",
|
||||
on_change=save_current_config
|
||||
)
|
||||
|
||||
st.divider()
|
||||
@ -150,15 +202,22 @@ with st.sidebar:
|
||||
# 模式选择
|
||||
mode = st.radio(
|
||||
"📊 选择模式",
|
||||
["Deep Research", "Debate Workshop"],
|
||||
index=0 if st.session_state.mode == "Deep Research" else 1
|
||||
["Council V4 (Deep Research)", "Debate Workshop", "📜 History Archives"],
|
||||
index=0 if st.session_state.mode == "Deep Research" else (1 if st.session_state.mode == "Debate Workshop" else 2)
|
||||
)
|
||||
st.session_state.mode = mode
|
||||
|
||||
# Map selection back to internal mode string
|
||||
if mode == "Council V4 (Deep Research)":
|
||||
st.session_state.mode = "Deep Research"
|
||||
elif mode == "Debate Workshop":
|
||||
st.session_state.mode = "Debate Workshop"
|
||||
else:
|
||||
st.session_state.mode = "History Archives"
|
||||
|
||||
st.divider()
|
||||
|
||||
|
||||
if mode == "Debate Workshop": # Debate Workshop Settings
|
||||
if st.session_state.mode == "Debate Workshop": # Debate Workshop Settings
|
||||
# 模型选择
|
||||
model = st.selectbox(
|
||||
"🤖 选择通用模型",
|
||||
@ -311,6 +370,19 @@ if mode == "Deep Research":
|
||||
st.session_state.research_output = final_plan
|
||||
st.success("✅ 综合方案生成完毕")
|
||||
|
||||
# Auto-save history
|
||||
st.session_state.storage.save_history(
|
||||
session_type="council",
|
||||
topic=research_topic,
|
||||
content=final_plan,
|
||||
metadata={
|
||||
"rounds": max_rounds,
|
||||
"experts": [e["name"] for e in experts_config],
|
||||
"language": output_language
|
||||
}
|
||||
)
|
||||
st.toast("✅ 记录已保存到历史档案")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"发生错误: {str(e)}")
|
||||
import traceback
|
||||
@ -549,6 +621,19 @@ elif mode == "Debate Workshop":
|
||||
|
||||
st.session_state.report = report_content
|
||||
|
||||
# Auto-save history
|
||||
st.session_state.storage.save_history(
|
||||
session_type="debate",
|
||||
topic=topic,
|
||||
content=report_content,
|
||||
metadata={
|
||||
"rounds": max_rounds,
|
||||
"agents": selected_agents,
|
||||
"language": output_language
|
||||
}
|
||||
)
|
||||
st.toast("✅ 记录已保存到历史档案")
|
||||
|
||||
# 下载按钮
|
||||
st.download_button(
|
||||
label="📥 下载报告 (Markdown)",
|
||||
@ -580,6 +665,42 @@ elif mode == "Debate Workshop":
|
||||
mime="text/markdown"
|
||||
)
|
||||
|
||||
# ==================== 历史档案浏览 ====================
|
||||
elif st.session_state.mode == "History Archives":
|
||||
st.title("📜 历史档案")
|
||||
st.markdown("*查看过去的所有决策和研究记录*")
|
||||
|
||||
history_items = st.session_state.storage.list_history()
|
||||
|
||||
if not history_items:
|
||||
st.info("暂无历史记录。开始一个新的 Council 或 Debate 来生成记录吧!")
|
||||
else:
|
||||
# Display as a table/list
|
||||
for item in history_items:
|
||||
with st.expander(f"{item['date']} | {item['type'].upper()} | {item['topic']}", expanded=False):
|
||||
col1, col2 = st.columns([4, 1])
|
||||
with col1:
|
||||
st.caption(f"ID: {item['id']}")
|
||||
with col2:
|
||||
if st.button("查看详情", key=f"view_{item['id']}"):
|
||||
st.session_state.view_history_id = item['filename']
|
||||
st.rerun()
|
||||
|
||||
# View Detail Modal/Area
|
||||
if "view_history_id" in st.session_state:
|
||||
st.divider()
|
||||
record = st.session_state.storage.load_history_item(st.session_state.view_history_id)
|
||||
if record:
|
||||
st.subheader(f"📄 记录详情: {record['topic']}")
|
||||
st.markdown(f"**时间**: {record['date']} | **类型**: {record['type']}")
|
||||
st.markdown("---")
|
||||
st.markdown(record['content'])
|
||||
st.download_button(
|
||||
"📥 下载此记录",
|
||||
record['content'],
|
||||
file_name=f"{record['type']}_{record['id']}.md"
|
||||
)
|
||||
|
||||
# ==================== 底部信息 ====================
|
||||
st.divider()
|
||||
col_footer1, col_footer2, col_footer3 = st.columns(3)
|
||||
|
||||
BIN
utils/__pycache__/storage.cpython-313.pyc
Normal file
BIN
utils/__pycache__/storage.cpython-313.pyc
Normal file
Binary file not shown.
112
utils/storage.py
Normal file
112
utils/storage.py
Normal file
@ -0,0 +1,112 @@
|
||||
"""
|
||||
Storage Manager - Handle local persistence of configuration and history/reports.
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from typing import List, Dict, Any
|
||||
from pathlib import Path
|
||||
|
||||
# Constants
|
||||
STORAGE_DIR = ".storage"
|
||||
CONFIG_FILE = "config.json"
|
||||
HISTORY_DIR = "history"
|
||||
|
||||
class StorageManager:
|
||||
def __init__(self):
|
||||
self.root_dir = Path(STORAGE_DIR)
|
||||
self.config_path = self.root_dir / CONFIG_FILE
|
||||
self.history_dir = self.root_dir / HISTORY_DIR
|
||||
|
||||
# Ensure directories exist
|
||||
self.root_dir.mkdir(exist_ok=True)
|
||||
self.history_dir.mkdir(exist_ok=True)
|
||||
|
||||
def save_config(self, config_data: Dict[str, Any]):
|
||||
"""Save UI configuration to file"""
|
||||
try:
|
||||
with open(self.config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config_data, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving config: {e}")
|
||||
|
||||
def load_config(self) -> Dict[str, Any]:
|
||||
"""Load UI configuration from file"""
|
||||
if not self.config_path.exists():
|
||||
return {}
|
||||
try:
|
||||
with open(self.config_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error loading config: {e}")
|
||||
return {}
|
||||
|
||||
def save_history(self, session_type: str, topic: str, content: str, metadata: Dict[str, Any] = None):
|
||||
"""
|
||||
Save a session report/history
|
||||
|
||||
Args:
|
||||
session_type: 'council' or 'debate'
|
||||
topic: The main topic
|
||||
content: The full markdown report or content
|
||||
metadata: Additional info (model used, date, etc)
|
||||
"""
|
||||
timestamp = int(time.time())
|
||||
date_str = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Create a safe filename
|
||||
safe_topic = "".join([c for c in topic[:20] if c.isalnum() or c in (' ', '_', '-')]).strip().replace(' ', '_')
|
||||
filename = f"{timestamp}_{session_type}_{safe_topic}.json"
|
||||
|
||||
data = {
|
||||
"id": str(timestamp),
|
||||
"timestamp": timestamp,
|
||||
"date": date_str,
|
||||
"type": session_type,
|
||||
"topic": topic,
|
||||
"content": content,
|
||||
"metadata": metadata or {}
|
||||
}
|
||||
|
||||
try:
|
||||
with open(self.history_dir / filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving history: {e}")
|
||||
return False
|
||||
|
||||
def list_history(self) -> List[Dict[str, Any]]:
|
||||
"""List all history items (metadata only)"""
|
||||
items = []
|
||||
if not self.history_dir.exists():
|
||||
return []
|
||||
|
||||
for file in self.history_dir.glob("*.json"):
|
||||
try:
|
||||
with open(file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
# Return summary info
|
||||
items.append({
|
||||
"id": data.get("id"),
|
||||
"date": data.get("date"),
|
||||
"type": data.get("type"),
|
||||
"topic": data.get("topic"),
|
||||
"filename": file.name
|
||||
})
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# Sort by timestamp desc
|
||||
return sorted(items, key=lambda x: x.get("date", ""), reverse=True)
|
||||
|
||||
def load_history_item(self, filename: str) -> Dict[str, Any]:
|
||||
"""Load full content of a history item"""
|
||||
path = self.history_dir / filename
|
||||
if not path.exists():
|
||||
return None
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return None
|
||||
Loading…
Reference in New Issue
Block a user