feat(hub): add data dashboard and AI chat with session history
Add /dashboard with daily PnL overview and loss alerts. Extend AI coach chat with history sidebar, delete/switch sessions, message copy, and trading vs general bot modes. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -140,12 +140,28 @@ def get_active_session() -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
def create_new_session(*, trading_day: str, title: str = "新对话") -> dict:
|
||||
CHAT_BOT_TRADING = "trading"
|
||||
CHAT_BOT_GENERAL = "general"
|
||||
CHAT_BOT_MODES = frozenset({CHAT_BOT_TRADING, CHAT_BOT_GENERAL})
|
||||
|
||||
|
||||
def _normalize_bot_mode(raw: Any) -> str:
|
||||
mode = (raw or CHAT_BOT_TRADING).strip().lower()
|
||||
return mode if mode in CHAT_BOT_MODES else CHAT_BOT_TRADING
|
||||
|
||||
|
||||
def create_new_session(
|
||||
*,
|
||||
trading_day: str,
|
||||
title: str = "新对话",
|
||||
bot_mode: str = CHAT_BOT_TRADING,
|
||||
) -> dict:
|
||||
store = load_chat_store()
|
||||
session = {
|
||||
"id": uuid.uuid4().hex,
|
||||
"trading_day": trading_day,
|
||||
"title": title,
|
||||
"bot_mode": _normalize_bot_mode(bot_mode),
|
||||
"created_at": _now_str(),
|
||||
"updated_at": _now_str(),
|
||||
"messages": [],
|
||||
@@ -193,6 +209,71 @@ def append_chat_message(
|
||||
return target
|
||||
|
||||
|
||||
def _session_list_item(s: dict, *, active_id: Optional[str]) -> dict:
|
||||
msgs = s.get("messages") or []
|
||||
preview = ""
|
||||
for m in reversed(msgs):
|
||||
if m.get("role") == "user":
|
||||
preview = str(m.get("content") or "").replace("\n", " ")[:48]
|
||||
break
|
||||
if not preview and msgs:
|
||||
last = msgs[-1]
|
||||
preview = str(last.get("content") or "").replace("\n", " ")[:48]
|
||||
sid = str(s.get("id") or "")
|
||||
return {
|
||||
"id": sid,
|
||||
"title": s.get("title") or "新对话",
|
||||
"bot_mode": _normalize_bot_mode(s.get("bot_mode")),
|
||||
"trading_day": s.get("trading_day"),
|
||||
"created_at": s.get("created_at"),
|
||||
"updated_at": s.get("updated_at"),
|
||||
"message_count": len(msgs),
|
||||
"preview": preview,
|
||||
"is_active": sid and sid == str(active_id or ""),
|
||||
}
|
||||
|
||||
|
||||
def list_chat_sessions(*, limit: int = 50) -> list[dict]:
|
||||
store = load_chat_store()
|
||||
active_id = store.get("active_session_id")
|
||||
sessions = list(store.get("sessions") or [])
|
||||
for s in sessions:
|
||||
s.setdefault("bot_mode", CHAT_BOT_TRADING)
|
||||
sessions.sort(key=lambda x: str(x.get("updated_at") or ""), reverse=True)
|
||||
return [_session_list_item(s, active_id=active_id) for s in sessions[: max(1, min(limit, 100))]]
|
||||
|
||||
|
||||
def set_active_session(session_id: str) -> dict:
|
||||
store = load_chat_store()
|
||||
target = None
|
||||
for s in store.get("sessions") or []:
|
||||
if str(s.get("id")) == str(session_id):
|
||||
target = s
|
||||
break
|
||||
if not target:
|
||||
raise KeyError("session_not_found")
|
||||
target.setdefault("bot_mode", CHAT_BOT_TRADING)
|
||||
store["active_session_id"] = target["id"]
|
||||
save_chat_store(store)
|
||||
return target
|
||||
|
||||
|
||||
def delete_chat_session(session_id: str) -> tuple[bool, Optional[str]]:
|
||||
store = load_chat_store()
|
||||
sessions = list(store.get("sessions") or [])
|
||||
new_sessions = [s for s in sessions if str(s.get("id")) != str(session_id)]
|
||||
if len(new_sessions) == len(sessions):
|
||||
return False, None
|
||||
active = store.get("active_session_id")
|
||||
new_active = active
|
||||
if str(active) == str(session_id):
|
||||
new_active = new_sessions[0]["id"] if new_sessions else None
|
||||
store["sessions"] = new_sessions
|
||||
store["active_session_id"] = new_active
|
||||
save_chat_store(store)
|
||||
return True, new_active
|
||||
|
||||
|
||||
def summary_excerpt_for_chat(trading_day: str, max_chars: int = 600) -> str:
|
||||
latest = get_latest_summary(trading_day)
|
||||
if not latest:
|
||||
|
||||
Reference in New Issue
Block a user