App.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { useCallback, useEffect, useRef, useState } from "react"
  2. import { useEvent } from "react-use"
  3. import { ExtensionMessage } from "../../src/shared/ExtensionMessage"
  4. import { ShowHumanRelayDialogMessage } from "../../src/shared/ExtensionMessage"
  5. import { vscode } from "./utils/vscode"
  6. import { telemetryClient } from "./utils/TelemetryClient"
  7. import { ExtensionStateContextProvider, useExtensionState } from "./context/ExtensionStateContext"
  8. import ChatView from "./components/chat/ChatView"
  9. import HistoryView from "./components/history/HistoryView"
  10. import SettingsView, { SettingsViewRef } from "./components/settings/SettingsView"
  11. import WelcomeView from "./components/welcome/WelcomeView"
  12. import McpView from "./components/mcp/McpView"
  13. import PromptsView from "./components/prompts/PromptsView"
  14. import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
  15. type Tab = "settings" | "history" | "mcp" | "prompts" | "chat"
  16. const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]>, Tab>> = {
  17. chatButtonClicked: "chat",
  18. settingsButtonClicked: "settings",
  19. promptsButtonClicked: "prompts",
  20. mcpButtonClicked: "mcp",
  21. historyButtonClicked: "history",
  22. }
  23. const App = () => {
  24. const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } =
  25. useExtensionState()
  26. const [showAnnouncement, setShowAnnouncement] = useState(false)
  27. const [tab, setTab] = useState<Tab>("chat")
  28. const settingsRef = useRef<SettingsViewRef>(null)
  29. // Human Relay Dialog Status
  30. const [humanRelayDialogState, setHumanRelayDialogState] = useState<{
  31. isOpen: boolean
  32. requestId: string
  33. promptText: string
  34. }>({
  35. isOpen: false,
  36. requestId: "",
  37. promptText: "",
  38. })
  39. const switchTab = useCallback((newTab: Tab) => {
  40. if (settingsRef.current?.checkUnsaveChanges) {
  41. settingsRef.current.checkUnsaveChanges(() => setTab(newTab))
  42. } else {
  43. setTab(newTab)
  44. }
  45. }, [])
  46. const onMessage = useCallback(
  47. (e: MessageEvent) => {
  48. const message: ExtensionMessage = e.data
  49. if (message.type === "action" && message.action) {
  50. const newTab = tabsByMessageAction[message.action]
  51. if (newTab) {
  52. switchTab(newTab)
  53. }
  54. }
  55. const mes: ShowHumanRelayDialogMessage = message as ShowHumanRelayDialogMessage
  56. // Processing displays human relay dialog messages
  57. if (mes.type === "showHumanRelayDialog" && mes.requestId && mes.promptText) {
  58. setHumanRelayDialogState({
  59. isOpen: true,
  60. requestId: mes.requestId,
  61. promptText: mes.promptText,
  62. })
  63. }
  64. },
  65. [switchTab],
  66. )
  67. // Processing Human Relay Dialog Submission
  68. const handleHumanRelaySubmit = (requestId: string, text: string) => {
  69. vscode.postMessage({
  70. type: "humanRelayResponse",
  71. requestId,
  72. text,
  73. })
  74. }
  75. // Handle Human Relay dialog box cancel
  76. const handleHumanRelayCancel = (requestId: string) => {
  77. vscode.postMessage({
  78. type: "humanRelayCancel",
  79. requestId,
  80. })
  81. }
  82. useEvent("message", onMessage)
  83. useEffect(() => {
  84. if (shouldShowAnnouncement) {
  85. setShowAnnouncement(true)
  86. vscode.postMessage({ type: "didShowAnnouncement" })
  87. }
  88. }, [shouldShowAnnouncement])
  89. useEffect(() => {
  90. if (didHydrateState) {
  91. telemetryClient.updateTelemetryState(telemetrySetting, telemetryKey, machineId)
  92. }
  93. }, [telemetrySetting, telemetryKey, machineId, didHydrateState])
  94. // Tell Extension that we are ready to receive messages
  95. useEffect(() => {
  96. vscode.postMessage({ type: "webviewDidLaunch" })
  97. }, [])
  98. if (!didHydrateState) {
  99. return null
  100. }
  101. // Do not conditionally load ChatView, it's expensive and there's state we
  102. // don't want to lose (user input, disableInput, askResponse promise, etc.)
  103. return showWelcome ? (
  104. <WelcomeView />
  105. ) : (
  106. <>
  107. {tab === "settings" && <SettingsView ref={settingsRef} onDone={() => setTab("chat")} />}
  108. {tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
  109. {tab === "mcp" && <McpView onDone={() => switchTab("chat")} />}
  110. {tab === "prompts" && <PromptsView onDone={() => switchTab("chat")} />}
  111. <ChatView
  112. isHidden={tab !== "chat"}
  113. showAnnouncement={showAnnouncement}
  114. hideAnnouncement={() => setShowAnnouncement(false)}
  115. showHistoryView={() => switchTab("history")}
  116. />
  117. {/* Human Relay Dialog */}
  118. <HumanRelayDialog
  119. isOpen={humanRelayDialogState.isOpen}
  120. requestId={humanRelayDialogState.requestId}
  121. promptText={humanRelayDialogState.promptText}
  122. onClose={() => setHumanRelayDialogState((prev) => ({ ...prev, isOpen: false }))}
  123. onSubmit={handleHumanRelaySubmit}
  124. onCancel={handleHumanRelayCancel}
  125. />
  126. </>
  127. )
  128. }
  129. const AppWithProviders = () => (
  130. <ExtensionStateContextProvider>
  131. <App />
  132. </ExtensionStateContextProvider>
  133. )
  134. export default AppWithProviders