* feat(studio): replace navbar navigation with collapsible sidebar Add an app-wide sidebar with hover-expand and pin-to-dock behavior. Navigation items (Studio, Recipes, Export, Chat) move from the center pill navbar to the sidebar. Chat threads and recipes render as collapsible sub-lists. Navbar simplified to logo + update + close. - Extend SidebarProvider with pinned/hovered state model - New AppSidebar with animated active indicator, sloth profile menu, theme toggle, guided tour, back/forward navigation - Chat page refactored to URL-driven view state via search params - Extract reusable hooks for chat thread and recipe sidebar data - Guard startViewTransition for browser compatibility - Wrap chat deletions in Dexie transaction for data integrity * feat(studio): move logo to sidebar and make navbar overlay - Sidebar is now full-height with logo in SidebarHeader - Collapsed sidebar shows sticker.png, expanded shows full logo - Navbar is absolute-positioned overlay (no layout space) - Main content extends to top, aligning with navbar controls * feat(studio): full-height sidebar with recents, edge-to-edge nav buttons - Sidebar outside max-w-7xl, pinned to left edge - Remove sidebar rounding, menu buttons rounded-md - Nav buttons flush to sidebar edges with no left rounding - Replace collapsible recipes/chat with flat nav items - Add Recents section with chat history (1 item when not on chat, full on chat) - New Chat as first nav item with PencilEdit02Icon - Cursor pointer on all sidebar buttons - Navbar temporarily hidden for screenshots * fix(studio): fix chat scroll, action bar hover, collapsible recents - Fix sticky composer by removing `relative` override on viewport footer - Action bar buttons only show on hover (autohide=always) - Remove floating border/shadow from action bar - Add scroll space above composer for last message actions - Back/forward buttons use router history (stay in-app) - Recents section collapsible with chevron on chat route - Set html/body/#root height for proper h-full chain * fix(studio): address review feedback, clean up unused code - Unhide navbar (was left hidden from screenshot) - Remove unused imports: SidebarMenuSub*, BubbleChatIcon, ColumnInsertIcon - Remove unused vars: recipeItems, activeRecipeId, canCompare, recipesOpen - Include compare query id in active sidebar selection - Use store type for contextUsage instead of inline type - Simplify noop in sidebar.tsx - Remove empty className prop * feat(studio): add mobile sidebar, recent runs section, and misc UX fixes * feat(studio): scaffold settings feature module with dialog store * feat(studio): add tri-state theme store for settings * feat(chat): add clear-all-chats and export-chat-history utils * feat(studio): add settings dialog shell with tab rail * feat(studio): add appearance tab with theme and sidebar pin * feat(studio): add settings general tab with hf token, auto-title, reset prefs * feat(studio): add settings chat tab with export and clear * feat(studio): add api keys tab with list and revoke flow * feat(studio): add create-key form and reveal dialog * feat(studio): add usage examples panel to api keys tab * feat(studio): add settings about tab with update and shutdown * feat(studio): add settings dropdown item and cmd-comma shortcut * feat(studio): remove legacy api-keys route and chat-sheet preference rows * fix(studio): settings dialog a11y + polish pass * feat(studio): inline api key reveal card replacing nested dialog * fix(studio): hide revoked keys from settings list * refactor(studio): strip navbar and hoist training unload guard * feat(studio): explicit sidebar toggle, remove hover-open and pin icons * fix(studio): use SidebarRight01Icon for collapsed sidebar open toggle * fix(studio): address code review findings for settings dialog * feat(studio): collapsible navigate group with standalone new-chat and compare * fix(studio): chat-only standalone actions, use ColumnInsertIcon for compare * fix(studio): sidebar new-chat/compare state reset and icon-mode collapsible * feat(studio): add compact logo assets for sidebar header * Fixed sidebar design * fix(studio): sidebar delete icon hover contrast and sizing * feat(studio): route-gate sidebar recents (chats off /studio, runs on /studio) * feat(studio): add chat search store * feat(studio): add chat search index hook with snapshot-on-open * feat(studio): add chat search command dialog with global shortcut * feat(studio): wire chat search into sidebar * fix(studio): trim hf token on save, add show/hide toggle, commit on close * revert(studio): restore original sidebar/border colors, brighten sidebar * feat(studio): forward overlayClassName through CommandDialog * fix(studio): wrap search dialog in Command context, redesign as flat 635px card * fix(studio): reserve right padding on recent items so delete icon stops overlapping title * fix(studio): skip hf token unmount-commit during reset-prefs reload * chore(studio): drop unused icon import and unreachable runs navigate branch * fix(studio): chat search index filters archived before limit, batches message query, picks up reasoning text * fix(studio): keep CommandEmpty in tree so empty state renders correctly * fix(studio): cap system prompt and chat template textareas so they scroll instead of growing * fix(studio): attach chat-compare tour anchor to sidebar compare button * fix(studio): persist system theme explicitly so next-themes does not clobber on reload * fix(studio): auto-switch to history tab when selecting a recent run from sidebar * UI overhaul: chatbox, scrollbar, sidebar, and compare view UI Changes: - Redesigned the Compare UI with general cleanup - Redesigned the Chatbox UI - Reduced the width of the user chat bubble for improved readability - Narrowed the user chat box across the content page - Adjusted thinking-box text color to be slightly darker - Removed faded text effect from chat messages - Removed faded text effect from the thinking box - Added a small LLM chat safety note at the bottom of the chatbox - Restyled the scrollbar Layout & Behavior: - Reworked the scrollbar to span the full height of the page (no top/bottom padding) and remain persistently visible when content is scrollable, rather than only on hover - Reworked the Configuration sidebar to span full height — removed rounded corners and borders, with the scrollbar adjusted to match the full top-to-bottom layout - Adjusted the top menu and bottom chatbox content areas to work correctly with the new full-page scroll behavior - Made chat content match the chatbox width, with content sliding slightly behind the chatbox when scrolling - Aligned chat text width with the chatbox for visual consistency, including how far the text extends behind the chatbox Fixes: - Fixed the chatbox not auto-expanding when typing multi-line input while bottom-positioned during an active chat (previously only worked before a chat had started) - Fixed positioning and design of the user chat hover menu buttons to match the assistant chat box — now displayed below the chat bubble instead of on the left side * Fix user message layout in thread component * swap code icon * fix compare layout * fix compare pane flex * Sidebar improvements and fixes - Added scrolling support to the sidebar so menus and recent chats no longer get hidden - Recent chats are now always visible in the sidebar, not hidden when in Studio, Recipes, or Export - Recent chat is now deselected when selecting other navigations - Fixed sidebar glitch where browser resize could make the sidebar and expand button disappear completely - Fixed glitch where the open-sidebar hover tooltip appeared above the logo when clicking expand sidebar - Reduced sidebar width on mobile to around 2/3 of the screen (was too wide) - Made the close-sidebar hover tooltip consistent with the rest of the design - Removed sidebar collapse/expand animation - Small adjustment to chat width * Fix route scrolling, polling, and theme sync issues * Fix Studio page scrolling --------- Co-authored-by: sneakr <hauzin@hotmail.com>
146 lines
5 KiB
TypeScript
146 lines
5 KiB
TypeScript
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { copyToClipboard } from "@/lib/copy-to-clipboard";
|
|
import {
|
|
ArrowDown01Icon,
|
|
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";
|
|
|
|
const TABS: { id: Lang; label: string }[] = [
|
|
{ id: "curl", label: "curl" },
|
|
{ id: "python", label: "Python" },
|
|
{ id: "tools", label: "Tools" },
|
|
];
|
|
|
|
function buildSnippets(base: string) {
|
|
return {
|
|
curl: `curl ${base}/v1/chat/completions \\
|
|
-H "Authorization: Bearer sk-unsloth-YOUR_KEY" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d '{
|
|
"messages": [{"role": "user", "content": "Hello"}],
|
|
"stream": true
|
|
}'`,
|
|
python: `from openai import OpenAI
|
|
|
|
client = OpenAI(
|
|
base_url="${base}/v1",
|
|
api_key="sk-unsloth-YOUR_KEY",
|
|
)
|
|
|
|
response = client.chat.completions.create(
|
|
model="current",
|
|
messages=[{"role": "user", "content": "Hello"}],
|
|
stream=True,
|
|
)
|
|
for chunk in response:
|
|
print(chunk.choices[0].delta.content or "", end="")`,
|
|
tools: `curl ${base}/v1/chat/completions \\
|
|
-H "Authorization: Bearer sk-unsloth-YOUR_KEY" \\
|
|
-H "Content-Type: application/json" \\
|
|
-d '{
|
|
"messages": [{"role": "user", "content": "Search Python 3.13 features"}],
|
|
"enable_tools": true,
|
|
"enabled_tools": ["web_search", "python"],
|
|
"stream": true
|
|
}'`,
|
|
};
|
|
}
|
|
|
|
export function UsageExamples() {
|
|
const [open, setOpen] = useState(false);
|
|
const [lang, setLang] = useState<Lang>("curl");
|
|
const [copied, setCopied] = useState(false);
|
|
const snippets = useMemo(
|
|
() =>
|
|
buildSnippets(
|
|
typeof window !== "undefined" ? window.location.origin : "",
|
|
),
|
|
[],
|
|
);
|
|
|
|
const handleCopy = () => {
|
|
if (copyToClipboard(snippets[lang])) {
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 1800);
|
|
}
|
|
};
|
|
|
|
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>
|
|
<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>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</section>
|
|
);
|
|
}
|