diff --git a/README.md b/README.md index e0fc8ee44c..849958bfbb 100644 --- a/README.md +++ b/README.md @@ -84,9 +84,8 @@ Replace `claude` with any supported agent: | Hermes Agent | `unsloth start hermes` | | OpenClaw | `unsloth start openclaw` | | OpenCode | `unsloth start opencode` | -| Pi Coding Agent | `unsloth start pi` | -Claude Code, Codex, OpenCode and Pi can keep their current model and use Unsloth as a local +Claude Code, Codex and OpenCode can keep their current model and use Unsloth as a local subagent: ```bash @@ -204,7 +203,7 @@ Read our [guide](https://unsloth.ai/docs/get-started/fine-tuning-llms-guide). Ad ## 🦥 Unsloth News - **AMD training**: Train, run RL, chat and deploy on AMD GPUs across Windows, WSL and Linux. [Guide](https://unsloth.ai/docs/basics/amd) - **GGUF hardware controls**: Choose GPU/layer placement, offload MoE experts and use multi-GPU or Tensor Parallelism. [#6414](https://github.com/unslothai/unsloth/pull/6414) -- **Local models for any agent**: Use `unsloth start` with Claude Code, Codex, Hermes, OpenCode, OpenClaw, Pi and more through Unsloth's OpenAI- and Anthropic-compatible APIs. [Guide](https://unsloth.ai/docs/basics/api) +- **Local models for any agent**: Use `unsloth start` with Claude Code, Codex, Hermes, OpenCode, OpenClaw and more through Unsloth's OpenAI- and Anthropic-compatible APIs. [Guide](https://unsloth.ai/docs/basics/api) - **MCP control endpoint**: Let compatible clients manage models, training, recipes, checkpoints and exports. [#7191](https://github.com/unslothai/unsloth/pull/7191) - **Local inference reliability**: Resume long chats faster, recover stalled downloads and reuse existing GGUF files. [#7204](https://github.com/unslothai/unsloth/pull/7204) • [#6858](https://github.com/unslothai/unsloth/pull/6858) • [#7209](https://github.com/unslothai/unsloth/pull/7209) - **New models**: [Qwen-AgentWorld](https://huggingface.co/unsloth/Qwen-AgentWorld-35B-A3B-GGUF), [Ornith](https://huggingface.co/unsloth/models?search=ornith), [Kimi K2.7 Code](https://unsloth.ai/docs/models/kimi-k2.7-code) and [MiniMax M3](https://unsloth.ai/docs/models/minimax-m3) diff --git a/studio/frontend/public/agent-logos/pi.svg b/studio/frontend/public/agent-logos/pi.svg deleted file mode 100644 index 3f8a77bd1a..0000000000 --- a/studio/frontend/public/agent-logos/pi.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - diff --git a/studio/frontend/src/features/chat/chat-providers-dialog.tsx b/studio/frontend/src/features/chat/chat-providers-dialog.tsx index 4ab560e5ad..7e0cebea4b 100644 --- a/studio/frontend/src/features/chat/chat-providers-dialog.tsx +++ b/studio/frontend/src/features/chat/chat-providers-dialog.tsx @@ -1454,7 +1454,7 @@ export function ChatProvidersSettings({
-
+
+ + {copied ? t("settings.agents.copied") : ""} + +
+ ); +} + function CommandBlock({ command }: { command: string }) { const t = useT(); const { copied, copy } = useCopyButton(command); return ( -
-
+    
+
         {command}
       
-
- - {command} - +
+ + {t("settings.agents.subagent.setupCommand")} + +
-
-
- - {t("settings.agents.subagent.usagePrompt", { agent: agent.name })} - - -
- - {prompt} - +
+ + {t("settings.agents.subagent.usagePrompt", { agent: agent.name })} + +
); @@ -665,7 +670,6 @@ export function AgentsTab() { const [modelSearch, setModelSearch] = useState(""); const [modelPickerOpen, setModelPickerOpen] = useState(false); const [variants, setVariants] = useState([]); - const [defaultVariant, setDefaultVariant] = useState(null); const [selectedVariant, setSelectedVariant] = useState( EXAMPLE_MODEL_VARIANT, ); @@ -986,7 +990,6 @@ export function AgentsTab() { return; } setVariants([]); - setDefaultVariant(null); setSelectedVariant(standaloneFile ? null : preferredVariant); setVariantsFailed(false); setVariantsLoading(false); @@ -999,7 +1002,6 @@ export function AgentsTab() { // A programmatic model change reaches here too, so clear the previous model's // quants up front rather than leaving them selectable until this resolves. setVariants([]); - setDefaultVariant(null); setVariantsLoading(true); // Offer the quants from the same place the command loads from, not remote-only ones. listGgufVariants(selectedModel, hfToken || undefined, { @@ -1023,7 +1025,6 @@ export function AgentsTab() { ).values(), ); setVariants(uniqueVariants); - setDefaultVariant(info.default_variant); const available = new Set( uniqueVariants.map((variant) => variant.quant), ); @@ -1045,7 +1046,6 @@ export function AgentsTab() { } setVariantsFailed(true); setVariants([]); - setDefaultVariant(null); setSelectedVariant(preferredVariant); if (preferredVariant) { setVariants([ @@ -1073,7 +1073,7 @@ export function AgentsTab() { // picker only ever offers GGUF models. return ( -
+
{/* data-settings-label lets indexed settings search scroll to these. */}

- + {/* The chip is the docs entry point, so no separate link is needed. */} + unsloth start - {" "} + {" "} {t("settings.agents.intro")}

- - - {t("settings.agents.readDocs")} - - -
-
-
- +
+
+ + {t("settings.agents.agent")} + + + {t("settings.agents.docs")} + + +
+ { - agentSelectionChanged.current = true; - setSelectedAgent(agent); - resetCopied(); - }} - > - - - - - {selectedAgentDetails.name} - - - - - {agents.map((agentId) => { - const agent = detailsFor(agentId); - return ( - - - - {agent.name} - {localDetection && - loaded && - detectedAgents.has(agent.id) ? ( - - {t("settings.agents.quickstart.installed")} - - ) : null} + + + + + + {selectedAgentDetails.name} - - ); - })} - - -
+
+ + + + {agents.map((agentId) => { + const agent = detailsFor(agentId); + return ( + + + + {agent.name} + {localDetection && + loaded && + detectedAgents.has(agent.id) ? ( + + {t("settings.agents.quickstart.installed")} + + ) : null} + + + ); + })} + + +
-
- + {variants.map((variant) => { - const metadata = [ - variant.quant === defaultVariant - ? t("settings.agents.recommended") - : null, - variant.downloaded ? t("settings.agents.downloaded") : null, - formatBytes( - variant.download_size_bytes ?? variant.size_bytes, - ), - ].filter(Boolean); + // Size only: the recommended/downloaded tags wrapped every + // row onto two lines and made the list hard to scan. + const size = formatBytes( + variant.download_size_bytes ?? variant.size_bytes, + ); return ( - - {variant.quant} - {metadata.length > 0 ? ( - - {metadata.join(" · ")} + + + {variant.quant} + + {size ? ( + + {size} ) : null} @@ -1346,27 +1348,16 @@ export function AgentsTab() {

) : null} -
-
- - {t("settings.agents.generatedCommand")} - - -
- - {command} - +
+ + {t("settings.agents.generatedCommand")} + +
-
+
@@ -1415,7 +1406,7 @@ export function AgentsTab() { title={t("settings.agents.passthrough.title")} description={t("settings.agents.passthrough.description")} > -
+
{PASSTHROUGH_EXAMPLES.map(({ agent, flags }) => ( ))} @@ -1426,7 +1417,7 @@ export function AgentsTab() { title={t("settings.agents.dryRun.title")} description={t("settings.agents.dryRun.description")} > -
+
diff --git a/studio/frontend/src/features/settings/tabs/resources-tab.tsx b/studio/frontend/src/features/settings/tabs/resources-tab.tsx index f45ddeba7c..298d159cf9 100644 --- a/studio/frontend/src/features/settings/tabs/resources-tab.tsx +++ b/studio/frontend/src/features/settings/tabs/resources-tab.tsx @@ -116,7 +116,7 @@ function MetricTile({ const percentKnown = isFiniteNumber(percent); const safePercent = clampPercent(percent); return ( -
+
{label} @@ -141,7 +141,7 @@ function MetricTile({
@@ -483,54 +483,46 @@ export function ResourcesTab() { ? formatPercent(safePercent) : unknownLabel; return ( + // One row per device: a full-width bar under a single GPU read + // as a page-wide rule, so the bar is sized to the row instead.
-
-
-
- {device.name ?? - t("settings.resources.gpu.unknownDevice")} -
-
- {ordinal === undefined - ? backendLabel - : `${t("settings.resources.gpu.deviceWithIndex", { - index: ordinal, - })}, ${backendLabel}`} -
+
+
+ {device.name ?? t("settings.resources.gpu.unknownDevice")}
-
- - {percentText}{" "} - {t("settings.resources.gpu.vramUtilization")} +
+ {ordinal === undefined + ? backendLabel + : `${t("settings.resources.gpu.deviceWithIndex", { + index: ordinal, + })}, ${backendLabel}`} +
+
+
+
+ + {t("settings.resources.gpu.used", { value: usedText })} + + + {t("settings.resources.gpu.free", { value: freeText })} + + + {t("settings.resources.gpu.total", { value: totalText })}
+ +
+ {percentText} {t("settings.resources.gpu.vramUtilization")} +
-
- - {t("settings.resources.gpu.used", { - value: usedText, - })} - - - {t("settings.resources.gpu.free", { - value: freeText, - })} - - - {t("settings.resources.gpu.total", { - value: totalText, - })} - -
-
); }) diff --git a/studio/frontend/src/i18n/locales/en.ts b/studio/frontend/src/i18n/locales/en.ts index bdfcf38231..2b5bf7f508 100644 --- a/studio/frontend/src/i18n/locales/en.ts +++ b/studio/frontend/src/i18n/locales/en.ts @@ -589,9 +589,9 @@ export const en = { agents: { title: "Agents", description: - "Connect coding agents like Claude Code and Codex to a model running locally in Unsloth with unsloth start.", + "Connect coding agents like Claude Code and Codex to a local model with unsloth start.", intro: - "connects Claude Code, Codex, Hermes, OpenClaw, OpenCode, Pi and other agents to a model served locally by Unsloth, fully offline on your own hardware. It runs an OpenAI-compatible server for the agent and never touches your agent's config files.", + "connects Claude Code, Codex, Hermes, OpenClaw, OpenCode and other agents to a model served locally by Unsloth, fully offline. It runs an OpenAI-compatible server and never touches your agent's config files.", readDocs: "Read the docs", copy: "Copy", copied: "Copied", @@ -660,7 +660,7 @@ export const en = { launch: "Launch the agent, or just print the command and environment.", persist: "Keep Unsloth-managed agent storage between runs.", asSubagent: - "Keep the parent on its current model and register Unsloth as a local subagent (Claude Code, Codex, OpenCode, and Pi).", + "Keep the parent on its current model and register Unsloth as a local subagent (Claude Code, Codex, and OpenCode).", apiKey: "Provide your Unsloth API key (or set UNSLOTH_API_KEY).", yolo: "Skip approval prompts. Use only in trusted environments.", }, diff --git a/studio/frontend/src/index.css b/studio/frontend/src/index.css index 4797401ead..34897c08bc 100644 --- a/studio/frontend/src/index.css +++ b/studio/frontend/src/index.css @@ -664,6 +664,13 @@ html[data-chat-font] .aui-root { in the same style pass that swaps the color tokens. Driving it from React state paints one frame late while the whole page recalculates, which briefly leaves the ring on the previous card. */ + +/* Dark mode drops the resting border. Same specificity as, and before, the + hover/selected rules below so those still paint a visible border. */ +.dark .palette-card { + border-color: transparent; +} + .palette-card:hover { border-color: var(--ring-soft); }