Skip to content

Commit 7c53c08

Browse files
committed
Optimistic update when user send a message
1 parent 179c2b3 commit 7c53c08

File tree

2 files changed

+32
-21
lines changed

2 files changed

+32
-21
lines changed

chat/src/components/ChatInterface.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ import { Alert, AlertTitle, AlertDescription } from "./ui/alert";
1111
import { ModeToggle } from "./mode-toggle";
1212

1313
interface Message {
14+
id: number;
1415
role: string;
1516
content: string;
16-
id: number;
17+
}
18+
19+
// Draft messages are used to optmistically update the UI
20+
// before the server responds.
21+
interface DraftMessage extends Omit<Message, "id"> {
22+
id?: number;
1723
}
1824

1925
interface MessageUpdateEvent {
@@ -28,7 +34,7 @@ interface StatusChangeEvent {
2834
}
2935

3036
export default function ChatInterface() {
31-
const [messages, setMessages] = useState<Message[]>([]);
37+
const [messages, setMessages] = useState<(Message | DraftMessage)[]>([]);
3238
const [loading, setLoading] = useState<boolean>(false);
3339
const [serverStatus, setServerStatus] = useState<string>("unknown");
3440
const searchParams = useSearchParams();
@@ -87,12 +93,18 @@ export default function ChatInterface() {
8793
const data: MessageUpdateEvent = JSON.parse(event.data);
8894

8995
setMessages((prevMessages) => {
96+
// Clean up draft messages
97+
const updatedMessages = [...prevMessages].filter(
98+
(m) => m.id !== undefined
99+
);
100+
90101
// Check if message with this ID already exists
91-
const existingIndex = prevMessages.findIndex((m) => m.id === data.id);
102+
const existingIndex = updatedMessages.findIndex(
103+
(m) => m.id === data.id
104+
);
92105

93106
if (existingIndex !== -1) {
94107
// Update existing message
95-
const updatedMessages = [...prevMessages];
96108
updatedMessages[existingIndex] = {
97109
role: data.role,
98110
content: data.message,
@@ -102,7 +114,7 @@ export default function ChatInterface() {
102114
} else {
103115
// Add new message
104116
return [
105-
...prevMessages,
117+
...updatedMessages,
106118
{
107119
role: data.role,
108120
content: data.message,
@@ -164,6 +176,10 @@ export default function ChatInterface() {
164176

165177
// For raw messages, don't set loading state as it's usually fast
166178
if (type === "user") {
179+
setMessages((prevMessages) => [
180+
...prevMessages,
181+
{ role: "user", content },
182+
]);
167183
setLoading(true);
168184
}
169185

@@ -263,7 +279,7 @@ export default function ChatInterface() {
263279
</div>
264280
)}
265281

266-
<MessageList messages={messages} loading={loading} />
282+
<MessageList messages={messages} />
267283
<MessageInput onSendMessage={sendMessage} disabled={loading} />
268284
</main>
269285
</div>

chat/src/components/MessageList.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ interface Message {
66
id: number;
77
}
88

9+
// Draft messages are used to optmistically update the UI
10+
// before the server responds.
11+
interface DraftMessage extends Omit<Message, "id"> {
12+
id?: number;
13+
}
14+
915
interface MessageListProps {
10-
messages: Message[];
11-
loading?: boolean;
16+
messages: (Message | DraftMessage)[];
1217
}
1318

14-
export default function MessageList({
15-
messages,
16-
loading = false,
17-
}: MessageListProps) {
19+
export default function MessageList({ messages }: MessageListProps) {
1820
// If no messages, show a placeholder
1921
if (messages.length === 0) {
2022
return (
@@ -73,15 +75,15 @@ export default function MessageList({
7375
<div className="p-4 flex flex-col gap-4 max-w-4xl mx-auto">
7476
{messages.map((message) => (
7577
<div
76-
key={message.id}
78+
key={message.id ?? "draft"}
7779
className={`${message.role === "user" ? "text-right" : ""}`}
7880
>
7981
<div
8082
className={`inline-block rounded-lg ${
8183
message.role === "user"
8284
? "bg-accent-foreground rounded-lg max-w-[90%] px-4 py-3 text-accent"
8385
: "max-w-[80ch]"
84-
}`}
86+
} ${message.id === undefined ? "animate-pulse" : ""}`}
8587
>
8688
<div
8789
className={`whitespace-pre-wrap break-words text-left text-sm ${
@@ -97,13 +99,6 @@ export default function MessageList({
9799
</div>
98100
</div>
99101
))}
100-
101-
{/* Loading indicator for message being sent */}
102-
{loading && (
103-
<div className="w-fit self-end">
104-
<LoadingDots />
105-
</div>
106-
)}
107102
</div>
108103
</div>
109104
);

0 commit comments

Comments
 (0)