diff --git a/apps/vscode/src/handlers/file.handler.ts b/apps/vscode/src/handlers/file.handler.ts index 50457e8cf..abc49cabf 100644 --- a/apps/vscode/src/handlers/file.handler.ts +++ b/apps/vscode/src/handlers/file.handler.ts @@ -165,6 +165,10 @@ const getImageDataUri: Handler = async ({ filePat const mime = IMAGE_MIME_TYPES[path.extname(resolved.relativePath).toLowerCase()]; if (!mime) return null; try { + // Same cap as the media picker: an oversized image inlined as a data URI + // would live in the webview DOM (and its decoded bitmap) forever. + const stat = await vscode.workspace.fs.stat(resolved.uri); + if (stat.size > 10 * 1024 * 1024) return null; const bytes = await vscode.workspace.fs.readFile(resolved.uri); return `data:${mime};base64,${Buffer.from(bytes).toString("base64")}`; } catch { diff --git a/apps/vscode/webview-ui/src/components/ChatArea.tsx b/apps/vscode/webview-ui/src/components/ChatArea.tsx index cc086b951..5d4f8e887 100644 --- a/apps/vscode/webview-ui/src/components/ChatArea.tsx +++ b/apps/vscode/webview-ui/src/components/ChatArea.tsx @@ -23,7 +23,8 @@ function ScrollButton() { } function MessageList() { - const { messages, isStreaming } = useChatStore(); + const messages = useChatStore((s) => s.messages); + const isStreaming = useChatStore((s) => s.isStreaming); return ( <> @@ -43,9 +44,9 @@ function MessageList() { } export function ChatArea() { - const { messages } = useChatStore(); + const messageCount = useChatStore((s) => s.messages.length); - if (messages.length === 0) { + if (messageCount === 0) { return (
diff --git a/apps/vscode/webview-ui/src/components/ChatMessage.tsx b/apps/vscode/webview-ui/src/components/ChatMessage.tsx index 91c660776..08d27c159 100644 --- a/apps/vscode/webview-ui/src/components/ChatMessage.tsx +++ b/apps/vscode/webview-ui/src/components/ChatMessage.tsx @@ -1,4 +1,4 @@ -import { useState, Fragment } from "react"; +import { useState, Fragment, memo } from "react"; import { IconLoader3, IconGitFork } from "@tabler/icons-react"; import { cn } from "@/lib/utils"; import { Content } from "@/lib/content"; @@ -146,7 +146,9 @@ interface ForkButtonProps { function ForkButton({ turnIndex, className }: ForkButtonProps) { const [showConfirm, setShowConfirm] = useState(false); const [isForking, setIsForking] = useState(false); - const { sessionId, isStreaming, loadSession } = useChatStore(); + const sessionId = useChatStore((s) => s.sessionId); + const isStreaming = useChatStore((s) => s.isStreaming); + const loadSession = useChatStore((s) => s.loadSession); const handleFork = () => { if (!sessionId || turnIndex < 0) return; @@ -234,7 +236,7 @@ function UserMessage({ message }: { message: ChatMessageType }) { function AssistantMessage({ message, turnIndex, isStreaming }: { message: ChatMessageType; turnIndex?: number; isStreaming?: boolean }) { const [previewMedia, setPreviewMedia] = useState(null); - const { isCompacting } = useChatStore(); + const isCompacting = useChatStore((s) => s.isCompacting); const steps = message.steps || []; const hasSteps = steps.length > 0; @@ -330,9 +332,9 @@ function hasMessageContent(message: ChatMessageType): boolean { return message.steps?.some((s) => s.items.length > 0) ?? false; } -export function ChatMessage({ message, turnIndex, isStreaming }: ChatMessageProps) { +export const ChatMessage = memo(function ChatMessage({ message, turnIndex, isStreaming }: ChatMessageProps) { if (message.role === "user") { return ; } return ; -} +}); diff --git a/apps/vscode/webview-ui/src/components/CompactionCard.tsx b/apps/vscode/webview-ui/src/components/CompactionCard.tsx index fa87df768..43e6d85e6 100644 --- a/apps/vscode/webview-ui/src/components/CompactionCard.tsx +++ b/apps/vscode/webview-ui/src/components/CompactionCard.tsx @@ -2,7 +2,7 @@ import { IconLoader2 } from "@tabler/icons-react"; import { useChatStore } from "@/stores"; export function CompactionCard() { - const { isCompacting } = useChatStore(); + const isCompacting = useChatStore((s) => s.isCompacting); return (