Skip to content

Commit 59df63f

Browse files
authored
Fix performance of chat panel during streaming (#3020)
* Still not perfect, something struggling during tool edit streaming * Pin bun version to avoid issues with bun 1.3 * Try bun 1.2.23
1 parent 26b24be commit 59df63f

File tree

10 files changed

+157
-154
lines changed

10 files changed

+157
-154
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ jobs:
1717

1818
# - uses: oven-sh/setup-bun@v1
1919
# with:
20-
# bun-version: latest
21-
20+
# bun-version: 1.2.21
2221
# - name: Cache dependencies
2322
# uses: actions/cache@v4
2423
# with:
@@ -41,7 +40,7 @@ jobs:
4140

4241
- uses: oven-sh/setup-bun@v1
4342
with:
44-
bun-version: latest
43+
bun-version: 1.2.21
4544

4645
- name: Cache dependencies
4746
uses: actions/cache@v4
@@ -65,7 +64,7 @@ jobs:
6564

6665
- uses: oven-sh/setup-bun@v1
6766
with:
68-
bun-version: latest
67+
bun-version: 1.2.21
6968

7069
- name: Cache dependencies
7170
uses: actions/cache@v4

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/assistant-message.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import type { ChatMessage } from '@onlook/models';
2+
import { observer } from 'mobx-react-lite';
3+
import { memo } from 'react';
24
import { MessageContent } from './message-content';
35

4-
export const AssistantMessage = ({ message, isStreaming }: { message: ChatMessage, isStreaming: boolean }) => {
6+
const AssistantMessageComponent = ({ message, isStreaming }: { message: ChatMessage, isStreaming: boolean }) => {
57
return (
68
<div className="px-4 py-2 text-small content-start flex flex-col text-wrap gap-2">
79
<MessageContent
@@ -13,3 +15,5 @@ export const AssistantMessage = ({ message, isStreaming }: { message: ChatMessag
1315
</div>
1416
);
1517
};
18+
19+
export const AssistantMessage = memo(observer(AssistantMessageComponent));

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/index.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import type { ChatMessage } from '@onlook/models';
22
import { Reasoning, ReasoningContent, ReasoningTrigger, Response } from '@onlook/ui/ai-elements';
33
import { cn } from '@onlook/ui/utils';
44
import type { ToolUIPart } from 'ai';
5+
import { observer } from 'mobx-react-lite';
56
import { ToolCallDisplay } from './tool-call-display';
67

7-
export const MessageContent = ({
8+
const MessageContentComponent = ({
89
messageId,
910
parts,
1011
applied,
@@ -69,3 +70,5 @@ export const MessageContent = ({
6970
</div>
7071
);
7172
};
73+
74+
export const MessageContent = observer(MessageContentComponent);

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-display.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { FuzzyEditFileTool, SearchReplaceEditTool, SearchReplaceMultiEditFileTool, TerminalCommandTool, TypecheckTool, WebSearchTool, WriteFileTool } from '@onlook/ai';
22
import type { WebSearchResult } from '@onlook/models';
33
import type { ToolUIPart } from 'ai';
4+
import { observer } from 'mobx-react-lite';
45
import stripAnsi from 'strip-ansi';
56
import { type z } from 'zod';
67
import { BashCodeDisplay } from '../../code-display/bash-code-display';
78
import { CollapsibleCodeBlock } from '../../code-display/collapsible-code-block';
89
import { SearchSourcesDisplay } from '../../code-display/search-sources-display';
910
import { ToolCallSimple } from './tool-call-simple';
1011

11-
export const ToolCallDisplay = ({
12+
const ToolCallDisplayComponent = ({
1213
messageId,
1314
toolPart,
1415
isStream,
@@ -224,4 +225,6 @@ export const ToolCallDisplay = ({
224225
key={toolPart.toolCallId}
225226
/>
226227
);
227-
}
228+
};
229+
230+
export const ToolCallDisplay = observer(ToolCallDisplayComponent);

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/message-content/tool-call-simple.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import { BaseTool, TOOLS_MAP } from '@onlook/ai';
22
import { Tool, ToolContent, ToolHeader, ToolInput, ToolOutput } from '@onlook/ui/ai-elements';
33
import { Icons } from '@onlook/ui/icons';
44
import type { ToolUIPart } from 'ai';
5+
import { memo } from 'react';
56

6-
export function ToolCallSimple({
7+
const ToolCallSimpleComponent = ({
78
toolPart,
89
className,
910
loading,
1011
}: {
1112
toolPart: ToolUIPart;
1213
className?: string;
1314
loading?: boolean;
14-
}) {
15+
}) => {
1516
const toolName = toolPart.type.split('-')[1] ?? '';
1617
const ToolClass = TOOLS_MAP.get(toolName);
1718
const Icon = ToolClass?.icon ?? Icons.QuestionMarkCircled;
@@ -21,12 +22,14 @@ export function ToolCallSimple({
2122
<Tool className={className}>
2223
<ToolHeader loading={loading} title={title} type={toolPart.type} state={toolPart.state} icon={<Icon className="w-4 h-4 flex-shrink-0" />} />
2324
<ToolContent>
24-
<ToolInput input={toolPart.input} />
25-
<ToolOutput errorText={toolPart.errorText} output={toolPart.output} />
25+
<ToolInput input={toolPart.input} isStreaming={loading} />
26+
<ToolOutput errorText={toolPart.errorText} output={toolPart.output} isStreaming={loading} />
2627
</ToolContent>
2728
</Tool>
28-
)
29-
}
29+
);
30+
};
31+
32+
export const ToolCallSimple = memo(ToolCallSimpleComponent);
3033

3134
function getDefaultToolLabel(toolName: string): string {
3235
return toolName?.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/chat-messages/user-message.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useRef, useState } from 'react';
1+
import React, { memo, useEffect, useRef, useState } from 'react';
22
import { nanoid } from 'nanoid';
33

44
import type { ChatMessage, GitMessageCheckpoint } from '@onlook/models';
@@ -21,6 +21,7 @@ import { cn } from '@onlook/ui/utils';
2121
import type { EditMessage } from '@/app/project/[id]/_hooks/use-chat';
2222
import { useEditorEngine } from '@/components/store/editor';
2323
import { restoreCheckpoint } from '@/components/store/editor/git';
24+
import { observer } from 'mobx-react-lite';
2425
import { SentContextPill } from '../context-pills/sent-context-pill';
2526
import { MessageContent } from './message-content';
2627
import { MultiBranchRevertModal } from './multi-branch-revert-modal';
@@ -41,7 +42,7 @@ export const getUserMessageContent = (message: ChatMessage) => {
4142
.join('');
4243
};
4344

44-
export const UserMessage = ({ onEditMessage, message }: UserMessageProps) => {
45+
const UserMessageComponent = ({ onEditMessage, message }: UserMessageProps) => {
4546
const editorEngine = useEditorEngine();
4647
const [isCopied, setIsCopied] = useState(false);
4748
const [isEditing, setIsEditing] = useState(false);
@@ -328,3 +329,5 @@ export const UserMessage = ({ onEditMessage, message }: UserMessageProps) => {
328329
</div>
329330
);
330331
};
332+
333+
export const UserMessage = memo(observer(UserMessageComponent));

apps/web/client/src/app/project/[id]/_components/right-panel/chat-tab/code-display/collapsible-code-block.tsx

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@onlook/ui/
55
import { Icons } from '@onlook/ui/icons';
66
import { cn, getTruncatedFileName } from '@onlook/ui/utils';
77
import { AnimatePresence, motion } from 'motion/react';
8-
import { useState } from 'react';
8+
import { observer } from 'mobx-react-lite';
9+
import { memo, useState } from 'react';
910

1011
interface CollapsibleCodeBlockProps {
1112
path: string;
@@ -16,7 +17,7 @@ interface CollapsibleCodeBlockProps {
1617
branchId?: string;
1718
}
1819

19-
export const CollapsibleCodeBlock = ({
20+
const CollapsibleCodeBlockComponent = ({
2021
path,
2122
content,
2223
isStream,
@@ -36,6 +37,10 @@ export const CollapsibleCodeBlock = ({
3637
return isOpen ? { height: 'auto', opacity: 1 } : { height: 0, opacity: 0 };
3738
};
3839

40+
const branch = branchId
41+
? editorEngine.branches.allBranches.find(b => b.id === branchId)
42+
: editorEngine.branches.activeBranch;
43+
3944
return (
4045
<div className="group relative">
4146
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
@@ -70,16 +75,11 @@ export const CollapsibleCodeBlock = ({
7075
)}
7176
>
7277
<span className="truncate flex-1 min-w-0">{getTruncatedFileName(path)}</span>
73-
{(() => {
74-
const branch = branchId
75-
? editorEngine.branches.allBranches.find(b => b.id === branchId)
76-
: editorEngine.branches.activeBranch;
77-
return branch && (
78-
<span className="text-foreground-tertiary group-hover:text-foreground-secondary text-mini ml-0.5 flex-shrink-0 truncate max-w-24">
79-
{' • '}{branch.name}
80-
</span>
81-
);
82-
})()}
78+
{branch && (
79+
<span className="text-foreground-tertiary group-hover:text-foreground-secondary text-mini ml-0.5 flex-shrink-0 truncate max-w-24">
80+
{' • '}{branch.name}
81+
</span>
82+
)}
8383
</div>
8484
</div>
8585
</CollapsibleTrigger>
@@ -93,28 +93,32 @@ export const CollapsibleCodeBlock = ({
9393
transition={{ duration: 0.2, ease: 'easeInOut' }}
9494
style={{ overflow: 'hidden' }}
9595
>
96-
<div className="border-t">
97-
<CodeBlock code={content} language="jsx" className="text-xs overflow-x-auto" />
98-
<div className="flex justify-end gap-1.5 p-1 border-t"> <Button
99-
size="sm"
100-
variant="ghost"
101-
className="h-7 px-2 text-foreground-secondary hover:text-foreground font-sans select-none"
102-
onClick={copyToClipboard}
103-
>
104-
{copied ? (
105-
<>
106-
<Icons.Check className="h-4 w-4 mr-2" />
107-
Copied
108-
</>
109-
) : (
110-
<>
111-
<Icons.Copy className="h-4 w-4 mr-2" />
112-
Copy
113-
</>
114-
)}
115-
</Button>
96+
{/* Only render this content when open to avoid rendering the expensive code block. */}
97+
{isOpen && (
98+
<div className="border-t">
99+
<CodeBlock code={content} language="jsx" isStreaming={isStream} className="text-xs overflow-x-auto" />
100+
<div className="flex justify-end gap-1.5 p-1 border-t">
101+
<Button
102+
size="sm"
103+
variant="ghost"
104+
className="h-7 px-2 text-foreground-secondary hover:text-foreground font-sans select-none"
105+
onClick={copyToClipboard}
106+
>
107+
{copied ? (
108+
<>
109+
<Icons.Check className="h-4 w-4 mr-2" />
110+
Copied
111+
</>
112+
) : (
113+
<>
114+
<Icons.Copy className="h-4 w-4 mr-2" />
115+
Copy
116+
</>
117+
)}
118+
</Button>
119+
</div>
116120
</div>
117-
</div>
121+
)}
118122
</motion.div>
119123
</AnimatePresence>
120124
</CollapsibleContent>
@@ -123,3 +127,5 @@ export const CollapsibleCodeBlock = ({
123127
</div >
124128
);
125129
};
130+
131+
export const CollapsibleCodeBlock = memo(observer(CollapsibleCodeBlockComponent));

bun.lock

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,8 @@
88
},
99
},
1010
"apps/admin": {
11-
"name": "@onlook/admin",
12-
"version": "0.1.0",
13-
"dependencies": {
14-
"@onlook/db": "*",
15-
"@onlook/ui": "*",
16-
"@onlook/utility": "*",
17-
"@supabase/ssr": "^0.6.1",
18-
"@supabase/supabase-js": "^2.45.4",
19-
"@t3-oss/env-nextjs": "^0.12.0",
20-
"@tanstack/react-query": "^5.69.0",
21-
"@trpc/client": "^11.0.0",
22-
"@trpc/react-query": "^11.0.0",
23-
"@trpc/server": "^11.0.0",
24-
"next": ">=15.5.3",
25-
"react": "^19.0.0",
26-
"react-dom": "^19.0.0",
27-
"server-only": "^0.0.1",
28-
"superjson": "^2.2.1",
29-
"zod": "^4.1.3",
30-
},
31-
"devDependencies": {
32-
"@onlook/eslint": "*",
33-
"@onlook/typescript": "*",
34-
"@tailwindcss/postcss": "^4.0.15",
35-
"@types/node": "^20.14.10",
36-
"@types/react": "^19.0.0",
37-
"@types/react-dom": "^19.0.0",
38-
"eslint": "^9.0.0",
39-
"postcss": "^8.5.3",
40-
"tailwindcss": "^4.0.15",
41-
"typescript": "^5.5.4",
42-
},
11+
"name": "admin",
12+
"version": "0.0.0",
4313
},
4414
"apps/backend": {
4515
"name": "@onlook/backend",
@@ -1350,8 +1320,6 @@
13501320

13511321
"@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="],
13521322

1353-
"@onlook/admin": ["@onlook/admin@workspace:apps/admin"],
1354-
13551323
"@onlook/ai": ["@onlook/ai@workspace:packages/ai"],
13561324

13571325
"@onlook/backend": ["@onlook/backend@workspace:apps/backend"],
@@ -1876,7 +1844,7 @@
18761844

18771845
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
18781846

1879-
"@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
1847+
"@types/node": ["@types/node@22.15.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw=="],
18801848

18811849
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
18821850

@@ -2008,6 +1976,8 @@
20081976

20091977
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
20101978

1979+
"admin": ["admin@workspace:apps/admin"],
1980+
20111981
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
20121982

20131983
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
@@ -4678,8 +4648,6 @@
46784648

46794649
"@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@11.4.4-cjs.2", "", { "dependencies": { "@octokit/types": "^13.7.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw=="],
46804650

4681-
"@onlook/docs/@types/node": ["@types/node@22.15.12", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-K0fpC/ZVeb8G9rm7bH7vI0KAec4XHEhBam616nVJCV51bKzJ6oA3luG4WdKoaztxe70QaNjS/xBmcDLmr4PiGw=="],
4682-
46834651
"@onlook/email/react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
46844652

46854653
"@onlook/email/react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
@@ -4690,6 +4658,10 @@
46904658

46914659
"@onlook/github/@types/node": ["@types/node@18.19.127", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA=="],
46924660

4661+
"@onlook/prettier/@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
4662+
4663+
"@onlook/scripts/@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
4664+
46934665
"@onlook/stripe/dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
46944666

46954667
"@onlook/ui/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
@@ -4700,6 +4672,8 @@
47004672

47014673
"@onlook/web-client/@types/culori": ["@types/culori@4.0.1", "", {}, "sha512-43M51r/22CjhbOXyGT361GZ9vncSVQ39u62x5eJdBQFviI8zWp2X5jzqg7k4M6PVgDQAClpy2bUe2dtwEgEDVQ=="],
47024674

4675+
"@onlook/web-client/@types/node": ["@types/node@20.19.17", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ=="],
4676+
47034677
"@onlook/web-client/ai": ["ai@5.0.26", "", { "dependencies": { "@ai-sdk/gateway": "1.0.15", "@ai-sdk/provider": "2.0.0", "@ai-sdk/provider-utils": "3.0.7", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4" } }, "sha512-bGNtG+nYQ2U+5mzuLbxIg9WxGQJ2u5jv2gYgP8C+CJ1YI4qqIjvjOgGEZWzvNet8jiOGIlqstsht9aQefKzmBw=="],
47044678

47054679
"@onlook/web-client/lucide-react": ["lucide-react@0.486.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-xWop/wMsC1ikiEVLZrxXjPKw4vU/eAip33G2mZHgbWnr4Nr5Rt4Vx4s/q1D3B/rQVbxjOuqASkEZcUxDEKzecw=="],

0 commit comments

Comments
 (0)