mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-09 19:45:17 +02:00
style: refine code-mode permission and command block UX
- Render permission block before the command block - Collapse permission details after a response; click header to expand - Drop status icons/badge; use minimal green / bold red blocks - Auto-collapse the running command block once it completes
This commit is contained in:
parent
404f3813e2
commit
1b4ae14fd7
3 changed files with 24 additions and 37 deletions
|
|
@ -2263,7 +2263,7 @@ function App() {
|
|||
return next
|
||||
})
|
||||
|
||||
if (event.toolCallId && event.toolName !== 'executeCommand') {
|
||||
if (event.toolCallId) {
|
||||
setToolOpenForTab(activeChatTabIdRef.current, event.toolCallId, false)
|
||||
}
|
||||
|
||||
|
|
@ -5730,7 +5730,6 @@ function App() {
|
|||
const response = tabState.permissionResponses.get(item.id) || null
|
||||
return (
|
||||
<React.Fragment key={item.id}>
|
||||
{rendered}
|
||||
<PermissionRequest
|
||||
toolCall={permRequest.toolCall}
|
||||
permission={permRequest.permission}
|
||||
|
|
@ -5759,6 +5758,7 @@ function App() {
|
|||
isProcessing={isActive && isProcessing}
|
||||
response={response}
|
||||
/>
|
||||
{rendered}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import {
|
|||
DropdownMenuItem,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AlertTriangleIcon, CheckCircleIcon, CheckIcon, ChevronDownIcon, RefreshCwIcon, Terminal, XCircleIcon, XIcon } from "lucide-react";
|
||||
import type { ComponentProps } from "react";
|
||||
import { AlertTriangleIcon, CheckIcon, ChevronDownIcon, RefreshCwIcon, Terminal, XIcon } from "lucide-react";
|
||||
import { useState, type ComponentProps } from "react";
|
||||
import { ToolCallPart } from "@x/shared/dist/message.js";
|
||||
import { ToolPermissionMetadata } from "@x/shared/dist/runs.js";
|
||||
import z from "zod";
|
||||
|
|
@ -70,14 +70,19 @@ export const PermissionRequest = ({
|
|||
const isResponded = response !== null;
|
||||
const isApproved = response === 'approve';
|
||||
|
||||
// Once a response is chosen, collapse the details to just the header.
|
||||
// Users can click the header to expand them again.
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const showDetails = !isResponded || expanded;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"not-prose mb-4 w-full rounded-md border",
|
||||
isResponded
|
||||
? isApproved
|
||||
? "border-green-500/50 bg-green-50/50 dark:bg-green-950/20"
|
||||
: "border-red-500/50 bg-red-50/50 dark:bg-red-950/20"
|
||||
? "border-green-500/60 bg-green-200/80 dark:border-green-500/40 dark:bg-green-900/40"
|
||||
: "border-[#fa2525]/70 bg-[#fa2525]/30 dark:border-[#fa2525]/60 dark:bg-[#fa2525]/30"
|
||||
: "border-amber-500/50 bg-amber-50/50 dark:bg-amber-950/20",
|
||||
className
|
||||
)}
|
||||
|
|
@ -85,17 +90,14 @@ export const PermissionRequest = ({
|
|||
>
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="flex items-start gap-3">
|
||||
{isResponded ? (
|
||||
isApproved ? (
|
||||
<CheckCircleIcon className="size-5 text-green-600 dark:text-green-500 shrink-0 mt-0.5" />
|
||||
) : (
|
||||
<XCircleIcon className="size-5 text-red-600 dark:text-red-500 shrink-0 mt-0.5" />
|
||||
)
|
||||
) : (
|
||||
{!isResponded && (
|
||||
<AlertTriangleIcon className="size-5 text-amber-600 dark:text-amber-500 shrink-0 mt-0.5" />
|
||||
)}
|
||||
<div className="flex-1 space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={cn("flex items-center gap-2", isResponded && "cursor-pointer select-none")}
|
||||
onClick={isResponded ? () => setExpanded((v) => !v) : undefined}
|
||||
>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-sm text-foreground">
|
||||
{isResponded ? (isApproved ? "Permission Granted" : "Permission Denied") : "Permission Required"}
|
||||
|
|
@ -114,30 +116,15 @@ export const PermissionRequest = ({
|
|||
</p>
|
||||
</div>
|
||||
{isResponded && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"shrink-0",
|
||||
isApproved
|
||||
? "bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400"
|
||||
: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-400"
|
||||
"size-4 shrink-0 text-muted-foreground transition-transform",
|
||||
expanded ? "rotate-180" : "rotate-0"
|
||||
)}
|
||||
>
|
||||
{isApproved ? (
|
||||
<>
|
||||
<CheckIcon className="size-3 mr-1" />
|
||||
Approved
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<XIcon className="size-3 mr-1" />
|
||||
Denied
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{command && (
|
||||
{showDetails && command && (
|
||||
<div className="rounded-md border bg-background/50 p-3 mt-3">
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
Command
|
||||
|
|
@ -147,7 +134,7 @@ export const PermissionRequest = ({
|
|||
</pre>
|
||||
</div>
|
||||
)}
|
||||
{filePermission && (
|
||||
{showDetails && filePermission && (
|
||||
<div className="rounded-md border bg-background/50 p-3 mt-3 space-y-3">
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
|
|
@ -175,7 +162,7 @@ export const PermissionRequest = ({
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!command && !filePermission && toolCall.arguments && (
|
||||
{showDetails && !command && !filePermission && toolCall.arguments && (
|
||||
<div className="rounded-md border bg-background/50 p-3 mt-3">
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1.5 uppercase tracking-wide">
|
||||
Arguments
|
||||
|
|
|
|||
|
|
@ -608,7 +608,6 @@ export function ChatSidebar({
|
|||
const response = tabState.permissionResponses.get(item.id) || null
|
||||
return (
|
||||
<React.Fragment key={item.id}>
|
||||
{rendered}
|
||||
<PermissionRequest
|
||||
toolCall={permRequest.toolCall}
|
||||
onApprove={() => onPermissionResponse(permRequest.toolCall.toolCallId, permRequest.subflow, 'approve')}
|
||||
|
|
@ -618,6 +617,7 @@ export function ChatSidebar({
|
|||
isProcessing={isActive && isProcessing}
|
||||
response={response}
|
||||
/>
|
||||
{rendered}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue