11import { Command } from "cmdk" ;
2- import { useState , useEffect } from "react" ;
2+ import { useState , useEffect , useMemo , useCallback } from "react" ;
33import "./commands.css" ;
44import { useCharmManager } from "@/contexts/CharmManagerContext" ;
55import { useMatch , useNavigate } from "react-router-dom" ;
66import { VisuallyHidden } from "@radix-ui/react-visually-hidden" ;
77import { DialogDescription , DialogTitle } from "@radix-ui/react-dialog" ;
88import { DitheredCube } from "./DitherCube" ;
9- import { CommandContext , CommandItem , CommandMode , commands , getTitle } from "./commands" ;
9+ import { CommandContext , CommandItem , CommandMode , commands , getChildren , getCommands , getTitle } from "./commands" ;
1010import { usePreferredLanguageModel } from "@/contexts/LanguageModelContext" ;
1111import { TranscribeInput } from "./TranscribeCommand" ;
12+ import { useBackgroundTasks } from "@/contexts/BackgroundTaskContext" ;
1213
1314function CommandProcessor ( {
1415 mode,
@@ -60,7 +61,7 @@ function CommandProcessor({
6061 { mode . options . map ( ( option ) => (
6162 < Command . Item
6263 key = { option . id }
63- onSelect = { ( ) => mode . command . handler ?.( context , option . value ) } // Use mode.command instead
64+ onSelect = { ( ) => mode . command . handler ?.( context , option . value ) }
6465 >
6566 { option . title }
6667 </ Command . Item >
@@ -77,16 +78,76 @@ export function CommandCenter() {
7778 const [ open , setOpen ] = useState ( false ) ;
7879 const [ loading , setLoading ] = useState ( false ) ;
7980 const [ mode , setMode ] = useState < CommandMode > ( { type : "main" } ) ;
80- const [ commandPath , setCommandPath ] = useState < CommandItem [ ] > ( [ ] ) ;
81+ const [ commandPathIds , setCommandPathIds ] = useState < string [ ] > ( [ ] ) ;
8182 const [ search , setSearch ] = useState ( "" ) ;
8283 const { modelId, setPreferredModel } = usePreferredLanguageModel ( ) ;
84+ const { stopJob, startJob, addJobMessage, listJobs, updateJobProgress } = useBackgroundTasks ( ) ;
8385
8486 const { charmManager } = useCharmManager ( ) ;
8587 const navigate = useNavigate ( ) ;
8688 const match = useMatch ( "/:replicaName/:charmId?/*" ) ;
8789 const focusedCharmId = match ?. params . charmId ?? null ;
8890 const focusedReplicaId = match ?. params . replicaName ?? null ;
8991
92+ const allCommands = useMemo ( ( ) => getCommands ( {
93+ charmManager,
94+ navigate,
95+ focusedCharmId,
96+ focusedReplicaId,
97+ setOpen,
98+ preferredModel : modelId ?? undefined ,
99+ setPreferredModel,
100+ setMode,
101+ loading,
102+ setLoading,
103+ setModeWithInput : ( mode : CommandMode , initialInput : string ) => {
104+ Promise . resolve ( ) . then ( ( ) => {
105+ setMode ( mode ) ;
106+ setSearch ( initialInput ) ;
107+ } ) ;
108+ } ,
109+ stopJob,
110+ startJob,
111+ addJobMessage,
112+ listJobs,
113+ updateJobProgress,
114+ commandPathIds,
115+ } ) , [
116+ charmManager ,
117+ navigate ,
118+ focusedCharmId ,
119+ focusedReplicaId ,
120+ modelId ,
121+ loading ,
122+ commandPathIds ,
123+ setMode ,
124+ setPreferredModel ,
125+ stopJob ,
126+ startJob ,
127+ addJobMessage ,
128+ listJobs ,
129+ updateJobProgress ,
130+ ] ) ;
131+
132+ const getCommandById = useCallback ( ( id : string ) : CommandItem | undefined => {
133+ const findInCommands = ( commands : CommandItem [ ] ) : CommandItem | undefined => {
134+ for ( const cmd of commands ) {
135+ if ( cmd . id === id ) return cmd ;
136+ if ( cmd . children ) {
137+ const found = findInCommands ( cmd . children ) ;
138+ if ( found ) return found ;
139+ }
140+ }
141+ return undefined ;
142+ } ;
143+ return findInCommands ( allCommands ) ;
144+ } , [ allCommands ] ) ;
145+
146+ const currentCommandPath = useMemo ( ( ) =>
147+ commandPathIds . map ( id => getCommandById ( id ) ) . filter ( ( cmd ) : cmd is CommandItem => ! ! cmd ) ,
148+ [ commandPathIds , getCommandById ]
149+ ) ;
150+
90151 useEffect ( ( ) => {
91152 const down = ( e : KeyboardEvent ) => {
92153 if ( e . key === "k" && ( e . metaKey || e . ctrlKey ) ) {
@@ -117,7 +178,7 @@ export function CommandCenter() {
117178 useEffect ( ( ) => {
118179 if ( ! open ) {
119180 setMode ( { type : "main" } ) ;
120- setCommandPath ( [ ] ) ;
181+ setCommandPathIds ( [ ] ) ;
121182 }
122183 } , [ open ] ) ;
123184
@@ -128,19 +189,18 @@ export function CommandCenter() {
128189 setOpen ( true ) ;
129190 setMode ( {
130191 type : "input" ,
131- command : commands . find ( ( cmd ) => cmd . id === "edit-recipe" ) ! ,
192+ command : allCommands . find ( ( cmd ) => cmd . id === "edit-recipe" ) ! ,
132193 placeholder : "What would you like to change?" ,
133194 } ) ;
134195 }
135196 } ;
136197
137198 const handleEditRecipeEvent = ( ) => {
138199 if ( focusedCharmId ) {
139- // Only open if there's a charm focused
140200 setOpen ( true ) ;
141201 setMode ( {
142202 type : "input" ,
143- command : commands . find ( ( cmd ) => cmd . id === "edit-recipe" ) ! ,
203+ command : allCommands . find ( ( cmd ) => cmd . id === "edit-recipe" ) ! ,
144204 placeholder : "What would you like to change?" ,
145205 } ) ;
146206 }
@@ -153,7 +213,7 @@ export function CommandCenter() {
153213 document . removeEventListener ( "keydown" , handleEditRecipe ) ;
154214 window . removeEventListener ( "edit-recipe-command" , handleEditRecipeEvent ) ;
155215 } ;
156- } , [ focusedCharmId ] ) ; // Add focusedCharmId as a dependency
216+ } , [ focusedCharmId , allCommands ] ) ;
157217
158218 const context : CommandContext = {
159219 charmManager,
@@ -172,28 +232,41 @@ export function CommandCenter() {
172232 setSearch ( initialInput ) ;
173233 } ) ;
174234 } ,
235+ stopJob,
236+ startJob,
237+ addJobMessage,
238+ listJobs,
239+ updateJobProgress,
240+ commandPathIds,
175241 } ;
176242
177243 const handleBack = ( ) => {
178- if ( commandPath . length === 1 ) {
244+ if ( commandPathIds . length === 1 ) {
179245 setMode ( { type : "main" } ) ;
180- setCommandPath ( [ ] ) ;
246+ setCommandPathIds ( [ ] ) ;
181247 } else {
182- setCommandPath ( ( prev ) => prev . slice ( 0 , - 1 ) ) ;
183- setMode ( {
184- type : "menu" ,
185- path : commandPath . slice ( 0 , - 1 ) ,
186- parent : commandPath [ commandPath . length - 2 ] ,
187- } ) ;
248+ setCommandPathIds ( prev => prev . slice ( 0 , - 1 ) ) ;
249+ const parentId = commandPathIds [ commandPathIds . length - 2 ] ;
250+ const parentCommand = getCommandById ( parentId ) ;
251+ if ( parentCommand ) {
252+ setMode ( {
253+ type : "menu" ,
254+ path : commandPathIds . slice ( 0 , - 1 ) ,
255+ parent : parentCommand ,
256+ } ) ;
257+ }
188258 }
189259 } ;
190260
191261 const getCurrentCommands = ( ) => {
192- const currentCommands =
193- commandPath . length === 0 ? commands : commandPath [ commandPath . length - 1 ] . children || [ ] ;
262+ const commands = commandPathIds . length === 0
263+ ? allCommands
264+ : getCommandById ( commandPathIds [ commandPathIds . length - 1 ] ) ?. children ?? [ ] ;
194265
195- return currentCommands . filter ( ( cmd ) => ! cmd . predicate || cmd . predicate ( context ) ) ;
266+ return ( commands )
267+ . filter ( cmd => ! cmd . predicate ) ;
196268 } ;
269+
197270 return (
198271 < Command . Dialog title = "Common" open = { open } onOpenChange = { setOpen } label = "Command Menu" >
199272 < VisuallyHidden >
@@ -229,7 +302,7 @@ export function CommandCenter() {
229302 if ( mode . type === "input" && e . key === "Enter" ) {
230303 e . preventDefault ( ) ;
231304 const command = mode . command ;
232- command . handler ?.( context , search ) ;
305+ command . handler ?.( search ) ;
233306 }
234307 } }
235308 style = { { flexGrow : 1 } }
@@ -248,14 +321,14 @@ export function CommandCenter() {
248321
249322 { mode . type === "main" || mode . type === "menu" ? (
250323 < >
251- { commandPath . length > 0 && (
324+ { commandPathIds . length > 0 && (
252325 < Command . Item onSelect = { handleBack } >
253- ← Back to { getTitle ( commandPath [ commandPath . length - 2 ] . title , context ) || "Main Menu" }
326+ ← Back to { getCommandById ( commandPathIds [ commandPathIds . length - 2 ] ) ? .title || "Main Menu" }
254327 </ Command . Item >
255328 ) }
256329
257330 { ( ( ) => {
258- const groups = getCurrentCommands ( ) . reduce (
331+ const groups : Record < string , CommandItem [ ] > = getCurrentCommands ( ) . reduce (
259332 ( acc , cmd ) => {
260333 const group = cmd . group || "Other" ;
261334 if ( ! acc [ group ] ) acc [ group ] = [ ] ;
@@ -272,15 +345,14 @@ export function CommandCenter() {
272345 key = { cmd . id }
273346 onSelect = { ( ) => {
274347 if ( cmd . children ) {
275- setCommandPath ( ( prev ) => [ ...prev , cmd ] ) ;
348+ setCommandPathIds ( ( prev ) => [ ...prev , cmd . id ] ) ;
276349 setMode ( {
277350 type : "menu" ,
278- path : [ ...commandPath , cmd ] ,
351+ path : [ ...commandPathIds , cmd . id ] ,
279352 parent : cmd ,
280353 } ) ;
281354 } else if ( cmd . type === "action" ) {
282355 cmd . handler ?.( context ) ;
283- // Only close if the handler doesn't set a new mode
284356 if ( ! cmd . handler || cmd . handler . length === 0 ) {
285357 setOpen ( false ) ;
286358 }
@@ -289,7 +361,7 @@ export function CommandCenter() {
289361 }
290362 } }
291363 >
292- { typeof cmd . title === "function" ? cmd . title ( context ) : cmd . title }
364+ { cmd . title }
293365 { cmd . children && " →" }
294366 </ Command . Item >
295367 ) ) }
@@ -300,13 +372,13 @@ export function CommandCenter() {
300372 ) : (
301373 < CommandProcessor
302374 mode = { mode }
303- command = { commandPath [ commandPath . length - 1 ] }
375+ command = { currentCommandPath [ currentCommandPath . length - 1 ] }
304376 context = { context }
305377 onComplete = { ( ) => {
306378 setMode ( {
307379 type : "menu" ,
308- path : commandPath ,
309- parent : commandPath [ commandPath . length - 1 ] ,
380+ path : commandPathIds ,
381+ parent : currentCommandPath [ currentCommandPath . length - 1 ] ,
310382 } ) ;
311383 } }
312384 />
0 commit comments