Refactor extension state into ExtensionStateContext

This commit is contained in:
Saoud Rizwan
2024-08-27 04:44:36 -04:00
parent e95bebacda
commit 3b5e018a60
14 changed files with 155 additions and 258 deletions

View File

@@ -18,29 +18,19 @@ import { ExtensionMessage } from "../../../src/shared/ExtensionMessage"
import { getKoduAddCreditsUrl, getKoduHomepageUrl, getKoduSignInUrl } from "../../../src/shared/kodu"
import { vscode } from "../utils/vscode"
import VSCodeButtonLink from "./VSCodeButtonLink"
import { useExtensionState } from "../context/ExtensionStateContext"
interface ApiOptionsProps {
showModelOptions: boolean
apiConfiguration?: ApiConfiguration
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
koduCredits?: number
apiErrorMessage?: string
vscodeUriScheme?: string
setDidAuthKodu?: React.Dispatch<React.SetStateAction<boolean>>
}
const ApiOptions: React.FC<ApiOptionsProps> = ({
showModelOptions,
apiConfiguration,
setApiConfiguration,
koduCredits,
apiErrorMessage,
vscodeUriScheme,
setDidAuthKodu,
}) => {
const ApiOptions: React.FC<ApiOptionsProps> = ({ showModelOptions, apiErrorMessage, setDidAuthKodu }) => {
const { apiConfiguration, setApiConfiguration, koduCredits, uriScheme } = useExtensionState()
const [, setDidFetchKoduCredits] = useState(false)
const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
setApiConfiguration((prev) => ({ ...prev, [field]: event.target.value }))
setApiConfiguration({ ...apiConfiguration, [field]: event.target.value })
}
const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
@@ -185,7 +175,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
</span>
</div>
<VSCodeButtonLink
href={getKoduAddCreditsUrl(vscodeUriScheme)}
href={getKoduAddCreditsUrl(uriScheme)}
style={{
width: "fit-content",
}}>
@@ -194,9 +184,7 @@ const ApiOptions: React.FC<ApiOptionsProps> = ({
</>
) : (
<div style={{ margin: "4px 0px" }}>
<VSCodeButtonLink
href={getKoduSignInUrl(vscodeUriScheme)}
onClick={() => setDidAuthKodu?.(true)}>
<VSCodeButtonLink href={getKoduSignInUrl(uriScheme)} onClick={() => setDidAuthKodu?.(true)}>
Sign in to Kodu
</VSCodeButtonLink>
</div>

View File

@@ -5,7 +5,7 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { ClaudeAsk, ClaudeMessage, ClaudeSay, ClaudeSayTool } from "../../../src/shared/ExtensionMessage"
import { COMMAND_OUTPUT_STRING } from "../../../src/shared/combineCommandSequences"
import { SyntaxHighlighterStyle } from "../utils/getSyntaxHighlighterStyleFromTheme"
import CodeBlock from "./CodeBlock/CodeBlock"
import CodeBlock from "./CodeBlock"
import Thumbnails from "./Thumbnails"
import { ApiProvider } from "../../../src/shared/api"

View File

@@ -4,56 +4,50 @@ import vsDarkPlus from "react-syntax-highlighter/dist/esm/styles/prism/vsc-dark-
import DynamicTextArea from "react-textarea-autosize"
import { useEvent, useMount } from "react-use"
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
import { ClaudeAsk, ClaudeMessage, ExtensionMessage } from "../../../src/shared/ExtensionMessage"
import { getApiMetrics } from "../../../src/shared/getApiMetrics"
import { ClaudeAsk, ExtensionMessage } from "../../../src/shared/ExtensionMessage"
import { combineApiRequests } from "../../../src/shared/combineApiRequests"
import { combineCommandSequences } from "../../../src/shared/combineCommandSequences"
import { getApiMetrics } from "../../../src/shared/getApiMetrics"
import { useExtensionState } from "../context/ExtensionStateContext"
import { getSyntaxHighlighterStyleFromTheme } from "../utils/getSyntaxHighlighterStyleFromTheme"
import { vscode } from "../utils/vscode"
import Announcement from "./Announcement"
import ChatRow from "./ChatRow"
import HistoryPreview from "./HistoryPreview"
import KoduPromo from "./KoduPromo"
import TaskHeader from "./TaskHeader"
import Thumbnails from "./Thumbnails"
import { HistoryItem } from "../../../src/shared/HistoryItem"
import { ApiConfiguration } from "../../../src/shared/api"
import KoduPromo from "./KoduPromo"
interface ChatViewProps {
version: string
messages: ClaudeMessage[]
taskHistory: HistoryItem[]
isHidden: boolean
vscodeThemeName?: string
showAnnouncement: boolean
selectedModelSupportsImages: boolean
selectedModelSupportsPromptCache: boolean
hideAnnouncement: () => void
showHistoryView: () => void
apiConfiguration?: ApiConfiguration
vscodeUriScheme?: string
shouldShowKoduPromo: boolean
koduCredits?: number
}
const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
const ChatView = ({
version,
messages,
taskHistory,
isHidden,
vscodeThemeName,
showAnnouncement,
selectedModelSupportsImages,
selectedModelSupportsPromptCache,
hideAnnouncement,
showHistoryView,
apiConfiguration,
vscodeUriScheme,
shouldShowKoduPromo,
koduCredits,
}: ChatViewProps) => {
const {
version,
claudeMessages: messages,
taskHistory,
themeName: vscodeThemeName,
apiConfiguration,
uriScheme,
shouldShowKoduPromo,
koduCredits,
} = useExtensionState()
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
const task = messages.length > 0 ? messages[0] : undefined // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see ClaudeDev.abort)
const modifiedMessages = useMemo(() => combineApiRequests(combineCommandSequences(messages.slice(1))), [messages])
@@ -490,7 +484,7 @@ const ChatView = ({
onClose={handleTaskCloseButtonClick}
isHidden={isHidden}
koduCredits={koduCredits}
vscodeUriScheme={vscodeUriScheme}
vscodeUriScheme={uriScheme}
apiProvider={apiConfiguration?.apiProvider}
/>
) : (
@@ -500,11 +494,11 @@ const ChatView = ({
version={version}
hideAnnouncement={hideAnnouncement}
apiConfiguration={apiConfiguration}
vscodeUriScheme={vscodeUriScheme}
vscodeUriScheme={uriScheme}
/>
)}
{apiConfiguration?.koduApiKey === undefined && !showAnnouncement && shouldShowKoduPromo && (
<KoduPromo vscodeUriScheme={vscodeUriScheme} style={{ margin: "10px 15px -10px 15px" }} />
<KoduPromo style={{ margin: "10px 15px -10px 15px" }} />
)}
<div style={{ padding: "0 20px", flexGrow: taskHistory.length > 0 ? undefined : 1 }}>
<h2>What can I do for you?</h2>
@@ -520,9 +514,7 @@ const ChatView = ({
permission), I can assist you in ways that go beyond simple code completion or tech support.
</p>
</div>
{taskHistory.length > 0 && (
<HistoryPreview taskHistory={taskHistory} showHistoryView={showHistoryView} />
)}
{taskHistory.length > 0 && <HistoryPreview showHistoryView={showHistoryView} />}
</>
)}
{task && (

View File

@@ -1,7 +1,7 @@
import { useMemo } from "react"
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"
import { getLanguageFromPath } from "../../utils/getLanguageFromPath"
import { SyntaxHighlighterStyle } from "../../utils/getSyntaxHighlighterStyleFromTheme"
import { getLanguageFromPath } from "../utils/getLanguageFromPath"
import { SyntaxHighlighterStyle } from "../utils/getSyntaxHighlighterStyleFromTheme"
/*
const vscodeSyntaxStyle: React.CSSProperties = {

View File

@@ -1,13 +1,13 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "../context/ExtensionStateContext"
import { vscode } from "../utils/vscode"
import { HistoryItem } from "../../../src/shared/HistoryItem"
type HistoryPreviewProps = {
taskHistory: HistoryItem[]
showHistoryView: () => void
}
const HistoryPreview = ({ taskHistory, showHistoryView }: HistoryPreviewProps) => {
const HistoryPreview = ({ showHistoryView }: HistoryPreviewProps) => {
const { taskHistory } = useExtensionState()
const handleHistorySelect = (id: string) => {
vscode.postMessage({ type: "showTaskWithId", text: id })
}

View File

@@ -1,13 +1,13 @@
import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
import { useExtensionState } from "../context/ExtensionStateContext"
import { vscode } from "../utils/vscode"
import { HistoryItem } from "../../../src/shared/HistoryItem"
type HistoryViewProps = {
taskHistory: HistoryItem[]
onDone: () => void
}
const HistoryView = ({ taskHistory, onDone }: HistoryViewProps) => {
const HistoryView = ({ onDone }: HistoryViewProps) => {
const { taskHistory } = useExtensionState()
const handleHistorySelect = (id: string) => {
vscode.postMessage({ type: "showTaskWithId", text: id })
}

View File

@@ -1,13 +1,15 @@
import React from "react"
import { getKoduSignInUrl } from "../../../src/shared/kodu"
import { vscode } from "../utils/vscode"
import { useExtensionState } from "../context/ExtensionStateContext"
interface KoduPromoProps {
vscodeUriScheme?: string
style?: React.CSSProperties
}
const KoduPromo: React.FC<KoduPromoProps> = ({ vscodeUriScheme, style }) => {
const KoduPromo: React.FC<KoduPromoProps> = ({ style }) => {
const { uriScheme } = useExtensionState()
function onClose() {
vscode.postMessage({ type: "didDismissKoduPromo" })
}
@@ -28,7 +30,7 @@ const KoduPromo: React.FC<KoduPromoProps> = ({ vscodeUriScheme, style }) => {
cursor: "pointer",
}}>
<a
href={getKoduSignInUrl(vscodeUriScheme)}
href={getKoduSignInUrl(uriScheme)}
style={{
textDecoration: "none",
color: "inherit",

View File

@@ -5,8 +5,8 @@ import {
VSCodeTextArea,
VSCodeTextField,
} from "@vscode/webview-ui-toolkit/react"
import React, { useEffect, useState } from "react"
import { ApiConfiguration } from "../../../src/shared/api"
import { useEffect, useState } from "react"
import { useExtensionState } from "../context/ExtensionStateContext"
import { validateApiConfiguration, validateMaxRequestsPerTask } from "../utils/validate"
import { vscode } from "../utils/vscode"
import ApiOptions from "./ApiOptions"
@@ -14,47 +14,35 @@ import ApiOptions from "./ApiOptions"
const IS_DEV = false // FIXME: use flags when packaging
type SettingsViewProps = {
version: string
apiConfiguration?: ApiConfiguration
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
koduCredits?: number
maxRequestsPerTask: string
setMaxRequestsPerTask: React.Dispatch<React.SetStateAction<string>>
customInstructions: string
setCustomInstructions: React.Dispatch<React.SetStateAction<string>>
onDone: () => void
alwaysAllowReadOnly: boolean
setAlwaysAllowReadOnly: React.Dispatch<React.SetStateAction<boolean>>
vscodeUriScheme?: string
}
const SettingsView = ({
version,
apiConfiguration,
setApiConfiguration,
koduCredits,
maxRequestsPerTask,
setMaxRequestsPerTask,
customInstructions,
setCustomInstructions,
onDone,
alwaysAllowReadOnly,
setAlwaysAllowReadOnly,
vscodeUriScheme,
}: SettingsViewProps) => {
const SettingsView = ({ onDone }: SettingsViewProps) => {
const {
apiConfiguration,
version,
maxRequestsPerTask,
customInstructions,
setCustomInstructions,
alwaysAllowReadOnly,
setAlwaysAllowReadOnly,
} = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const [maxRequestsErrorMessage, setMaxRequestsErrorMessage] = useState<string | undefined>(undefined)
const [maxRequestsPerTaskString, setMaxRequestsPerTaskString] = useState<string>(
maxRequestsPerTask?.toString() || ""
)
const handleSubmit = () => {
const apiValidationResult = validateApiConfiguration(apiConfiguration)
const maxRequestsValidationResult = validateMaxRequestsPerTask(maxRequestsPerTask)
const maxRequestsValidationResult = validateMaxRequestsPerTask(maxRequestsPerTaskString)
setApiErrorMessage(apiValidationResult)
setMaxRequestsErrorMessage(maxRequestsValidationResult)
if (!apiValidationResult && !maxRequestsValidationResult) {
vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
vscode.postMessage({ type: "maxRequestsPerTask", text: maxRequestsPerTask })
vscode.postMessage({ type: "maxRequestsPerTask", text: maxRequestsPerTaskString })
vscode.postMessage({ type: "customInstructions", text: customInstructions })
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly })
onDone()
@@ -112,14 +100,7 @@ const SettingsView = ({
<div
style={{ flexGrow: 1, overflowY: "scroll", paddingRight: 8, display: "flex", flexDirection: "column" }}>
<div style={{ marginBottom: 5 }}>
<ApiOptions
apiConfiguration={apiConfiguration}
setApiConfiguration={setApiConfiguration}
showModelOptions={true}
koduCredits={koduCredits}
apiErrorMessage={apiErrorMessage}
vscodeUriScheme={vscodeUriScheme}
/>
<ApiOptions showModelOptions={true} apiErrorMessage={apiErrorMessage} />
</div>
<div style={{ marginBottom: 5 }}>
@@ -141,13 +122,13 @@ const SettingsView = ({
<div style={{ marginBottom: 5 }}>
<VSCodeTextArea
value={customInstructions}
value={customInstructions ?? ""}
style={{ width: "100%" }}
rows={4}
placeholder={
'e.g. "Run unit tests at the end", "Use TypeScript with async/await", "Speak in Spanish"'
}
onInput={(e: any) => setCustomInstructions(e.target?.value || "")}>
onInput={(e: any) => setCustomInstructions(e.target?.value ?? "")}>
<span style={{ fontWeight: "500" }}>Custom Instructions</span>
</VSCodeTextArea>
<p
@@ -162,10 +143,10 @@ const SettingsView = ({
<div>
<VSCodeTextField
value={maxRequestsPerTask}
value={maxRequestsPerTaskString}
style={{ width: "100%" }}
placeholder="20"
onInput={(e: any) => setMaxRequestsPerTask(e.target?.value)}>
onInput={(e: any) => setMaxRequestsPerTaskString(e.target?.value ?? "")}>
<span style={{ fontWeight: "500" }}>Maximum # Requests Per Task</span>
</VSCodeTextField>
<p

View File

@@ -1,24 +1,18 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import React, { useEffect, useState } from "react"
import { ApiConfiguration } from "../../../src/shared/api"
import { getKoduSignInUrl } from "../../../src/shared/kodu"
import { useExtensionState } from "../context/ExtensionStateContext"
import { validateApiConfiguration } from "../utils/validate"
import { vscode } from "../utils/vscode"
import ApiOptions from "./ApiOptions"
import { getKoduSignInUrl } from "../../../src/shared/kodu"
interface WelcomeViewProps {
apiConfiguration?: ApiConfiguration
setApiConfiguration: React.Dispatch<React.SetStateAction<ApiConfiguration | undefined>>
vscodeUriScheme?: string
setDidAuthKoduFromWelcome: React.Dispatch<React.SetStateAction<boolean>>
}
const WelcomeView: React.FC<WelcomeViewProps> = ({
apiConfiguration,
setApiConfiguration,
vscodeUriScheme,
setDidAuthKoduFromWelcome,
}) => {
const WelcomeView: React.FC<WelcomeViewProps> = ({ setDidAuthKoduFromWelcome }) => {
const { apiConfiguration, uriScheme } = useExtensionState()
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
const disableLetsGoButton = apiErrorMessage != null
@@ -67,20 +61,14 @@ const WelcomeView: React.FC<WelcomeViewProps> = ({
}}></i>
<span>
Explore Claude's capabilities with $20 free credits from{" "}
<VSCodeLink href={getKoduSignInUrl(vscodeUriScheme)} style={{ display: "inline" }}>
<VSCodeLink href={getKoduSignInUrl(uriScheme)} style={{ display: "inline" }}>
Kodu
</VSCodeLink>
</span>
</div>
<div style={{ marginTop: "10px" }}>
<ApiOptions
apiConfiguration={apiConfiguration}
setApiConfiguration={setApiConfiguration}
showModelOptions={false}
vscodeUriScheme={vscodeUriScheme}
setDidAuthKodu={setDidAuthKoduFromWelcome}
/>
<ApiOptions showModelOptions={false} setDidAuthKodu={setDidAuthKoduFromWelcome} />
{apiConfiguration?.apiProvider !== "kodu" && (
<VSCodeButton onClick={handleSubmit} disabled={disableLetsGoButton} style={{ marginTop: "3px" }}>
Let's go!