Commit graph

146 commits

Author SHA1 Message Date
Roland Tannous
7f353acfd4
fix: skip download progress polling for exported GGUF models (#4709)
* fix: skip download progress polling for exported GGUF models

* fix: revert isLocalGgufDir change — exported GGUFs are file paths, not dirs

* fix: set isDownloaded true for all adapters in LoraModelPicker
2026-03-31 07:21:23 +04:00
Roland Tannous
562e54fc6e
Fix HF cache default and show LM Studio models in chat/inference (#4653)
* fix: default HF cache to standard platform path instead of legacy Unsloth cache

* feat: show LM Studio and local models in chat Fine-tuned tab

* feat: show LM Studio models in Hub models tab

* fix: fetch local models after auth refresh completes

* Revert "fix: fetch local models after auth refresh completes"

This reverts commit cfd61f0ac7.

* fix: increase llama-server health check timeout to 600s for large models

* feat: expandable GGUF variant picker for LM Studio local models

* fix: show GGUF variant label for locally loaded LM Studio models

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: show publisher name in LM Studio model labels

* fix: set model_id for loose GGUF files in LM Studio publisher dirs

* fix: show publisher prefix in Fine-tuned tab LM Studio models

* fix: only use model_id for lmstudio source models

* fix: only show LM Studio models in Hub tab on Mac/chat-only mode

* fix: respect XDG_CACHE_HOME, handle Windows paths in isLocalPath, refresh LM Studio on remount

- _setup_cache_env now reads XDG_CACHE_HOME (falls back to ~/.cache)
  instead of hard-coding ~/.cache/huggingface. This follows the standard
  HF cache resolution chain and respects distro/container overrides.

- isLocalPath in GgufVariantExpander uses a regex that covers Windows
  drive letters (C:\, D:/), UNC paths (\\server\share), relative paths
  (./, ../), and tilde (~/) -- not just startsWith("/").

- HubModelPicker.useEffect now calls listLocalModels() before the
  alreadyCached early-return gate so LM Studio models are always
  refreshed on remount. Also seeds useState from _lmStudioCache for
  instant display on re-open.

* fix: add comment explaining isLocalPath regex for Windows/cross-platform paths

* fix: prioritize unsloth publisher in LM Studio model list

* fix: scope unsloth-first sort to LM Studio models on all platforms

* fix: add missing _lmStudioCache module-level declaration

* fix: prioritize unsloth publisher before timestamp sort in LM Studio group

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-27 06:59:27 -07:00
Wasim Yousef Said
4ab7fb1f7b
fix: replace navbar shutdown text button with icon-only button (#4655) 2026-03-27 05:44:59 -07:00
Wasim Yousef Said
887b8cb1c2
fix: add auth + UX improvements to shutdown button (#4642)
* Studio shutdown button

* fix: add auth to shutdown endpoint and improve UX

- Add JWT auth (Depends(get_current_subject)) to POST /api/shutdown
- Use authFetch instead of bare fetch in shutdown dialog
- Only show beforeunload prompt when training is running
- Remove Ctrl+W/Cmd+W interception (browsers don't allow it)
- Store shutdown task on app.state to prevent GC

---------

Co-authored-by: Datta Nimmaturi <venkatadattasainimmaturi@gmail.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-27 04:36:08 -07:00
Wasim Yousef Said
07abcb46de
fix: normalize search matching for recommended models and LoRA picker (#4615)
Recommended models matching the query were filtered from HF results but the Recommended section was hidden during search, causing them to vanish entirely.

- Show filtered recommended models during search by introducing `filteredRecommendedIds`
- Switch `recommendedSet` to use filtered IDs when searching so dedup against HF results is correct
- Hide empty "Hugging Face" label when recommended matches cover the query
- Add `normalizeForSearch` helper to strip separators (spaces, hyphens, underscores, dots) so queries like "llama 3" match "Llama-3.2-1B" and "qwen 2.5" matches "Qwen2.5-7B" in both the recommended model filter and the LoRA adapter filter
2026-03-26 03:40:11 -07:00
Daniel Han
55d24d7c49
feat(studio): editable context length with Apply/Reset for GGUF settings (#4592)
* feat(studio): editable context length with Apply/Reset for GGUF model settings

Previously the Context Length field was read-only and the backend
hardcoded `-c 0`, ignoring custom values entirely. KV Cache Dtype also
triggered an immediate model reload with no way to cancel.

Backend:
- llama_cpp.py: pass the actual n_ctx value to `-c` instead of always 0
- models/inference.py: relax max_seq_length to 0..1048576 (0 = model
  default) so GGUF models with large context windows are supported

Frontend:
- chat-runtime-store: add customContextLength and loadedKvCacheDtype
  state fields for dirty tracking
- chat-settings-sheet: make Context Length an editable number input,
  stop KV Cache Dtype from auto-reloading, show Apply/Reset buttons
  when either setting has been changed
- use-chat-model-runtime: send customContextLength as max_seq_length
  in the load request, reset after successful load

* fix: preserve maxSeqLength for non-GGUF models in load request

customContextLength ?? 0 sent max_seq_length=0 for non-GGUF models,
breaking the finetuning/inference path that needs the slider value.

Now uses a three-way branch:
- customContextLength set: use it (user edited GGUF context)
- GGUF without custom: 0 (model's native context)
- Non-GGUF: maxSeqLength from the sampling slider

* fix: keep max_seq_length default at 4096 for non-GGUF callers

Only relax the bounds (ge=0 for GGUF's "model default" mode,
le=1048576 for large context windows). The default stays at 4096
so API callers that omit max_seq_length still get a sane value
for non-GGUF models.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(studio): rename trust remote code toggle and hide when no model selected

- Rename "Trust remote code" to "Enable custom code"
- Shorten subtitle to "Only enable if sure"
- Hide the toggle when no model is loaded (already hidden for GGUFs)

* fix: restore ge=128 for max_seq_length validation

Keep the minimum at 128 so the API rejects nonsensical values.
GGUF path now sends the model's native context length (from
ggufContextLength) instead of 0 when the user has not customized it.
The upper bound stays at 1048576 for large-context GGUF models.

* feat(studio): replace Context Length input with slider

Use a ParamSlider (512 to model's native context, step 512) instead
of a small number input. Shows "Max" when at the model's native
context length. Consistent with the other slider controls in the
settings panel.

* feat(studio): add editable number input alongside Context Length slider

The slider and number input stay synced -- dragging the slider updates
the number, typing a number moves the slider. The input also accepts
values beyond the slider range for power users who need custom context
lengths larger than the model default.

* fix(studio): widen context length input and use 1024 step for slider

Make the number input wider (100px) so large values like 262144 are
fully visible. Change slider step from 512 to 1024 and min from 512
to 1024.

* fix(studio): context length number input increments by 1024

* fix(studio): cap context length input at model's native max

Adds max attribute and clamps typed/incremented values so the context
length cannot exceed the GGUF model's reported context window.

* fix(studio): point "What's new" link to changelog page

Changed from /blog to /docs/new/changelog.

* fix(studio): preserve custom context length after Apply, remove stale subtitle

- After a reload with a custom context length, keep the user's value
  in the UI instead of snapping back to the model's native max.
  ggufContextLength always reports the model's native metadata value
  regardless of what -c was passed, so we need to preserve
  customContextLength when it differs from native.
- Remove "Reload to apply." from KV Cache Dtype subtitle since the
  Apply/Reset buttons now handle this.

* feat(studio): auto-enable Search and Code tools when model supports them

Previously toolsEnabled and codeToolsEnabled stayed false after loading
a model even if it reported supports_tools=true. Now both toggles are
automatically enabled when the loaded model supports tool calling,
matching the existing behavior for reasoning.

* fix(studio): auto-enable tools in autoLoadSmallestModel path

The suggestion cards trigger autoLoadSmallestModel which bypasses
selectModel entirely. It was hardcoding toolsEnabled: false and
codeToolsEnabled: false even when the model supports tool calling.
Now both are set from the load response, matching the selectModel
behavior. Also sets kvCacheDtype/loadedKvCacheDtype for dirty
tracking consistency.

* fix(studio): re-read tool flags after auto-loading model

The runtime state was captured once at the start of the chat adapter's
run(), before autoLoadSmallestModel() executes. After auto-load enables
tools in the store, the request was still built with the stale snapshot
that had toolsEnabled=false. Now re-reads the store after auto-load so
the first message includes tools.

* fix(studio): re-read entire runtime state after auto-load, not just tools

The runtime snapshot (including params.checkpoint, model id, and all
tool/reasoning flags) was captured once before auto-load. After
autoLoadSmallestModel sets the checkpoint and enables tools, the
request was still built with stale params (empty checkpoint, tools
disabled). Now re-reads the full store state after auto-load so the
first message has the correct model, tools, and reasoning flags.

* feat(studio): add Hugging Face token field in Preferences

Adds a password input under Configuration > Preferences for users to
enter their HF token. The token is persisted in localStorage and
passed to all model validate/load/download calls, replacing the
previously hardcoded null. This enables downloading gated and private
models.

* fix(studio): use model native context for GGUF auto-load, show friendly errors

The auto-load paths and selectModel for GGUF were sending
max_seq_length=4096 which now actually limits the context window
(since we fixed the backend to respect n_ctx). Changed to send 0
for GGUF, which means "use model's native context size".

Also replaced generic "An internal error occurred" messages with
user-friendly descriptions for known errors like context size
exceeded and lost connections.

LoadRequest validation changed to ge=0 to allow the GGUF "model
default" signal. The frontend slider still enforces min=128 for
non-GGUF models.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(studio): filter out FP8 models from model search results

Hide models matching *-FP8-* or *FP8-Dynamic* from both the
recommended list and HF search results. These models are not
yet supported in the inference UI.

---------

Co-authored-by: Daniel Han <danielhanchen@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-25 08:32:38 -07:00
Lee Jackson
cc1be75621
studio: stabilize reasoning panel scroll behavior and prevent composer overlap (#4587)
* fix(studio): reasoning panel scroll and thread footer overlap

* refactor(studio): dedupe reasoning scroll lock teardown
2026-03-25 05:32:31 -07:00
Etherll
d69d60ff19
perf(studio): upgrade to Vite 8 + auto-install bun for faster frontend builds (#4522)
* perf(studio): upgrade to Vite 8 + auto-install bun for 3x faster frontend builds

* fix(studio): make bun-to-npm fallback actually reachable

setup.sh used run_quiet() for the bun install attempt, but run_quiet
calls exit on failure. This killed the script before the npm fallback
could run, making the "falling back to npm" branch dead code.

Replace the run_quiet call with a direct bun invocation that captures
output to a temp file (same pattern, but returns instead of exiting).

Also clean up partial node_modules left by a failed bun install before
falling back to npm, in both setup.sh and build.sh. Without this, npm
inherits a corrupted node_modules tree from the failed bun run.

* fix(studio): restore commonjsOptions for dagre CJS interop

The previous commit removed build.commonjsOptions, assuming Vite 8's
Rolldown handles CJS natively. While optimizeDeps.include covers the
dev server (pre-bundling), it does NOT apply to production builds.

The resolve.alias still points @dagrejs/dagre to its .cjs.js entry,
so without commonjsOptions the production bundle fails to resolve
the CJS default export. This causes "TypeError: e is not a function"
on /chat after build (while dev mode works fine).

Restore the original commonjsOptions block to fix production builds.

* fix(studio): use motion/react instead of legacy framer-motion import

* fix(studio): address PR review findings for Vite 8 + bun upgrade

Fixes:
  - Remove bun.lock from repo and add to .gitignore (npm is source of truth)
  - Use & bun install *> $null pattern in setup.ps1 for reliable $LASTEXITCODE
  - Add Remove-Item node_modules before npm fallback in setup.ps1
  - Print bun install failure log in setup.sh before discarding
  - Add Refresh-Environment after npm install -g bun in setup.ps1
  - Tighten Node version check to ^20.19.0 || >=22.12.0 (Vite 8 requirement)
  - Add engines field to package.json
  - Use string comparison for _install_ok in build.sh
  - Remove explicit framer-motion ^11.18.2 from package.json (motion pulls
    framer-motion ^12.38.0 as its own dependency — the old pin caused a
    version conflict)

* Fix Colab Node bypass and bun.lock stale-build trigger

Gate the Colab Node shortcut on NODE_OK=true so Colab
environments with a Node version too old for Vite 8 fall
through to the nvm install path instead of silently proceeding.

Exclude bun.lock from the stale-build probe in both setup.sh
and setup.ps1 so it does not force unnecessary frontend rebuilds
on every run.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
Co-authored-by: Shine1i <wasimysdev@gmail.com>
2026-03-25 04:27:41 -07:00
Wasim Yousef Said
926e74509d
feat(chat): cleaner tool UI, inline LaTeX, clickable links (#4561)
* feat(chat): ghost-style tool containers

Remove borders and card styling from tool call UI. ToolFallback
uses minimal padding with indented content. ToolGroup defaults
to ghost variant with subtle background for multi-tool grouping.

* feat(chat): compact web search source pills

Switch sources from vertical full-width badges to horizontal
wrapping pills with smaller icons.

* feat(chat): left-accent code and terminal tool UI

Replace bordered card layout with a left border accent for
Python and Terminal tool output. Add timer cleanup on unmount
for the copy button in both components.

* feat(chat): inline latex and clickable links

Enable single-dollar $...$ math rendering via createMathPlugin.
Add styled link component with target=_blank for external links.

* fix(chat): inline generating indicator, static tailwind classes, misc fixes

Move generating indicator from viewport footer into assistant
message using AnimatedShinyText shimmer. Only shows when message
content is empty, hides once tool calls or text appear.

Use static size class map in SourceIcon for Tailwind v4 compat.
Use unique keys for web search sources. Remove px-3 from ghost
tool group variant.

* fix(chat): only show generating indicator while message is running

Hide the shimmer when message is cancelled or errored with no
content, preventing stale loading UI on empty completed messages.

* fix: escape currency dollar signs in LaTeX math rendering and fix TS build error

- Add preprocessLaTeX() in lib/latex.ts to escape currency patterns ($5, $1,000, $5.99, $100K)
  before they reach the math parser, preventing false positives when singleDollarTextMath is enabled.
  Code blocks and already-escaped dollars are left untouched.
- Use preprocessLaTeX via useMemo in markdown-text.tsx so Streamdown receives clean input.
- Fix TS18048 in thread.tsx: message.status?.type (optional chaining) since status can be undefined.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-25 02:06:03 -07:00
Wasim Yousef Said
3dc212e218
fix: always show chat tool icons (#4525)
* fix: always show chat tool icons, gray out when model doesn't support them

Tool icons (Think, Search, Code) were hidden unless a model was loaded
and supported those features. Now they're always visible so users can
see and pre-select them. If a loaded model doesn't support a feature,
the button gets grayed out and disabled instead of being removed.

* refactor: centralize Qwen thinking params in store

* fix: disable tool buttons when no model is loaded

Change disabled condition from `modelLoaded && !supportsX` to
`!modelLoaded || !supportsX` so buttons are grayed out both when
no model is loaded and when the loaded model lacks the capability.

* Fix Qwen3 param clobbering and restore SuggestionItem capability guards

- Revert setReasoningEnabled() in the store to a pure boolean setter.
  Moving the Qwen3 param logic into it caused reconnect/load/refresh
  paths (which also call setReasoningEnabled) to silently overwrite
  user-customized or server-provided temperature/topP/topK/minP.

- Restore applyQwenThinkingParams() as a standalone function called
  only from explicit user toggle click handlers in thread.tsx and
  shared-composer.tsx, matching the pre-PR behavior.

- Re-add supportsReasoning/supportsTools guards in the SuggestionItem
  click handler so that clicking a suggestion card only activates
  tool toggles the loaded model actually supports.

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-24 03:26:56 -07:00
Daniel Han
77b21333fb
fix(studio): restore scroll lock on reasoning panel collapse (#4545)
PR #4543 removed useScrollLock from ReasoningRoot, causing the thread
viewport to jump when a user collapses a reasoning panel. Restore the
hook to freeze scrollTop during the 200ms collapse animation, matching
the pattern used by tool-fallback.tsx and tool-group.tsx.
2026-03-24 02:27:06 -07:00
Lee Jackson
45e4a0473a
studio: stop scroll hijack during generation and fix thinking panel layout shift (#4543)
* fix(chat): stabilize thinking panel and thread scroll during generation

* fix: match ChatGPT scroll and thinking panel behavior

- Remove autoScroll={false} from thread viewport to restore default
  follow-scroll during streaming (pauses when user scrolls up, resumes
  at bottom)
- Rewrite reasoning panel state: auto-opens on stream start, user can
  close during streaming, auto-collapses when reasoning ends, user can
  re-expand after collapse

---------

Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-23 22:33:46 -07:00
Daniel Han
62e3de181f
Update weather dashboard suggestion to request HTML code output (#4523)
The previous prompt "Show me a live weather dashboard, no API key needed"
was too vague. The new wording explicitly asks for HTML code, which
produces more useful and consistent responses.
2026-03-22 06:09:48 -07:00
Wasim Yousef Said
50cccfd55e
feat(chat): server-side timings, context display & source hover cards (#4467)
* feat(chat): add server-side timings and context display for GGUF

Extract timings/usage metadata from llama-server SSE stream and forward
through the full stack. Replace client-side estimates with accurate
server-reported metrics (prompt eval, tok/s, token counts, cache hits).
Add context window usage bar to chat top nav.

* feat(chat): source badges with hover cards and 2-row collapse

- Add hover cards to source badges showing favicon, title, URL and
  snippet description on hover
- Limit source badges to 2 rows with +X more expand/collapse
- Parse snippet from web search results for hover card descriptions
- Replace individual Source rendering with grouped SourcesGroup component

* fix(chat): add null guards for server timings edge cases

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix(chat): reset contextUsage on thread switch, remove unused context-display

* fix(chat): stop double-counting completion tokens in tool-calling path

* fix(chat): skip metadata events in llm_assist consumers

* fix(chat): hide context usage bar in compare mode

* fix(chat): harden timings pipeline and context usage persistence

Accumulate prompt_ms, predicted_ms, and predicted_n from intermediate
tool-detection passes so the final metadata reflects total server work.
Persist contextUsage in message metadata (Dexie) and restore on thread
load. Add type guard in gguf_stream_chunks for unexpected dict events.
Clear contextUsage when entering compare mode.

* feat(chat): make GGUF stream metadata OpenAI-compatible

* fix(chat): address PR review feedback

* feat(chat): address PR review feedback

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-20 23:42:01 -07:00
Wasim Yousef Said
6c2bfebb20
fix(studio): mobile navbar layout and chat settings sheet (#4458)
* fix(studio): mobile navbar layout and chat settings sheet

* fix(studio): portal select dropdowns inside sheet modal subtree
2026-03-19 02:04:53 -07:00
Manan Shah
e793c378db
turning data recipes on for mac (#4454) 2026-03-19 11:11:27 +04:00
Daniel Han
e38212281a
Fix TypeScript build errors in studio frontend (#4429)
- tool-ui-python.tsx: use explicit tuple type instead of `as const` to
  match the mutable `[BundledTheme, BundledTheme]` expected by Streamdown
- chat-adapter.ts: add missing `argsText` field required by
  ToolCallMessagePart and fix `args` type to use ReadonlyJSONObject
2026-03-18 08:44:38 -07:00
Daniel Han
9c95148045
Fix tool call parsing, add tool outputs panel and UI improvements (#4416)
* Add elapsed timer to tool status pill in Studio

Show a count-up seconds timer (0s, 1s, 2s, ...) next to the tool status
text in the composer area. Helps users gauge how long a tool call (web
search, code execution) has been running. Timer resets when a new tool
starts and disappears when all tools finish.

* Fix tool call parsing, add tool outputs panel and reasoning copy button

Backend:
- Rewrite tool call XML parser to use balanced-brace JSON extraction
  instead of greedy regex, fixing truncation on nested braces in
  code/JSON arguments
- Handle optional closing tags (</tool_call>, </function>, </parameter>)
  that models frequently omit
- Support bare <function=...> tags without <tool_call> wrapper
- Strip tool call markup from streamed content so raw XML never leaks
  into the chat UI
- Use a persistent ~/studio_sandbox/ working directory for tool
  execution so files persist across calls within a session
- Emit tool_start/tool_end SSE events so the frontend can display
  tool inputs and outputs

Frontend:
- Add collapsible "Tool Outputs" panel below assistant messages showing
  each tool call's input and output with copy buttons
- Add copy button to reasoning blocks
- Add elapsed timer to tool status pill
- Update project URLs in pyproject.toml (http -> https, add docs link)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add interactive HTML preview with fullscreen toggle for code blocks

HTML code fences now render an interactive sandboxed iframe preview
below the syntax-highlighted code, similar to how SVG fences show
an image preview. The iframe uses sandbox="allow-scripts" to allow
JavaScript execution while blocking access to the parent page.

Includes a fullscreen toggle (enlarge/minimize button) that expands
the preview into a viewport overlay, dismissible via button, Escape
key, or backdrop click. A streaming placeholder prevents partial
HTML from rendering mid-stream.

* Add tool call settings: auto-heal toggle, max iterations, timeout

Add three user-configurable tool call settings to the Studio Settings panel:

- Auto Heal Tool Calls: toggle to control fallback XML parsing of malformed
  tool calls from model output (default: on)
- Max Tool Calls Per Message: slider 0-40 + Max to cap tool call iterations
  per message (default: 10)
- Max Tool Call Duration: slider 1-30 minutes + Max to set per-tool-call
  execution timeout (default: 5 minutes)

All settings persist to localStorage and flow through the full stack:
frontend store -> API request -> Pydantic model -> route -> llama_cpp -> tools.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix tool call timeout: respect no-limit and apply to web search

- Use a sentinel to distinguish timeout=None (no limit) from the default
  (300s). Previously None was silently replaced with _EXEC_TIMEOUT.
- Pass the configured timeout to DDGS() for web searches so the setting
  applies uniformly to all tool types.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add input validation bounds and per-thread sandbox isolation

- Add ge=0 constraint to max_tool_calls_per_message (rejects negative values)
- Add ge=1 constraint to tool_call_timeout (minimum 1 second)
- Thread session_id from frontend through backend to tool execution
- Scope sandbox directories per conversation: ~/studio_sandbox/{thread_id}/
- Backwards compatible: API callers without session_id use ~/studio_sandbox/

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix non-monotonic streaming and Python temp script path

- Split tool markup stripping into closed-only (mid-stream) and full
  (final flush) to prevent cumulative text from shrinking mid-stream
- Enforce monotonicity: only emit when cleaned text grows, so the
  proxy's delta logic (cumulative[len(prev_text):]) never breaks
- Place Python temp scripts in the sandbox workdir instead of /tmp so
  sys.path[0] points to the sandbox and cross-call imports work

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Sanitize session_id to prevent path traversal in sandbox

Strip path separators and parent-dir references from session_id before
using it as a directory name. Verify the resolved path stays under
~/studio_sandbox/ as a second guard.

* feat(chat): proper assistant-ui tool call UIs with sources

Replace custom metadata-based ToolOutputsGroup with native assistant-ui
tool-call content parts. Backend SSE tool_start/tool_end events now emit
proper { type: "tool-call" } parts from the adapter, enabling per-tool
UIs registered via tools.by_name in MessagePrimitive.Parts.

- Web search: Globe icon, Source badges with favicons, auto-collapse
  when LLM starts responding
- Python: Code icon, syntax-highlighted code via Streamdown/shiki,
  output block with copy
- Terminal: Terminal icon, command in trigger, output with copy
- ToolGroup wraps consecutive tool calls (skips for single calls)
- Sources component renders URL badges at end of message
- Flattened code block CSS (single border, no nested boxes)

* fix(inference): respect empty enabled_tools allowlist

`if payload.enabled_tools:` is falsy for [], falling through to
ALL_TOOLS. Use `is not None` so an explicit empty list disables
all tools as intended.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Shine1i <wasimysdev@gmail.com>
2026-03-18 08:28:02 -07:00
Wasim Yousef Said
7b07ad0fa3
fix(studio): UI fixes for chat and studio routes (#4419)
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
2026-03-18 03:47:13 -07:00
Daniel Han
65acefd2a6
feat(studio): infinite scroll for recommended models list (#4414)
* feat(studio): infinite scroll for recommended models list

The model selector showed a hard cap of 4 GGUFs + 4 safetensors in the
Recommended section. Users who wanted to browse more had to search
manually on Hugging Face.

Backend: increase the default model pool from 8+8 to 40+40 (the HF
fetch already pulls 80, so no extra network cost).

Frontend: replace the static 4+4 cap with on-demand lazy loading.
A page counter tracks how many groups of 4 to show per category.
An IntersectionObserver on a sentinel div at the bottom of the list
increments the page when the user scrolls down. Models are interleaved
in groups of 4 GGUFs then 4 hub models per page for a balanced view.

Key implementation details:
- Callback ref for the sentinel so the observer attaches reliably on
  first popover open (useRef would miss the initial mount)
- Observer disconnects after each fire and re-attaches via useEffect
  with a 100ms layout delay to prevent runaway page loading
- VRAM info fetched incrementally via useRecommendedModelVram on the
  visible slice only
- recommendedSet uses visible IDs so HF search dedup stays correct

* refactor: address review feedback on recommended infinite scroll

- Simplify visibleRecommendedIds: use findIndex to locate the GGUF/hub
  split point instead of re-filtering the entire array each time.
  recommendedIds is already sorted GGUF-first, so a single slice is
  enough.

- Fix VRAM refetch churn: pass the full recommendedIds (stable across
  page increments) to useRecommendedModelVram instead of the growing
  visibleRecommendedIds slice. The hook derives its stableKey from the
  sorted+joined input, so passing the same pool on every page avoids
  redundant HF modelInfo requests.
2026-03-18 03:17:01 -07:00
Daniel Han
0acd1c7eec
studio: improve onboarding UX, tooltips, and training defaults (#4355)
* studio: improve onboarding UX, tooltips, and training defaults

- Change splash text to "Train and run LLMs locally"
- Add "Chat Only" card with BubbleChatIcon to skip directly to chat
- Add Skip/Skip to Chat buttons in sidebar and footer
- Back button on step 1 returns to splash screen instead of being disabled
- Change "Watch video guide" to "Get started with our guide" with new URL
- Update intro text to mention all model types + chat
- Make all tooltips clickable (in addition to hover) via React context
- Strip surrounding quotes from pasted HF tokens
- Rename "Eval Split" to "Evaluation Split"
- Add SparklesIcon to "Auto Detect" format option
- Change step 4 heading to "Choose your training parameters"
- Default max_steps to 60
- Learning rate displayed in scientific notation with +/- stepper
- Context length options capped by model's max_position_embeddings (via AutoConfig)
- Fix "QLORA"/"LORA" to "QLoRA"/"LoRA" in summary step
- Backend: add max_position_embeddings to model config endpoint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* compare for 2 diff models

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* resolving gemini comments

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: disable thinking for Qwen3.5 <9B and always for AI Assist

- Change Qwen3.5 thinking threshold from <=2B to <9B (0.8B, 2B, 4B
  all disable thinking by default; 9B+ enables it)
- Always pass enable_thinking=False in AI Assist helper calls
  (_run_with_helper and _generate_with_backend) regardless of chat
  thinking settings

* studio: address PR review comments

- Extract _get_max_position_embeddings helper to DRY config extraction
- Fix "Skip to Chat" to navigate to /chat on step 1 (was /studio)

* fix: comment out debug print statements

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: skip Shiki highlighting for incomplete SVG code fences

While streaming SVG content, the syntax highlighter (Shiki) re-parses
the entire growing SVG on every token, blocking the main thread and
freezing the code area until the fence closes. Show a plain-text
preview for incomplete SVG fences instead, similar to how Mermaid
diagrams show a placeholder while streaming.

* studio: fix default top_k from 50/40 to 20 for chat inference

Per Qwen3.5 docs (unsloth.ai/docs/models/qwen3.5), top_k should be 20
for both thinking and non-thinking modes. The model-specific config in
inference_defaults.json already had top_k=20 for Qwen3.5, but the
generic fallback defaults were wrong:
- Frontend DEFAULT_INFERENCE_PARAMS.topK: 50 -> 20
- Backend generate_chat_completion top_k: 40 -> 20
- Backend generate_chat_completion_with_tools top_k: 40 -> 20
- Frontend title generation top_k: 40 -> 20

* studio: set universal inference defaults for unknown models

Default params for any model without specific config:
  temperature=0.6, top_p=0.95, top_k=20, min_p=0.01,
  presence_penalty=0.0, repetition_penalty=1.0

Models with entries in inference_defaults.json (Qwen3.5, Gemma-3,
Llama, etc.) override these with their recommended values.

Updated in: frontend DEFAULT_INFERENCE_PARAMS, backend Pydantic
request models, and backend generate_chat_completion defaults.

* studio: only trust_remote_code for unsloth/ models in AutoConfig

Only set trust_remote_code=True when the model name starts with
"unsloth/". All other models default to False for safety.

* studio: move Generating spinner above the composer

The "Generating" spinner was below the send message bar, causing
the bar to jump up and down. Move it above the composer in both
the regular thread view and the welcome/empty view.

* studio: adjust toast close button position away from edge

Move the X close button on toasts (like "Starting model...") from
top-1.5 to top-3 and add right-3, giving more breathing room from
the top-right corner.

* studio: make Think button smaller with tighter icon-text gap

Reduce gap from 1.5 to 0.5, padding from px-2.5/py-1 to px-2/py-0.5,
and icon from size-3.5 to size-3.

* studio: multiple onboarding and chat UX improvements

- Move Generating spinner above composer (fixes jumping send bar)
- Make Think button smaller with tighter icon-text gap
- Chat card now inside grid (same size as Audio/Embeddings cards)
- Rename "Chat Only" to "Chat"
- Chat card requires Continue to proceed (no auto-advance)
- Continue on Chat selection skips onboarding and goes to /chat
- Tooltip (i) click on Chat card doesn't trigger navigation
- Step 1 footer Back button goes back to splash (label is "Back")
- Splash "Skip Onboarding" renamed to "Skip to Chat", navigates to /chat
- Toast close button moved away from edge

* studio: align Skip to Chat button, add Skip to footer

- Sidebar "Skip to Chat" now uses primary (green) Button style with
  arrow icon, full width, aligned like step items. Shows on all steps.
- Footer: added "Skip" outline button next to Continue that goes
  directly to /studio with progress saved (markOnboardingDone)

* studio: change default max steps from 30 to 60 in toggle hook

The DEFAULT_MAX_STEPS in use-max-steps-epochs-toggle.ts was still 30,
used as fallback when toggling from epochs back to max steps.

* studio: extend context length options to 262K

CONTEXT_LENGTHS now includes 65536, 131072, 262144 in addition to
the existing 512-32768 range. The onboarding step filters these by
the model's max_position_embeddings (e.g. Nemotron-3-Nano-4B has
262144), showing powers of 2 up to the model's maximum.

* studio: auto-select LoRA vs QLoRA based on model size and GPU memory

After selecting a model in onboarding, detect the total model weight
file size from HF Hub (safetensors/bin files). Then estimate memory
needed: model_size_gb * 1.5 * context_scale, where context_scale is:
  - <=8192 tokens: 1.0x
  - >8192 tokens: 1.7x
  - >=16384 tokens: 2.0x
  - >=32768 tokens: 4.0x

If the estimate fits in free GPU VRAM, default to LoRA (16-bit).
Otherwise default to QLoRA (4-bit).

Backend changes:
- Add model_size_bytes to ModelDetails (models.py)
- Add _get_model_size_bytes() using HfApi.repo_info (routes/models.py)
- Add vram_free_gb to get_gpu_summary (hardware.py)

Frontend changes:
- Add autoSelectTrainingMethod() in training-config-store.ts
- Called after model defaults are loaded
- Add model_size_bytes to ModelConfigResponse type
- Add vramFreeGb to HardwareInfo hook

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: rename "Importing ML libraries..." to "Importing Unsloth..."

* studio: show model/dataset in training status, fix LoRA/QLoRA casing

- Training status now shows 'Training "model_name"' and 'Dataset = ...'
  instead of generic "Starting training..."
- Fix Studio progress section to show QLoRA/LoRA instead of QLORA/LORA

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: rename 'Skip to Chat' to 'Skip Onboarding' on splash screen

* studio: add presence_penalty support for chat inference

Add presence_penalty as a parameter across the full stack:
- Backend: llama_cpp.py generate_chat_completion/with_tools, Pydantic
  models (inference.py), routes/inference.py pass-through
- Frontend: InferenceParams type, DEFAULT_INFERENCE_PARAMS (0.0),
  chat-adapter.ts payload, chat-settings-sheet.tsx slider (0-2),
  model defaults loading from inference_defaults.json
- Set Qwen3.5 default presence_penalty to 1.5 per official docs
- Default for unknown models is 0.0 (off)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: fix Chat card deselecting Text and aligning with other cards

* studio: fix presence_penalty not loading from inference defaults

The inference_config.py load_inference_config() was not including
presence_penalty in the returned config dict, so the Qwen3.5
default of 1.5 from inference_defaults.json never reached the
frontend. Added it to the config builder.

* studio: add delete button for cached models in model selector

Add trash icon on each downloaded model row (GGUF and safetensors) with
confirmation dialog. Backend DELETE /api/models/delete-cached endpoint
uses huggingface_hub scan_cache_dir + delete_revisions to cleanly remove
cached repos, refusing if the model is currently loaded.

* studio: restore inference defaults, reasoning, and tools on page refresh

On page refresh with a model already loaded, the frontend was not
re-applying model-specific inference defaults (presence_penalty,
temperature, etc.) or restoring reasoning/tools support flags.

Backend: Add inference config, supports_reasoning, supports_tools,
and context_length to InferenceStatusResponse.

Frontend: In the refresh callback, when an active model is detected,
apply mergeRecommendedInference and restore reasoning/tools flags
with proper Qwen3.5 size-based defaults.

* studio: fix delete dialog closing before async completes

Prevent AlertDialogAction's default close behavior with
e.preventDefault() so the dialog stays open during deletion.
Also block onOpenChange dismiss while deleting is in progress.

* fix: add Dict and Any imports to inference models

* studio: fix Qwen3.5 reasoning threshold in frontend load path

The frontend loadModel handler had the old threshold (<=2) for
disabling reasoning on small Qwen3.5 models. Changed to <9 to
match the backend. This was causing 4B to not properly disable
thinking by default when auto-loaded.

* studio: move GGUF delete to per-variant level

For GGUF repos, the trash icon now appears on each downloaded variant
row inside the quantization expander instead of on the repo-level row.
Backend accepts optional variant param to delete specific GGUF files
(blob + symlink) rather than the entire repo cache.

* studio: restore ggufContextLength on page refresh

The Max Tokens slider was capped at 32768 on page refresh because
ggufContextLength was not restored from the status response.
Now set it from statusRes.context_length on reconnect.

* fix: remove <think> from Qwen3.5 response template marker

The train-on-responses-only feature uses template markers to find
where the assistant response starts. The Qwen3.5 response marker
included '<think>\n' which is only present when thinking mode is
enabled. With thinking disabled (default for <9B), the marker
never matched, causing 100% of samples to be dropped.

Changed response marker from '<|im_start|>assistant\n<think>\n'
to '<|im_start|>assistant\n' which works regardless of thinking mode.

* studio: fix sloth ASCII art alignment in training overlay

* fix: correct sloth ASCII art alignment to match Unsloth banner

* studio: add Python and terminal tool calling to chat

Register python and terminal tools alongside web search. Python
executor validates imports (stdlib only) via unsloth_zoo
rl_environments, runs code in a subprocess sandbox with 5-min
timeout and cancel support. Terminal executor blocks dangerous
commands (rm, sudo, etc.) and runs in a temp directory.

Update llama_cpp tool loop to show tool-specific status messages
and pass cancel_event through to executors. Rename composer
toggle from "Search" to "Tools" and show TerminalIcon for
execution status pills.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: fix Nemotron/transformers 5.x support, onboarding navigation, port binding

Backend:
- Dynamic transformers 5.x detection via tokenizer_config.json fetch
  (checks for TokenizersBackend class, cached per-model)
- Bump transformers 5.x version from 5.2.0 to 5.3.0 across all workers,
  setup scripts (setup.sh, setup.ps1)
- Auto-enable trust_remote_code for unsloth/* models needing transformers 5.x
  (workaround for NemotronH config parsing bug in transformers)
- Auto-install mamba-ssm/causal-conv1d for SSM models (NemotronH, Falcon-H1)
  with --no-build-isolation --no-deps to avoid torch version conflicts
- Add SO_REUSEADDR to port check in run.py (fixes Colab proxy stale connection
  falsely reporting port as in-use)

Frontend:
- Fix "Skip to Chat" navigation: use window.location.href instead of React
  Router navigate() to bypass useEffect redirect race
- Fix "Skip Onboarding" on splash: navigates to /studio (not /chat)
- Fix onboarding guard: only check isOnboardingDone() on initial mount
- Fix Chat card on step 1: add sr-only spacer for consistent alignment
- Fix Chat+Text both selected: clear RadioGroup value when Chat is selected

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: split tools toggle into Search and Code buttons

Replace the single "Tools" toggle with two independent toggles:
- "Search" (globe icon) enables web search only
- "Code" (terminal icon) enables Python and terminal execution

Add enabled_tools list field to the inference payload so the
backend only registers the tools the user has toggled on. Both
toggles appear in the main composer and the compare composer.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: fix tool calling import validation and error logging

Replace unsloth_zoo-dependent import checker with a standalone
ast-based validator using sys.stdlib_module_names. This properly
blocks non-stdlib imports (numpy, requests, etc.) and returns a
clear error message to the model so it can rewrite using only
stdlib.

Add full traceback to tool streaming error logs for debugging.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: parse gpt-oss harmony channels for clean safetensors chat output

gpt-oss models emit multi-channel output via harmony protocol tokens
(<|channel|>analysis<|message|>... and <|channel|>final<|message|>...).
TextIteratorStreamer with skip_special_tokens=True strips the special
tokens but leaves channel names concatenated with content, producing
garbled output like "analysisWe need to...assistantfinalHello!".

Add HarmonyTextStreamer that decodes with skip_special_tokens=False,
parses harmony markup via regex, and emits <think>analysis</think>
for the analysis channel and plain text for the final channel --
reusing the existing frontend reasoning UI.

Also expose supports_reasoning=True for non-GGUF gpt-oss models in
the /status endpoint so the frontend enables the Think toggle.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: use unsloth_zoo for Python sandbox validation

Set UNSLOTH_IS_PRESENT=1 and import check_python_modules and
check_signal_escape_patterns directly from unsloth_zoo instead
of a standalone fallback. This gives us the full Unsloth
validation including stdlib-only import checks and signal/timeout
escape pattern detection.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: allow all imports in Python tool sandbox

Remove stdlib-only import restriction. Keep signal escape
pattern detection via unsloth_zoo for safety.

* studio: fix ReadTimeout on tool streaming final pass

The 0.5s read timeout used for cancel-checking during streaming
also fires when waiting for the first response from llama-server
(e.g. reasoning model thinking for 15+ seconds). Add
_stream_with_retry() context manager that retries on ReadTimeout
while checking cancel_event, so the model has unlimited time to
think before producing the first token. Applied to both the
regular streaming path and the tool-calling final pass.

* fix: rewrite HarmonyTextStreamer with stateful incremental parsing

The delta-on-transformed approach had two critical bugs:

1. Before the full <|channel|>X<|message|> pattern was complete, the
   strip-tokens fallback emitted "analysis" as plain text. Then when
   the regex matched, _transform returned a completely different format
   (<think>...</think>) and the delta was computed against the wrong
   base string, producing fragments like "think>", "nk>", ">".

2. Even with full matches, the closing </think> tag shifted position
   as content grew, so text[prev_len:] produced garbled deltas.

Replace with stateful incremental parsing that:
- Buffers until a complete channel+message pair is seen
- Emits <think> once when analysis channel first appears
- Streams analysis content deltas (computed on channel content directly)
- Emits </think> once when final channel first appears
- Streams final content deltas
- Closes open think tags in end()

Also skip the generic all_special_tokens stripping in
_clean_generated_text for gpt-oss since HarmonyTextStreamer already
produces clean output and the generic stripping was mangling <think>
tags.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: strip all <|...|> tokens in gpt-oss cleanup, not just harmony subset

The gpt-oss tokenizer has added tokens like <|return|> (id=200002) that
are not part of the harmony channel protocol but can leak into output.
The previous regex only stripped channel|message|start|end tokens.

Broaden the _clean_generated_text regex for gpt-oss to <\|[a-z_]+\|>
which catches all pipe-delimited tokens (return, constrain, reserved,
etc.) without matching <think>/<\/think> tags.

Verified: gpt-oss all_special_tokens are only <|return|>,
<|reserved_200017|>, <|startoftext|> -- none overlap with <think>.
The harmony tokens (channel, message, start, end) are added_tokens
but not in all_special_tokens.

* fix: hide config-only model repos from cached models list

Repos that only have metadata/config files cached (no .safetensors or
.bin weight files) were showing up in the Downloaded list with tiny
sizes like "1.8 KB" or "24 KB". These are just leftover config
snapshots from architecture checks, not usable models.

Filter the cached-models endpoint to only include repos that contain
actual model weight files (.safetensors or .bin).

* studio: fix toast description text contrast in dark mode

Add explicit !text-muted-foreground to toast description classNames
so secondary text (e.g. "Releases VRAM and resets inference state.")
is readable in dark mode.

* studio: fix Chat card icon alignment with size-4 spacer

Replace sr-only span (takes no space) with a size-4 shrink-0 div
matching the RadioGroupItem dimensions in other cards, so the Chat
icon aligns vertically with Text/Audio/Vision/Embeddings icons.

---------

Co-authored-by: workspace <user@workspace.local>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Manan17 <shahmanan170602@gmail.com>
Co-authored-by: Roland Tannous <rolandtannous@gravityq.ai>
2026-03-17 07:46:07 -07:00
Daniel Han
37fe04f7bf
studio: add SVG preview, fix streaming bug and model selector state (#4354)
- Add SVG preview rendering below code blocks using safe data URI
  in <img> tag. Includes sanitization to block script/event handlers.
- Fix GGUF streaming crash: cache response.iter_text() iterator
  instead of creating a new one on every loop iteration.
- Fix model selector showing "Select model..." after auto-load by
  re-reading store state after setCheckpoint before setParams.
- Remove unused warmupToastShown variable (TS6133 build error).
- Change default suggestion to "Draw an SVG of a cute sloth".
2026-03-17 02:34:05 -07:00
Daniel Han
eeffa4c065
studio: web search, KV cache dtype, training progress, inference fixes
## Summary
- Add web search tool calling for GGUF models (Search toggle, DuckDuckGo via ddgs)
- Add KV cache dtype dropdown (f16/bf16/q8_0/q5_1/q4_1) in Chat Settings
- Fix Qwen3/3.5 inference defaults per official docs (thinking on/off params)
- Enable reasoning by default for Qwen3.5 4B and 9B
- Replace "Generating" toast with inline spinner
- Fix stop button via asyncio.to_thread (event loop no longer blocked)
- Fix CUDA 12 compat lib paths for llama-server on CUDA 13 systems
- Fix auto-load model name not appearing in selector
- Training progress messages + dataset_num_proc fix

Integrated PRs:
- #4327 (imagineer99): BETA badge alignment (already in tree)
- #4340 (Manan Shah): prioritize training models in model selection
- #4344 (Roland Tannous): setup.sh macOS python version compatibility
- #4345 (Manan Shah): revamp model+dataset checking logic
2026-03-17 00:30:01 -07:00
Daniel Han
44dcf30b9b
studio: per-model inference defaults, GGUF slider fix, reasoning toggle (#4325)
* studio: extract param count from model name as fallback

When HuggingFace API doesn't return totalParams for a model,
extract the param count from the model name (e.g. "Qwen3-0.6B"
-> "0.6B", "Llama-3.2-1B-Instruct" -> "1B"). Applied to both
the recommended list and HF search results.

* studio: read GGUF context_length via fast header parser, set max tokens

- Fast GGUF metadata reader (~30-55ms) parses only KV header, skips
  tensor data and large arrays (tokenizer vocab etc)
- Extracts context_length and chat_template from GGUF metadata
- Returns context_length in LoadResponse for frontend to use
- Frontend sets maxTokens to actual context_length for GGUFs (e.g.
  262144 for Qwen3.5-9B, 131072 for Qwen2.5-7B)
- Max Tokens slider shows "Max" and is locked for GGUFs
- Auto-load path also uses actual context_length from load response
- Toast auto-dismiss (5s) and close button for auto-load toast

* studio: GGUF TTS audio support (from PR #4318)

Add GGUF TTS audio generation via llama-server. When a GGUF model
loads, the backend probes its vocabulary to detect audio codecs
(SNAC/BiCodec/DAC/CSM/Whisper). If detected, the codec is pre-loaded
and the model is reported as audio to the frontend.

During chat, TTS models route to the audio generation path which sends
a per-codec prompt to llama-server's /completion endpoint, extracts
generated tokens/text, and decodes to WAV using AudioCodecManager.

Also strips base64 audio data from prior assistant messages to prevent
context overflow.

Co-authored-by: Manan Shah <mananshah511@gmail.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Remove package-lock.json from tracking

* studio: per-model inference defaults, GGUF max tokens fix, reasoning toggle

- Add inference_defaults.json with per-model-family sampling parameters
  for ~50 families (Qwen3.5, Qwen3, Gemma-3, Llama-3, DeepSeek, etc.).
  Values sourced from unslothai/docs and Ollama params blobs.

- Family-based lookup in inference_config.py: extracts model family from
  identifier, matches against patterns (longest match first), merges with
  priority: model-specific YAML > family JSON > default.yaml.

- Fix GGUF Max Tokens slider locked at "Max": store ggufContextLength
  separately from maxTokens so the slider is adjustable (step=64).

- Fix Ministral YAML: top_p was literal string "default", now 0.95.

- Add reasoning toggle for thinking models (Qwen3.5, Qwen3, DeepSeek-R1,
  DeepSeek-V3.1, etc.): detect enable_thinking support from GGUF chat
  template metadata, pass --jinja to llama-server, send
  chat_template_kwargs per-request. Frontend shows "Reasoning is ON/OFF"
  pill button next to attachment button in composer.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: remove default system prompt injection

Backend was injecting "You are a helpful AI assistant." when no system
prompt was provided. Neither unslothai/docs nor Ollama specify a default
system prompt for most models. Now defaults to empty string, letting the
model's own chat template handle system behavior.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: use lightbulb icons and "Think" label for reasoning toggle

Lightbulb on when thinking enabled, lightbulb-off when disabled.
Label is just "Think" in both states; grayed out styling when off.

* studio: fix HTML file upload breaking chat

Replace SimpleTextAttachmentAdapter with custom TextAttachmentAdapter
(excludes text/html) and HtmlAttachmentAdapter that strips tags via
DOMParser, removing scripts/styles and extracting readable text content
instead of dumping raw HTML markup into the conversation.

* studio: show chat template in Configuration panel

Display the model's Jinja2 chat template in a new "Chat Template"
section under Settings (now open by default). For GGUFs, reads from
GGUF metadata; for safetensors, reads from tokenizer.chat_template.

Template is editable with a "Restore default chat template" button
that appears when modified. Section only shows when a model with a
chat template is loaded.

* studio: editable chat template with Apply & Reload

Chat template section now functional:
- Editing the template shows "Apply & Reload" (reloads model with
  custom template) and "Revert changes" buttons
- For GGUFs: writes template to temp .jinja file, passes
  --chat-template-file to llama-server on reload
- For non-GGUF: passes chat_template_override in load request
- Settings section now open by default
- selectModel supports forceReload to reload same model

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* studio: fix DeepSeek reasoning detection and auto-load metadata

- Set _model_identifier before _read_gguf_metadata so DeepSeek
  "thinking" template detection works (was always None before)
- Populate ggufContextLength, supportsReasoning, reasoningEnabled,
  defaultChatTemplate in autoLoadSmallestModel GGUF path

* studio: add spacing before BETA badge in navbar

Add gap-1.5 on the logo Link container to space the BETA label
from the wordmark.

Co-authored-by: Imagineer99 <Imagineer99@users.noreply.github.com>

* studio: vertically center BETA badge with logo

---------

Co-authored-by: Manan Shah <mananshah511@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Imagineer99 <Imagineer99@users.noreply.github.com>
2026-03-16 06:37:55 -07:00
Daniel Han
991a2bfc35 studio: GGUF unlimited context, auto-load, wait-for-load, UX fixes
- Use -c 0 for llama-server (model's native context size, no 4096 cap)
- Run non-GGUF backend.load_model in asyncio.to_thread for progress polling
- Auto-load smallest downloaded model when user chats without selecting one
- Wait for in-progress model load before inference (no "No model loaded" error)
- Add modelLoading flag to zustand store for cross-component coordination
- Dynamic top models: send 8 GGUFs + 8 hub models, frontend caps 4+4 after dedup
- Case-insensitive dedup: downloaded models correctly hide from recommended list
- Prevent duplicate toasts: guard against double selectModel calls
- Model selector waits for cached data before rendering (no empty flash)
- Toast close button positioned at top-right with proper spacing
- Sampling section expanded by default in chat settings
- Global toast close button styling fix
2026-03-16 02:46:56 -07:00
Daniel Han
b985471637 Increase default max tokens to 8192, disable repetition penalty
- maxTokens: 2048 -> 8192. The old 2048 limit caused generation to
  stop mid-output for longer responses (e.g. reasoning/thinking models
  that produce long chain-of-thought before the answer).
- repetitionPenalty: 1.1 -> 1.0 (disabled). Most models handle
  repetition well on their own. A penalty of 1.1 can hurt quality
  for creative tasks like code generation and ASCII art.
- Change welcome message from "Run LLMs or test your fine-tune" to
  "Chat with your model".
2026-03-16 02:46:56 -07:00
Daniel Han
9cbeecc16a Incorporate PR #4304 toast UX improvements
Merge the toast UX refactor from PR #4304 (by @Shine1i):
- Toast duration 5s default with close button (X) for manual dismiss
- Inline progress bar component (ModelLoadInlineStatus) shown in the
  header after toast is dismissed
- Model switch warning only for image compatibility (not generic)
- activeThreadId tracked in store via ActiveThreadSync
- Loading state cleanup via resetLoadingUi helper
- Toast uses Infinity duration during loading with onDismiss handler

Re-applied non-GGUF download progress additions on top:
- getDownloadProgress for all models (not just GGUF)
- hasShownProgress flag, loadingModelRef race condition checks
- First poll at 500ms, bytes-only fallback when expected size unknown
2026-03-16 02:46:56 -07:00
Daniel Han
2642f6d21d Add sloth emoji to section labels, friendlier network error
- Add sloth emoji prefix to "Downloaded" and "Recommended" section
  labels in the Hub model picker so they are visually distinct.
- Replace browser network errors ("NetworkError when attempting to
  fetch resource" / "Failed to fetch") with a clearer message:
  "Studio isn't running -- please relaunch it."
2026-03-16 02:46:56 -07:00
Daniel Han
f20c7ca54d Friendlier unsupported model errors, show estimated download size
1. Backend: When a model fails with "No config file found" or similar
   unsupported-model errors, wrap the message with "This model is not
   supported yet. Try a different model." instead of showing the raw
   Unsloth exception.

2. Frontend: Compute estimated download size from the HF search API's
   safetensors.parameters dtype breakdown (BF16=2B/param, I32=4B/param,
   F32=4B/param, etc.) and show it in the model picker instead of just
   the param count. For example, Kimi-K2.5 now shows "~554 GB" instead
   of "171B" (which was misleading since 171B params != 171GB download).
2026-03-16 02:46:56 -07:00
Daniel Han
1471c63b96 Fix download progress bugs: false completion, stale UI, dedup
Three fixes on top of the download progress feature:

1. Backend: Replace broken "no .incomplete = done" completion check
   with a 95% byte threshold. HF downloads files sequentially, so
   between files there are briefly no .incomplete files even though
   the download is far from done (e.g. Kimi-K2.5 reported "done"
   after downloading 22KB of config files out of 595GB).

2. Frontend: Track hasShownProgress flag. Only show "Download
   complete. Loading into memory..." if we actually displayed
   download progress before. For already-cached models where the
   first poll returns progress=1.0, this avoids the misleading
   "Download complete" message.

3. Frontend: Deduplicate recommended vs downloaded -- filter out
   models already in the "Downloaded" section. Cache the fetched
   lists at module level so re-mounting the popover does not flash
   an empty "Downloaded" section.
2026-03-16 02:46:56 -07:00
Daniel Han
b84f167d5a Add download progress bar for non-GGUF models in Chat
Previously only GGUF models showed download progress in Chat. Non-GGUF
models (safetensors, bnb quantized, etc.) showed a static message with
no progress indication. This adds progress tracking for all model types
and fixes several related issues.

Backend:
- Add /api/models/download-progress endpoint that checks the HF cache
  blobs directory for completed and .incomplete files. Uses model_info()
  (cached per repo) to determine expected total size for percentage.
- Add /api/models/cached-models endpoint that lists non-GGUF model repos
  from the HF cache via scan_cache_dir().
- Fix progress stuck at 0.99: when no .incomplete files remain, report
  1.0 immediately (blob deduplication can make byte totals mismatch).

Frontend:
- Remove the ggufVariant gate so download progress polling works for all
  non-cached models, not just GGUFs.
- Use GGUF-specific endpoint when variant + expectedBytes available,
  otherwise use the general download-progress endpoint.
- Fix toast stuck after load: check loadingModelRef.current before and
  after the async poll to prevent overwriting the success toast.
- First poll at 500ms instead of waiting for the 2s interval.
- Show downloaded non-GGUF models in the Hub model picker "Downloaded"
  section alongside GGUFs.
2026-03-16 02:46:56 -07:00
Manan Shah
b2dce8e3a8
chat only with gguf for mac devices (#4300)
* chat only with gguf for mac devices

* resolving gpt comments

* add change-password for chat only

* hide lora adaptors dropdown

* solving gpt comments

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* addressing the comment

* fixing auth flow

---------

Co-authored-by: Datta Nimmaturi <venkatadattasainimmaturi@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-15 23:20:48 +04:00
Daniel Han
11612f6dc9 studio: fix GGUF download UX -- progress bar, cancel, sorting, auto-scroll
- Run GGUF load_model in asyncio.to_thread so the event loop stays free
  for progress polling during download (was blocking all requests).
- Extract download phase out of the lock in LlamaCppBackend.load_model
  so unload_model/cancel can take effect immediately during download.
- Fix "downloaded" badge for split GGUFs: check total cached bytes
  across all shards vs expected size, not just first shard existence.
- Respect CUDA_VISIBLE_DEVICES in /api/system GPU reporting so the
  frontend GGUF fit estimation uses actual available VRAM.
- Sort tight variants (need CPU offload) smallest-first instead of
  largest-first -- closer to GPU budget = faster inference.
- Fix cancel: use refs instead of React state for abort controller and
  toast ID so both cancel buttons (text + toast) work reliably. Make
  cancel synchronous (fire-and-forget unload) for instant UI response.
  Check abortCtrl.signal.aborted after loadModel returns to prevent
  ghost model state. Skip rollback and suppress errors on cancel.
- Dynamic top 4 GGUF models fetched from HF API sorted by downloads,
  prepended to the default recommended list.
- Remove turnAnchor="top" for auto-scroll to bottom during generation.
- Set default toast duration to 10s (was infinite for loading toasts).
- Deduplicate cached GGUF repos using scan_cache_dir API (fixes
  Qwen/X-GGUF vs qwen/x-gguf duplicates from lowercased HF cache).
- Pre-compile repo_id validation regex to silence CodeQL ReDoS warning.
- Change welcome text and default suggestion text.
2026-03-15 05:24:06 -07:00
Daniel Han
475ba417dc studio: context-aware loading text + download progress bar
1. Loading text: shows "Loading model..." for cached models,
   "Downloading model..." for new downloads. Toast description
   adapts accordingly.

2. Download progress: polls /api/models/gguf-download-progress every
   2s during downloads, updating the toast with percentage and GB
   downloaded. Progress is estimated by checking the HF cache folder
   size against the expected total bytes.

3. Passes isDownloaded and expectedBytes through the full chain from
   variant click to selectModel for accurate UI state.
2026-03-15 05:24:06 -07:00
Daniel Han
7b65073311 studio: show 'downloaded' badge instead of 'recommended' when variant is cached 2026-03-15 05:24:06 -07:00
Daniel Han
bcb382def9 studio: sort downloaded GGUF variants before recommended
Downloaded variants now take priority over the recommended badge in
sort order. Within the same tier (downloaded+fits, etc.), recommended
still sorts first. Order: downloaded -> recommended -> fits -> tight -> OOM
2026-03-15 05:24:06 -07:00
Daniel Han
4d35699c65 studio: show downloaded status in GGUF variant list, sort downloaded first
- Backend: /gguf-variants now checks HF cache for each variant's file
  and returns a downloaded flag per variant
- Frontend: downloaded variants sort before non-downloaded (after
  recommended), and show a green "downloaded" badge
- Sort order: recommended -> downloaded+fits -> downloaded+tight ->
  fits -> tight -> OOM
2026-03-15 05:24:06 -07:00
Daniel Han
897d8b426a studio: interruptible GGUF downloads, cached models endpoint, Downloaded section
1. Interruptible downloads: load_model now checks a cancel event
   between shard downloads. unload_model sets the event so cancel
   stops the download at the next shard boundary.

2. /api/models/cached-gguf endpoint: scans the HF cache for
   already-downloaded GGUF repos with their total size and cache path.

3. "Downloaded" section in Hub model picker: shows cached GGUF repos
   at the top (before Recommended) so users can quickly re-load
   previously downloaded models without re-downloading.
2026-03-15 05:24:06 -07:00
Daniel Han
7b19cb418e studio: sort TIGHT (CPU offload) GGUF variants after GPU-only fits
Sort order is now: recommended -> fits (largest first) -> tight/CPU
offload (largest first) -> OOM (smallest first). Previously tight
variants were mixed with fits variants.
2026-03-15 05:24:06 -07:00
Daniel Han
5bb783850a studio: GGUF OOM accounts for CPU offload via --fit (GPU + system RAM)
Updated GGUF fit classification to match llama-server's --fit behavior:

- fits:  model <= 70% of total GPU memory (all GPUs)
- tight: model > 70% GPU but <= 70% GPU + 70% available system RAM
         (llama-server uses --fit to offload layers to CPU)
- OOM:   model exceeds both GPU and system RAM budgets

useGpuInfo now also returns systemRamAvailableGb from /api/system so the
frontend can compute the combined GPU+RAM budget.
2026-03-15 05:24:06 -07:00
Daniel Han
c9c485a7b0 studio: use nvidia-smi for all GPUs + 70% VRAM threshold for GGUF OOM
Two fixes for accurate GGUF OOM detection:

1. /api/system now uses nvidia-smi to enumerate all physical GPUs
   instead of torch.cuda which only sees CUDA_VISIBLE_DEVICES. This
   matches llama-server which can use all GPUs regardless of the env
   var. Falls back to torch-based detection if nvidia-smi unavailable.

2. Frontend GGUF OOM check now uses 70% of total GPU memory as the
   budget, matching the PR's _select_gpus logic (30% reserved for KV
   cache and compute buffers). Previously used checkVramFit's 100%
   threshold which was too generous.
2026-03-15 05:24:06 -07:00
Daniel Han
4600131fea studio: sort OOM GGUF variants smallest-to-largest
OOM variants are more useful sorted ascending by size since smaller ones
are more likely to run with --fit. Non-OOM variants remain largest-first
(best quality).
2026-03-15 05:24:06 -07:00
Daniel Han
ea45370ab8 studio: use total multi-GPU VRAM for OOM checks, recommend smallest when all OOM
Two fixes for GGUF variant dropdown:

1. useGpuInfo now sums memory across all GPU devices instead of only
   reading devices[0]. This matches llama-server's multi-GPU allocation
   where models can be split across GPUs.

2. When the backend-recommended variant (e.g. UD-Q4_K_XL) exceeds total
   GPU VRAM, the frontend picks the largest variant that fits instead.
   If all variants are OOM, it recommends the smallest one (most likely
   to work with --fit).
2026-03-15 05:24:06 -07:00
Daniel Han
10c4db04d8 studio: fix React hooks order -- move useMemo before early returns
The useMemo for sortedVariants was placed after the loading/error early
returns, which violated React's rules of hooks (hooks must be called in
the same order every render). Move it before the conditional returns.

Fixes: Minified React error #310
2026-03-15 05:24:06 -07:00
Daniel Han
3c1b8d7ab7 studio: sort GGUF dropdown client-side -- recommended first, OOM last, rest by size descending
Move the sort logic from the backend to the frontend GgufVariantExpander
component where GPU VRAM info is available. The backend now does a simple
size-descending sort. The frontend pins the recommended variant at the
top, pushes OOM variants to the bottom, and sorts the rest by file size
descending (largest/best quality first).
2026-03-15 05:24:06 -07:00
Wasim Yousef Said
e280b0bebc
miscallenous studio (#4293)
* miscallenous studio

* chore: upload dataset misc

* chore: redudancy studio cleanup

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: adress the pr comments

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: adress comments about recipes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-03-15 14:42:11 +04:00
Roland Tannous
47654cb91c Final cleanup 2026-03-12 18:28:04 +00:00
Roland Tannous
a2baf80511 Update license headers 2026-03-12 17:23:10 +00:00
imagineer99
014695b38a fix: scope overflow-visible to studio collapsibles 2026-03-11 11:26:43 +00:00
imagineer99
984f4a4acb fix: input focus outline clipping 2026-03-11 11:11:57 +00:00