App.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import { useCallback, useEffect, useRef, useState } from "react"
  2. import { useEvent } from "react-use"
  3. import { ExtensionMessage } from "../../src/shared/ExtensionMessage"
  4. import TranslationProvider from "./i18n/TranslationContext"
  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. type HumanRelayDialogState = {
  17. isOpen: boolean
  18. requestId: string
  19. promptText: string
  20. }
  21. const tabsByMessageAction: Partial<Record<NonNullable<ExtensionMessage["action"]>, Tab>> = {
  22. chatButtonClicked: "chat",
  23. settingsButtonClicked: "settings",
  24. promptsButtonClicked: "prompts",
  25. mcpButtonClicked: "mcp",
  26. historyButtonClicked: "history",
  27. }
  28. const App = () => {
  29. const { didHydrateState, showWelcome, shouldShowAnnouncement, telemetrySetting, telemetryKey, machineId } =
  30. useExtensionState()
  31. const [showAnnouncement, setShowAnnouncement] = useState(false)
  32. const [tab, setTab] = useState<Tab>("chat")
  33. const [humanRelayDialogState, setHumanRelayDialogState] = useState<HumanRelayDialogState>({
  34. isOpen: false,
  35. requestId: "",
  36. promptText: "",
  37. })
  38. const settingsRef = useRef<SettingsViewRef>(null)
  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. if (message.type === "showHumanRelayDialog" && message.requestId && message.promptText) {
  56. const { requestId, promptText } = message
  57. setHumanRelayDialogState({ isOpen: true, requestId, promptText })
  58. }
  59. },
  60. [switchTab],
  61. )
  62. useEvent("message", onMessage)
  63. useEffect(() => {
  64. if (shouldShowAnnouncement) {
  65. setShowAnnouncement(true)
  66. vscode.postMessage({ type: "didShowAnnouncement" })
  67. }
  68. }, [shouldShowAnnouncement])
  69. useEffect(() => {
  70. if (didHydrateState) {
  71. telemetryClient.updateTelemetryState(telemetrySetting, telemetryKey, machineId)
  72. }
  73. }, [telemetrySetting, telemetryKey, machineId, didHydrateState])
  74. // Tell the extension that we are ready to receive messages.
  75. useEffect(() => vscode.postMessage({ type: "webviewDidLaunch" }), [])
  76. if (!didHydrateState) {
  77. return null
  78. }
  79. // Do not conditionally load ChatView, it's expensive and there's state we
  80. // don't want to lose (user input, disableInput, askResponse promise, etc.)
  81. return showWelcome ? (
  82. <WelcomeView />
  83. ) : (
  84. <>
  85. {tab === "prompts" && <PromptsView onDone={() => switchTab("chat")} />}
  86. {tab === "mcp" && <McpView onDone={() => switchTab("chat")} />}
  87. {tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
  88. {tab === "settings" && <SettingsView ref={settingsRef} onDone={() => setTab("chat")} />}
  89. <ChatView
  90. isHidden={tab !== "chat"}
  91. showAnnouncement={showAnnouncement}
  92. hideAnnouncement={() => setShowAnnouncement(false)}
  93. showHistoryView={() => switchTab("history")}
  94. />
  95. <HumanRelayDialog
  96. isOpen={humanRelayDialogState.isOpen}
  97. requestId={humanRelayDialogState.requestId}
  98. promptText={humanRelayDialogState.promptText}
  99. onClose={() => setHumanRelayDialogState((prev) => ({ ...prev, isOpen: false }))}
  100. onSubmit={(requestId, text) => vscode.postMessage({ type: "humanRelayResponse", requestId, text })}
  101. onCancel={(requestId) => vscode.postMessage({ type: "humanRelayCancel", requestId })}
  102. />
  103. </>
  104. )
  105. }
  106. const AppWithProviders = () => (
  107. <ExtensionStateContextProvider>
  108. <TranslationProvider>
  109. <App />
  110. </TranslationProvider>
  111. </ExtensionStateContextProvider>
  112. )
  113. export default AppWithProviders