Files
hanzo.ai/components/settings/Usage.tsx
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

106 lines
4.4 KiB
TypeScript

'use client'
/**
* Usage, read-only.
*
* The plan leads (`GET /v1/ai/limits`, @hanzo/app/lib/limits): its period, a
* share per class of model with when it resets, the plan's terms in plain words
* and the ways to more — never an amount or a count of what a plan includes.
* What the plan allows besides is named without figures (agents, models). The
* spend below is the one usage panel every Hanzo surface renders (@hanzo/usage),
* reading as the reader in the acting org, and only for a reader with no plan: a
* plan holder's usage is the shares above, never money (spendShown).
*/
import { useCallback } from 'react'
import { useIam } from '@hanzo/iam/react'
import { Text, XStack, YStack } from '@hanzo/ui'
import { PlanUsage } from '@hanzo/ui/product/PlanUsage'
import { UsagePanel } from '@hanzo/usage/panel'
import { scope } from '@hanzo/app/lib/session'
import { api } from '@hanzo/app/lib/api'
import { payPage } from '@hanzo/app/lib/pay'
import { say } from '@hanzo/app/lib/org'
import { setCreditsAfterAllowance, spendShown, useLimits } from '@hanzo/app/lib/limits'
import { ready, usePlans } from '@/lib/api/use'
import { useStanding } from './Frame'
import { Heading, Line, Section } from './kit'
export function Usage() {
const { org, tier, tierWrong } = useStanding()
const { accessToken } = useIam()
const read = useLimits(Boolean(org), org)
const { limits } = read
// A plan's name is the plan authority's, read when the page opens (lib/api).
const catalog = ready(usePlans())
const name = (slug?: string | null) => (slug && catalog?.find((p) => p.id === slug)?.name) || null
// The panel reads as whoever holds the bearer; the org this browser works in
// rides beside it, the same headers every other read here carries.
const scoped = useCallback<typeof fetch>((input, init) => {
const headers = new Headers(init?.headers)
for (const [k, v] of Object.entries(scope())) headers.set(k, v)
return fetch(input, { ...init, headers })
}, [])
if (!org) return null
// The plan is named once, by the usage policy that reads it, and the tier's own
// plan beside it: a tier's name is the CLASS of plan (every paid rung is Pro), so
// printing it here called a Max 20x org a Pro one under its own Max 20x heading.
const rung = tier?.tier
const named = name(limits?.plan) || name(tier?.plan) || rung?.displayName || rung?.name
return (
<>
<Heading title="Usage" lede="What this organization's plan includes, and what it has spent." />
{limits ? (
<Section title="Plan usage" ruled={false}>
<PlanUsage
limits={limits}
plan={name(limits.plan) || undefined}
notice={read.notice}
onCredits={(on) => setCreditsAfterAllowance(on).then(read.reload)}
creditsHref={payPage()}
addCreditsHref={payPage('/amount/card')}
/>
</Section>
) : null}
<Section title="Plan" ruled={Boolean(limits)} lede={named ? `On the ${named} plan.` : undefined}>
{tierWrong ? <Line tone="bad">The plan could not be read: {say(tierWrong)}</Line> : null}
{!tier && !tierWrong ? <Line>Reading the plan…</Line> : null}
{tier ? (
<YStack>
<XStack items="center" gap="$3" py="$2.5" borderBottomWidth={1} borderColor="$edge">
<Text fontSize="$3" color="$ink" flex={1}>
Agents
</Text>
<Text fontSize="$3" color="$ink">
{tier.tier.unlimitedAgents ? 'No limit' : tier.tier.maxAgents.toLocaleString('en-US')}
</Text>
</XStack>
<XStack items="center" gap="$3" py="$2.5">
<Text fontSize="$3" color="$ink" flex={1}>
Models
</Text>
<Text fontSize="$3" color="$ink" numberOfLines={2} maxW="60%" text="end">
{(tier.tier.allowedModels ?? []).includes('*') ? 'Every model' : (tier.tier.allowedModels ?? []).join(', ') || 'None'}
</Text>
</XStack>
</YStack>
) : null}
</Section>
{spendShown(read) ? (
<Section title="Spend">
{accessToken ? (
<UsagePanel baseUrl={api()} token={accessToken} fetch={scoped} upgrade={payPage('/cart')} title="" />
) : (
<Line>Sign in to see what has been spent.</Line>
)}
</Section>
) : null}
</>
)
}