mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-28 07:09:50 +00:00
259 lines
12 KiB
TypeScript
259 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { CopyIcon, CheckIcon, DownloadIcon } from 'lucide-react';
|
|
|
|
type Mode = 'oneliner' | 'quick' | 'hackable' | 'macos';
|
|
type PM = 'npm' | 'pnpm';
|
|
type OS = 'unix' | 'windows';
|
|
type WinShell = 'powershell' | 'cmd';
|
|
type HackableMode = 'installer' | 'pnpm';
|
|
|
|
const WINDOWS_PS = 'iwr -useb https://hanzo.bot/install.ps1 | iex';
|
|
const WINDOWS_PS_BETA = '& ([scriptblock]::Create((iwr -useb https://hanzo.bot/install.ps1))) -Tag beta';
|
|
const WINDOWS_CMD = 'curl -fsSL https://hanzo.bot/install.cmd -o install.cmd && install.cmd && del install.cmd';
|
|
const WINDOWS_CMD_BETA = 'curl -fsSL https://hanzo.bot/install.cmd -o install.cmd && install.cmd --tag beta && del install.cmd';
|
|
|
|
function CopyButton({ text }: { text: string }) {
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
const copy = useCallback(async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
} catch {}
|
|
}, [text]);
|
|
|
|
return (
|
|
<button
|
|
onClick={copy}
|
|
className={`opacity-60 group-hover/line:opacity-100 ml-auto flex items-center justify-center w-7 h-7 border-none rounded-md cursor-pointer transition-all shrink-0 ${
|
|
copied ? 'bg-[rgba(0,229,204,0.2)] text-[#00e5cc]' : 'bg-[rgba(255,255,255,0.1)] text-fd-muted-foreground hover:bg-[rgba(255,255,255,0.2)] hover:text-fd-foreground'
|
|
}`}
|
|
title="Copy"
|
|
>
|
|
{copied ? <CheckIcon className="w-3.5 h-3.5" /> : <CopyIcon className="w-3.5 h-3.5" />}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
function TabButton({ active, onClick, children, variant = 'mode' }: {
|
|
active: boolean;
|
|
onClick: () => void;
|
|
children: React.ReactNode;
|
|
variant?: 'mode' | 'option' | 'beta';
|
|
}) {
|
|
const base = 'font-mono text-[0.7rem] px-2.5 py-1 border-none rounded cursor-pointer transition-all';
|
|
const styles = {
|
|
mode: active
|
|
? `${base} bg-[#00e5cc] text-[#050810] font-semibold`
|
|
: `${base} bg-transparent text-fd-muted-foreground hover:text-fd-secondary-foreground`,
|
|
option: active
|
|
? `${base} bg-brand text-[#050810] font-semibold`
|
|
: `${base} bg-transparent text-fd-muted-foreground hover:text-fd-secondary-foreground`,
|
|
beta: active
|
|
? `${base} bg-[rgba(0,229,204,0.15)] border border-[#00e5cc] text-[#00e5cc]`
|
|
: `${base} bg-transparent border border-fd-border text-fd-muted-foreground hover:border-[#00e5cc] hover:text-[#00e5cc]`,
|
|
};
|
|
return <button onClick={onClick} className={styles[variant]}>{children}</button>;
|
|
}
|
|
|
|
export function InstallTabs() {
|
|
const [mode, setMode] = useState<Mode>('oneliner');
|
|
const [pm, setPm] = useState<PM>('npm');
|
|
const [os, setOs] = useState<OS>('unix');
|
|
const [winShell, setWinShell] = useState<WinShell>('powershell');
|
|
const [hackable, setHackable] = useState<HackableMode>('installer');
|
|
const [beta, setBeta] = useState(false);
|
|
const [osPickerOpen, setOsPickerOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (typeof navigator !== 'undefined') {
|
|
const isWin = navigator.platform?.toLowerCase().includes('win') ||
|
|
navigator.userAgent?.toLowerCase().includes('windows');
|
|
if (isWin) setOs('windows');
|
|
}
|
|
}, []);
|
|
|
|
const getOnelinerCmd = () => {
|
|
if (os === 'unix') return beta
|
|
? 'curl -fsSL https://hanzo.bot/install.sh | bash -s -- --beta'
|
|
: 'curl -fsSL https://hanzo.bot/install.sh | bash';
|
|
if (winShell === 'cmd') return beta ? WINDOWS_CMD_BETA : WINDOWS_CMD;
|
|
return beta ? WINDOWS_PS_BETA : WINDOWS_PS;
|
|
};
|
|
|
|
const getInstallCmd = () => {
|
|
const suffix = beta ? '@beta' : '';
|
|
return pm === 'npm' ? `npm i -g hanzo-bot${suffix}` : `pnpm add -g hanzo-bot${suffix}`;
|
|
};
|
|
|
|
const comments = {
|
|
oneliner: beta
|
|
? "# Living on the edge. Bugs are features you found first."
|
|
: "# Works everywhere. Installs everything. You're welcome.",
|
|
quickInstall: beta ? '# Install Hanzo Bot (beta)' : '# Install Hanzo Bot',
|
|
quickOnboard: beta ? '# Meet your experimental bot' : '# Meet your bot',
|
|
};
|
|
|
|
return (
|
|
<section className="mb-14">
|
|
<h2 className="text-xl font-semibold flex items-center gap-2.5 mb-5">
|
|
<span className="text-brand font-bold">⟩</span> Quick Start
|
|
</h2>
|
|
<div className="rounded-xl border border-fd-border bg-fd-muted/30 overflow-hidden">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-2 px-4 py-3 bg-[rgba(0,0,0,0.3)] border-b border-fd-border flex-wrap">
|
|
<span className="w-3 h-3 rounded-full bg-[#ff5f57]" />
|
|
<span className="w-3 h-3 rounded-full bg-[#febc2e]" />
|
|
<span className="w-3 h-3 rounded-full bg-[#28c840]" />
|
|
|
|
<div className="flex gap-1 bg-[rgba(0,0,0,0.3)] p-0.5 rounded-md ml-2">
|
|
{(['oneliner', 'quick', 'hackable', 'macos'] as Mode[]).map((m) => (
|
|
<TabButton key={m} active={mode === m} onClick={() => { setMode(m); setOsPickerOpen(false); }} variant="mode">
|
|
{m === 'oneliner' ? 'One-liner' : m === 'quick' ? 'npm' : m === 'hackable' ? 'Hackable' : 'macOS'}
|
|
</TabButton>
|
|
))}
|
|
</div>
|
|
|
|
{mode === 'oneliner' && !osPickerOpen && (
|
|
<div className="flex items-center gap-2 ml-auto font-mono text-xs text-fd-muted-foreground">
|
|
<span className="text-fd-secondary-foreground">{os === 'unix' ? 'macOS/Linux' : 'Windows'}</span>
|
|
<button
|
|
onClick={() => setOsPickerOpen(true)}
|
|
className="bg-none border-none text-brand font-mono text-[0.7rem] cursor-pointer px-1.5 py-0.5 rounded hover:bg-[rgba(255,77,77,0.15)] hover:text-[#00e5cc] transition-all"
|
|
>
|
|
change
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{mode === 'oneliner' && osPickerOpen && (
|
|
<div className="flex gap-1 bg-[rgba(0,0,0,0.3)] p-0.5 rounded-md ml-auto">
|
|
<TabButton active={os === 'unix'} onClick={() => { setOs('unix'); setOsPickerOpen(false); }} variant="option">macOS/Linux</TabButton>
|
|
<TabButton active={os === 'windows'} onClick={() => { setOs('windows'); setOsPickerOpen(false); }} variant="option">Windows</TabButton>
|
|
</div>
|
|
)}
|
|
|
|
{mode === 'oneliner' && os === 'windows' && (
|
|
<div className="flex gap-1 bg-[rgba(0,0,0,0.3)] p-0.5 rounded-md ml-2">
|
|
<TabButton active={winShell === 'powershell'} onClick={() => setWinShell('powershell')} variant="option">PowerShell</TabButton>
|
|
<TabButton active={winShell === 'cmd'} onClick={() => setWinShell('cmd')} variant="option">CMD</TabButton>
|
|
</div>
|
|
)}
|
|
|
|
{mode === 'quick' && (
|
|
<div className="flex gap-1 bg-[rgba(0,0,0,0.3)] p-0.5 rounded-md ml-auto">
|
|
<TabButton active={pm === 'npm'} onClick={() => setPm('npm')} variant="option">npm</TabButton>
|
|
<TabButton active={pm === 'pnpm'} onClick={() => setPm('pnpm')} variant="option">pnpm</TabButton>
|
|
</div>
|
|
)}
|
|
|
|
{mode === 'hackable' && (
|
|
<div className="flex gap-1 bg-[rgba(0,0,0,0.3)] p-0.5 rounded-md ml-auto">
|
|
<TabButton active={hackable === 'installer'} onClick={() => setHackable('installer')} variant="option">installer</TabButton>
|
|
<TabButton active={hackable === 'pnpm'} onClick={() => setHackable('pnpm')} variant="option">pnpm</TabButton>
|
|
</div>
|
|
)}
|
|
|
|
{(mode === 'oneliner' || mode === 'quick') && (
|
|
<div className="ml-2">
|
|
<TabButton active={beta} onClick={() => setBeta(!beta)} variant="beta">
|
|
<span className="font-bold text-[0.8rem]">β</span>{' '}
|
|
<span className="uppercase tracking-wider">Beta</span>
|
|
</TabButton>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="px-5 py-4 font-mono text-sm leading-relaxed">
|
|
{mode === 'oneliner' && (
|
|
<>
|
|
<div className="text-fd-muted-foreground italic py-1">{comments.oneliner}</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span>{getOnelinerCmd()}</span>
|
|
<CopyButton text={getOnelinerCmd()} />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{mode === 'quick' && (
|
|
<>
|
|
<div className="text-fd-muted-foreground italic py-1">{comments.quickInstall}</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span className="text-[#00e5cc]">{getInstallCmd()}</span>
|
|
<CopyButton text={getInstallCmd()} />
|
|
</div>
|
|
<div className="text-fd-muted-foreground italic py-1">{comments.quickOnboard}</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span>hanzo-bot onboard</span>
|
|
<CopyButton text="hanzo-bot onboard" />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{mode === 'hackable' && hackable === 'installer' && (
|
|
<>
|
|
<div className="text-fd-muted-foreground italic py-1"># For those who read source code for fun</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span>curl -fsSL https://hanzo.bot/install.sh | bash -s -- --install-method git</span>
|
|
<CopyButton text="curl -fsSL https://hanzo.bot/install.sh | bash -s -- --install-method git" />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{mode === 'hackable' && hackable === 'pnpm' && (
|
|
<>
|
|
<div className="text-fd-muted-foreground italic py-1"># You clearly know what you're doing</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span>git clone https://github.com/hanzoai/bot.git</span>
|
|
<CopyButton text="git clone https://github.com/hanzoai/bot.git" />
|
|
</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span>cd hanzo-bot && pnpm install && pnpm run build</span>
|
|
<CopyButton text="cd hanzo-bot && pnpm install && pnpm run build" />
|
|
</div>
|
|
<div className="text-fd-muted-foreground italic py-1"># You built it, now meet it</div>
|
|
<div className="flex items-center gap-2 py-1 group/line">
|
|
<span className="text-brand select-none">$</span>
|
|
<span>pnpm run hanzo-bot onboard</span>
|
|
<CopyButton text="pnpm run hanzo-bot onboard" />
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{mode === 'macos' && (
|
|
<div className="flex flex-col items-center gap-4 py-6 text-center">
|
|
<div className="flex flex-col gap-1">
|
|
<span className="text-lg font-semibold text-fd-foreground">Companion App (Beta)</span>
|
|
<span className="text-sm text-fd-muted-foreground">Menubar access to your bot. Works great alongside the CLI.</span>
|
|
</div>
|
|
<a
|
|
href="https://github.com/hanzoai/bot/releases/latest"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center gap-2.5 px-7 py-3.5 bg-gradient-to-br from-brand to-[#991b1b] rounded-xl text-white font-semibold no-underline transition-all hover:-translate-y-0.5 hover:shadow-[0_8px_30px_rgba(255,77,77,0.4)]"
|
|
>
|
|
<DownloadIcon className="w-5 h-5" />
|
|
Download for macOS
|
|
</a>
|
|
<span className="text-xs text-fd-muted-foreground">Requires macOS 14+ · Universal Binary</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<p className="mt-4 text-sm text-fd-muted-foreground text-center">
|
|
Works on macOS, Windows & Linux. The one-liner installs Node.js and everything else for you.
|
|
</p>
|
|
</section>
|
|
);
|
|
}
|