11import { useState } from "react"
2- import { ChevronDown } from "lucide-react"
3- import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
2+ import { ChevronDown , Check , X } from "lucide-react"
43import { useAppTranslation } from "@src/i18n/TranslationContext"
54import { cn } from "@src/lib/utils"
65
@@ -12,45 +11,120 @@ interface CommandPattern {
1211interface CommandPatternSelectorProps {
1312 patterns : CommandPattern [ ]
1413 allowedCommands : string [ ]
15- onPatternChange : ( pattern : string ) => void
14+ deniedCommands : string [ ]
15+ onAllowPatternChange : ( pattern : string ) => void
16+ onDenyPatternChange : ( pattern : string ) => void
1617}
1718
18- export const CommandPatternSelector = ( { patterns, allowedCommands, onPatternChange } : CommandPatternSelectorProps ) => {
19+ export const CommandPatternSelector = ( {
20+ patterns,
21+ allowedCommands,
22+ deniedCommands,
23+ onAllowPatternChange,
24+ onDenyPatternChange,
25+ } : CommandPatternSelectorProps ) => {
1926 const { t } = useAppTranslation ( )
2027 const [ isExpanded , setIsExpanded ] = useState ( false )
2128
2229 if ( patterns . length === 0 ) {
2330 return null
2431 }
2532
33+ const getPatternStatus = ( pattern : string ) : "allowed" | "denied" | "none" => {
34+ if ( allowedCommands . includes ( pattern ) ) return "allowed"
35+ if ( deniedCommands . includes ( pattern ) ) return "denied"
36+ return "none"
37+ }
38+
39+ const handleAllowClick = ( pattern : string ) => {
40+ const status = getPatternStatus ( pattern )
41+ if ( status === "denied" ) {
42+ // Remove from denied list first
43+ onDenyPatternChange ( pattern )
44+ }
45+ // Toggle allow status
46+ onAllowPatternChange ( pattern )
47+ }
48+
49+ const handleDenyClick = ( pattern : string ) => {
50+ const status = getPatternStatus ( pattern )
51+ if ( status === "allowed" ) {
52+ // Remove from allowed list first
53+ onAllowPatternChange ( pattern )
54+ }
55+ // Toggle deny status
56+ onDenyPatternChange ( pattern )
57+ }
58+
2659 return (
2760 < div className = "border-t border-vscode-panel-border bg-vscode-sideBar-background/30" >
2861 < button
2962 onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
3063 className = "flex items-center gap-2 w-full px-3 py-2 text-xs text-vscode-descriptionForeground hover:text-vscode-foreground hover:bg-vscode-list-hoverBackground transition-all"
31- aria-label = { isExpanded ? "Collapse allowed commands section" : "Expand allowed commands section" }
64+ aria-label = { isExpanded ? "Collapse command management section" : "Expand command management section" }
3265 aria-expanded = { isExpanded } >
3366 < ChevronDown
3467 className = { cn ( "size-3 transition-transform duration-200" , {
3568 "rotate-0" : isExpanded ,
3669 "-rotate-90" : ! isExpanded ,
3770 } ) }
3871 />
39- < span className = "font-medium" > { t ( "chat:commandExecution.addToAllowedCommands " ) } </ span >
72+ < span className = "font-medium" > { t ( "chat:commandExecution.manageCommands " ) } </ span >
4073 </ button >
4174 { isExpanded && (
42- < div className = "px-3 pb-3 space-y-1.5" >
43- { patterns . map ( ( item , index ) => (
44- < div key = { `${ item . pattern } -${ index } ` } className = "ml-5" >
45- < VSCodeCheckbox
46- checked = { allowedCommands . includes ( item . pattern ) }
47- onChange = { ( ) => onPatternChange ( item . pattern ) }
48- className = "text-xs"
49- aria-label = { `Allow command pattern: ${ item . pattern } ` } >
50- < span className = "font-mono text-vscode-foreground" > { item . pattern } </ span >
51- </ VSCodeCheckbox >
52- </ div >
53- ) ) }
75+ < div className = "px-3 pb-3 space-y-2" >
76+ < div className = "text-xs text-vscode-descriptionForeground mb-2 ml-5" >
77+ { t ( "chat:commandExecution.commandManagementDescription" ) }
78+ </ div >
79+ { patterns . map ( ( item , index ) => {
80+ const status = getPatternStatus ( item . pattern )
81+ return (
82+ < div key = { `${ item . pattern } -${ index } ` } className = "ml-5 flex items-center gap-2" >
83+ < div className = "flex-1" >
84+ < span className = "font-mono text-xs text-vscode-foreground" > { item . pattern } </ span >
85+ { item . description && (
86+ < span className = "text-xs text-vscode-descriptionForeground ml-2" >
87+ - { item . description }
88+ </ span >
89+ ) }
90+ </ div >
91+ < div className = "flex items-center gap-1" >
92+ < button
93+ onClick = { ( ) => handleAllowClick ( item . pattern ) }
94+ className = { cn (
95+ "p-1 rounded transition-all" ,
96+ status === "allowed"
97+ ? "bg-green-500/20 text-green-500 hover:bg-green-500/30"
98+ : "text-vscode-descriptionForeground hover:text-green-500 hover:bg-green-500/10" ,
99+ ) }
100+ aria-label = {
101+ status === "allowed"
102+ ? `Remove ${ item . pattern } from allowed list`
103+ : `Add ${ item . pattern } to allowed list`
104+ }
105+ title = { status === "allowed" ? "Remove from allowed" : "Add to allowed" } >
106+ < Check className = "size-3.5" />
107+ </ button >
108+ < button
109+ onClick = { ( ) => handleDenyClick ( item . pattern ) }
110+ className = { cn (
111+ "p-1 rounded transition-all" ,
112+ status === "denied"
113+ ? "bg-red-500/20 text-red-500 hover:bg-red-500/30"
114+ : "text-vscode-descriptionForeground hover:text-red-500 hover:bg-red-500/10" ,
115+ ) }
116+ aria-label = {
117+ status === "denied"
118+ ? `Remove ${ item . pattern } from denied list`
119+ : `Add ${ item . pattern } to denied list`
120+ }
121+ title = { status === "denied" ? "Remove from denied" : "Add to denied" } >
122+ < X className = "size-3.5" />
123+ </ button >
124+ </ div >
125+ </ div >
126+ )
127+ } ) }
54128 </ div >
55129 ) }
56130 </ div >
0 commit comments