Deep compare chatrow props to update when message changes

This commit is contained in:
Saoud Rizwan
2024-09-07 04:57:38 -04:00
parent bc9fdd1fba
commit 715db7129d
7 changed files with 32 additions and 46 deletions

View File

@@ -16,6 +16,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vscode/webview-ui-toolkit": "^1.4.0",
"fast-deep-equal": "^3.1.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",

View File

@@ -11,6 +11,7 @@
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vscode/webview-ui-toolkit": "^1.4.0",
"fast-deep-equal": "^3.1.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-markdown": "^9.0.1",

View File

@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from "react"
import { useCallback, useEffect, useState } from "react"
import { useEvent } from "react-use"
import { ExtensionMessage } from "../../src/shared/ExtensionMessage"
import { normalizeApiConfiguration } from "./components/ApiOptions"
import ChatView from "./components/ChatView"
import HistoryView from "./components/HistoryView"
import SettingsView from "./components/SettingsView"
@@ -10,7 +9,7 @@ import { ExtensionStateContextProvider, useExtensionState } from "./context/Exte
import { vscode } from "./utils/vscode"
const AppContent = () => {
const { didHydrateState, showWelcome, apiConfiguration, shouldShowAnnouncement } = useExtensionState()
const { didHydrateState, showWelcome, shouldShowAnnouncement } = useExtensionState()
const [showSettings, setShowSettings] = useState(false)
const [showHistory, setShowHistory] = useState(false)
const [showAnnouncement, setShowAnnouncement] = useState(false)
@@ -39,10 +38,6 @@ const AppContent = () => {
useEvent("message", handleMessage)
const { selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
useEffect(() => {
if (shouldShowAnnouncement) {
setShowAnnouncement(true)
@@ -70,8 +65,6 @@ const AppContent = () => {
}}
isHidden={showSettings || showHistory}
showAnnouncement={showAnnouncement}
selectedModelSupportsImages={selectedModelInfo.supportsImages}
selectedModelSupportsPromptCache={selectedModelInfo.supportsPromptCache}
hideAnnouncement={() => {
setShowAnnouncement(false)
}}

View File

@@ -1,5 +1,4 @@
import { VSCodeButton, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { ApiConfiguration } from "../../../src/shared/api"
import { memo } from "react"
// import VSCodeButtonLink from "./VSCodeButtonLink"
// import { getOpenRouterAuthUrl } from "./ApiOptions"
@@ -8,13 +7,11 @@ import { memo } from "react"
interface AnnouncementProps {
version: string
hideAnnouncement: () => void
apiConfiguration?: ApiConfiguration
vscodeUriScheme?: string
}
/*
You must update the latestAnnouncementId in ClaudeDevProvider for new announcements to show to users. This new id will be compared with whats in state for the 'last announcement shown', and if it's different then the announcement will render. As soon as an announcement is shown, the id will be updated in state. This ensures that announcements are not shown more than once, even if the user doesn't close it themselves.
*/
const Announcement = ({ version, hideAnnouncement, apiConfiguration, vscodeUriScheme }: AnnouncementProps) => {
const Announcement = ({ version, hideAnnouncement }: AnnouncementProps) => {
return (
<div
style={{

View File

@@ -7,6 +7,7 @@ import CodeAccordian from "./CodeAccordian"
import CodeBlock from "./CodeBlock"
import Terminal from "./Terminal"
import Thumbnails from "./Thumbnails"
import deepEqual from "fast-deep-equal"
interface ChatRowProps {
message: ClaudeMessage
@@ -17,17 +18,21 @@ interface ChatRowProps {
handleSendStdin: (text: string) => void
}
const ChatRow = memo((props: ChatRowProps) => {
// we cannot return null as virtuoso does not support it, so we use a separate visibleMessages array to filter out messages that should not be rendered
return (
<div
style={{
padding: "10px 6px 10px 15px",
}}>
<ChatRowContent {...props} />
</div>
)
})
const ChatRow = memo(
(props: ChatRowProps) => {
// we cannot return null as virtuoso does not support it, so we use a separate visibleMessages array to filter out messages that should not be rendered
return (
<div
style={{
padding: "10px 6px 10px 15px",
}}>
<ChatRowContent {...props} />
</div>
)
},
// memo does shallow comparison of props, so we need to do deep comparison of arrays/objects whose properties might change
deepEqual
)
export default ChatRow

View File

@@ -14,27 +14,19 @@ import ChatRow from "./ChatRow"
import HistoryPreview from "./HistoryPreview"
import TaskHeader from "./TaskHeader"
import Thumbnails from "./Thumbnails"
import { normalizeApiConfiguration } from "./ApiOptions"
interface ChatViewProps {
isHidden: boolean
showAnnouncement: boolean
selectedModelSupportsImages: boolean
selectedModelSupportsPromptCache: boolean
hideAnnouncement: () => void
showHistoryView: () => void
}
const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
const ChatView = ({
isHidden,
showAnnouncement,
selectedModelSupportsImages,
selectedModelSupportsPromptCache,
hideAnnouncement,
showHistoryView,
}: ChatViewProps) => {
const { version, claudeMessages: messages, taskHistory, apiConfiguration, uriScheme } = useExtensionState()
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
const { version, claudeMessages: messages, taskHistory, apiConfiguration } = 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)
@@ -313,12 +305,16 @@ const ChatView = ({
startNewTask()
}, [startNewTask])
const { selectedModelInfo } = useMemo(() => {
return normalizeApiConfiguration(apiConfiguration)
}, [apiConfiguration])
const selectImages = useCallback(() => {
vscode.postMessage({ type: "selectImages" })
}, [])
const shouldDisableImages =
!selectedModelSupportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
!selectedModelInfo.supportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
const handlePaste = useCallback(
async (e: React.ClipboardEvent) => {
@@ -495,7 +491,7 @@ const ChatView = ({
task={task}
tokensIn={apiMetrics.totalTokensIn}
tokensOut={apiMetrics.totalTokensOut}
doesModelSupportPromptCache={selectedModelSupportsPromptCache}
doesModelSupportPromptCache={selectedModelInfo.supportsPromptCache}
cacheWrites={apiMetrics.totalCacheWrites}
cacheReads={apiMetrics.totalCacheReads}
totalCost={apiMetrics.totalCost}
@@ -503,14 +499,7 @@ const ChatView = ({
/>
) : (
<>
{showAnnouncement && (
<Announcement
version={version}
hideAnnouncement={hideAnnouncement}
apiConfiguration={apiConfiguration}
vscodeUriScheme={uriScheme}
/>
)}
{showAnnouncement && <Announcement version={version} hideAnnouncement={hideAnnouncement} />}
<div style={{ padding: "0 20px", flexGrow: taskHistory.length > 0 ? undefined : 1 }}>
<h2>What can I do for you?</h2>
<p>

View File

@@ -80,7 +80,7 @@ const StyledPre = styled.pre<{ theme: any }>`
.join("")}
`
const CodeBlock = memo(function CodeBlock({ source }: { source?: string }) {
const CodeBlock = memo(({ source }: { source?: string }) => {
const { theme } = useExtensionState()
const [reactContent, setMarkdownSource] = useRemark({
remarkPlugins: [