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.
161 lines
6.6 KiB
TypeScript
161 lines
6.6 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* hanzo.app: the three ways to get the Hanzo App — Download, Install CLI, Open
|
|
* in web — and nothing else. This export's /app, served as hanzo.app's root
|
|
* (lib/host.ts `GET`).
|
|
*
|
|
* Every choice is a thing a reader can take today. Download is the native build
|
|
* `data/downloads.ts` resolved against the real release (`NATIVE`), the reader's
|
|
* own platform first; Install CLI is the line the download page prints; Open in
|
|
* web is the chat at hanzo.ai/chat, which signs a stranger in first. A phone has
|
|
* no build to take, so its Download says to open the app in the browser instead.
|
|
*/
|
|
|
|
import { useState, type ReactNode } from 'react'
|
|
import { Anchor, Paragraph, SizableText, Text, XStack, YStack } from '@hanzo/gui'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import { Check, Copy, Download, Globe, Terminal, type LucideIcon } from 'lucide-react'
|
|
import { PageHero } from '@/components/marketing/page-kit'
|
|
import { BuildRow } from '@/components/download/surface-card'
|
|
import { INSTALL, NATIVE, weigh, type Build } from '@/data/downloads'
|
|
import { useHydrated } from '@hanzo/app/lib/hydrated'
|
|
import { current, type Platform } from '@/lib/platform'
|
|
import { WEB } from '@hanzo/app/lib/host'
|
|
import { Action } from '@hanzo/ui/marketing'
|
|
|
|
/** Where every build and checksum is listed. */
|
|
const ALL = 'https://hanzo.ai/download'
|
|
const CLI = 'https://docs.hanzo.ai/docs/cli'
|
|
|
|
/** The words a build's name starts with, per platform that can take one. */
|
|
const NAMES: Partial<Record<Platform, string>> = { mac: 'macOS', windows: 'Windows', linux: 'Linux' }
|
|
|
|
/** The reader's own build, once the browser has said what it runs on. */
|
|
function mine(platform: Platform | null): Build | undefined {
|
|
const name = platform ? NAMES[platform] : undefined
|
|
return name ? NATIVE?.builds?.find((b) => b.name.startsWith(name)) : undefined
|
|
}
|
|
|
|
function Card({ icon: Icon, title, body, children }: { icon: LucideIcon; title: string; body: string; children: ReactNode }) {
|
|
return (
|
|
<YStack rounded={18} borderWidth={1} borderColor="$color04" bg="$color2" p="$5" gap="$4" $md={{ p: '$6' }} height="100%">
|
|
<XStack items="center" gap="$2.5">
|
|
<Icon size={18} strokeWidth={1.75} aria-hidden />
|
|
<SizableText render="h2" fontSize="$7" fontWeight="600" letterSpacing={-0.3} color="$color">
|
|
{title}
|
|
</SizableText>
|
|
</XStack>
|
|
<Paragraph fontSize="$3" color="$color11">
|
|
{body}
|
|
</Paragraph>
|
|
{children}
|
|
</YStack>
|
|
)
|
|
}
|
|
|
|
/** The install line, and the one control that puts it on the clipboard. */
|
|
function Line({ command }: { command: string }) {
|
|
const [copied, setCopied] = useState(false)
|
|
const copy = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(command)
|
|
setCopied(true)
|
|
window.setTimeout(() => setCopied(false), 2000)
|
|
} catch {
|
|
/* no clipboard here: the line stays on screen to select by hand */
|
|
}
|
|
}
|
|
return (
|
|
<XStack items="center" gap="$2" rounded="$4" borderWidth={1} borderColor="$color04" bg="$background" pl="$3.5" pr="$1.5" py="$1.5">
|
|
<SizableText fontFamily="$mono" fontSize="$2" color="$color" flex={1} minW={0} whiteSpace="nowrap" overflowX="auto">
|
|
{command}
|
|
</SizableText>
|
|
<Action render="button" type="button" onClick={copy} aria-label={copied ? 'Copied' : 'Copy the install command'} minH={36} px={14}>
|
|
{copied ? <Check size={14} aria-hidden /> : <Copy size={14} aria-hidden />}
|
|
{copied ? 'Copied' : 'Copy'}
|
|
</Action>
|
|
</XStack>
|
|
)
|
|
}
|
|
|
|
export function Get() {
|
|
// The export renders no guess; the browser says what it runs on once hydrated.
|
|
const platform = useHydrated() ? current() : null
|
|
const own = mine(platform)
|
|
const rest = NATIVE?.builds?.filter((b) => b !== own) ?? []
|
|
const phone = platform === 'ios' || platform === 'android'
|
|
|
|
return (
|
|
<>
|
|
<PageHero
|
|
eyebrow="Hanzo App"
|
|
title="Get the Hanzo App"
|
|
lede="Chat, build and ship with one account — on your computer, in your terminal, or right here in the browser."
|
|
/>
|
|
<YStack px="$4" pb="$12" $sm={{ px: '$8' }}>
|
|
<YStack width="100%" maxW={1120} mx="auto">
|
|
<Grid columns={{ min: 280, max: 3 }} gap={16}>
|
|
<Card
|
|
icon={Download}
|
|
title="Download"
|
|
body={
|
|
NATIVE?.id === 'desktop'
|
|
? 'The desktop app, with a global hotkey: ask from any window and put the answer back where you were typing.'
|
|
: 'The Hanzo binary for your machine: the same agent as the app, working in your own checkout, and the whole cloud from one command.'
|
|
}
|
|
>
|
|
{phone ? (
|
|
<Paragraph fontSize="$3" color="$color11">
|
|
There is no build for a phone. Open the app in this browser instead.
|
|
</Paragraph>
|
|
) : own ? (
|
|
<Action fill href={own.url} aria-label={`Download for ${own.name}`} self="flex-start">
|
|
<Download size={15} aria-hidden />
|
|
Download for {own.name}
|
|
<Text opacity={0.6} color="inherit">{weigh(own.size)}</Text>
|
|
</Action>
|
|
) : null}
|
|
{rest.length ? (
|
|
<YStack>
|
|
{rest.map((b) => (
|
|
<BuildRow key={b.name} build={b} surface={NATIVE?.name ?? 'Hanzo'} />
|
|
))}
|
|
</YStack>
|
|
) : null}
|
|
<Anchor href={ALL} self="flex-start">
|
|
<SizableText fontSize="$2" color="$color11" hoverStyle={{ color: '$color' }}>
|
|
All downloads and checksums →
|
|
</SizableText>
|
|
</Anchor>
|
|
</Card>
|
|
|
|
<Card
|
|
icon={Terminal}
|
|
title="Install CLI"
|
|
body="One line in your terminal. Then hanzo in any repository is a coding session, and every product of the cloud is a command."
|
|
>
|
|
{INSTALL ? <Line command={INSTALL} /> : null}
|
|
<Anchor href={CLI} self="flex-start">
|
|
<SizableText fontSize="$2" color="$color11" hoverStyle={{ color: '$color' }}>
|
|
Read the CLI docs →
|
|
</SizableText>
|
|
</Anchor>
|
|
</Card>
|
|
|
|
<Card
|
|
icon={Globe}
|
|
title="Open in web"
|
|
body="Nothing to install. Sign in and the app opens in this browser, Chat and Dev, on the same account as every build here."
|
|
>
|
|
<Action fill href={WEB} self="flex-start">
|
|
Open Hanzo
|
|
</Action>
|
|
</Card>
|
|
</Grid>
|
|
</YStack>
|
|
</YStack>
|
|
</>
|
|
)
|
|
}
|