usage.rsannotatedusage.rssource110 lines · 4.0 KB · raw

/usage: what the parents' screen shows about thinking and the natural voice.

3use ::log::{debug, error, trace, warn};
4use serde::Serialize;
5use whiskers_ports::{Allowance, Clock, HasAllowance, HasClock, HasHousehold, HasVoice, Hub, ThinkingUsage, Voice};
7use super::{Route, sealed};
8use crate::Response;

POST /usage: both allowances, for the parents' screen. Needs the household's choices, the allowance, the voice (whose credits it reports) and the clock.

12pub struct Usage;
14impl sealed::Sealed for Usage {}
15
16impl<C: HasHousehold + HasAllowance + HasVoice + HasClock> Route<C> for Usage {
17    const PATHS: &'static [&'static str] = &["/usage"];
18    async fn answer(&self, adapter: &C, _: &str, _: &str) -> Response {
19        usage(adapter).await
20    }
21}

The thinking half of the reply.

24#[derive(Serialize, Debug, PartialEq, Eq)]
25pub(crate) struct TokenUsage {
26    used: u64,
27    limit: Option<u64>,
28    window_hours: u64,

When the oldest spending leaves the window, so room starts to return; None if nothing is spent.

30    frees_up_at_ms: Option<u64>,
31}
33impl From<ThinkingUsage> for TokenUsage {
34    fn from(u: ThinkingUsage) -> Self {
35        Self { used: u.used.get(), limit: u.limit.map(|l| l.get()), window_hours: u.window_hours, frees_up_at_ms: u.frees_up_at.map(|m| m.get()) }
36    }
37}

The voice half of the reply.

40#[derive(Serialize, Debug, Default, PartialEq, Eq)]
41pub(crate) struct VoiceUsage {
42    configured: bool,

The service's own daily allowance, in characters.

44    daily_cap: u32,
45    daily_spent: u32,
46    last_error: Option<String>,

The voice service's monthly credits; None when they could not be read (see plan_error).

48    tier: Option<String>,
49    used: Option<u64>,
50    limit: Option<u64>,
51    resets_at_unix: Option<u64>,
52    plan_error: Option<String>,
53}
55async fn usage<C: HasHousehold + HasAllowance + HasVoice + HasClock>(adapter: &C) -> Response {
56    let config = match adapter.household().current().await {
57        Ok(h) => h.config(),
58        Err(e) => {
59            error!("/usage: the household's choices cannot be read: {e}");
60            return Response::empty(500);
61        }
62    };
63    let now = adapter.clock().now();
64    let tokens: TokenUsage = match adapter.allowance().thinking_usage(now, &config.tokens).await {
65        Ok(u) => u.into(),
66        Err(e) => {
67            error!("/usage: the thinking allowance cannot be read: {e}");
68            return Response::empty(500);
69        }
70    };
71    debug!("/usage: tokens used {} of {:?}", tokens.used, tokens.limit);
72    let voice = match voice_usage(adapter, config.voice_daily_chars).await {
73        Ok(v) => v,
74        Err(e) => {
75            error!("/usage: the voice allowance cannot be read: {e}");
76            return Response::empty(500);
77        }
78    };
79    Response::of(&serde_json::json!({ "voice": voice, "tokens": tokens }))
80}

The usage the parents see: the service's own counters now, and the voice service's monthly credits (only if it is set up, since asking is a call out).

84async fn voice_usage<C: HasAllowance + HasVoice + HasClock>(adapter: &C, daily_cap: u32) -> Result<VoiceUsage, whiskers_ports::StoreError> {
85    let spend = adapter.allowance().voice_spend(adapter.clock().now()).await?;
86    let configured = adapter.voice().configured().await;
87    let mut u = VoiceUsage {
88        configured,
89        daily_cap,
90        daily_spent: spend.spent_today,
91        last_error: spend.last_failure.map(|f| f.to_string()),
92        ..VoiceUsage::default()
93    };
94    if configured {
95        match adapter.voice().credits().await {
96            Ok(c) => {
97                u.tier = c.tier;
98                u.used = c.used;
99                u.limit = c.limit;
100                u.resets_at_unix = c.resets_at_unix;
101            }
102            Err(e) => {
103                warn!("voice usage: the voice service's credits could not be read: {e}");
104                u.plan_error = Some(e.to_string());
105            }
106        }
107    }
108    trace!("voice usage: {} of {} chars spent today", u.daily_spent, u.daily_cap);
109    Ok(u)
110}