Skip to content

Commit 336cccc

Browse files
committed
feat: implement incremental message updates for improved UI performance
- Modified Task.ts to send only new messages instead of entire state - Added messageCreated and messageUpdated event types to ExtensionMessage - Updated webview handler to process incremental message updates - Enhanced postStateToWebview to handle existing tasks with incremental loading - Improved performance by reducing data transfer and UI re-renders This change significantly improves UI responsiveness, especially for tasks with many messages.
1 parent 5041880 commit 336cccc

File tree

4 files changed

+51
-4
lines changed

4 files changed

+51
-4
lines changed

src/core/task/Task.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,8 @@ export class Task extends EventEmitter<TaskEvents> {
544544
private async addToClineMessages(message: ClineMessage) {
545545
this.clineMessages.push(message)
546546
const provider = this.providerRef.deref()
547-
await provider?.postStateToWebview()
547+
// Send only the new message instead of the entire state
548+
await provider?.postMessageToWebview({ type: "messageCreated", clineMessage: message })
548549
this.emit("message", { action: "created", message })
549550
await this.saveClineMessages()
550551

@@ -1006,6 +1007,9 @@ export class Task extends EventEmitter<TaskEvents> {
10061007
// the task first.
10071008
this.apiConversationHistory = await this.getSavedApiConversationHistory()
10081009

1010+
// Send initial state to webview (this will now handle incremental message loading)
1011+
await this.providerRef.deref()?.postStateToWebview()
1012+
10091013
const lastClineMessage = this.clineMessages
10101014
.slice()
10111015
.reverse()
@@ -1443,7 +1447,13 @@ export class Task extends EventEmitter<TaskEvents> {
14431447
} satisfies ClineApiReqInfo)
14441448

14451449
await this.saveClineMessages()
1446-
await provider?.postStateToWebview()
1450+
// Send only the updated API request message instead of the entire state
1451+
if (provider && lastApiReqIndex >= 0) {
1452+
await provider.postMessageToWebview({
1453+
type: "messageUpdated",
1454+
clineMessage: this.clineMessages[lastApiReqIndex],
1455+
})
1456+
}
14471457

14481458
try {
14491459
let cacheWriteTokens = 0
@@ -1642,6 +1652,7 @@ export class Task extends EventEmitter<TaskEvents> {
16421652

16431653
await abortStream(cancelReason, streamingFailedMessage)
16441654

1655+
const provider = this.providerRef.deref()
16451656
const history = await provider?.getTaskWithId(this.taskId)
16461657

16471658
if (history) {
@@ -1652,6 +1663,8 @@ export class Task extends EventEmitter<TaskEvents> {
16521663
this.isStreaming = false
16531664
}
16541665

1666+
const provider = this.providerRef.deref()
1667+
16551668
if (inputTokens > 0 || outputTokens > 0 || cacheWriteTokens > 0 || cacheReadTokens > 0) {
16561669
TelemetryService.instance.captureLlmCompletion(this.taskId, {
16571670
inputTokens,
@@ -1701,7 +1714,13 @@ export class Task extends EventEmitter<TaskEvents> {
17011714

17021715
updateApiReqMsg()
17031716
await this.saveClineMessages()
1704-
await this.providerRef.deref()?.postStateToWebview()
1717+
// Send only the updated API request message instead of the entire state
1718+
if (provider && lastApiReqIndex >= 0) {
1719+
await provider.postMessageToWebview({
1720+
type: "messageUpdated",
1721+
clineMessage: this.clineMessages[lastApiReqIndex],
1722+
})
1723+
}
17051724

17061725
// Now add to apiConversationHistory.
17071726
// Need to save assistant responses to file before proceeding to

src/core/webview/ClineProvider.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1332,7 +1332,26 @@ export class ClineProvider
13321332

13331333
async postStateToWebview() {
13341334
const state = await this.getStateToPostToWebview()
1335-
this.postMessageToWebview({ type: "state", state })
1335+
1336+
// Check if we're loading an existing task with messages
1337+
const currentCline = this.getCurrentCline()
1338+
const hasExistingMessages = currentCline && currentCline.clineMessages.length > 0
1339+
1340+
if (hasExistingMessages) {
1341+
// Send state without messages first
1342+
const stateWithoutMessages = { ...state, clineMessages: [] }
1343+
await this.postMessageToWebview({ type: "state", state: stateWithoutMessages })
1344+
1345+
// Then send messages incrementally with a small delay for smooth rendering
1346+
for (const message of currentCline.clineMessages) {
1347+
await this.postMessageToWebview({ type: "messageCreated", clineMessage: message })
1348+
// Small delay to prevent overwhelming the webview
1349+
await delay(10)
1350+
}
1351+
} else {
1352+
// Normal state update for new tasks or tasks without messages
1353+
await this.postMessageToWebview({ type: "state", state })
1354+
}
13361355

13371356
// Check MDM compliance and send user to account tab if not compliant
13381357
if (!this.checkMdmCompliance()) {

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export interface ExtensionMessage {
6767
| "workspaceUpdated"
6868
| "invoke"
6969
| "messageUpdated"
70+
| "messageCreated"
7071
| "mcpServers"
7172
| "enhancedPrompt"
7273
| "commitSearchResults"

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,14 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
330330
setCommands(message.commands ?? [])
331331
break
332332
}
333+
case "messageCreated": {
334+
const clineMessage = message.clineMessage!
335+
setState((prevState) => ({
336+
...prevState,
337+
clineMessages: [...prevState.clineMessages, clineMessage],
338+
}))
339+
break
340+
}
333341
case "messageUpdated": {
334342
const clineMessage = message.clineMessage!
335343
setState((prevState) => {

0 commit comments

Comments
 (0)