feat: fix UI performance issue during long tasks with incremental message updates

- Add incrementalStateUpdate message type to ExtensionMessage.ts
- Implement postIncrementalUpdateToWebview() method in ClineProvider.ts
- Update Task.ts to use incremental updates instead of full state posts
- Add webview handler for incremental updates in ExtensionStateContext.tsx
- Maintain backward compatibility for initial loads and reconnections
- Reduces data transfer from O(n²) to O(1) per message update

Fixes #6513
This commit is contained in:
Roo Code 2025-07-31 21:52:47 +00:00
parent 5041880da0
commit a373d6cb04
4 changed files with 84 additions and 3 deletions

View file

@ -544,7 +544,12 @@ export class Task extends EventEmitter<TaskEvents> {
private async addToClineMessages(message: ClineMessage) {
this.clineMessages.push(message)
const provider = this.providerRef.deref()
await provider?.postStateToWebview()
// Use incremental update instead of posting full state for better performance
await provider?.postIncrementalUpdateToWebview({
newMessages: [message],
})
this.emit("message", { action: "created", message })
await this.saveClineMessages()
@ -566,7 +571,12 @@ export class Task extends EventEmitter<TaskEvents> {
private async updateClineMessage(message: ClineMessage) {
const provider = this.providerRef.deref()
await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message })
// Use incremental update for message updates as well
await provider?.postIncrementalUpdateToWebview({
updatedMessages: [message],
})
this.emit("message", { action: "updated", message })
const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled()
@ -598,7 +608,13 @@ export class Task extends EventEmitter<TaskEvents> {
this.emit("taskTokenUsageUpdated", this.taskId, tokenUsage)
await this.providerRef.deref()?.updateTaskHistory(historyItem)
const provider = this.providerRef.deref()
await provider?.updateTaskHistory(historyItem)
// Send incremental update for task history
await provider?.postIncrementalUpdateToWebview({
taskHistoryUpdate: historyItem,
})
} catch (error) {
console.error("Failed to save Roo messages:", error)
}
@ -926,6 +942,7 @@ export class Task extends EventEmitter<TaskEvents> {
// messages from previous session).
this.clineMessages = []
this.apiConversationHistory = []
// Use full state update for initial task start
await this.providerRef.deref()?.postStateToWebview()
await this.say("text", task, images)

View file

@ -1340,6 +1340,22 @@ export class ClineProvider
}
}
/**
* Posts incremental updates to the webview instead of the full state.
* This significantly improves performance for long conversations by only
* sending new or updated messages instead of the entire conversation history.
*/
async postIncrementalUpdateToWebview(options: {
newMessages?: import("@roo-code/types").ClineMessage[]
updatedMessages?: import("@roo-code/types").ClineMessage[]
taskHistoryUpdate?: import("@roo-code/types").HistoryItem
}) {
await this.postMessageToWebview({
type: "incrementalStateUpdate",
incrementalUpdate: options,
})
}
/**
* Fetches marketplace data on demand to avoid blocking main state updates
*/

View file

@ -62,6 +62,7 @@ export interface ExtensionMessage {
type:
| "action"
| "state"
| "incrementalStateUpdate"
| "selectedImages"
| "theme"
| "workspaceUpdated"
@ -135,6 +136,11 @@ export interface ExtensionMessage {
| "switchTab"
invoke?: "newChat" | "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage"
state?: ExtensionState
incrementalUpdate?: {
newMessages?: ClineMessage[]
updatedMessages?: ClineMessage[]
taskHistoryUpdate?: HistoryItem
}
images?: string[]
filePaths?: string[]
openedTabs?: Array<{

View file

@ -312,6 +312,48 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
}
break
}
case "incrementalStateUpdate": {
const update = message.incrementalUpdate!
setState((prevState) => {
let newClineMessages = [...prevState.clineMessages]
// Add new messages
if (update.newMessages) {
newClineMessages.push(...update.newMessages)
}
// Update existing messages
if (update.updatedMessages) {
for (const updatedMessage of update.updatedMessages) {
const index = findLastIndex(newClineMessages, (msg) => msg.ts === updatedMessage.ts)
if (index !== -1) {
newClineMessages[index] = updatedMessage
}
}
}
// Update task history if provided
let newTaskHistory = prevState.taskHistory
if (update.taskHistoryUpdate) {
const historyIndex = newTaskHistory.findIndex(
(item) => item.id === update.taskHistoryUpdate!.id,
)
if (historyIndex !== -1) {
newTaskHistory = [...newTaskHistory]
newTaskHistory[historyIndex] = update.taskHistoryUpdate
} else {
newTaskHistory = [...newTaskHistory, update.taskHistoryUpdate]
}
}
return {
...prevState,
clineMessages: newClineMessages,
taskHistory: newTaskHistory,
}
})
break
}
case "theme": {
if (message.text) {
setTheme(convertTextMateToHljs(JSON.parse(message.text)))