usePickerHandlers.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { useCallback } from "react"
  2. import type { WebviewMessage } from "@roo-code/types"
  3. import type {
  4. AutocompletePickerState,
  5. AutocompleteInputHandle,
  6. ModeResult,
  7. HistoryResult,
  8. } from "../components/autocomplete/index.js"
  9. import { useCLIStore } from "../store.js"
  10. import { useUIStateStore } from "../stores/uiStateStore.js"
  11. export interface UsePickerHandlersOptions {
  12. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  13. autocompleteRef: React.RefObject<AutocompleteInputHandle<any>>
  14. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  15. followupAutocompleteRef: React.RefObject<AutocompleteInputHandle<any>>
  16. sendToExtension: ((msg: WebviewMessage) => void) | null
  17. showInfo: (msg: string, duration?: number) => void
  18. seenMessageIds: React.MutableRefObject<Set<string>>
  19. firstTextMessageSkipped: React.MutableRefObject<boolean>
  20. }
  21. export interface UsePickerHandlersReturn {
  22. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  23. handlePickerStateChange: (state: AutocompletePickerState<any>) => void
  24. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  25. handlePickerSelect: (item: any) => void
  26. handlePickerClose: () => void
  27. handlePickerIndexChange: (index: number) => void
  28. }
  29. /**
  30. * Hook to handle autocomplete picker interactions.
  31. *
  32. * Responsibilities:
  33. * - Handle picker state changes from AutocompleteInput
  34. * - Handle item selection (special handling for modes and history items)
  35. * - Handle mode switching via picker
  36. * - Handle task switching via history picker
  37. * - Handle picker close and index change
  38. */
  39. export function usePickerHandlers({
  40. autocompleteRef,
  41. followupAutocompleteRef,
  42. sendToExtension,
  43. showInfo,
  44. seenMessageIds,
  45. firstTextMessageSkipped,
  46. }: UsePickerHandlersOptions): UsePickerHandlersReturn {
  47. const { isLoading, currentTaskId, setCurrentTaskId } = useCLIStore()
  48. const { pickerState, setPickerState } = useUIStateStore()
  49. /**
  50. * Handle picker state changes from AutocompleteInput
  51. */
  52. const handlePickerStateChange = useCallback(
  53. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  54. (state: AutocompletePickerState<any>) => {
  55. setPickerState(state)
  56. },
  57. [setPickerState],
  58. )
  59. /**
  60. * Handle item selection from external PickerSelect
  61. */
  62. const handlePickerSelect = useCallback(
  63. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  64. (item: any) => {
  65. // Check if this is a mode selection.
  66. if (pickerState.activeTrigger?.id === "mode" && item && typeof item === "object" && "slug" in item) {
  67. const modeItem = item as ModeResult
  68. if (sendToExtension) {
  69. sendToExtension({ type: "mode", text: modeItem.slug })
  70. }
  71. autocompleteRef.current?.closePicker()
  72. followupAutocompleteRef.current?.closePicker()
  73. }
  74. // Check if this is a history item selection.
  75. else if (pickerState.activeTrigger?.id === "history" && item && typeof item === "object" && "id" in item) {
  76. const historyItem = item as HistoryResult
  77. // Don't allow task switching while a task is in progress (loading).
  78. if (isLoading) {
  79. showInfo("Cannot switch tasks while task is in progress", 2000)
  80. autocompleteRef.current?.closePicker()
  81. followupAutocompleteRef.current?.closePicker()
  82. return
  83. }
  84. // If selecting the same task that's already loaded, just close the picker.
  85. if (historyItem.id === currentTaskId) {
  86. autocompleteRef.current?.closePicker()
  87. followupAutocompleteRef.current?.closePicker()
  88. return
  89. }
  90. // Send showTaskWithId message to extension to resume the task
  91. if (sendToExtension) {
  92. // Use selective reset that preserves global state (taskHistory, modes, commands)
  93. useCLIStore.getState().resetForTaskSwitch()
  94. // Set the resuming flag so message handlers know we're resuming
  95. // This prevents skipping the first text message (which is historical)
  96. useCLIStore.getState().setIsResumingTask(true)
  97. // Track which task we're switching to
  98. setCurrentTaskId(historyItem.id)
  99. // Reset refs to avoid stale state across task switches
  100. seenMessageIds.current.clear()
  101. firstTextMessageSkipped.current = false
  102. // Send message to resume the selected task
  103. // This triggers createTaskWithHistoryItem -> postStateToWebview
  104. // which includes clineMessages and handles mode restoration
  105. sendToExtension({ type: "showTaskWithId", text: historyItem.id })
  106. }
  107. // Close the picker
  108. autocompleteRef.current?.closePicker()
  109. followupAutocompleteRef.current?.closePicker()
  110. } else {
  111. // Handle other item selections normally
  112. autocompleteRef.current?.handleItemSelect(item)
  113. followupAutocompleteRef.current?.handleItemSelect(item)
  114. }
  115. },
  116. [
  117. pickerState.activeTrigger,
  118. isLoading,
  119. showInfo,
  120. currentTaskId,
  121. setCurrentTaskId,
  122. sendToExtension,
  123. autocompleteRef,
  124. followupAutocompleteRef,
  125. seenMessageIds,
  126. firstTextMessageSkipped,
  127. ],
  128. )
  129. /**
  130. * Handle picker close from external PickerSelect
  131. */
  132. const handlePickerClose = useCallback(() => {
  133. autocompleteRef.current?.closePicker()
  134. followupAutocompleteRef.current?.closePicker()
  135. }, [autocompleteRef, followupAutocompleteRef])
  136. /**
  137. * Handle picker index change from external PickerSelect
  138. */
  139. const handlePickerIndexChange = useCallback(
  140. (index: number) => {
  141. autocompleteRef.current?.handleIndexChange(index)
  142. followupAutocompleteRef.current?.handleIndexChange(index)
  143. },
  144. [autocompleteRef, followupAutocompleteRef],
  145. )
  146. return {
  147. handlePickerStateChange,
  148. handlePickerSelect,
  149. handlePickerClose,
  150. handlePickerIndexChange,
  151. }
  152. }