From 184b7861169ebb32f0d982ce7ba50eb6ec16febb Mon Sep 17 00:00:00 2001 From: Shine1i Date: Sun, 15 Feb 2026 16:27:44 +0100 Subject: [PATCH 01/12] fix setup: fish alias, venv no activate --- setup.sh | 59 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/setup.sh b/setup.sh index e5a41d371f..63de38b512 100755 --- a/setup.sh +++ b/setup.sh @@ -81,13 +81,17 @@ echo "✅ Frontend built to studio/frontend/dist" # ── 6. Python venv + deps ── echo "" echo "Setting up Python environment..." -python3 -m venv .venv -source .venv/bin/activate -run_quiet "pip upgrade" pip install --upgrade pip +if [ ! -d "$SCRIPT_DIR/.venv" ]; then + python3 -m venv "$SCRIPT_DIR/.venv" +fi + +# Avoid shell-specific activation; call venv python directly. +VENV_PY="$SCRIPT_DIR/.venv/bin/python" +run_quiet "pip upgrade" "$VENV_PY" -m pip install --upgrade pip echo " Installing unsloth-zoo + unsloth..." -run_quiet "pip install unsloth" pip install unsloth-zoo unsloth +run_quiet "pip install unsloth" "$VENV_PY" -m pip install unsloth-zoo unsloth echo " Installing studio dependencies..." -run_quiet "pip install extras" pip install typer fastapi uvicorn pydantic matplotlib pandas nest_asyncio "datasets==4.3.0" pyjwt easydict addict +run_quiet "pip install extras" "$VENV_PY" -m pip install typer fastapi uvicorn pydantic matplotlib pandas nest_asyncio "datasets==4.3.0" pyjwt easydict addict echo "✅ Python dependencies installed" # ── 7. Add shell alias ── @@ -95,23 +99,56 @@ echo "✅ Python dependencies installed" # This alias hardcodes the venv python path so users don't need to activate. echo "" REPO_DIR="$SCRIPT_DIR" +ALIAS_CMD="${REPO_DIR}/.venv/bin/python ${REPO_DIR}/cli.py ui -f ${REPO_DIR}/studio/frontend/dist" -if ! grep -qF "unsloth-ui" ~/.bashrc 2>/dev/null; then - cat >> ~/.bashrc </dev/null; then + cat >> "$FISH_RC" </dev/null; then + cat >> "$ZSH_RC" </dev/null; then + cat >> "$BASH_RC" < Date: Sun, 15 Feb 2026 16:36:53 +0100 Subject: [PATCH 02/12] wip setup: py312 --- setup.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 63de38b512..cf3c24eb32 100755 --- a/setup.sh +++ b/setup.sh @@ -81,8 +81,25 @@ echo "✅ Frontend built to studio/frontend/dist" # ── 6. Python venv + deps ── echo "" echo "Setting up Python environment..." +DESIRED_PY="${UNSLOTH_PYTHON:-3.12}" + +if [ -x "$SCRIPT_DIR/.venv/bin/python" ]; then + VENV_VER="$("$SCRIPT_DIR/.venv/bin/python" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true)" + if ! "$SCRIPT_DIR/.venv/bin/python" -c 'import pip' >/dev/null 2>&1; then + VENV_VER="${VENV_VER:-unknown}" + mv "$SCRIPT_DIR/.venv" "$SCRIPT_DIR/.venv.bak-nopip-py${VENV_VER}-$(date +%Y%m%d%H%M%S)" + elif [ "$VENV_VER" != "$DESIRED_PY" ]; then + mv "$SCRIPT_DIR/.venv" "$SCRIPT_DIR/.venv.bak-py${VENV_VER:-unknown}-$(date +%Y%m%d%H%M%S)" + fi +fi + if [ ! -d "$SCRIPT_DIR/.venv" ]; then - python3 -m venv "$SCRIPT_DIR/.venv" + if command -v uv >/dev/null 2>&1; then + run_quiet "uv python install" uv python install "$DESIRED_PY" + run_quiet "uv venv" uv venv --seed -p "$DESIRED_PY" "$SCRIPT_DIR/.venv" + else + python3 -m venv "$SCRIPT_DIR/.venv" + fi fi # Avoid shell-specific activation; call venv python directly. From 2ffdd59925321bf5e4a04f645a95cf6d69aa17e4 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Sun, 15 Feb 2026 16:44:14 +0100 Subject: [PATCH 03/12] chat compare: send use_adapter --- .../src/features/chat/api/chat-adapter.ts | 20 +++++++++++++++++++ .../frontend/src/features/chat/types/api.ts | 1 + 2 files changed, 21 insertions(+) diff --git a/studio/frontend/src/features/chat/api/chat-adapter.ts b/studio/frontend/src/features/chat/api/chat-adapter.ts index 2b2356e847..899af5f006 100644 --- a/studio/frontend/src/features/chat/api/chat-adapter.ts +++ b/studio/frontend/src/features/chat/api/chat-adapter.ts @@ -1,5 +1,6 @@ import type { ChatModelAdapter } from "@assistant-ui/react"; import { streamChatCompletions } from "./chat-api"; +import { db } from "../db"; import { useChatRuntimeStore } from "../stores/chat-runtime-store"; import { hasClosedThinkTag, @@ -81,6 +82,23 @@ function findLatestUserImageBase64(messages: RunMessages): string | undefined { return undefined; } +async function resolveUseAdapter( + threadId: string | undefined, +): Promise { + if (!threadId) { + return undefined; + } + try { + const thread = await db.threads.get(threadId); + if (!thread?.pairId) { + return undefined; + } + return thread.modelType === "lora"; + } catch { + return undefined; + } +} + export function createOpenAIStreamAdapter(): ChatModelAdapter { return { async *run({ messages, abortSignal, unstable_threadId }) { @@ -104,6 +122,7 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { }); } const imageBase64 = findLatestUserImageBase64(messages); + const useAdapter = await resolveUseAdapter(unstable_threadId); const threadKey = unstable_threadId || "__default"; let waitingFirstChunk = true; @@ -124,6 +143,7 @@ export function createOpenAIStreamAdapter(): ChatModelAdapter { top_k: params.topK, repetition_penalty: params.repetitionPenalty, image_base64: imageBase64, + ...(useAdapter === undefined ? {} : { use_adapter: useAdapter }), }, abortSignal, ); diff --git a/studio/frontend/src/features/chat/types/api.ts b/studio/frontend/src/features/chat/types/api.ts index f53bd6ca75..48ea76c4dd 100644 --- a/studio/frontend/src/features/chat/types/api.ts +++ b/studio/frontend/src/features/chat/types/api.ts @@ -63,6 +63,7 @@ export interface OpenAIChatCompletionsRequest { top_k: number; repetition_penalty: number; image_base64?: string; + use_adapter?: boolean | string | null; } export interface OpenAIChatDelta { From 8529f89a7531bf69a3706e8717efe71967642855 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Sun, 15 Feb 2026 16:58:24 +0100 Subject: [PATCH 04/12] fix lora: outputs path local --- studio/backend/utils/paths/path_utils.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/studio/backend/utils/paths/path_utils.py b/studio/backend/utils/paths/path_utils.py index 7743952b6b..856e478bc2 100644 --- a/studio/backend/utils/paths/path_utils.py +++ b/studio/backend/utils/paths/path_utils.py @@ -41,6 +41,13 @@ def is_local_path(path: str) -> bool: if not path: return False + # If it exists on disk, treat as local (covers relative paths like "outputs/foo"). + try: + if Path(normalize_path(path)).expanduser().exists(): + return True + except Exception: + pass + # Obvious HF patterns if path.count('/') == 1 and not path.startswith(('/', '.', '~')): return False # Looks like org/model format From b83eeab603456376733c3ab31c6b9167d93702f1 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Sun, 15 Feb 2026 17:34:32 +0100 Subject: [PATCH 05/12] fix: ensure consistent message order in chat runtime by improving sort logic and adding fallback for createdAt --- .../src/features/chat/runtime-provider.tsx | 24 +++++++++++++++---- .../src/features/chat/shared-composer.tsx | 4 +++- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/studio/frontend/src/features/chat/runtime-provider.tsx b/studio/frontend/src/features/chat/runtime-provider.tsx index a94e25b67a..a904a72e92 100644 --- a/studio/frontend/src/features/chat/runtime-provider.tsx +++ b/studio/frontend/src/features/chat/runtime-provider.tsx @@ -287,10 +287,19 @@ function ThreadHistoryProvider({ if (!remoteId) { return { messages: [] }; } - const msgs = await db.messages - .where("threadId") - .equals(remoteId) - .sortBy("createdAt"); + const roleOrder: Record = { + system: 0, + user: 1, + assistant: 2, + }; + const msgs = await db.messages.where("threadId").equals(remoteId).toArray(); + msgs.sort((a, b) => { + if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt; + const aOrder = roleOrder[a.role] ?? 99; + const bOrder = roleOrder[b.role] ?? 99; + if (aOrder !== bOrder) return aOrder - bOrder; + return a.id < b.id ? -1 : a.id > b.id ? 1 : 0; + }); return ExportedMessageRepository.fromArray(msgs.map(toThreadMessage)); }, @@ -301,13 +310,18 @@ function ThreadHistoryProvider({ ? JSON.parse(JSON.stringify(message.content)) : []; const custom = message.metadata?.custom; + const existing = await db.messages.get(message.id); + const createdAt = + existing?.createdAt ?? + message.createdAt?.getTime?.() ?? + Date.now(); await db.messages.put({ id: message.id, threadId: remoteId, role: message.role, content, ...(custom && Object.keys(custom).length > 0 && { metadata: custom }), - createdAt: message.createdAt?.getTime() ?? Date.now(), + createdAt, }); }, }), diff --git a/studio/frontend/src/features/chat/shared-composer.tsx b/studio/frontend/src/features/chat/shared-composer.tsx index 185a6ac823..f849d3f5b7 100644 --- a/studio/frontend/src/features/chat/shared-composer.tsx +++ b/studio/frontend/src/features/chat/shared-composer.tsx @@ -52,7 +52,9 @@ export function RegisterCompareHandle({ } const currentHandles = handlesRef.current; currentHandles[name] = { - append: (content) => aui.thread().append({ role: "user", content }), + // fixes occasional reorder on reload. + append: (content) => + aui.thread().append({ role: "user", content, createdAt: new Date() } as never), cancel: () => aui.thread().cancelRun(), isRunning: () => aui.thread().getState().isRunning, }; From 9a4f71c93934c0cec95439cdbb8bdf4bbaf67cc7 Mon Sep 17 00:00:00 2001 From: Shine1i Date: Sun, 15 Feb 2026 18:08:46 +0100 Subject: [PATCH 06/12] chore: remove unused `ComponentExample` and associated imports and auto title generate --- studio/frontend/src/app/router.tsx | 2 - studio/frontend/src/app/routes/home.tsx | 15 - .../src/components/component-example.tsx | 1318 ----------------- .../src/features/chat/api/chat-adapter.ts | 2 + .../frontend/src/features/chat/chat-page.tsx | 4 + .../src/features/chat/chat-settings-sheet.tsx | 20 +- .../src/features/chat/runtime-provider.tsx | 170 ++- .../chat/stores/chat-runtime-store.ts | 47 + 8 files changed, 226 insertions(+), 1352 deletions(-) delete mode 100644 studio/frontend/src/app/routes/home.tsx delete mode 100644 studio/frontend/src/components/component-example.tsx diff --git a/studio/frontend/src/app/router.tsx b/studio/frontend/src/app/router.tsx index 27e0ee9fdf..de8564d035 100644 --- a/studio/frontend/src/app/router.tsx +++ b/studio/frontend/src/app/router.tsx @@ -2,7 +2,6 @@ import { createRouter } from "@tanstack/react-router"; import { Route as rootRoute } from "./routes/__root"; import { Route as chatRoute } from "./routes/chat"; import { Route as gridTestRoute } from "./routes/grid-test"; -import { Route as homeRoute } from "./routes/home"; import { Route as loginRoute } from "./routes/login"; import { Route as onboardingRoute } from "./routes/onboarding"; import { Route as exportRoute } from "./routes/export"; @@ -10,7 +9,6 @@ import { Route as signupRoute } from "./routes/signup"; import { Route as studioRoute } from "./routes/studio"; const routeTree = rootRoute.addChildren([ - homeRoute, onboardingRoute, loginRoute, signupRoute, diff --git a/studio/frontend/src/app/routes/home.tsx b/studio/frontend/src/app/routes/home.tsx deleted file mode 100644 index bf2f3a6b58..0000000000 --- a/studio/frontend/src/app/routes/home.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { ComponentExample } from "@/components/component-example"; -import { createRoute } from "@tanstack/react-router"; -import { requireAuth } from "../auth-guards"; -import { Route as rootRoute } from "./__root"; - -export const Route = createRoute({ - getParentRoute: () => rootRoute, - path: "/", - beforeLoad: () => requireAuth(), - component: HomePage, -}); - -function HomePage() { - return ; -} diff --git a/studio/frontend/src/components/component-example.tsx b/studio/frontend/src/components/component-example.tsx deleted file mode 100644 index 3171e1abc0..0000000000 --- a/studio/frontend/src/components/component-example.tsx +++ /dev/null @@ -1,1318 +0,0 @@ -import * as React from "react"; - -import { Example, ExampleWrapper } from "@/components/example"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "@/components/ui/accordion"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogMedia, - AlertDialogTitle, - AlertDialogTrigger, -} from "@/components/ui/alert-dialog"; -import { - Avatar, - AvatarBadge, - AvatarFallback, - AvatarGroup, - AvatarGroupCount, - AvatarImage, -} from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardAction, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - Combobox, - ComboboxContent, - ComboboxEmpty, - ComboboxInput, - ComboboxItem, - ComboboxList, -} from "@/components/ui/combobox"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuPortal, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuShortcut, - DropdownMenuSub, - DropdownMenuSubContent, - DropdownMenuSubTrigger, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { - Pagination, - PaginationContent, - PaginationItem, - PaginationLink, - PaginationNext, - PaginationPrevious, -} from "@/components/ui/pagination"; -import { Progress } from "@/components/ui/progress"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - Sheet, - SheetContent, - SheetDescription, - SheetFooter, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet"; -import { Skeleton } from "@/components/ui/skeleton"; -import { Slider } from "@/components/ui/slider"; -import { Switch } from "@/components/ui/switch"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Textarea } from "@/components/ui/textarea"; -import { Toggle } from "@/components/ui/toggle"; -import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { - AlertCircleIcon, - BluetoothIcon, - CodeIcon, - ComputerIcon, - CreditCardIcon, - DownloadIcon, - EyeIcon, - File01Icon, - FileIcon, - FloppyDiskIcon, - FolderIcon, - FolderOpenIcon, - HelpCircleIcon, - InformationCircleIcon, - KeyboardIcon, - LanguageCircleIcon, - LayoutIcon, - LogoutIcon, - MailIcon, - MoonIcon, - MoreHorizontalCircle01Icon, - MoreVerticalCircle01Icon, - NotificationIcon, - PaintBoardIcon, - PanelRightIcon, - PlusSignIcon, - SearchIcon, - SettingsIcon, - ShieldIcon, - SunIcon, - TextBoldIcon, - TextItalicIcon, - TextUnderlineIcon, - UserIcon, -} from "@hugeicons/core-free-icons"; -import { HugeiconsIcon } from "@hugeicons/react"; - -export function ComponentExample() { - return ( - - - - - - - - - - - - - - ); -} - -function CardExample() { - return ( - - -
- mymind on Unsplash - - Observability Plus is replacing Monitoring - - Switch to the improved way to explore your data, with natural - language. Monitoring will no longer be available on the Pro plan in - November, 2025 - - - - - - - - - - - - - Allow accessory to connect? - - Do you want to allow the USB accessory to connect to this - device? - - - - Don't allow - Allow - - - - - Warning - - - - - ); -} - -const frameworks = [ - "Next.js", - "SvelteKit", - "Nuxt.js", - "Remix", - "Astro", -] as const; - -function FormExample() { - const [notifications, setNotifications] = React.useState({ - email: true, - sms: false, - push: true, - }); - const [theme, setTheme] = React.useState("light"); - - return ( - - - - User Information - Please fill in your details below - - - - - - - - File - - - New File - ⌘N - - - - New Folder - ⇧⌘N - - - - - Open Recent - - - - - Recent Projects - - - Project Alpha - - - - Project Beta - - - - - More Projects - - - - - - Project Gamma - - - - Project Delta - - - - - - - - - - Browse... - - - - - - - - - Save - ⌘S - - - - Export - ⇧⌘E - - - - - View - - setNotifications({ - ...notifications, - email: checked === true, - }) - } - > - - Show Sidebar - - - setNotifications({ - ...notifications, - sms: checked === true, - }) - } - > - - Show Status Bar - - - - - Theme - - - - - Appearance - - - - Light - - - - Dark - - - - System - - - - - - - - - - Account - - - Profile - ⇧⌘P - - - - Billing - - - - - Settings - - - - - Preferences - - - Keyboard Shortcuts - - - - Language - - - - - Notifications - - - - - - Notification Types - - - setNotifications({ - ...notifications, - push: checked === true, - }) - } - > - - Push Notifications - - - setNotifications({ - ...notifications, - email: checked === true, - }) - } - > - - Email Notifications - - - - - - - - - - - Privacy & Security - - - - - - - - - - - Help & Support - - - - Documentation - - - - - - - Sign Out - ⇧⌘Q - - - - - - - -
- -
- - Name - - - - Role - - -
- - - Framework - - - - - No frameworks found. - - {(item) => ( - - {item} - - )} - - - - - - Comments -