mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-30 14:58:08 +00:00
Every product page ended on "Start building with X" and a filled Get your API key, then one screen later on the layout's "Build what's next." and a filled Try Hanzo, in a different button shape: two closing calls a reader had to choose between. StartBuilding becomes Related: the product's docs link and its related capabilities, no heading and no button, so the closing call below is the one way in. And the open-source row no longer shows a "0 stars" pill, which read as an abandoned repository.
137 lines
5.4 KiB
TypeScript
137 lines
5.4 KiB
TypeScript
/**
|
|
* /integrations — every SDK, framework and language Hanzo answers as.
|
|
*
|
|
* NO TAILWIND. This is a SERVER page and stays one. Its own sections take no
|
|
* `Band`/`Head`, because `Head` carries a scroll entrance and they have never
|
|
* had one — giving them motion would be a change to the picture smuggled in as
|
|
* a conversion. The frame is `Box render="section"` with the paddings the page
|
|
* already stated; the type is @hanzo/ui's marketing components. The agent prompt
|
|
* and the closing block follow the column rather than sit inside it: each is a
|
|
* full-bleed `Band` carrying its own rule, gutter and measure, and nesting one
|
|
* in this page's 1152 column doubles the gutter and stops the rule short of the
|
|
* viewport.
|
|
*
|
|
* A card is `Leaf`, the site's one card-that-IS-a-link: it owns the resting
|
|
* edge, the hover and the reduced-motion opt-out. The rest of the type is
|
|
* `Line`, one rung of the ramp per call.
|
|
*/
|
|
|
|
import { INTEGRATIONS, INTEGRATION_CATEGORIES, type Integration } from '@/lib/integrations'
|
|
import { Box } from '@/components/ui'
|
|
import { Display, Lede, Leaf, Lift, Line, type LineProps } from '@hanzo/ui/marketing'
|
|
import { Grid } from '@hanzo/ui/grid'
|
|
import { AgentPrompt } from '@/components/product/AgentPrompt'
|
|
import { Related } from '@/components/product/Related'
|
|
import { product } from '@/lib/product'
|
|
import { Code } from '@/components/code'
|
|
|
|
/** What the agent prompt and the closing block read, off the one table that
|
|
* holds a product's name, route, audited command and docs page. */
|
|
const PRODUCT = product('integrations')
|
|
|
|
const CARD = { p: 20, borderColor: "var(--border)", display: "flex", flexDirection: "column", gap: 8, bg: "var(--card)", color: "var(--foreground)", hoverStyle: { borderColor: "color-mix(in oklab, var(--brand) 50%, transparent)" } } as const
|
|
|
|
/**
|
|
* A heading BENEATH the page's own, and under `Title`'s rung: these are a
|
|
* card's label and a shelf's label, not a section's argument, so they state
|
|
* their size rather than borrowing a role and then overriding two thirds of it.
|
|
*/
|
|
const Sub = (p: LineProps) => (
|
|
<Line render="h2" display="block" fontSize="$4" lineHeight="var(--leading-snug)" fontWeight="600" color="var(--foreground)" {...p} />
|
|
)
|
|
|
|
function IntegrationCard({ integration }: { integration: Integration }) {
|
|
return (
|
|
<Leaf href={`/integrations/${integration.slug}`} {...CARD} hoverStyle={{ borderColor: "color-mix(in oklab, var(--brand) 50%, transparent)" }}>
|
|
<Box display="flex" flexDirection="row" items="center" gap={12}>
|
|
<Line fontSize="$7" color="inherit">{integration.icon}</Line>
|
|
<Lift fontWeight="600">
|
|
{integration.name}
|
|
</Lift>
|
|
</Box>
|
|
{/* Two lines of description, and no more — the card is a fixed size in a grid. */}
|
|
<Line render="p" numberOfLines={2} fontSize="$2" color="var(--muted-foreground)">
|
|
{integration.description}
|
|
</Line>
|
|
<Line fontSize="$1" textTransform="capitalize" color="color-mix(in oklab, var(--muted-foreground) 60%, transparent)">
|
|
{INTEGRATION_CATEGORIES[integration.category]}
|
|
</Line>
|
|
</Leaf>
|
|
)
|
|
}
|
|
|
|
export default function IntegrationsPage() {
|
|
const categories = Object.entries(INTEGRATION_CATEGORIES) as [Integration['category'], string][]
|
|
|
|
return (
|
|
<Box minH="100vh">
|
|
<Box
|
|
width="100%"
|
|
maxW={1152}
|
|
mx="auto"
|
|
px={16}
|
|
py={80}
|
|
>
|
|
{/* Header */}
|
|
<Box mb={64}>
|
|
<Display mb={16}>
|
|
Works with everything
|
|
</Display>
|
|
<Lede maxW={672}>
|
|
Hanzo AI is fully OpenAI-compatible. Use any SDK, framework, or language you already know.
|
|
Drop in{' '}
|
|
<Line render="code" fontSize="$2" bg="var(--muted)" px={6} py={2} rounded="var(--radius)" color="inherit">https://api.hanzo.ai/v1</Line>{' '}
|
|
as the base URL and your Hanzo API key.
|
|
</Lede>
|
|
</Box>
|
|
|
|
{/* Quick start */}
|
|
<Box
|
|
mb={64}
|
|
rounded="var(--radius-xl)"
|
|
borderWidth={1}
|
|
borderColor="var(--border)"
|
|
bg="var(--card)"
|
|
p={24}
|
|
>
|
|
<Sub mb={12}>Quick start — any OpenAI SDK</Sub>
|
|
{/* Three clients, one base URL. The comments are `#`, so it reads as Python. */}
|
|
<Code
|
|
lang="python"
|
|
rounded="var(--radius-md)"
|
|
code={`# Python
|
|
client = OpenAI(base_url="https://api.hanzo.ai/v1", api_key=HANZO_API_KEY)
|
|
|
|
# TypeScript
|
|
const client = new OpenAI({ baseURL: "https://api.hanzo.ai/v1", apiKey: HANZO_API_KEY });
|
|
|
|
# Go
|
|
client := openai.NewClient(option.WithBaseURL("https://api.hanzo.ai/v1"), option.WithAPIKey(HANZO_API_KEY))`}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Categories */}
|
|
{categories.map(([category, label]) => {
|
|
const items = INTEGRATIONS.filter((i) => i.category === category)
|
|
if (items.length === 0) return null
|
|
return (
|
|
<Box key={category} render="section" mb={48}>
|
|
<Sub mb={24} fontSize="$7" fontWeight="700">
|
|
{label}
|
|
</Sub>
|
|
<Grid columns={{ min: 280, max: 3 }} gap={16}>
|
|
{items.map((integration) => (
|
|
<IntegrationCard key={integration.slug} integration={integration} />
|
|
))}
|
|
</Grid>
|
|
</Box>
|
|
)
|
|
})}
|
|
</Box>
|
|
|
|
{PRODUCT && <AgentPrompt product={PRODUCT} />}
|
|
{PRODUCT && <Related product={PRODUCT} />}
|
|
</Box>
|
|
)
|
|
}
|