Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .Jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Bolt's Journal

## 2024-05-23 - Initial Setup
**Learning:** Bolt journal initialized.
**Action:** Always check this file for critical learnings before starting.

## 2024-05-23 - Component Separation for Performance
**Learning:** `ChatInterface` was re-rendering the entire message list (including `ReactMarkdown` parsing inside `MessageItem`) on every keystroke because the input state was in the same component. Even with `React.memo` on `MessageItem`, the list reconciliation cost adds up.
**Action:** Split components that handle frequent state updates (like inputs) from heavy list renderers. Created `MessageList` to isolate the list rendering from the input state.
39 changes: 8 additions & 31 deletions src/components/chat/ChatInterface.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useState, useEffect, useRef } from "react";
import { useState } from "react";
import { useChatStore } from "@/store/chat";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Send, Bot, Loader2, RefreshCw, Terminal } from "lucide-react";
import { MessageItem } from "./MessageItem";
import { Send, RefreshCw, Terminal } from "lucide-react";
import { MessageList } from "./MessageList";

export function ChatInterface() {
const { messages, sendMessage, isLoading, resetChat } = useChatStore();
const [input, setInput] = useState("");
const scrollRef = useRef<HTMLDivElement>(null);

const handleSend = async () => {
if (!input.trim()) return;
Expand All @@ -24,10 +23,6 @@ export function ChatInterface() {
}
};

useEffect(() => {
scrollRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, isLoading]);

return (
<div className="flex flex-col min-h-[50vh] max-h-[85vh] h-auto w-full max-w-4xl mx-auto glass-panel rounded-2xl overflow-hidden shadow-2xl transition-all duration-500 ease-in-out animate-in fade-in slide-in-from-bottom-4">

Expand All @@ -50,29 +45,11 @@ export function ChatInterface() {
</div>

{/* CHAT AREA */}
<div className="flex-1 overflow-y-auto p-4 space-y-6 scroll-smooth custom-scrollbar">
{messages.map((msg) => (
<MessageItem
key={msg.id}
message={msg}
onSendMessage={sendMessage}
/>
))}

{isLoading && (
<div className="flex gap-4 max-w-[90%] mr-auto animate-in fade-in slide-in-from-left-2">
<div className="h-8 w-8 rounded-full bg-violet-500/10 text-violet-400 flex items-center justify-center animate-pulse border border-violet-500/20">
<Bot className="h-4 w-4" />
</div>
<div className="bg-white/5 border border-white/10 p-4 rounded-2xl rounded-tl-none flex items-center gap-2 text-zinc-500 text-sm backdrop-blur-sm">
<Loader2 className="h-3 w-3 animate-spin" />
Thinking...
</div>
</div>
)}

<div ref={scrollRef} />
</div>
<MessageList
messages={messages}
isLoading={isLoading}
onSendMessage={sendMessage}
/>

{/* INPUT AREA */}
<div className="p-4 bg-black/20 border-t border-white/5 backdrop-blur-md">
Expand Down
48 changes: 48 additions & 0 deletions src/components/chat/MessageList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { memo, useEffect, useRef } from "react";
import { ChatMessage } from "@/types/chat";
import { Bot, Loader2 } from "lucide-react";
import { MessageItem } from "./MessageItem";

interface MessageListProps {
messages: ChatMessage[];
isLoading: boolean;
onSendMessage: (content: string) => void;
}

const MessageList = memo(({ messages, isLoading, onSendMessage }: MessageListProps) => {
const scrollRef = useRef<HTMLDivElement>(null);

useEffect(() => {
scrollRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages, isLoading]);

return (
<div className="flex-1 overflow-y-auto p-4 space-y-6 scroll-smooth custom-scrollbar">
{messages.map((msg) => (
<MessageItem
key={msg.id}
message={msg}
onSendMessage={onSendMessage}
/>
))}

{isLoading && (
<div className="flex gap-4 max-w-[90%] mr-auto animate-in fade-in slide-in-from-left-2">
<div className="h-8 w-8 rounded-full bg-violet-500/10 text-violet-400 flex items-center justify-center animate-pulse border border-violet-500/20">
<Bot className="h-4 w-4" />
</div>
<div className="bg-white/5 border border-white/10 p-4 rounded-2xl rounded-tl-none flex items-center gap-2 text-zinc-500 text-sm backdrop-blur-sm">
<Loader2 className="h-3 w-3 animate-spin" />
Thinking...
</div>
</div>
)}

<div ref={scrollRef} />
</div>
);
});

MessageList.displayName = "MessageList";

export { MessageList };