useTaskSubmit.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { useCallback } from "react"
  2. import { randomUUID } from "crypto"
  3. import type { WebviewMessage } from "@roo-code/types"
  4. import { getGlobalCommand } from "../../lib/utils/commands.js"
  5. import { useCLIStore } from "../store.js"
  6. import { useUIStateStore } from "../stores/uiStateStore.js"
  7. export interface UseTaskSubmitOptions {
  8. sendToExtension: ((msg: WebviewMessage) => void) | null
  9. runTask: ((prompt: string) => Promise<void>) | null
  10. seenMessageIds: React.MutableRefObject<Set<string>>
  11. firstTextMessageSkipped: React.MutableRefObject<boolean>
  12. }
  13. export interface UseTaskSubmitReturn {
  14. handleSubmit: (text: string) => Promise<void>
  15. handleApprove: () => void
  16. handleReject: () => void
  17. }
  18. /**
  19. * Hook to handle task submission, user responses, and approvals.
  20. *
  21. * Responsibilities:
  22. * - Process user message submissions
  23. * - Detect and handle global commands (like /new)
  24. * - Handle pending ask responses
  25. * - Start new tasks or continue existing ones
  26. * - Handle Y/N approval responses
  27. */
  28. export function useTaskSubmit({
  29. sendToExtension,
  30. runTask,
  31. seenMessageIds,
  32. firstTextMessageSkipped,
  33. }: UseTaskSubmitOptions): UseTaskSubmitReturn {
  34. const {
  35. pendingAsk,
  36. hasStartedTask,
  37. isComplete,
  38. addMessage,
  39. setPendingAsk,
  40. setHasStartedTask,
  41. setLoading,
  42. setComplete,
  43. setError,
  44. } = useCLIStore()
  45. const { setShowCustomInput, setIsTransitioningToCustomInput } = useUIStateStore()
  46. /**
  47. * Handle user text submission (from input or followup question)
  48. */
  49. const handleSubmit = useCallback(
  50. async (text: string) => {
  51. if (!sendToExtension || !text.trim()) {
  52. return
  53. }
  54. const trimmedText = text.trim()
  55. if (trimmedText === "__CUSTOM__") {
  56. return
  57. }
  58. // Check for CLI global action commands (e.g., /new)
  59. if (trimmedText.startsWith("/")) {
  60. const commandMatch = trimmedText.match(/^\/(\w+)(?:\s|$)/)
  61. if (commandMatch && commandMatch[1]) {
  62. const globalCommand = getGlobalCommand(commandMatch[1])
  63. if (globalCommand?.action === "clearTask") {
  64. // Reset CLI state and send clearTask to extension.
  65. useCLIStore.getState().reset()
  66. // Reset component-level refs to avoid stale message tracking.
  67. seenMessageIds.current.clear()
  68. firstTextMessageSkipped.current = false
  69. sendToExtension({ type: "clearTask" })
  70. // Re-request state, commands and modes since reset() cleared them.
  71. sendToExtension({ type: "requestCommands" })
  72. sendToExtension({ type: "requestModes" })
  73. return
  74. }
  75. }
  76. }
  77. if (pendingAsk) {
  78. addMessage({ id: randomUUID(), role: "user", content: trimmedText })
  79. sendToExtension({
  80. type: "askResponse",
  81. askResponse: "messageResponse",
  82. text: trimmedText,
  83. })
  84. setPendingAsk(null)
  85. setShowCustomInput(false)
  86. setIsTransitioningToCustomInput(false)
  87. setLoading(true)
  88. } else if (!hasStartedTask) {
  89. setHasStartedTask(true)
  90. setLoading(true)
  91. addMessage({ id: randomUUID(), role: "user", content: trimmedText })
  92. try {
  93. if (runTask) {
  94. await runTask(trimmedText)
  95. }
  96. } catch (err) {
  97. setError(err instanceof Error ? err.message : String(err))
  98. setLoading(false)
  99. }
  100. } else {
  101. if (isComplete) {
  102. setComplete(false)
  103. }
  104. setLoading(true)
  105. addMessage({ id: randomUUID(), role: "user", content: trimmedText })
  106. sendToExtension({
  107. type: "askResponse",
  108. askResponse: "messageResponse",
  109. text: trimmedText,
  110. })
  111. }
  112. },
  113. [
  114. sendToExtension,
  115. runTask,
  116. pendingAsk,
  117. hasStartedTask,
  118. isComplete,
  119. addMessage,
  120. setPendingAsk,
  121. setHasStartedTask,
  122. setLoading,
  123. setComplete,
  124. setError,
  125. setShowCustomInput,
  126. setIsTransitioningToCustomInput,
  127. seenMessageIds,
  128. firstTextMessageSkipped,
  129. ],
  130. )
  131. /**
  132. * Handle approval (Y key)
  133. */
  134. const handleApprove = useCallback(() => {
  135. if (!sendToExtension) {
  136. return
  137. }
  138. sendToExtension({ type: "askResponse", askResponse: "yesButtonClicked" })
  139. setPendingAsk(null)
  140. setLoading(true)
  141. }, [sendToExtension, setPendingAsk, setLoading])
  142. /**
  143. * Handle rejection (N key)
  144. */
  145. const handleReject = useCallback(() => {
  146. if (!sendToExtension) {
  147. return
  148. }
  149. sendToExtension({ type: "askResponse", askResponse: "noButtonClicked" })
  150. setPendingAsk(null)
  151. setLoading(true)
  152. }, [sendToExtension, setPendingAsk, setLoading])
  153. return {
  154. handleSubmit,
  155. handleApprove,
  156. handleReject,
  157. }
  158. }