Initial public release

This commit is contained in:
Bohdan Triapitsyn
2025-12-07 19:32:53 +02:00
commit 4b2edf7318
319 changed files with 81600 additions and 0 deletions
@@ -0,0 +1,139 @@
import React from 'react';
import { RiFileCopyLine, RiCheckLine, RiExternalLinkLine } from '@remixicon/react';
const INSTALL_COMMAND = 'curl -fsSL https://opencode.ai/install | bash';
const POLL_INTERVAL_MS = 3000;
type OnboardingScreenProps = {
onCliAvailable?: () => void;
};
function BashCommand({ onCopy }: { onCopy: () => void }) {
return (
<div className="flex items-center justify-center gap-3">
<code>
<span style={{ color: 'var(--syntax-keyword)' }}>curl</span>
<span className="text-muted-foreground"> -fsSL </span>
<span style={{ color: 'var(--syntax-string)' }}>https://opencode.ai/install</span>
<span className="text-muted-foreground"> | </span>
<span style={{ color: 'var(--syntax-keyword)' }}>bash</span>
</code>
<button
onClick={onCopy}
className="inline-flex items-center text-muted-foreground hover:text-foreground transition-colors"
title="Copy to clipboard"
>
<RiFileCopyLine className="h-4 w-4" />
</button>
</div>
);
}
const HINT_DELAY_MS = 30000;
export function OnboardingScreen({ onCliAvailable }: OnboardingScreenProps) {
const [copied, setCopied] = React.useState(false);
const [showHint, setShowHint] = React.useState(false);
React.useEffect(() => {
const timer = setTimeout(() => setShowHint(true), HINT_DELAY_MS);
return () => clearTimeout(timer);
}, []);
const checkCliAvailability = React.useCallback(async (): Promise<boolean> => {
try {
const response = await fetch('/health');
if (!response.ok) return false;
const data = await response.json();
return data.cliAvailable === true;
} catch {
return false;
}
}, []);
const handleCopy = React.useCallback(async () => {
try {
await navigator.clipboard.writeText(INSTALL_COMMAND);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
}, []);
React.useEffect(() => {
const poll = async () => {
const available = await checkCliAvailability();
if (available) {
onCliAvailable?.();
}
};
const interval = setInterval(poll, POLL_INTERVAL_MS);
poll();
return () => clearInterval(interval);
}, [checkCliAvailability, onCliAvailable]);
return (
<div className="h-full flex items-center justify-center bg-transparent p-8 relative">
<div className="w-full space-y-4 text-center">
<div className="space-y-4">
<h1 className="text-3xl font-semibold tracking-tight text-foreground">
Welcome to OpenChamber
</h1>
<p className="text-muted-foreground">
<a
href="https://opencode.ai"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center gap-1"
>
OpenCode CLI
<RiExternalLinkLine className="h-4 w-4" />
</a>
{' '}is required to continue.
</p>
</div>
<div className="flex justify-center">
<div className="bg-background/60 backdrop-blur-sm border border-border rounded-lg px-5 py-3 font-mono text-sm w-fit">
{copied ? (
<div className="flex items-center justify-center gap-2" style={{ color: 'var(--status-success)' }}>
<RiCheckLine className="h-4 w-4" />
Copied to clipboard
</div>
) : (
<BashCommand onCopy={handleCopy} />
)}
</div>
</div>
<a
href="https://opencode.ai/docs"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-muted-foreground hover:text-foreground transition-colors inline-flex items-center gap-1 justify-center"
>
View documentation
<RiExternalLinkLine className="h-3 w-3" />
</a>
<p className="text-sm text-muted-foreground animate-pulse">
Waiting for OpenCode installation...
</p>
</div>
{showHint && (
<div className="absolute bottom-8 left-0 right-0 text-center space-y-1">
<p className="text-sm text-muted-foreground/70">
Already installed? Make sure <code className="text-foreground/70">opencode</code> is in your PATH
</p>
<p className="text-sm text-muted-foreground/70">
or set <code className="text-foreground/70">OPENCODE_BINARY</code> environment variable.
</p>
</div>
)}
</div>
);
}