mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +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.
153 lines
5.6 KiB
TypeScript
153 lines
5.6 KiB
TypeScript
import type { ZenModel } from '@zenlm/models'
|
|
import { canonicalOrg, getOrgAndSlug, modelPagePath } from '@hanzo/app/lib/path'
|
|
// What a provider and a model are called: the rooms' rules, the one copy.
|
|
export { ORG_DISPLAY_NAMES, orgDisplayName, modelName } from '@hanzo/app/lib/models'
|
|
|
|
export interface ModelSpec {
|
|
params?: string
|
|
activeParams?: string
|
|
context?: number
|
|
arch?: string
|
|
}
|
|
|
|
export interface ModelPrice {
|
|
input?: number | null
|
|
output?: number | null
|
|
}
|
|
|
|
export interface ModelData {
|
|
id: string
|
|
name: string
|
|
fullName?: string
|
|
provider: string
|
|
description?: string
|
|
context?: number
|
|
modalities?: string[]
|
|
/** @zenlm/models' own union. Widened to `string` here once, which is what let
|
|
* six `status !== 'coming-soon'` filters compile and silently match every
|
|
* model — dead code that read as a safeguard. Narrow, so the next one fails. */
|
|
status: 'available' | 'cloud-only'
|
|
category: string
|
|
spec?: ModelSpec
|
|
features?: string[]
|
|
huggingface?: string
|
|
github?: string
|
|
aliases?: string[]
|
|
tier?: string
|
|
generation?: string
|
|
/** USD per million tokens, as the live catalog states it. */
|
|
pricing?: ModelPrice | null
|
|
/** The family the gateway files it under (`kai`, `enso`, `zen`), if one of ours. */
|
|
family?: string
|
|
/** Where it is called: a model whose output is a decision answers at /v1/decisions. */
|
|
endpoint?: '/v1/decisions' | '/v1/chat/completions'
|
|
}
|
|
|
|
export interface ModelsResponse {
|
|
total: number
|
|
models: ModelData[]
|
|
}
|
|
|
|
/**
|
|
* A description a page is allowed to print.
|
|
*
|
|
* The live catalog fills an absent description with the placeholder
|
|
* `Zen model: <id>`, and that string is wrong twice over. It repeats the slug
|
|
* already printed beside it, and it calls a ROUTED model a Zen model — Zen is
|
|
* Hanzo's own family, so `Zen model: claude-opus-4.5` states something false
|
|
* about a model the platform routes rather than trains. The ids themselves are
|
|
* correct and public: they are what a caller passes to api.hanzo.ai, which is
|
|
* why the quick-start on each page prints them.
|
|
*
|
|
* Dropped HERE, at the one boundary both the live catalog and the static
|
|
* fallback pass through, so no page has to remember. The index page carried its
|
|
* own guard and the detail page did not, which put the placeholder in the
|
|
* visible lede, the meta description, the Open Graph and Twitter cards and the
|
|
* JSON-LD `description` of 116 pages — four of those being what a search engine
|
|
* indexes rather than what a visitor reads.
|
|
*/
|
|
export function described(m: ModelData): ModelData {
|
|
const d = m.description?.trim()
|
|
return d && !/^zen model:/i.test(d) ? m : { ...m, description: undefined }
|
|
}
|
|
|
|
export function zenToModelData(z: ZenModel): ModelData {
|
|
return {
|
|
id: z.id,
|
|
name: z.name,
|
|
fullName: z.fullName,
|
|
provider: 'hanzo',
|
|
description: z.description,
|
|
context: z.spec?.context,
|
|
modalities: z.modalities as string[],
|
|
status: z.status,
|
|
category: z.category,
|
|
spec: z.spec ? {
|
|
params: z.spec.params,
|
|
activeParams: z.spec.activeParams ?? undefined,
|
|
context: z.spec.context,
|
|
arch: z.spec.arch,
|
|
} : undefined,
|
|
features: z.features,
|
|
huggingface: z.huggingface ?? undefined,
|
|
github: z.github ?? undefined,
|
|
aliases: z.aliases,
|
|
tier: z.tier,
|
|
generation: z.generation,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A price per million tokens, as a person reads it. Free is stated as Free rather
|
|
* than as $0.00, because that is the fact somebody is looking for; a price nobody
|
|
* published is stated as nothing at all rather than as zero.
|
|
*
|
|
* Three decimals under a dollar and two above it: at $0.05 the second decimal is
|
|
* the whole number, and at $15 the third is noise. THAT RULE IS STATED HERE ONLY.
|
|
* A column of rates wants the money and not the unit — the unit belongs to the
|
|
* column head, said once, rather than to each of five hundred cells — so `rate`
|
|
* is the money and `formatPrice` is the money with its unit, and neither can
|
|
* round differently from the other.
|
|
*/
|
|
export function rate(usdPerMillion?: number | null): string | null {
|
|
if (usdPerMillion === null || usdPerMillion === undefined) return null
|
|
if (usdPerMillion === 0) return 'Free'
|
|
return `$${usdPerMillion.toFixed(usdPerMillion < 1 ? 3 : 2)}`
|
|
}
|
|
|
|
export function formatPrice(usdPerMillion?: number | null): string | null {
|
|
const money = rate(usdPerMillion)
|
|
return money === null || money === 'Free' ? money : `${money} / M tokens`
|
|
}
|
|
|
|
export { canonicalOrg, getOrgAndSlug, modelPagePath }
|
|
|
|
/**
|
|
* How many tokens a model holds. The routing table states no window for most
|
|
* models of ours, so the build joins the price list's in (lib/models/served):
|
|
* by the time a page holds a row, `context` is already the whole answer, and
|
|
* the spec's is what the Zen release states where nothing else does.
|
|
*/
|
|
export function context(m: Pick<ModelData, 'context' | 'spec'>): number {
|
|
return m.context ?? m.spec?.context ?? 0
|
|
}
|
|
|
|
export function formatContext(ctx: number | undefined): string {
|
|
if (!ctx) return 'N/A'
|
|
if (ctx >= 1_000_000) return `${Math.round(ctx / 1_000_000)}M`
|
|
if (ctx >= 1_000) return `${Math.round(ctx / 1000)}K`
|
|
return String(ctx)
|
|
}
|
|
|
|
|
|
|
|
|
|
// Monochrome: modality badges are distinguished by their label, not by hue.
|
|
//
|
|
// `MODALITY_STYLES` used to live here — seven keys, every one answering the
|
|
// same pair of utility classes, so a lookup table was deciding a single value.
|
|
// Worse, its three readers each wrote `?? { bg: 'bg-secondary', text:
|
|
// 'text-muted-foreground' }`, so an unlisted modality rendered in a DIFFERENT
|
|
// grey than a listed one and nothing said which. The chip states the value now:
|
|
// `components/models/kit`'s `Modality`.
|