Studio: polish Bypass permissions toggle and add it to chat menu settings (#6442)
* Studio: polish Bypass permissions toggle and add it to chat menu settings - Recolor the pill, menu item and settings caption from alarm red to a bright yellow accent via a new --bypass token (light and dark). - Use the shield icon on the composer pill (X on hover, like other pills). - Expand the composer to two rows the moment Bypass permissions is on. - Keep Bypass leftmost among tool pills; Compare still sits ahead of it. - Add a Bypass permissions row to Settings > Chat menu so it can be pinned. - Widen the "More" submenu so the label fits on one line. * Studio: use shield-ban icon and lighten the Bypass permissions pill - Swap the lucide shield-off icon for the Hugeicons shield-ban across the pill, menu item and settings row. - Lighten the pill background and deepen the yellow text a touch so the label stays readable on a near-white tint. * Studio: drop the Bypass permissions resting fill, keep it on hover Show only the yellow icon and label at rest; the rounded hover pill picks up the yellow accent like the other toggles. * Studio: lighten the Bypass permissions hover fill Drop the hover tint so the yellow label stays readable on hover. --------- Co-authored-by: Unsloth <michaelhan@Michaels-MacBook-Pro.local>
This commit is contained in:
parent
0d81a6055c
commit
ecbb64b492
6 changed files with 82 additions and 36 deletions
|
|
@ -125,6 +125,7 @@ import {
|
|||
Image03Icon,
|
||||
McpServerIcon,
|
||||
PencilRulerIcon,
|
||||
ShieldBanIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
|
|
@ -1247,6 +1248,7 @@ const Composer: FC<{
|
|||
const artifactsEnabled = useChatRuntimeStore((s) => s.artifactsEnabled);
|
||||
const mcpEnabledForChat = useChatRuntimeStore((s) => s.mcpEnabledForChat);
|
||||
const ragEnabled = useChatRuntimeStore((s) => s.ragEnabled);
|
||||
const bypassPermissions = useChatRuntimeStore((s) => s.bypassPermissions);
|
||||
// More than 4 pills: collapse to icons only. Search and Code always show;
|
||||
// Images, RAG, Canvas and MCP are conditional.
|
||||
const pillsCompact =
|
||||
|
|
@ -1368,6 +1370,8 @@ const Composer: FC<{
|
|||
}, [composerText, draftKey]);
|
||||
// Two-row layout shows once the input wraps or a tool is on. Tools can
|
||||
// pre-select before a model loads, so an active toggle expands it either way.
|
||||
// Bypass permissions counts too: turning it on should drop the composer into
|
||||
// the two-row layout immediately, same as Search/Code.
|
||||
const composerExpanded =
|
||||
isMultiline ||
|
||||
hasAttachments ||
|
||||
|
|
@ -1377,7 +1381,8 @@ const Composer: FC<{
|
|||
imageToolsEnabled ||
|
||||
ragEnabled ||
|
||||
artifactsEnabled ||
|
||||
mcpEnabledForChat;
|
||||
mcpEnabledForChat ||
|
||||
bypassPermissions;
|
||||
// react-textarea-autosize re-measures only on value change or window resize,
|
||||
// not on the width swap from expanding, so it keeps the taller height and
|
||||
// leaves a stray blank row. Nudge a resize whenever input width changes.
|
||||
|
|
@ -2430,7 +2435,7 @@ const ArtifactsToggle: FC = () => {
|
|||
);
|
||||
};
|
||||
|
||||
// Red pill shown while Bypass Permissions is on; click to turn it off.
|
||||
// Claude gold pill shown while Bypass permissions is on; click to turn it off.
|
||||
// Mirror of shared-composer's badge so both composers surface the state.
|
||||
const BypassPermissionsToggle: FC = () => {
|
||||
const bypassPermissions = useChatRuntimeStore((s) => s.bypassPermissions);
|
||||
|
|
@ -2445,11 +2450,17 @@ const BypassPermissionsToggle: FC = () => {
|
|||
className="composer-pill-btn"
|
||||
data-active="true"
|
||||
data-variant="danger"
|
||||
aria-label="Disable Bypass Permissions"
|
||||
title="Bypass Permissions is on (no confirmation, no sandbox). Click to turn off."
|
||||
aria-label="Disable Bypass permissions"
|
||||
title="Bypass permissions is on (no confirmation, no sandbox). Click to turn off."
|
||||
>
|
||||
<XIcon className="size-3" />
|
||||
<span>Bypass Permissions</span>
|
||||
<PillGlyph>
|
||||
<HugeiconsIcon
|
||||
icon={ShieldBanIcon}
|
||||
strokeWidth={2}
|
||||
className="size-[15px]"
|
||||
/>
|
||||
</PillGlyph>
|
||||
<span>Bypass permissions</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
@ -2959,7 +2970,7 @@ const ComposerToolsMenu: FC<{ side?: "top" | "bottom" }> = ({
|
|||
<MoreHorizontalIcon className="size-4" />
|
||||
More
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="unsloth-plus-menu w-[232px]">
|
||||
<DropdownMenuSubContent className="unsloth-plus-menu w-[248px]">
|
||||
{overflowPlusItems.map((id) => (
|
||||
<Fragment key={id}>{plusMenuNodes[id]}</Fragment>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
||||
|
||||
import { ShieldOffIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { ShieldBanIcon } from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
||||
import {
|
||||
|
|
@ -35,7 +35,7 @@ export function BypassPermissionsMenuItem() {
|
|||
<>
|
||||
<DropdownMenuItem
|
||||
className={
|
||||
bypassPermissions ? "text-destructive font-medium" : undefined
|
||||
bypassPermissions ? "text-bypass font-medium" : undefined
|
||||
}
|
||||
onSelect={(e) => {
|
||||
if (bypassPermissions) {
|
||||
|
|
@ -46,8 +46,8 @@ export function BypassPermissionsMenuItem() {
|
|||
}
|
||||
}}
|
||||
>
|
||||
<ShieldOffIcon />
|
||||
Bypass Permissions
|
||||
<HugeiconsIcon icon={ShieldBanIcon} strokeWidth={2} />
|
||||
Bypass permissions
|
||||
{bypassPermissions ? (
|
||||
<HugeiconsIcon icon={Tick02Icon} strokeWidth={2} className="ml-auto" />
|
||||
) : null}
|
||||
|
|
@ -55,9 +55,9 @@ export function BypassPermissionsMenuItem() {
|
|||
<AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Enable Bypass Permissions?</AlertDialogTitle>
|
||||
<AlertDialogTitle>Enable Bypass permissions?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Bypass Permissions is dangerous since the AI model might delete,
|
||||
Bypass permissions is dangerous since the AI model might delete,
|
||||
corrupt your machine, and or cause real world damage to you or the
|
||||
world - only accept if you are certain
|
||||
</AlertDialogDescription>
|
||||
|
|
|
|||
|
|
@ -1762,7 +1762,7 @@ function ConfirmToolCallsToggle() {
|
|||
</div>
|
||||
{bypassPermissions ? (
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
Overridden by Bypass Permissions
|
||||
Overridden by Bypass permissions
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -1788,7 +1788,7 @@ function BypassPermissionsToggle() {
|
|||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="min-w-0 text-[13px] font-medium leading-[1.25] tracking-nav text-nav-fg">
|
||||
Bypass Permissions
|
||||
Bypass permissions
|
||||
</span>
|
||||
<InfoHint>
|
||||
Dangerous. Runs every tool call with no confirmation and disables
|
||||
|
|
@ -1806,16 +1806,16 @@ function BypassPermissionsToggle() {
|
|||
/>
|
||||
</div>
|
||||
{bypassPermissions ? (
|
||||
<span className="text-[11px] text-destructive">
|
||||
<span className="text-[11px] text-bypass">
|
||||
Tool calls run with no confirmation and no sandbox.
|
||||
</span>
|
||||
) : null}
|
||||
<AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<AlertDialogContent size="sm">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Enable Bypass Permissions?</AlertDialogTitle>
|
||||
<AlertDialogTitle>Enable Bypass permissions?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Bypass Permissions is dangerous since the AI model might delete,
|
||||
Bypass permissions is dangerous since the AI model might delete,
|
||||
corrupt your machine, and or cause real world damage to you or the
|
||||
world - only accept if you are certain
|
||||
</AlertDialogDescription>
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
Image03Icon,
|
||||
McpServerIcon,
|
||||
PencilRulerIcon,
|
||||
ShieldBanIcon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { useNavigate } from "@tanstack/react-router";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
|
@ -1593,7 +1594,7 @@ export function SharedComposer({
|
|||
<MoreHorizontalIcon className="size-4" />
|
||||
More
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent className="unsloth-plus-menu w-[232px]">
|
||||
<DropdownMenuSubContent className="unsloth-plus-menu w-[248px]">
|
||||
{overflowPlusItems.map((id) => (
|
||||
<Fragment key={id}>{plusMenuNodes[id]}</Fragment>
|
||||
))}
|
||||
|
|
@ -1616,6 +1617,29 @@ export function SharedComposer({
|
|||
</PillGlyph>
|
||||
<span>Compare</span>
|
||||
</button>
|
||||
{/* Bypass sits immediately after Compare and ahead of every other
|
||||
tool pill (Search, Code, ...) so the active danger state reads
|
||||
first; only Compare outranks it. */}
|
||||
{bypassPermissions && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBypassPermissions(false)}
|
||||
className="composer-pill-btn"
|
||||
data-active="true"
|
||||
data-variant="danger"
|
||||
aria-label="Disable Bypass permissions"
|
||||
title="Bypass permissions is on (no confirmation, no sandbox). Click to turn off."
|
||||
>
|
||||
<PillGlyph>
|
||||
<HugeiconsIcon
|
||||
icon={ShieldBanIcon}
|
||||
strokeWidth={2}
|
||||
className="size-[15px]"
|
||||
/>
|
||||
</PillGlyph>
|
||||
<span>Bypass permissions</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
disabled={searchDisabled}
|
||||
|
|
@ -1731,20 +1755,6 @@ export function SharedComposer({
|
|||
</button>
|
||||
) : null}
|
||||
{mcpEnabledForChat ? <McpComposerButton side="top" /> : null}
|
||||
{bypassPermissions && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBypassPermissions(false)}
|
||||
className="composer-pill-btn"
|
||||
data-active="true"
|
||||
data-variant="danger"
|
||||
aria-label="Disable Bypass Permissions"
|
||||
title="Bypass Permissions is on (no confirmation, no sandbox). Click to turn off."
|
||||
>
|
||||
<XIcon className="size-3" />
|
||||
<span>Bypass Permissions</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{/* mr-0.5 matches the send button inset from the edge in normal chat;
|
||||
gap-1.5 matches its control spacing. */}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import {
|
|||
Folder01Icon,
|
||||
McpServerIcon,
|
||||
PencilRulerIcon,
|
||||
ShieldBanIcon,
|
||||
Upload01Icon,
|
||||
} from "@hugeicons/core-free-icons";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
|
|
@ -130,6 +131,17 @@ const PLUS_MENU_SETTINGS: { id: PlusMenuItemId; label: string; icon: ReactNode }
|
|||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "bypassPermissions",
|
||||
label: "Bypass permissions",
|
||||
icon: (
|
||||
<HugeiconsIcon
|
||||
icon={ShieldBanIcon}
|
||||
strokeWidth={2}
|
||||
className={PLUS_MENU_ICON_CLASS}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export function ChatTab() {
|
||||
|
|
|
|||
|
|
@ -129,6 +129,8 @@
|
|||
--accent: oklch(0.9596 0.0275 167.8295);
|
||||
--accent-foreground: oklch(0.2868 0.0649 159.9823);
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
/* Bypass permissions accent: bright, saturated yellow (not dark gold). */
|
||||
--bypass: #c99d00;
|
||||
--border: oklch(0.9208 0.0101 164.8536);
|
||||
--input: oklch(0.9208 0.0101 164.8536);
|
||||
--ring: #17b88b;
|
||||
|
|
@ -251,6 +253,8 @@
|
|||
--accent: #3a3d43;
|
||||
--accent-foreground: #ececee;
|
||||
--destructive: oklch(0.6368 0.2078 25.3313);
|
||||
/* Bypass permissions accent: bright, saturated neon yellow on dark. */
|
||||
--bypass: #ffd60a;
|
||||
/* --border / --input one step lighter than --muted so outlines and form
|
||||
borders stay visible on muted surfaces (right config panel, export
|
||||
tiles, quant chips) and keep subtle contrast on card. */
|
||||
|
|
@ -344,6 +348,7 @@
|
|||
--color-input: var(--input);
|
||||
--color-border: var(--border);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-bypass: var(--bypass);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
|
|
@ -1047,10 +1052,18 @@
|
|||
.composer-pill-btn[data-active="true"] {
|
||||
color: var(--primary);
|
||||
}
|
||||
/* Bypass Permissions badge: red, always-visible active warning. */
|
||||
/* Bypass permissions badge: bright yellow text, no resting fill; the
|
||||
rounded hover pill picks up the yellow accent like other toggles. */
|
||||
.composer-pill-btn[data-variant="danger"] {
|
||||
@apply bg-destructive/10 hover:bg-destructive/15;
|
||||
color: var(--destructive);
|
||||
background-color: transparent;
|
||||
color: var(--bypass);
|
||||
}
|
||||
.composer-pill-btn[data-variant="danger"]:hover {
|
||||
background-color: color-mix(in oklab, var(--bypass) 7%, transparent);
|
||||
}
|
||||
/* Match the hover-X circle to the yellow accent instead of the green primary. */
|
||||
.composer-pill-btn[data-variant="danger"] .composer-pill-x {
|
||||
background-color: color-mix(in oklab, var(--bypass) 13%, transparent);
|
||||
}
|
||||
|
||||
/* With more than 4 tools on, drop pill labels to icons only to cut clutter.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue