Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

command-selector.tsx 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import type { FC } from 'react'
  2. import { useEffect, useMemo } from 'react'
  3. import { Command } from 'cmdk'
  4. import { useTranslation } from 'react-i18next'
  5. import type { ActionItem } from './actions/types'
  6. import { slashCommandRegistry } from './actions/commands/registry'
  7. type Props = {
  8. actions: Record<string, ActionItem>
  9. onCommandSelect: (commandKey: string) => void
  10. searchFilter?: string
  11. commandValue?: string
  12. onCommandValueChange?: (value: string) => void
  13. originalQuery?: string
  14. }
  15. const CommandSelector: FC<Props> = ({ actions, onCommandSelect, searchFilter, commandValue, onCommandValueChange, originalQuery }) => {
  16. const { t } = useTranslation()
  17. // Check if we're in slash command mode
  18. const isSlashMode = originalQuery?.trim().startsWith('/') || false
  19. // Get slash commands from registry
  20. const slashCommands = useMemo(() => {
  21. if (!isSlashMode) return []
  22. const allCommands = slashCommandRegistry.getAllCommands()
  23. const filter = searchFilter?.toLowerCase() || '' // searchFilter already has '/' removed
  24. return allCommands.filter((cmd) => {
  25. if (!filter) return true
  26. return cmd.name.toLowerCase().includes(filter)
  27. }).map(cmd => ({
  28. key: `/${cmd.name}`,
  29. shortcut: `/${cmd.name}`,
  30. title: cmd.name,
  31. description: cmd.description,
  32. }))
  33. }, [isSlashMode, searchFilter])
  34. const filteredActions = useMemo(() => {
  35. if (isSlashMode) return []
  36. return Object.values(actions).filter((action) => {
  37. // Exclude slash action when in @ mode
  38. if (action.key === '/') return false
  39. if (!searchFilter)
  40. return true
  41. const filterLower = searchFilter.toLowerCase()
  42. return action.shortcut.toLowerCase().includes(filterLower)
  43. })
  44. }, [actions, searchFilter, isSlashMode])
  45. const allItems = isSlashMode ? slashCommands : filteredActions
  46. useEffect(() => {
  47. if (allItems.length > 0 && onCommandValueChange) {
  48. const currentValueExists = allItems.some(item => item.shortcut === commandValue)
  49. if (!currentValueExists)
  50. onCommandValueChange(allItems[0].shortcut)
  51. }
  52. }, [searchFilter, allItems.length])
  53. if (allItems.length === 0) {
  54. return (
  55. <div className="p-4">
  56. <div className="flex items-center justify-center py-8 text-center text-text-tertiary">
  57. <div>
  58. <div className="text-sm font-medium text-text-tertiary">
  59. {t('app.gotoAnything.noMatchingCommands')}
  60. </div>
  61. <div className="mt-1 text-xs text-text-quaternary">
  62. {t('app.gotoAnything.tryDifferentSearch')}
  63. </div>
  64. </div>
  65. </div>
  66. </div>
  67. )
  68. }
  69. return (
  70. <div className="p-4">
  71. <div className="mb-3 text-left text-sm font-medium text-text-secondary">
  72. {isSlashMode ? t('app.gotoAnything.groups.commands') : t('app.gotoAnything.selectSearchType')}
  73. </div>
  74. <Command.Group className="space-y-1">
  75. {allItems.map(item => (
  76. <Command.Item
  77. key={item.key}
  78. value={item.shortcut}
  79. className="flex cursor-pointer items-center rounded-md
  80. p-2.5
  81. transition-all
  82. duration-150 hover:bg-state-base-hover aria-[selected=true]:bg-state-base-hover-alt"
  83. onSelect={() => onCommandSelect(item.shortcut)}
  84. >
  85. <span className="min-w-[4.5rem] text-left font-mono text-xs text-text-tertiary">
  86. {item.shortcut}
  87. </span>
  88. <span className="ml-3 text-sm text-text-secondary">
  89. {isSlashMode ? (
  90. (() => {
  91. const slashKeyMap: Record<string, string> = {
  92. '/theme': 'app.gotoAnything.actions.themeCategoryDesc',
  93. '/language': 'app.gotoAnything.actions.languageChangeDesc',
  94. '/account': 'app.gotoAnything.actions.accountDesc',
  95. '/feedback': 'app.gotoAnything.actions.feedbackDesc',
  96. '/doc': 'app.gotoAnything.actions.docDesc',
  97. '/community': 'app.gotoAnything.actions.communityDesc',
  98. }
  99. return t(slashKeyMap[item.key] || item.description)
  100. })()
  101. ) : (
  102. (() => {
  103. const keyMap: Record<string, string> = {
  104. '@app': 'app.gotoAnything.actions.searchApplicationsDesc',
  105. '@plugin': 'app.gotoAnything.actions.searchPluginsDesc',
  106. '@knowledge': 'app.gotoAnything.actions.searchKnowledgeBasesDesc',
  107. '@node': 'app.gotoAnything.actions.searchWorkflowNodesDesc',
  108. }
  109. return t(keyMap[item.key])
  110. })()
  111. )}
  112. </span>
  113. </Command.Item>
  114. ))}
  115. </Command.Group>
  116. </div>
  117. )
  118. }
  119. export default CommandSelector