mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 20:31:37 -05:00
Performance optimizations with memoization and useCallbacks
This commit is contained in:
@@ -215,7 +215,7 @@ const ChatView = ({
|
||||
}
|
||||
}, [messages.length])
|
||||
|
||||
const handleSendMessage = () => {
|
||||
const handleSendMessage = useCallback(() => {
|
||||
const text = inputValue.trim()
|
||||
if (text || selectedImages.length > 0) {
|
||||
if (messages.length === 0) {
|
||||
@@ -248,26 +248,33 @@ const ChatView = ({
|
||||
// setPrimaryButtonText(undefined)
|
||||
// setSecondaryButtonText(undefined)
|
||||
}
|
||||
}
|
||||
}, [inputValue, selectedImages, messages.length, claudeAsk])
|
||||
|
||||
const handleSendStdin = (text: string) => {
|
||||
if (claudeAsk === "command_output") {
|
||||
vscode.postMessage({
|
||||
type: "askResponse",
|
||||
askResponse: "messageResponse",
|
||||
text: COMMAND_STDIN_STRING + text,
|
||||
})
|
||||
setClaudeAsk(undefined)
|
||||
// don't need to disable since extension relinquishes control back immediately
|
||||
// setTextAreaDisabled(true)
|
||||
// setEnableButtons(false)
|
||||
}
|
||||
}
|
||||
const handleSendStdin = useCallback(
|
||||
(text: string) => {
|
||||
if (claudeAsk === "command_output") {
|
||||
vscode.postMessage({
|
||||
type: "askResponse",
|
||||
askResponse: "messageResponse",
|
||||
text: COMMAND_STDIN_STRING + text,
|
||||
})
|
||||
setClaudeAsk(undefined)
|
||||
// don't need to disable since extension relinquishes control back immediately
|
||||
// setTextAreaDisabled(true)
|
||||
// setEnableButtons(false)
|
||||
}
|
||||
},
|
||||
[claudeAsk]
|
||||
)
|
||||
|
||||
const startNewTask = useCallback(() => {
|
||||
vscode.postMessage({ type: "clearTask" })
|
||||
}, [])
|
||||
|
||||
/*
|
||||
This logic depends on the useEffect[messages] above to set claudeAsk, after which buttons are shown and we then send an askResponse to the extension.
|
||||
*/
|
||||
const handlePrimaryButtonClick = () => {
|
||||
const handlePrimaryButtonClick = useCallback(() => {
|
||||
switch (claudeAsk) {
|
||||
case "api_req_failed":
|
||||
case "command":
|
||||
@@ -288,9 +295,9 @@ const ChatView = ({
|
||||
setEnableButtons(false)
|
||||
// setPrimaryButtonText(undefined)
|
||||
// setSecondaryButtonText(undefined)
|
||||
}
|
||||
}, [claudeAsk, startNewTask])
|
||||
|
||||
const handleSecondaryButtonClick = () => {
|
||||
const handleSecondaryButtonClick = useCallback(() => {
|
||||
switch (claudeAsk) {
|
||||
case "api_req_failed":
|
||||
case "mistake_limit_reached":
|
||||
@@ -307,67 +314,72 @@ const ChatView = ({
|
||||
setEnableButtons(false)
|
||||
// setPrimaryButtonText(undefined)
|
||||
// setSecondaryButtonText(undefined)
|
||||
}
|
||||
}, [claudeAsk, startNewTask])
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const isComposing = event.nativeEvent?.isComposing ?? false
|
||||
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
|
||||
event.preventDefault()
|
||||
handleSendMessage()
|
||||
}
|
||||
}
|
||||
|
||||
const handleTaskCloseButtonClick = () => {
|
||||
startNewTask()
|
||||
}
|
||||
|
||||
const startNewTask = () => {
|
||||
vscode.postMessage({ type: "clearTask" })
|
||||
}
|
||||
|
||||
const selectImages = () => {
|
||||
vscode.postMessage({ type: "selectImages" })
|
||||
}
|
||||
|
||||
const handlePaste = async (e: React.ClipboardEvent) => {
|
||||
const items = e.clipboardData.items
|
||||
const acceptedTypes = ["png", "jpeg", "webp"] // supported by anthropic and openrouter (jpg is just a file extension but the image will be recognized as jpeg)
|
||||
const imageItems = Array.from(items).filter((item) => {
|
||||
const [type, subtype] = item.type.split("/")
|
||||
return type === "image" && acceptedTypes.includes(subtype)
|
||||
})
|
||||
if (!shouldDisableImages && imageItems.length > 0) {
|
||||
e.preventDefault()
|
||||
const imagePromises = imageItems.map((item) => {
|
||||
return new Promise<string | null>((resolve) => {
|
||||
const blob = item.getAsFile()
|
||||
if (!blob) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
if (reader.error) {
|
||||
console.error("Error reading file:", reader.error)
|
||||
resolve(null)
|
||||
} else {
|
||||
const result = reader.result
|
||||
resolve(typeof result === "string" ? result : null)
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
})
|
||||
const imageDataArray = await Promise.all(imagePromises)
|
||||
const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
|
||||
//.map((dataUrl) => dataUrl.split(",")[1]) // strip the mime type prefix, sharp doesn't need it
|
||||
if (dataUrls.length > 0) {
|
||||
setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE))
|
||||
} else {
|
||||
console.warn("No valid images were processed")
|
||||
const handleKeyDown = useCallback(
|
||||
(event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const isComposing = event.nativeEvent?.isComposing ?? false
|
||||
if (event.key === "Enter" && !event.shiftKey && !isComposing) {
|
||||
event.preventDefault()
|
||||
handleSendMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[handleSendMessage]
|
||||
)
|
||||
|
||||
const handleTaskCloseButtonClick = useCallback(() => {
|
||||
startNewTask()
|
||||
}, [startNewTask])
|
||||
|
||||
const selectImages = useCallback(() => {
|
||||
vscode.postMessage({ type: "selectImages" })
|
||||
}, [])
|
||||
|
||||
const shouldDisableImages =
|
||||
!selectedModelSupportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
|
||||
|
||||
const handlePaste = useCallback(
|
||||
async (e: React.ClipboardEvent) => {
|
||||
const items = e.clipboardData.items
|
||||
const acceptedTypes = ["png", "jpeg", "webp"] // supported by anthropic and openrouter (jpg is just a file extension but the image will be recognized as jpeg)
|
||||
const imageItems = Array.from(items).filter((item) => {
|
||||
const [type, subtype] = item.type.split("/")
|
||||
return type === "image" && acceptedTypes.includes(subtype)
|
||||
})
|
||||
if (!shouldDisableImages && imageItems.length > 0) {
|
||||
e.preventDefault()
|
||||
const imagePromises = imageItems.map((item) => {
|
||||
return new Promise<string | null>((resolve) => {
|
||||
const blob = item.getAsFile()
|
||||
if (!blob) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
if (reader.error) {
|
||||
console.error("Error reading file:", reader.error)
|
||||
resolve(null)
|
||||
} else {
|
||||
const result = reader.result
|
||||
resolve(typeof result === "string" ? result : null)
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
})
|
||||
const imageDataArray = await Promise.all(imagePromises)
|
||||
const dataUrls = imageDataArray.filter((dataUrl): dataUrl is string => dataUrl !== null)
|
||||
//.map((dataUrl) => dataUrl.split(",")[1]) // strip the mime type prefix, sharp doesn't need it
|
||||
if (dataUrls.length > 0) {
|
||||
setSelectedImages((prevImages) => [...prevImages, ...dataUrls].slice(0, MAX_IMAGES_PER_MESSAGE))
|
||||
} else {
|
||||
console.warn("No valid images were processed")
|
||||
}
|
||||
}
|
||||
},
|
||||
[shouldDisableImages, setSelectedImages]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedImages.length === 0) {
|
||||
@@ -469,8 +481,21 @@ const ChatView = ({
|
||||
return text
|
||||
}, [task])
|
||||
|
||||
const shouldDisableImages =
|
||||
!selectedModelSupportsImages || textAreaDisabled || selectedImages.length >= MAX_IMAGES_PER_MESSAGE
|
||||
const itemContent = useCallback(
|
||||
(index: number, message: any) => (
|
||||
<ChatRow
|
||||
key={message.ts}
|
||||
message={message}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={expandedRows[message.ts] || false}
|
||||
onToggleExpand={() => toggleRowExpansion(message.ts)}
|
||||
lastModifiedMessage={modifiedMessages.at(-1)}
|
||||
isLast={index === visibleMessages.length - 1}
|
||||
handleSendStdin={handleSendStdin}
|
||||
/>
|
||||
),
|
||||
[expandedRows, syntaxHighlighterStyle, modifiedMessages, visibleMessages.length, handleSendStdin]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -540,18 +565,7 @@ const ChatView = ({
|
||||
// }}
|
||||
increaseViewportBy={{ top: 0, bottom: Number.MAX_SAFE_INTEGER }} // hack to make sure the last message is always rendered to get truly perfect scroll to bottom animation when new messages are added (Number.MAX_SAFE_INTEGER is safe for arithmetic operations, which is all virtuoso uses this value for in src/sizeRangeSystem.ts)
|
||||
data={visibleMessages} // messages is the raw format returned by extension, modifiedMessages is the manipulated structure that combines certain messages of related type, and visibleMessages is the filtered structure that removes messages that should not be rendered
|
||||
itemContent={(index, message) => (
|
||||
<ChatRow
|
||||
key={message.ts}
|
||||
message={message}
|
||||
syntaxHighlighterStyle={syntaxHighlighterStyle}
|
||||
isExpanded={expandedRows[message.ts] || false}
|
||||
onToggleExpand={() => toggleRowExpansion(message.ts)}
|
||||
lastModifiedMessage={modifiedMessages.at(-1)}
|
||||
isLast={index === visibleMessages.length - 1}
|
||||
handleSendStdin={handleSendStdin}
|
||||
/>
|
||||
)}
|
||||
itemContent={itemContent}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user