@@ -86,6 +86,8 @@ export function MentionMenu({
8686 getActiveMentionQueryAtPosition,
8787 getCaretPos,
8888 submenuActiveIndex,
89+ mentionActiveIndex,
90+ openSubmenuFor,
8991 } = mentionMenu
9092
9193 const {
@@ -282,6 +284,21 @@ export function MentionMenu({
282284 // Show filtered aggregated view when there's a query
283285 const showAggregatedView = currentQuery . length > 0
284286
287+ // Folder order for keyboard navigation - matches render order
288+ const FOLDER_ORDER = [
289+ 'Chats' , // 0
290+ 'Workflows' , // 1
291+ 'Knowledge' , // 2
292+ 'Blocks' , // 3
293+ 'Workflow Blocks' , // 4
294+ 'Templates' , // 5
295+ 'Logs' , // 6
296+ 'Docs' , // 7
297+ ] as const
298+
299+ // Get active folder based on navigation when not in submenu and no query
300+ const isInFolderNavigationMode = ! openSubmenuFor && ! showAggregatedView
301+
285302 // Compute caret viewport position via mirror technique for precise anchoring
286303 const textareaEl = mentionMenu . textareaRef . current
287304 if ( ! textareaEl ) return null
@@ -372,7 +389,184 @@ export function MentionMenu({
372389 >
373390 < PopoverBackButton />
374391 < PopoverScrollArea ref = { menuListRef } className = 'space-y-[2px]' >
375- { showAggregatedView ? (
392+ { openSubmenuFor ? (
393+ // Submenu view - showing contents of a specific folder
394+ < >
395+ { openSubmenuFor === 'Chats' && (
396+ < >
397+ { mentionData . isLoadingPastChats ? (
398+ < LoadingState />
399+ ) : mentionData . pastChats . length === 0 ? (
400+ < EmptyState message = 'No past chats' />
401+ ) : (
402+ mentionData . pastChats . map ( ( chat , index ) => (
403+ < PopoverItem
404+ key = { chat . id }
405+ onClick = { ( ) => insertPastChatMention ( chat ) }
406+ data-idx = { index }
407+ active = { index === submenuActiveIndex }
408+ >
409+ < span className = 'truncate' > { chat . title || 'New Chat' } </ span >
410+ </ PopoverItem >
411+ ) )
412+ ) }
413+ </ >
414+ ) }
415+ { openSubmenuFor === 'Workflows' && (
416+ < >
417+ { mentionData . isLoadingWorkflows ? (
418+ < LoadingState />
419+ ) : mentionData . workflows . length === 0 ? (
420+ < EmptyState message = 'No workflows' />
421+ ) : (
422+ mentionData . workflows . map ( ( wf , index ) => (
423+ < PopoverItem
424+ key = { wf . id }
425+ onClick = { ( ) => insertWorkflowMention ( wf ) }
426+ data-idx = { index }
427+ active = { index === submenuActiveIndex }
428+ >
429+ < div
430+ className = 'relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
431+ style = { { backgroundColor : wf . color || '#3972F6' } }
432+ />
433+ < span className = 'truncate' > { wf . name || 'Untitled Workflow' } </ span >
434+ </ PopoverItem >
435+ ) )
436+ ) }
437+ </ >
438+ ) }
439+ { openSubmenuFor === 'Knowledge' && (
440+ < >
441+ { mentionData . isLoadingKnowledge ? (
442+ < LoadingState />
443+ ) : mentionData . knowledgeBases . length === 0 ? (
444+ < EmptyState message = 'No knowledge bases' />
445+ ) : (
446+ mentionData . knowledgeBases . map ( ( kb , index ) => (
447+ < PopoverItem
448+ key = { kb . id }
449+ onClick = { ( ) => insertKnowledgeMention ( kb ) }
450+ data-idx = { index }
451+ active = { index === submenuActiveIndex }
452+ >
453+ < span className = 'truncate' > { kb . name || 'Untitled' } </ span >
454+ </ PopoverItem >
455+ ) )
456+ ) }
457+ </ >
458+ ) }
459+ { openSubmenuFor === 'Blocks' && (
460+ < >
461+ { mentionData . isLoadingBlocks ? (
462+ < LoadingState />
463+ ) : mentionData . blocksList . length === 0 ? (
464+ < EmptyState message = 'No blocks found' />
465+ ) : (
466+ mentionData . blocksList . map ( ( blk , index ) => {
467+ const Icon = blk . iconComponent
468+ return (
469+ < PopoverItem
470+ key = { blk . id }
471+ onClick = { ( ) => insertBlockMention ( blk ) }
472+ data-idx = { index }
473+ active = { index === submenuActiveIndex }
474+ >
475+ < div
476+ className = 'relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
477+ style = { { backgroundColor : blk . bgColor || '#6B7280' } }
478+ >
479+ { Icon && < Icon className = '!h-[10px] !w-[10px] text-white' /> }
480+ </ div >
481+ < span className = 'truncate' > { blk . name || blk . id } </ span >
482+ </ PopoverItem >
483+ )
484+ } )
485+ ) }
486+ </ >
487+ ) }
488+ { openSubmenuFor === 'Workflow Blocks' && (
489+ < >
490+ { mentionData . isLoadingWorkflowBlocks ? (
491+ < LoadingState />
492+ ) : mentionData . workflowBlocks . length === 0 ? (
493+ < EmptyState message = 'No blocks in this workflow' />
494+ ) : (
495+ mentionData . workflowBlocks . map ( ( blk , index ) => {
496+ const Icon = blk . iconComponent
497+ return (
498+ < PopoverItem
499+ key = { blk . id }
500+ onClick = { ( ) => insertWorkflowBlockMention ( blk ) }
501+ data-idx = { index }
502+ active = { index === submenuActiveIndex }
503+ >
504+ < div
505+ className = 'relative flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center overflow-hidden rounded-[4px]'
506+ style = { { backgroundColor : blk . bgColor || '#6B7280' } }
507+ >
508+ { Icon && < Icon className = '!h-[10px] !w-[10px] text-white' /> }
509+ </ div >
510+ < span className = 'truncate' > { blk . name || blk . id } </ span >
511+ </ PopoverItem >
512+ )
513+ } )
514+ ) }
515+ </ >
516+ ) }
517+ { openSubmenuFor === 'Templates' && (
518+ < >
519+ { mentionData . isLoadingTemplates ? (
520+ < LoadingState />
521+ ) : mentionData . templatesList . length === 0 ? (
522+ < EmptyState message = 'No templates found' />
523+ ) : (
524+ mentionData . templatesList . map ( ( tpl , index ) => (
525+ < PopoverItem
526+ key = { tpl . id }
527+ onClick = { ( ) => insertTemplateMention ( tpl ) }
528+ data-idx = { index }
529+ active = { index === submenuActiveIndex }
530+ >
531+ < span className = 'flex-1 truncate' > { tpl . name } </ span >
532+ < span className = 'text-[#868686] text-[10px] dark:text-[#868686]' >
533+ { tpl . stars }
534+ </ span >
535+ </ PopoverItem >
536+ ) )
537+ ) }
538+ </ >
539+ ) }
540+ { openSubmenuFor === 'Logs' && (
541+ < >
542+ { mentionData . isLoadingLogs ? (
543+ < LoadingState />
544+ ) : mentionData . logsList . length === 0 ? (
545+ < EmptyState message = 'No executions found' />
546+ ) : (
547+ mentionData . logsList . map ( ( log , index ) => (
548+ < PopoverItem
549+ key = { log . id }
550+ onClick = { ( ) => insertLogMention ( log ) }
551+ data-idx = { index }
552+ active = { index === submenuActiveIndex }
553+ >
554+ < span className = 'min-w-0 flex-1 truncate' > { log . workflowName } </ span >
555+ < span className = 'text-[#AEAEAE] text-[10px] dark:text-[#AEAEAE]' > ·</ span >
556+ < span className = 'whitespace-nowrap text-[10px]' >
557+ { formatTimestamp ( log . createdAt ) }
558+ </ span >
559+ < span className = 'text-[#AEAEAE] text-[10px] dark:text-[#AEAEAE]' > ·</ span >
560+ < span className = 'text-[10px] capitalize' >
561+ { ( log . trigger || 'manual' ) . toLowerCase ( ) }
562+ </ span >
563+ </ PopoverItem >
564+ ) )
565+ ) }
566+ </ >
567+ ) }
568+ </ >
569+ ) : showAggregatedView ? (
376570 // Aggregated filtered view
377571 < >
378572 { filteredAggregatedItems . length === 0 ? (
@@ -406,6 +600,8 @@ export function MentionMenu({
406600 id = 'chats'
407601 title = 'Chats'
408602 onOpen = { ( ) => mentionData . ensurePastChatsLoaded ( ) }
603+ active = { isInFolderNavigationMode && mentionActiveIndex === 0 }
604+ data-idx = { 0 }
409605 >
410606 { mentionData . isLoadingPastChats ? (
411607 < LoadingState />
@@ -424,6 +620,8 @@ export function MentionMenu({
424620 id = 'workflows'
425621 title = 'All workflows'
426622 onOpen = { ( ) => mentionData . ensureWorkflowsLoaded ( ) }
623+ active = { isInFolderNavigationMode && mentionActiveIndex === 1 }
624+ data-idx = { 1 }
427625 >
428626 { mentionData . isLoadingWorkflows ? (
429627 < LoadingState />
@@ -446,6 +644,8 @@ export function MentionMenu({
446644 id = 'knowledge'
447645 title = 'Knowledge Bases'
448646 onOpen = { ( ) => mentionData . ensureKnowledgeLoaded ( ) }
647+ active = { isInFolderNavigationMode && mentionActiveIndex === 2 }
648+ data-idx = { 2 }
449649 >
450650 { mentionData . isLoadingKnowledge ? (
451651 < LoadingState />
@@ -464,6 +664,8 @@ export function MentionMenu({
464664 id = 'blocks'
465665 title = 'Blocks'
466666 onOpen = { ( ) => mentionData . ensureBlocksLoaded ( ) }
667+ active = { isInFolderNavigationMode && mentionActiveIndex === 3 }
668+ data-idx = { 3 }
467669 >
468670 { mentionData . isLoadingBlocks ? (
469671 < LoadingState />
@@ -491,6 +693,8 @@ export function MentionMenu({
491693 id = 'workflow-blocks'
492694 title = 'Workflow Blocks'
493695 onOpen = { ( ) => mentionData . ensureWorkflowBlocksLoaded ( ) }
696+ active = { isInFolderNavigationMode && mentionActiveIndex === 4 }
697+ data-idx = { 4 }
494698 >
495699 { mentionData . isLoadingWorkflowBlocks ? (
496700 < LoadingState />
@@ -518,6 +722,8 @@ export function MentionMenu({
518722 id = 'templates'
519723 title = 'Templates'
520724 onOpen = { ( ) => mentionData . ensureTemplatesLoaded ( ) }
725+ active = { isInFolderNavigationMode && mentionActiveIndex === 5 }
726+ data-idx = { 5 }
521727 >
522728 { mentionData . isLoadingTemplates ? (
523729 < LoadingState />
@@ -535,7 +741,13 @@ export function MentionMenu({
535741 ) }
536742 </ PopoverFolder >
537743
538- < PopoverFolder id = 'logs' title = 'Logs' onOpen = { ( ) => mentionData . ensureLogsLoaded ( ) } >
744+ < PopoverFolder
745+ id = 'logs'
746+ title = 'Logs'
747+ onOpen = { ( ) => mentionData . ensureLogsLoaded ( ) }
748+ active = { isInFolderNavigationMode && mentionActiveIndex === 6 }
749+ data-idx = { 6 }
750+ >
539751 { mentionData . isLoadingLogs ? (
540752 < LoadingState />
541753 ) : mentionData . logsList . length === 0 ? (
@@ -557,7 +769,12 @@ export function MentionMenu({
557769 ) }
558770 </ PopoverFolder >
559771
560- < PopoverItem rootOnly onClick = { ( ) => insertDocsMention ( ) } >
772+ < PopoverItem
773+ rootOnly
774+ onClick = { ( ) => insertDocsMention ( ) }
775+ active = { isInFolderNavigationMode && mentionActiveIndex === 7 }
776+ data-idx = { 7 }
777+ >
561778 < span > Docs</ span >
562779 </ PopoverItem >
563780 </ >
0 commit comments