Sketch out approval UX
This commit is contained in:
@@ -1,4 +1,14 @@
|
||||
import { SendIcon, StopCircleIcon, X } from "lucide-react";
|
||||
import {
|
||||
SendIcon,
|
||||
StopCircleIcon,
|
||||
X,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
AlertTriangle,
|
||||
AlertOctagon,
|
||||
FileText,
|
||||
Check,
|
||||
} from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ModelPicker } from "@/components/ModelPicker";
|
||||
@@ -10,6 +20,9 @@ import { useStreamChat } from "@/hooks/useStreamChat";
|
||||
import { useChats } from "@/hooks/useChats";
|
||||
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
||||
import { useLoadApp } from "@/hooks/useLoadApp";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
|
||||
interface ChatInputProps {
|
||||
chatId?: number;
|
||||
@@ -94,6 +107,7 @@ export function ChatInput({ chatId, onSubmit }: ChatInputProps) {
|
||||
)}
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col space-y-2 border border-border rounded-lg bg-(--background-lighter) shadow-sm">
|
||||
<ChatInputActions />
|
||||
<div className="flex items-start space-x-2 ">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
@@ -136,3 +150,155 @@ export function ChatInput({ chatId, onSubmit }: ChatInputProps) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatInputActions() {
|
||||
const [autoApprove, setAutoApprove] = useState(false);
|
||||
const [isDetailsVisible, setIsDetailsVisible] = useState(false);
|
||||
|
||||
const handleApprove = () => {
|
||||
console.log("Approve clicked");
|
||||
// Add approve logic here
|
||||
};
|
||||
|
||||
const handleReject = () => {
|
||||
console.log("Reject clicked");
|
||||
// Add reject logic here
|
||||
};
|
||||
|
||||
// Placeholder data
|
||||
const securityRisks = [
|
||||
{
|
||||
type: "warning",
|
||||
title: "Potential XSS Vulnerability",
|
||||
description: "User input is directly rendered without sanitization.",
|
||||
},
|
||||
{
|
||||
type: "danger",
|
||||
title: "Hardcoded API Key",
|
||||
description: "API key found in plain text in configuration file.",
|
||||
},
|
||||
];
|
||||
|
||||
const filesChanged = [
|
||||
{
|
||||
name: "ChatInput.tsx",
|
||||
path: "src/components/chat/ChatInput.tsx",
|
||||
summary: "Added review actions and details section.",
|
||||
},
|
||||
{
|
||||
name: "api.ts",
|
||||
path: "src/lib/api.ts",
|
||||
summary: "Refactored API call structure.",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="border-b border-border">
|
||||
<div className="p-2">
|
||||
{/* Row 1: Title, Expand Icon, and Security Chip */}
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<button
|
||||
className="flex items-center text-left text-sm font-medium hover:bg-muted p-1 rounded justify-start"
|
||||
onClick={() => setIsDetailsVisible(!isDetailsVisible)}
|
||||
>
|
||||
{isDetailsVisible ? (
|
||||
<ChevronUp size={16} className="mr-1" />
|
||||
) : (
|
||||
<ChevronDown size={16} className="mr-1" />
|
||||
)}
|
||||
Review: foo bar changes
|
||||
</button>
|
||||
{securityRisks.length > 0 && (
|
||||
<span className="bg-red-100 text-red-700 text-xs font-medium px-2 py-0.5 rounded-full">
|
||||
Security risks found
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Row 2: Buttons and Toggle */}
|
||||
<div className="flex items-center justify-start space-x-2">
|
||||
<Button
|
||||
className="px-8"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleApprove}
|
||||
>
|
||||
<Check size={16} className="mr-1" />
|
||||
Approve
|
||||
</Button>
|
||||
<Button
|
||||
className="px-8"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleReject}
|
||||
>
|
||||
<X size={16} className="mr-1" />
|
||||
Reject
|
||||
</Button>
|
||||
<div className="flex items-center space-x-1 ml-auto">
|
||||
{/* Basic HTML checkbox styled to look like a toggle */}
|
||||
<input
|
||||
type="checkbox"
|
||||
id="auto-approve"
|
||||
checked={autoApprove}
|
||||
onChange={(e) => setAutoApprove(e.target.checked)}
|
||||
className="relative peer shrink-0 appearance-none w-8 h-4 border border-input rounded-full bg-input checked:bg-primary cursor-pointer after:absolute after:w-3 after:h-3 after:top-[1px] after:left-[2px] after:bg-background after:rounded-full after:transition-all checked:after:translate-x-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<label htmlFor="auto-approve" className="text-xs cursor-pointer">
|
||||
Auto-approve
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDetailsVisible && (
|
||||
<div className="p-3 border-t border-border bg-muted/50 text-sm">
|
||||
<div className="mb-3">
|
||||
<h4 className="font-semibold mb-1">Security Risks</h4>
|
||||
<ul className="space-y-1">
|
||||
{securityRisks.map((risk, index) => (
|
||||
<li key={index} className="flex items-start space-x-2">
|
||||
{risk.type === "warning" ? (
|
||||
<AlertTriangle
|
||||
size={16}
|
||||
className="text-yellow-500 mt-0.5 flex-shrink-0"
|
||||
/>
|
||||
) : (
|
||||
<AlertOctagon
|
||||
size={16}
|
||||
className="text-red-500 mt-0.5 flex-shrink-0"
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<span className="font-medium">{risk.title}:</span>{" "}
|
||||
<span>{risk.description}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4 className="font-semibold mb-1">Files Changed</h4>
|
||||
<ul className="space-y-1">
|
||||
{filesChanged.map((file, index) => (
|
||||
<li key={index} className="flex items-center space-x-2">
|
||||
<FileText
|
||||
size={16}
|
||||
className="text-muted-foreground flex-shrink-0"
|
||||
/>
|
||||
<span title={file.path} className="truncate cursor-default">
|
||||
{file.name}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs truncate">
|
||||
- {file.summary}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
22
src/components/ui/label.tsx
Normal file
22
src/components/ui/label.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Label({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
return (
|
||||
<LabelPrimitive.Root
|
||||
data-slot="label"
|
||||
className={cn(
|
||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Label }
|
||||
29
src/components/ui/switch.tsx
Normal file
29
src/components/ui/switch.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Switch({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||
return (
|
||||
<SwitchPrimitive.Root
|
||||
data-slot="switch"
|
||||
className={cn(
|
||||
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
data-slot="switch-thumb"
|
||||
className={cn(
|
||||
"bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
||||
)}
|
||||
/>
|
||||
</SwitchPrimitive.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export { Switch }
|
||||
Reference in New Issue
Block a user