feat(profiles): serve a cross-profile project tree and per-profile usage totals
`projects.tree` answers for the backend's own profile, so the grouped sidebar had nothing to draw once the user asked to see every profile. Run the same authoritative builder once per profile against that profile's state.db and merge the results by folder, so one checkout is one group no matter how many profiles work in it, and the owning profile rides on each session row where the badge and filter can read it. Group totals are summed in SQL rather than over the loaded page — a number that shrank as you scrolled would be worse than no number. Scope the batched sidebar slices while we're here: cron and messaging came back cross-profile unconditionally, which is why a concrete profile showed another profile's Telegram threads and cronjobs. Closes #65710 Closes #42651 Closes #70629
This commit is contained in:
@@ -505,6 +505,15 @@ def _project_for_session(session: dict, index: _FolderIndex, resolve: Optional[R
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _session_cost(session: dict) -> float:
|
||||
"""A session's spend, billed if the provider reported it, else estimated."""
|
||||
for key in ("actual_cost_usd", "estimated_cost_usd"):
|
||||
value = session.get(key)
|
||||
if value:
|
||||
return float(value)
|
||||
return 0.0
|
||||
|
||||
|
||||
def _project_node(
|
||||
*,
|
||||
pid: str,
|
||||
@@ -514,6 +523,7 @@ def _project_node(
|
||||
session_count: int,
|
||||
last_active: float,
|
||||
preview_sessions: list[dict],
|
||||
sessions: Optional[list[dict]] = None,
|
||||
color: Any = None,
|
||||
icon: Any = None,
|
||||
is_auto: bool = False,
|
||||
@@ -529,6 +539,11 @@ def _project_node(
|
||||
"isNoProject": is_no_project,
|
||||
"sessionCount": session_count,
|
||||
"lastActive": last_active,
|
||||
# Totals over the same sessions `sessionCount` counts, so a project's
|
||||
# header can add up what its rows show. The window the caller loaded is
|
||||
# the whole truth either way — count and totals can't disagree.
|
||||
"totalTokens": sum((s.get("input_tokens") or 0) + (s.get("output_tokens") or 0) for s in sessions or []),
|
||||
"totalCostUsd": sum(_session_cost(s) for s in sessions or []),
|
||||
"repos": repos,
|
||||
"previewSessions": preview_sessions,
|
||||
}
|
||||
@@ -612,6 +627,7 @@ def build_tree(
|
||||
session_count=len(psessions),
|
||||
last_active=_last_active(psessions),
|
||||
preview_sessions=_previews(psessions),
|
||||
sessions=psessions,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -693,6 +709,7 @@ def build_tree(
|
||||
session_count=repo_node["sessionCount"],
|
||||
last_active=_last_active(auto_sessions),
|
||||
preview_sessions=_previews(auto_sessions),
|
||||
sessions=auto_sessions,
|
||||
is_auto=True,
|
||||
)
|
||||
)
|
||||
@@ -761,6 +778,7 @@ def build_tree(
|
||||
session_count=len(homeless),
|
||||
last_active=_last_active(homeless),
|
||||
preview_sessions=_previews(homeless),
|
||||
sessions=homeless,
|
||||
is_no_project=True,
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user