sigmadock-ui 0.1.2

Native GPUI workspace for supervising local coding agents
use crate::Workspace;
use gpui::{Context, SharedString, div, prelude::*, px, relative, rgb};
use serde_json::json;
impl Workspace {
    pub(crate) fn load_usage(&mut self, cx: &mut Context<Self>) {
        if self.usage_loading {
            return;
        }
        let Some(worker) = self.selected.clone() else {
            return;
        };
        self.usage_open = true;
        self.usage_loading = true;
        self.usage_error = None;
        let client = self.client.clone();
        cx.spawn(async move |this, cx| {
            let id = worker.clone();
            let result = cx
                .background_executor()
                .spawn(async move {
                    client
                        .call("agent_usage", json!({"worker_id":id}))
                        .and_then(|value| {
                            Ok(serde_json::from_value::<sigmadock_core::AgentUsage>(value)?)
                        })
                })
                .await;
            let _ = this.update(cx, |this, cx| {
                this.usage_loading = false;
                if this.selected.as_deref() == Some(&worker) {
                    match result {
                        Ok(report) => this.usage_report = Some(report),
                        Err(error) => this.usage_error = Some(error.to_string()),
                    }
                }
                cx.notify();
            });
        })
        .detach();
        cx.notify();
    }
    pub(crate) fn usage_panel(&self, cx: &mut Context<Self>) -> gpui::AnyElement {
        let mut panel = div()
            .id("usage-pane")
            .max_h(px(280.))
            .overflow_y_scroll()
            .p_3()
            .rounded_md()
            .bg(rgb(self.theme.panel))
            .flex()
            .flex_col()
            .gap_2()
            .child(
                div()
                    .flex()
                    .gap_3()
                    .items_center()
                    .child(
                        div()
                            .flex_1()
                            .child("Subscription usage · shared account limits"),
                    )
                    .child(
                        div()
                            .id("refresh-usage")
                            .p_1()
                            .bg(rgb(self.theme.button))
                            .cursor_pointer()
                            .child(if self.usage_loading {
                                "Loading…"
                            } else {
                                "Refresh"
                            })
                            .on_click(cx.listener(|this, _, _, cx| this.load_usage(cx))),
                    )
                    .child(
                        div()
                            .id("close-usage")
                            .cursor_pointer()
                            .child("Close ×")
                            .on_click(cx.listener(|this, _, _, cx| {
                                this.usage_open = false;
                                cx.notify();
                            })),
                    ),
            );
        if let Some(worker) = self
            .workers
            .iter()
            .find(|worker| Some(&worker.id) == self.selected.as_ref())
        {
            if worker.agent == "codex" {
                panel = panel.child("Refresh starts a read-only Codex account query using your existing CLI sign-in. It may contact the provider; it creates no agent turn.");
            }
            if worker.agent == "claude" {
                let id = worker.id.clone();
                let enabled = !worker.usage_reporting;
                panel = panel.child("Optional collection uses a session-local Claude status line on the next launch/resume. It temporarily replaces that session’s custom status line; global settings stay unchanged.")
                    .child(div().id("configure-usage").p_1().bg(rgb(self.theme.button)).cursor_pointer().child(if enabled { "Enable on next launch" } else { "Disable collection" }).on_click(cx.listener(move |this, _, _, cx| this.run_action("configure_usage",json!({"worker_id":id,"enabled":enabled}),cx))));
            }
        }
        if let Some(error) = &self.usage_error {
            panel = panel.child(format!(
                "Usage unavailable: {error}. Check the CLI version, sign-in and connection."
            ));
        }
        let Some(report) = &self.usage_report else {
            return panel
                .child(if self.usage_loading {
                    "Reading available provider data…"
                } else {
                    "No data loaded."
                })
                .into_any_element();
        };
        let age = sigmadock_core::unix_time().saturating_sub(report.recorded_at);
        panel = panel.child(format!(
            "Source: {} · Plan: {} · observed {}s ago (Unix {})",
            report.source,
            report.plan.as_deref().unwrap_or("not provided"),
            age,
            report.recorded_at
        ));
        if age > 300 {
            panel = panel.child("This cached report is over five minutes old. Refresh Codex, or wait for new Claude status-line output.");
        }
        if report.windows.is_empty() {
            panel = panel.child(
                "Usage allowance and remaining quota are unavailable; no estimate is shown.",
            );
        }
        for (index, window) in report.windows.iter().enumerate() {
            let reset = window
                .resets_at
                .map(|at| {
                    format!(
                        " · resets in {} min (Unix {at})",
                        at.saturating_sub(sigmadock_core::unix_time()).div_ceil(60)
                    )
                })
                .unwrap_or_else(|| " · reset time not provided".into());
            let duration = window
                .duration_minutes
                .map(|minutes| format!(" · {minutes}-minute window"))
                .unwrap_or_default();
            panel = panel.child(
                div()
                    .id(SharedString::from(format!("usage-window-{index}")))
                    .flex()
                    .flex_col()
                    .gap_1()
                    .child(format!(
                        "{}{}: {:.1}% used · {:.1}% remaining{}",
                        window.name,
                        duration,
                        window.used_percent,
                        100. - window.used_percent,
                        reset
                    ))
                    .child(
                        div()
                            .w_full()
                            .h(px(6.))
                            .rounded_md()
                            .bg(rgb(self.theme.border))
                            .child(
                                div()
                                    .w(relative((window.used_percent / 100.) as f32))
                                    .h_full()
                                    .rounded_md()
                                    .bg(rgb(self.theme.accent)),
                            ),
                    ),
            );
        }
        if let Some(tokens) = report.lifetime_tokens {
            panel = panel.child(format!("Provider account lifetime activity: {tokens} tokens (not the current subscription window)."));
        }
        if report.context_input_tokens.is_some() || report.context_output_tokens.is_some() {
            panel = panel.child(format!("Current worker context: {} input / {} output tokens. Context counts are not session totals or subscription consumption.",report.context_input_tokens.map(|n|n.to_string()).unwrap_or_else(||"unknown".into()),report.context_output_tokens.map(|n|n.to_string()).unwrap_or_else(||"unknown".into())));
        }
        for warning in &report.warnings {
            panel = panel.child(
                div()
                    .text_sm()
                    .text_color(rgb(self.theme.muted))
                    .child(warning.clone()),
            );
        }
        panel.into_any_element()
    }
}