mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
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.
106 lines
4.4 KiB
TypeScript
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}
|
|
</>
|
|
)
|
|
}
|