Studio: Always show API usage examples and docs links (#5270)

* chore: always show API usage examples and docs links

* chore: add colon to API setup docs label

* settings: move help links, add API shortcut, and update API copy
This commit is contained in:
Lee Jackson 2026-05-04 14:29:26 +01:00 committed by GitHub
commit 820b5c2e20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 120 additions and 114 deletions

View file

@ -31,16 +31,14 @@ import {
import { useAnimatedThemeToggle } from "@/components/ui/animated-theme-toggler";
import { cn } from "@/lib/utils";
import {
Book03Icon,
ChefHatIcon,
ColumnInsertIcon,
CursorInfo02Icon,
Delete02Icon,
Download03Icon,
GemIcon,
MessageSearch01Icon,
Key01Icon,
Search01Icon,
NewReleasesIcon,
PowerIcon,
PencilEdit02Icon,
LayoutAlignLeftIcon,
@ -547,6 +545,15 @@ export function AppSidebar() {
<span>Settings</span>
<DropdownMenuShortcut>,</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem
onSelect={() => useSettingsDialogStore.getState().openDialog("api-keys")}
>
<HugeiconsIcon icon={Key01Icon} strokeWidth={1.75} className="size-[18px]" />
<span>API Keys</span>
<span className="ml-auto rounded-[6px] border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] leading-none font-semibold text-emerald-700 dark:text-emerald-300">
New
</span>
</DropdownMenuItem>
<DropdownMenuItem
ref={anchorRef as React.Ref<HTMLDivElement>}
onSelect={(e) => { e.preventDefault(); toggleTheme(); }}
@ -571,47 +578,6 @@ export function AppSidebar() {
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator className="mx-2.5! my-2.5! h-0! border-t border-border/70 bg-transparent!" />
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<a
href="https://unsloth.ai/docs"
target="_blank"
rel="noopener noreferrer"
>
<HugeiconsIcon icon={Book03Icon} strokeWidth={1.75} className="size-[18px]" />
<span>Learn More</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a
href="https://unsloth.ai/docs/new/changelog"
target="_blank"
rel="noopener noreferrer"
>
<HugeiconsIcon
icon={NewReleasesIcon}
strokeWidth={1.75}
className="size-[18px]"
/>
<span>What's New</span>
</a>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<a
href="https://github.com/unslothai/unsloth/issues"
target="_blank"
rel="noopener noreferrer"
>
<HugeiconsIcon
icon={MessageSearch01Icon}
strokeWidth={1.75}
className="size-[18px]"
/>
<span>Feedback</span>
</a>
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator className="mx-2.5! my-2.5! h-0! border-t border-border/70 bg-transparent!" />
<DropdownMenuItem onSelect={() => setShutdownOpen(true)}>
<HugeiconsIcon icon={PowerIcon} strokeWidth={1.75} className="size-[18px]" />
<span>Shutdown</span>

View file

@ -4,12 +4,11 @@
import { cn } from "@/lib/utils";
import { copyToClipboard } from "@/lib/copy-to-clipboard";
import {
ArrowDown01Icon,
ArrowUpRight01Icon,
Copy01Icon,
Tick02Icon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { AnimatePresence, motion } from "motion/react";
import { useMemo, useState } from "react";
type Lang = "curl" | "python" | "tools";
@ -20,6 +19,29 @@ const TABS: { id: Lang; label: string }[] = [
{ id: "tools", label: "Tools" },
];
const DOC_LINKS = [
{
label: "Claude Code",
href: "https://unsloth.ai/docs/basics/claude-code",
},
{
label: "Codex",
href: "https://unsloth.ai/docs/basics/codex",
},
{
label: "OpenClaw",
href: "https://unsloth.ai/docs/integrations/openclaw",
},
{
label: "OpenCode",
href: "https://unsloth.ai/docs/integrations/opencode",
},
{
label: "Hermes Agent",
href: "https://unsloth.ai/docs/integrations/hermes-agent",
},
];
function buildSnippets(base: string) {
return {
curl: `curl ${base}/v1/chat/completions \\
@ -56,7 +78,6 @@ for chunk in response:
}
export function UsageExamples() {
const [open, setOpen] = useState(false);
const [lang, setLang] = useState<Lang>("curl");
const [copied, setCopied] = useState(false);
const snippets = useMemo(
@ -76,71 +97,62 @@ export function UsageExamples() {
return (
<section className="flex flex-col">
<button
type="button"
onClick={() => setOpen((o) => !o)}
className="flex w-fit items-center gap-1.5 rounded text-xs font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-expanded={open}
>
<HugeiconsIcon
icon={ArrowDown01Icon}
className={cn("size-3.5 transition-transform", open && "rotate-180")}
/>
{open ? "Hide usage examples" : "Show usage examples"}
</button>
<AnimatePresence initial={false}>
{open && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.18, ease: [0.165, 0.84, 0.44, 1] }}
className="overflow-hidden"
>
<div className="mt-3 overflow-hidden rounded-lg border border-border bg-muted/20">
<div className="flex items-center justify-between border-b border-border px-2 py-1.5">
<div className="flex items-center gap-0.5">
{TABS.map((t) => {
const active = lang === t.id;
return (
<button
key={t.id}
type="button"
onClick={() => setLang(t.id)}
aria-pressed={active}
className={cn(
"rounded px-2 py-1 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
active
? "bg-background text-foreground shadow-border"
: "text-muted-foreground hover:text-foreground",
)}
>
{t.label}
</button>
);
})}
</div>
<h2 className="mb-2 text-sm font-semibold text-foreground">Usage examples</h2>
<div className="overflow-hidden rounded-lg border border-border bg-muted/20">
<div className="flex items-center justify-between border-b border-border px-2 py-1.5">
<div className="flex items-center gap-0.5">
{TABS.map((t) => {
const active = lang === t.id;
return (
<button
key={t.id}
type="button"
onClick={handleCopy}
className="flex items-center gap-1 rounded px-1.5 py-1 text-[11px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label="Copy snippet"
onClick={() => setLang(t.id)}
aria-pressed={active}
className={cn(
"rounded px-2 py-1 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
active
? "bg-background text-foreground shadow-border"
: "text-muted-foreground hover:text-foreground",
)}
>
<HugeiconsIcon
icon={copied ? Tick02Icon : Copy01Icon}
className={cn("size-3.5", copied && "text-emerald-600")}
/>
{copied ? "Copied" : "Copy"}
{t.label}
</button>
</div>
<pre className="overflow-x-auto p-3 font-mono text-[11px] leading-relaxed text-foreground">
{snippets[lang]}
</pre>
</div>
</motion.div>
)}
</AnimatePresence>
);
})}
</div>
<button
type="button"
onClick={handleCopy}
className="flex items-center gap-1 rounded px-1.5 py-1 text-[11px] text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label="Copy snippet"
>
<HugeiconsIcon
icon={copied ? Tick02Icon : Copy01Icon}
className={cn("size-3.5", copied && "text-emerald-600")}
/>
{copied ? "Copied" : "Copy"}
</button>
</div>
<pre className="overflow-x-auto p-3 font-mono text-[11px] leading-relaxed text-foreground">
{snippets[lang]}
</pre>
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 border-t border-border px-3 py-2 text-[11px] text-muted-foreground">
<span>Setup docs:</span>
{DOC_LINKS.map((link) => (
<a
key={link.href}
href={link.href}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-0.5 rounded font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
{link.label}
<HugeiconsIcon icon={ArrowUpRight01Icon} className="size-3" />
</a>
))}
</div>
</div>
</section>
);
}

View file

@ -31,6 +31,7 @@ interface TabDef {
id: SettingsTab;
label: string;
icon: typeof Settings02Icon;
badge?: string;
}
const TABS: TabDef[] = [
@ -38,8 +39,8 @@ const TABS: TabDef[] = [
{ id: "profile", label: "Profile", icon: UserIcon },
{ id: "appearance", label: "Appearance", icon: PaintBrush02Icon },
{ id: "chat", label: "Chat", icon: Message01Icon },
{ id: "api-keys", label: "API Keys", icon: Key01Icon },
{ id: "about", label: "About", icon: SparklesIcon },
{ id: "api-keys", label: "API Keys", icon: Key01Icon, badge: "New" },
{ id: "about", label: "Help", icon: SparklesIcon },
];
function renderTab(tab: SettingsTab) {
@ -121,7 +122,12 @@ export function SettingsDialog() {
strokeWidth={1.5}
className="relative z-10 size-[18px]"
/>
<span className="relative z-10">{tab.label}</span>
<span className="relative z-10 min-w-0 truncate">{tab.label}</span>
{tab.badge ? (
<span className="relative z-10 ml-auto rounded-[6px] border border-emerald-500/25 bg-emerald-500/10 px-1.5 py-0.5 text-[10px] leading-none font-semibold text-emerald-700 dark:text-emerald-300">
{tab.badge}
</span>
) : null}
</button>
);
})}

View file

@ -12,6 +12,7 @@ import {
Book03Icon,
Cancel01Icon,
MessageNotification01Icon,
NewReleasesIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { useEffect, useState } from "react";
@ -48,9 +49,9 @@ export function AboutTab() {
return (
<div className="flex flex-col gap-6">
<header className="flex flex-col gap-1">
<h1 className="text-lg font-semibold font-heading">About</h1>
<h1 className="text-lg font-semibold font-heading">Help</h1>
<p className="text-xs text-muted-foreground">
Unsloth Studio build info and support.
Documentation, release notes, feedback, and Studio build info.
</p>
</header>
@ -79,6 +80,18 @@ export function AboutTab() {
<HugeiconsIcon icon={ArrowUpRight01Icon} className="size-3" />
</a>
</SettingsRow>
<SettingsRow label="Release notes">
<a
href="https://unsloth.ai/docs/new/changelog"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground"
>
<HugeiconsIcon icon={NewReleasesIcon} className="size-3.5" />
What's new
<HugeiconsIcon icon={ArrowUpRight01Icon} className="size-3" />
</a>
</SettingsRow>
<SettingsRow label="Feedback">
<a
href="https://github.com/unslothai/unsloth/issues"

View file

@ -65,7 +65,16 @@ export function ApiKeysTab() {
<header className="flex flex-col gap-1">
<h1 className="text-lg font-semibold font-heading">API Keys</h1>
<p className="text-xs text-muted-foreground">
Access Unsloth Studio programmatically via the OpenAI-compatible API.
Access Unsloth programmatically via the OpenAI-compatible API.{" "}
<a
href="https://unsloth.ai/docs/basics/api"
target="_blank"
rel="noreferrer"
className="font-medium text-foreground underline decoration-border underline-offset-2 transition-colors hover:decoration-foreground"
>
Read the API docs
</a>
.
</p>
</header>