perf: P0 performance optimizations — memo, base64 cleanup, startup speed, bump v0.17.5
Six independent fixes targeting startup lag, streaming jank, and excessive payloads: Step 0 — MessageItem React.memo + useMemo (MessageItem.tsx) - Wrap MessageItem with React.memo so historical messages skip re-render when `message` ref is stable during streaming - Add useMemo for parseToolBlocks + pairTools (deps: message.content), parseMessageFiles (deps: text), token_usage JSON.parse (deps: message.token_usage) - Extract assistant content rendering into separate memo'd AssistantContent component to cache parseBatchPlan / parseImageGen chain (deps: displayText, messageId) - Root cause: 50 msgs × 10 SSE events/s = 500 full parses/s causing UI jank Step 1 — Strip base64 from new messages (ChatView.tsx, route.ts) - ChatView: remove `data: f.data` from fileMeta in display content HTML comment - route.ts: skip base64 data in fileAttachments when filePath exists (file already on disk) - Root cause: base64 image data inflated message content to MB-level Step 2 — Sanitize old messages in API (messages/route.ts) - Add stripFileData() to strip base64 `data` fields from <!--files:...--> comments before returning messages from GET /api/chat/sessions/:id/messages - Handles legacy data already stored with base64 Step 3 — Reduce initial fetch limit 100 → 30 (page.tsx, messages/route.ts) - Default limit changed from 100 to 30 on both frontend and backend - "Load more" functionality unchanged Step 4 — ChatListPanel dedup requests (ChatListPanel.tsx) - Remove duplicate pathname-dependent useEffect (was causing 2 fetches per navigation) - Add AbortController to cancel in-flight requests on new fetch - Add 300ms debounce for event-driven fetches (session-created/session-updated) Step 5 — Electron startup: show window before server ready (electron/main.ts) - createWindow() now accepts optional URL, defaults to inline loading HTML - Loading page: dark theme, centered spinner, "Starting CodePilot..." text - Startup: startServer() → createWindow(loading) → waitForServer() → loadURL(real) - Also updated activate handler for same early-window pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
c14fdb07ae
commit
132fdeb255
9 files changed
+232
-133
No files matched your search
@@ -149,7 +149,7 @@ export async function POST(request: NextRequest) {
|
||||
name: f.name,
|
||||
type: f.type,
|
||||
size: f.size,
|
||||
data: f.data,
|
||||
data: meta?.filePath ? '' : f.data, // Skip base64 data if file is already saved to disk
|
||||
filePath: meta?.filePath,
|
||||
};
|
||||
})
|
||||
|
||||
@@ -2,6 +2,22 @@ import { NextRequest } from 'next/server';
|
||||
import { getMessages, getSession } from '@/lib/db';
|
||||
import type { MessagesResponse } from '@/types';
|
||||
|
||||
/** Strip base64 `data` fields from <!--files:...--> HTML comments in message content */
|
||||
function stripFileData(content: string): string {
|
||||
const match = content.match(/^<!--files:(.*?)-->/);
|
||||
if (!match) return content;
|
||||
try {
|
||||
const files = JSON.parse(match[1]);
|
||||
const cleaned = files.map((f: Record<string, unknown>) => {
|
||||
const { data, ...rest } = f;
|
||||
return rest;
|
||||
});
|
||||
return `<!--files:${JSON.stringify(cleaned)}-->${content.slice(match[0].length)}`;
|
||||
} catch {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
@@ -17,11 +33,16 @@ export async function GET(
|
||||
const limitParam = searchParams.get('limit');
|
||||
const beforeParam = searchParams.get('before');
|
||||
|
||||
const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10) || 100, 1), 500) : 100;
|
||||
const limit = limitParam ? Math.min(Math.max(parseInt(limitParam, 10) || 30, 1), 500) : 30;
|
||||
const beforeRowId = beforeParam ? parseInt(beforeParam, 10) || undefined : undefined;
|
||||
|
||||
const { messages, hasMore } = getMessages(id, { limit, beforeRowId });
|
||||
const response: MessagesResponse = { messages, hasMore };
|
||||
// Sanitize: strip base64 data from file attachments in old messages
|
||||
const sanitizedMessages = messages.map(m => ({
|
||||
...m,
|
||||
content: stripFileData(m.content),
|
||||
}));
|
||||
const response: MessagesResponse = { messages: sanitizedMessages, hasMore };
|
||||
return Response.json(response);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to fetch messages';
|
||||
|
||||
Reference in new issue
Block a user