mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
/chat signed out is the buying agent (components/sales/Guest, Agent): the
question the home composer carried in ?q= is asked of cloud's
POST /v1/ai/sales, the reply is drawn as text, and each next step the
server built is a control: a call (the lead is filed first, then the
calendar opens on it), the sign-up, a plan's card checkout, Add funds. A
quiet line says it is a limited preview and how many messages are left; at
the ceiling the sign-up takes the composer's place. A conversation's own
address still signs in, and a signed-in reader keeps the app.
/buying-specialist is the same agent; its keyword script, its typed prices
and its claims to see an account are gone. The model pages' no-key card is
the real lane, POST /v1/chat/public, not a guest key that answers 401.
Measured in the site's names: chat_started, cta_clicked {cta: kind, plan,
agent: sales}, sales_contacted. The floating privacy button is hidden as
room.css meant: the rule names its tag to outrank gui's atomic class.
404 lines
14 KiB
TypeScript
404 lines
14 KiB
TypeScript
/**
|
|
* /models — the one overview of Hanzo's three model families, then every model
|
|
* the gateway answers for and what it costs.
|
|
*
|
|
* Order: the promise and the scale; `Family` (what Zen, Enso and Kai each do,
|
|
* each card to its own page); `Versus` (how Enso and Zen are had); the featured
|
|
* Enso and Zen models; one flagship per other lab; the priced catalogue; a
|
|
* thread to try; the request itself. The counts and the priced catalogue are
|
|
* read when the page opens (components/api/count, components/models/Catalog);
|
|
* the featured cards are the models this build wrote pages for, by id, so one
|
|
* the gateway stops serving drops off rather than 404s.
|
|
*/
|
|
import { KEYS } from '@/components/home/nav-data'
|
|
import { Live } from '@/components/models/Live'
|
|
import { ProviderMark } from '@hanzo/app/ProviderMark'
|
|
import { TryChat } from '@/components/models/TryChat'
|
|
import { Modality, Sample, Tile } from '@/components/models/kit'
|
|
import { GuestKey } from '@/components/models/guest'
|
|
import type { ModelData } from '@/lib/models'
|
|
import { context, getOrgAndSlug, formatContext, modelName, modelPagePath } from '@/lib/models'
|
|
import { fetchModels } from '@/lib/models/served'
|
|
import Catalog from '@/components/models/Catalog'
|
|
import { Tally } from '@/components/api/count'
|
|
import { Box, Text, View, XStack } from '@/components/ui'
|
|
import { Action, Display, Lede, Title } from '@hanzo/ui/marketing'
|
|
import { Grid, Cell } from '@hanzo/ui/grid'
|
|
import { Request } from '@/components/models/preview'
|
|
import { Band, Card } from '@/components/marketing/band'
|
|
import { AgentPrompt } from '@/components/product/AgentPrompt'
|
|
import { Family } from '@/components/family/Family'
|
|
import { Versus } from '@/components/family/Versus'
|
|
import { Related } from '@/components/product/Related'
|
|
import { product } from '@/lib/product'
|
|
import { NEXT, NOW } from '@/lib/zen'
|
|
import { KAI as RESULTS } from '@/lib/bench-data'
|
|
import { p50 } from '@/lib/kai'
|
|
import { Code } from '@/components/code'
|
|
import { CREATIVE_MODELS } from '@/lib/creative'
|
|
|
|
export const revalidate = 3600
|
|
|
|
/** What the shared product blocks say about this page: the audited command, the
|
|
* route the catalog proves it by, and the docs page or null. */
|
|
const PRODUCT = product('models')
|
|
|
|
/** The ground a band lifts to when it is told from its neighbour by colour as
|
|
* well as by a rule. */
|
|
const LIFT = 'color-mix(in oklab, var(--secondary) 10%, transparent)'
|
|
|
|
/**
|
|
* A card deck that never leaves a hole in the last row. Two-up, always: three
|
|
* tracks pass through two as the measure narrows, and a count that divides by
|
|
* three then strands its ninth card half across the row. Two divides everything
|
|
* but an odd count, and an odd count's last card simply takes the whole row.
|
|
*/
|
|
const tail = (n: number) => (n % 2 === 0 ? undefined : '1 / -1')
|
|
|
|
/** Ours, trained by Hanzo and served here only. */
|
|
const ENSO = ['enso', 'enso-flash', 'enso-ultra']
|
|
|
|
/** Zoo Labs Foundation's, weights published: the generation that answers now
|
|
* first (lib/zen), then the one before it and the specialists. */
|
|
const ZEN = [...NOW.ids, 'zen5', 'zen5-pro', 'zen5-coder', 'zen5-flash', 'zen-vl', 'zen-embedding']
|
|
|
|
const KAI = ['kai']
|
|
|
|
/** Kai's results card reads the research runs (lib/kai): the first harness claim
|
|
* that holds, the measured p50 when there is one, and the record sentence. */
|
|
const RESULT = RESULTS.claims.find((c) => c.board === 'kai-harness')
|
|
const TIMED = p50(RESULTS)
|
|
|
|
const FEATURED = [...ENSO, ...ZEN, ...KAI]
|
|
|
|
/**
|
|
* A model's mark, in the page's ink: Hanzo's H for ours (Enso, Kai and the
|
|
* creative models), Zoo Labs Foundation's venn for Zen, a lab's own mark for
|
|
* every other model.
|
|
*/
|
|
function markOf(id: string) {
|
|
if (/^zen/.test(id)) return <ProviderMark org="zoo" model={id} ink />
|
|
if ([...ENSO, ...KAI].includes(id)) return <ProviderMark org="hanzo" ink />
|
|
return <ProviderMark org={getOrgAndSlug(id).org} model={id} />
|
|
}
|
|
|
|
/** Where Zen's weights are published, and who makes them with us. */
|
|
const ZENLM = 'https://huggingface.co/zenlm'
|
|
const ZOO = 'https://zoo.ngo'
|
|
|
|
/** One flagship per lab, in the order we would point a visitor at. The model is
|
|
* resolved from the live catalogue, so a version bump follows on its own. */
|
|
const TOP_LABS = ['anthropic', 'openai', 'google', 'deepseek', 'x-ai', 'meta', 'mistral', 'cohere']
|
|
|
|
/** A lab's headline general-purpose model: no alias namespace, no point
|
|
* variant, no speed or size cut, no dated snapshot, no specialised modality. */
|
|
function isCanonical(id: string): boolean {
|
|
const slug = id.split('/').pop() ?? id
|
|
if (id.startsWith('~') || id.includes(':')) return false
|
|
if (/(?:^|[-])(?:fast|flash|mini|nano|tiny|small|lite|air|preview|exp|latest|thinking|code|base|instruct|image|video|audio|voice|tts|embed|embedding|rerank|ocr|guard|moderation)(?:$|[-])/i.test(slug))
|
|
return false
|
|
if (/-\d{6,}$/.test(slug)) return false
|
|
return true
|
|
}
|
|
|
|
function ModelCard({ model }: { model: ModelData }) {
|
|
const ctx = context(model)
|
|
return (
|
|
<Tile
|
|
size="lg"
|
|
href={modelPagePath(model.id)}
|
|
mark={markOf(model.id)}
|
|
name={modelName(model)}
|
|
id={model.id}
|
|
ctx={ctx ? formatContext(ctx) : undefined}
|
|
blurb={model.description}
|
|
>
|
|
{model.modalities && model.modalities.length > 0 && (
|
|
<XStack flexWrap="wrap" gap="$1">
|
|
{model.modalities.slice(0, 3).map((m) => (
|
|
<Modality key={m} name={m} />
|
|
))}
|
|
</XStack>
|
|
)}
|
|
</Tile>
|
|
)
|
|
}
|
|
|
|
export default async function ModelsPage() {
|
|
const data = await fetchModels()
|
|
const available = data.models
|
|
const byId = new Map(available.map((m) => [m.id, m]))
|
|
|
|
const enso = ENSO.flatMap((id) => byId.get(id) ?? [])
|
|
const zen = ZEN.flatMap((id) => byId.get(id) ?? [])
|
|
const kai = KAI.flatMap((id) => byId.get(id) ?? [])
|
|
const featured = new Set(FEATURED.flatMap((id) => byId.get(id) ?? []).map((m) => m.id))
|
|
const top = TOP_LABS.flatMap((lab) => {
|
|
const m = available.find(
|
|
(x) => getOrgAndSlug(x.id).org.replace(/^~/, '') === lab && !featured.has(x.id) && isCanonical(x.id),
|
|
)
|
|
return m ? [m] : []
|
|
})
|
|
|
|
const dEnso = tail(enso.length)
|
|
const dZen = tail(zen.length + 1)
|
|
const dKai = tail(kai.length + 2)
|
|
const dTop = tail(top.length)
|
|
|
|
// The sample calls an id this catalogue serves: the first featured one.
|
|
const called = FEATURED.find((id) => byId.has(id))
|
|
const request =
|
|
called &&
|
|
`curl https://api.hanzo.ai/v1/chat/completions \\
|
|
-H "Authorization: Bearer $HANZO_API_KEY" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d '{
|
|
"model": "${called}",
|
|
"messages": [{ "role": "user", "content": "Hello" }]
|
|
}'`
|
|
|
|
return (
|
|
<Box minH="100vh" $platform-web={{ color: 'var(--foreground)' }}>
|
|
<Box
|
|
render="section"
|
|
position="relative"
|
|
overflow="hidden"
|
|
pt={128}
|
|
pb={96}
|
|
px="$4"
|
|
$sm={{ px: '$5' }}
|
|
$md={{ pb: 112 }}
|
|
$lg={{ px: '$6' }}
|
|
>
|
|
<View
|
|
aria-hidden
|
|
position="absolute"
|
|
t={0}
|
|
l="50%"
|
|
x="-50%"
|
|
width={800}
|
|
height={480}
|
|
pointerEvents="none"
|
|
backgroundImage="radial-gradient(circle, var(--white-10) 0%, transparent 68%)"
|
|
filter="blur(100px)"
|
|
/>
|
|
<Box position="relative" z="var(--z-raised)" width="100%" maxW={896} mx="auto" $platform-web={{ textAlign: 'center' }}>
|
|
<Live />
|
|
<Display mb={24}>Every model. One endpoint.</Display>
|
|
<Lede mb={32} width="100%" maxW={672} mx="auto">
|
|
One API key for every lab. Switch models by changing one string.
|
|
</Lede>
|
|
{/* The scale, one quiet line: 43 characters at its longest, one row at 390. */}
|
|
<Text render="p" display="block" mb="$8" fontSize="$2" lineHeight="$2" color="var(--muted-foreground)">
|
|
<Tally of="totalModels" />{' '}
|
|
models
|
|
<Text aria-hidden px="$2">
|
|
·
|
|
</Text>
|
|
<Tally of="providers" />{' '}
|
|
providers
|
|
<Text aria-hidden px="$2">
|
|
·
|
|
</Text>
|
|
<Tally of="freeModels" />{' '}
|
|
free to call
|
|
</Text>
|
|
<XStack flexWrap="wrap" justify="center" gap="$4">
|
|
<Action href={KEYS} fill>
|
|
Get free API key
|
|
</Action>
|
|
</XStack>
|
|
<Box mt="$8" maxW={640} mx="auto" $platform-web={{ textAlign: 'left' }}>
|
|
<GuestKey />
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
|
|
<Family />
|
|
<Versus />
|
|
|
|
{PRODUCT && <AgentPrompt product={PRODUCT} />}
|
|
|
|
<Band pad="$11" measure={1152}>
|
|
<Title quiet mb={40}>
|
|
Enso models
|
|
</Title>
|
|
<Grid columns={{ min: 250, max: 2 }} gap={24}>
|
|
{enso.map((model, i) =>
|
|
i === enso.length - 1 ? (
|
|
<Cell key={model.id} col={dEnso}>
|
|
<ModelCard model={model} />
|
|
</Cell>
|
|
) : (
|
|
<ModelCard key={model.id} model={model} />
|
|
),
|
|
)}
|
|
</Grid>
|
|
</Band>
|
|
|
|
<Band pad="$11" measure={1152}>
|
|
<Title quiet mb={8}>
|
|
Zen models
|
|
</Title>
|
|
<Lede mb={40} maxW={720}>
|
|
Open weights, made with{' '}
|
|
<Text render={<a href={ZOO} />} color="inherit" $platform-web={{ textDecoration: 'underline', textUnderlineOffset: '3px' }}>
|
|
Zoo Labs Foundation
|
|
</Text>
|
|
. Download every Zen model at{' '}
|
|
<Text render={<a href={ZENLM} />} color="inherit" $platform-web={{ textDecoration: 'underline', textUnderlineOffset: '3px' }}>
|
|
huggingface.co/zenlm
|
|
</Text>
|
|
, or call it here.
|
|
</Lede>
|
|
<Grid columns={{ min: 250, max: 2 }} gap={24}>
|
|
{zen.map((model) => (
|
|
<ModelCard key={model.id} model={model} />
|
|
))}
|
|
<Cell col={dZen}>
|
|
<Tile size="lg" mark={markOf(NEXT.id)} name={NEXT.name} id={NEXT.id} blurb={NEXT.blurb}>
|
|
<Request />
|
|
</Tile>
|
|
</Cell>
|
|
</Grid>
|
|
</Band>
|
|
|
|
<Band pad="$11" measure={1152}>
|
|
<Title quiet mb={40}>
|
|
Kai decision models
|
|
</Title>
|
|
<Grid columns={{ min: 250, max: 2 }} gap={24}>
|
|
{kai.map((model) => (
|
|
<ModelCard key={model.id} model={model} />
|
|
))}
|
|
<Tile
|
|
size="lg"
|
|
href="/kai#benchmarks"
|
|
mark={markOf('kai')}
|
|
name="Decision Benchmarks"
|
|
id={RESULT ? `${RESULT.figure} ${RESULT.label.split(',')[0].toLowerCase()}` : undefined}
|
|
ctx={TIMED ? `${TIMED.ms} ms` : undefined}
|
|
blurb={RESULTS.headline.lede}
|
|
>
|
|
<XStack flexWrap="wrap" gap="$1">
|
|
<Modality name="benchmarks" />
|
|
</XStack>
|
|
</Tile>
|
|
<Cell col={dKai}>
|
|
<Tile
|
|
size="lg"
|
|
href="/kai#types"
|
|
mark={markOf('kai')}
|
|
name="Autonomous Agent Steering"
|
|
id="decision-ready"
|
|
blurb="A choice, a score or a yes/no, each with its probabilities, for agent loops such as Hanzo Dev's."
|
|
>
|
|
<XStack flexWrap="wrap" gap="$1">
|
|
<Modality name="choice" />
|
|
<Modality name="score" />
|
|
<Modality name="noul" />
|
|
</XStack>
|
|
</Tile>
|
|
</Cell>
|
|
</Grid>
|
|
</Band>
|
|
|
|
<Band id="creative" pad="$11" measure={1152}>
|
|
<Title quiet mb={8}>
|
|
Creative models
|
|
</Title>
|
|
<Lede mb={40} maxW={720}>
|
|
Video, images, 3D, sound effects, voice and music, each its own model, in research preview.
|
|
</Lede>
|
|
<Grid columns={{ min: 280, max: 3 }} gap={24}>
|
|
{CREATIVE_MODELS.map((m) => {
|
|
const endpoint = {
|
|
koma: 'api.hanzo.ai/v1/video',
|
|
iro: 'api.hanzo.ai/v1/images',
|
|
mori: 'api.hanzo.ai/v1/3d',
|
|
oto: 'api.hanzo.ai/v1/foley',
|
|
koe: 'api.hanzo.ai/v1/audio/speech',
|
|
uta: 'api.hanzo.ai/v1/music',
|
|
}[m.slug] ?? `api.hanzo.ai/v1/${m.slug}`
|
|
return (
|
|
<Tile
|
|
key={m.id}
|
|
size="lg"
|
|
href={m.route}
|
|
mark={<ProviderMark org="hanzo" ink />}
|
|
name={m.name}
|
|
id={m.modelId}
|
|
ctx="Research preview"
|
|
blurb={m.lede}
|
|
>
|
|
<XStack flexWrap="wrap" gap="$1" items="center" justify="space-between" width="100%">
|
|
<Modality name={m.role.toLowerCase()} />
|
|
<Text
|
|
fontSize="$1"
|
|
color="var(--muted-foreground)"
|
|
fontFamily="$mono"
|
|
>
|
|
{endpoint}
|
|
</Text>
|
|
</XStack>
|
|
</Tile>
|
|
)
|
|
})}
|
|
</Grid>
|
|
|
|
</Band>
|
|
|
|
<Band pad="$11" measure={1152} ground={LIFT}>
|
|
<Title quiet mb={40}>
|
|
Flagships from other labs
|
|
</Title>
|
|
<Grid columns={{ min: 250, max: 2 }} gap={24}>
|
|
{top.map((model, i) =>
|
|
i === top.length - 1 ? (
|
|
<Cell key={model.id} col={dTop}>
|
|
<ModelCard model={model} />
|
|
</Cell>
|
|
) : (
|
|
<ModelCard key={model.id} model={model} />
|
|
),
|
|
)}
|
|
</Grid>
|
|
</Band>
|
|
|
|
<Band pad="$11" measure={1152}>
|
|
<Title quiet>Every model, and what it costs</Title>
|
|
<Lede mt="$3.5" mb={40} maxW={672}>
|
|
Dollars per million tokens, in and out, from the live catalog.
|
|
</Lede>
|
|
<Catalog />
|
|
</Band>
|
|
|
|
<Band pad="$11" measure={1152} ground={LIFT}>
|
|
<Title quiet>Try any model</Title>
|
|
<Lede mt="$3.5" mb={40} maxW={672}>
|
|
Test any model directly on the gateway. Switch models by changing one string.
|
|
</Lede>
|
|
<TryChat />
|
|
</Band>
|
|
|
|
{request ? (
|
|
<Band pad="$11" measure={896}>
|
|
<Card p="$6" $md={{ p: '$8' }}>
|
|
<Box mb="$6" $platform-web={{ textAlign: 'center' }}>
|
|
<Title quiet>Your first request</Title>
|
|
<Lede mt="$3.5" mx="auto" maxW={672}>
|
|
The chat-completions shape you already send, at api.hanzo.ai/v1.
|
|
</Lede>
|
|
</Box>
|
|
<Sample lang="bash">
|
|
<Code lang="bash" code={request} />
|
|
</Sample>
|
|
</Card>
|
|
</Band>
|
|
) : null}
|
|
|
|
{PRODUCT && <Related product={PRODUCT} />}
|
|
</Box>
|
|
)
|
|
}
|