replace chat polling with websocket
This commit is contained in:
@@ -4,9 +4,9 @@ import dynamic from "next/dynamic";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { createStompClient } from "@/lib/chatSocket";
|
||||
|
||||
const API_BASE = "";
|
||||
const POLL_INTERVAL = 2500;
|
||||
|
||||
function AiChatPage() {
|
||||
const { user, token, loading: authLoading } = useAuth();
|
||||
@@ -27,7 +27,7 @@ function AiChatPage() {
|
||||
const messagesEndRef = useRef(null);
|
||||
const messagesAreaRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
const pollRef = useRef(null);
|
||||
const stompRef = useRef(null);
|
||||
const lastMessageIdRef = useRef(null);
|
||||
const fileInputRef = useRef(null);
|
||||
const lastScrolledIdRef = useRef(null);
|
||||
@@ -100,37 +100,30 @@ function AiChatPage() {
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const startPolling = useCallback((convId) => {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
pollRef.current = setInterval(async () => {
|
||||
if (!token || !convId) return;
|
||||
try {
|
||||
const [msgsRes, convRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/api/v1/chat/conversations/${convId}/messages`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
fetch(`${API_BASE}/api/v1/chat/conversations/${convId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
]);
|
||||
if (msgsRes.ok) {
|
||||
const data = await msgsRes.json();
|
||||
if (Array.isArray(data)) {
|
||||
const lastId = data.length > 0 ? data[data.length - 1].id : null;
|
||||
if (lastId !== lastMessageIdRef.current) {
|
||||
lastMessageIdRef.current = lastId;
|
||||
setMessages(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (convRes.ok) {
|
||||
const convData = await convRes.json();
|
||||
setConversation(convData);
|
||||
}
|
||||
} catch {
|
||||
// silent
|
||||
}
|
||||
}, POLL_INTERVAL);
|
||||
const connectStomp = useCallback((convId) => {
|
||||
if (stompRef.current) {
|
||||
stompRef.current.deactivate();
|
||||
stompRef.current = null;
|
||||
}
|
||||
const client = createStompClient(token);
|
||||
client.onConnect = () => {
|
||||
client.subscribe(`/topic/chat/conversations/${convId}`, (frame) => {
|
||||
try {
|
||||
const msg = JSON.parse(frame.body);
|
||||
setMessages((prev) => prev.some((m) => m.id === msg.id) ? prev : [...prev, msg]);
|
||||
lastMessageIdRef.current = msg.id;
|
||||
} catch { /* silent */ }
|
||||
});
|
||||
client.subscribe(`/topic/chat/conversations`, (frame) => {
|
||||
try {
|
||||
const conv = JSON.parse(frame.body);
|
||||
if (conv.id === convId) setConversation(conv);
|
||||
setConversations((prev) => prev.map((c) => c.id === conv.id ? conv : c));
|
||||
} catch { /* silent */ }
|
||||
});
|
||||
};
|
||||
stompRef.current = client;
|
||||
client.activate();
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,16 +184,16 @@ function AiChatPage() {
|
||||
fetchConversations(),
|
||||
]);
|
||||
setLoadingConv(false);
|
||||
startPolling(convId);
|
||||
connectStomp(convId);
|
||||
router.replace(`/ai-chat?id=${convId}`, { scroll: false });
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return () => {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||
};
|
||||
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, startPolling, fetchConversations, router]);
|
||||
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations, router]);
|
||||
|
||||
async function handleSend(e) {
|
||||
e?.preventDefault();
|
||||
@@ -313,7 +306,7 @@ function AiChatPage() {
|
||||
}
|
||||
|
||||
async function handleNewConversation() {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||
setError(null);
|
||||
setLoadingConv(true);
|
||||
try {
|
||||
@@ -335,7 +328,7 @@ function AiChatPage() {
|
||||
setConversation(conv);
|
||||
await Promise.all([fetchMessages(conv.id), fetchConversations()]);
|
||||
setLoadingConv(false);
|
||||
startPolling(conv.id);
|
||||
connectStomp(conv.id);
|
||||
router.replace(`/ai-chat?id=${conv.id}`, { scroll: false });
|
||||
} catch {
|
||||
setError("Network error. Please try again.");
|
||||
@@ -344,14 +337,14 @@ function AiChatPage() {
|
||||
}
|
||||
|
||||
async function switchConversation(convId) {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||
setMessages([]);
|
||||
setError(null);
|
||||
setLoadingConv(true);
|
||||
await fetchConversation(convId);
|
||||
await fetchMessages(convId);
|
||||
setLoadingConv(false);
|
||||
startPolling(convId);
|
||||
connectStomp(convId);
|
||||
router.replace(`/ai-chat?id=${convId}`, { scroll: false });
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import dynamic from "next/dynamic";
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
import { createStompClient } from "@/lib/chatSocket";
|
||||
|
||||
const API_BASE = "";
|
||||
const POLL_INTERVAL = 2500;
|
||||
|
||||
function ChatPage() {
|
||||
const { user, token, loading: authLoading } = useAuth();
|
||||
@@ -27,7 +27,7 @@ function ChatPage() {
|
||||
const messagesEndRef = useRef(null);
|
||||
const messagesAreaRef = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
const pollRef = useRef(null);
|
||||
const stompRef = useRef(null);
|
||||
const lastMessageIdRef = useRef(null);
|
||||
const fileInputRef = useRef(null);
|
||||
const lastScrolledIdRef = useRef(null);
|
||||
@@ -114,39 +114,30 @@ function ChatPage() {
|
||||
}
|
||||
}, [token]);
|
||||
|
||||
const startPolling = useCallback((convId) => {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
pollRef.current = setInterval(async () => {
|
||||
if (!token || !convId) return;
|
||||
try {
|
||||
const [msgsRes, convRes] = await Promise.all([
|
||||
fetch(`${API_BASE}/api/v1/chat/conversations/${convId}/messages`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
fetch(`${API_BASE}/api/v1/chat/conversations/${convId}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}),
|
||||
]);
|
||||
if (msgsRes.ok) {
|
||||
const data = await msgsRes.json();
|
||||
if (Array.isArray(data)) {
|
||||
const lastId = data.length > 0 ? data[data.length - 1].id : null;
|
||||
if (lastId !== lastMessageIdRef.current) {
|
||||
lastMessageIdRef.current = lastId;
|
||||
setMessages(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (convRes.ok) {
|
||||
const convData = await convRes.json();
|
||||
setConversation(convData);
|
||||
}
|
||||
}
|
||||
|
||||
catch {
|
||||
//Silent
|
||||
}
|
||||
}, POLL_INTERVAL);
|
||||
const connectStomp = useCallback((convId) => {
|
||||
if (stompRef.current) {
|
||||
stompRef.current.deactivate();
|
||||
stompRef.current = null;
|
||||
}
|
||||
const client = createStompClient(token);
|
||||
client.onConnect = () => {
|
||||
client.subscribe(`/topic/chat/conversations/${convId}`, (frame) => {
|
||||
try {
|
||||
const msg = JSON.parse(frame.body);
|
||||
setMessages((prev) => prev.some((m) => m.id === msg.id) ? prev : [...prev, msg]);
|
||||
lastMessageIdRef.current = msg.id;
|
||||
} catch { /* silent */ }
|
||||
});
|
||||
client.subscribe(`/topic/chat/conversations`, (frame) => {
|
||||
try {
|
||||
const conv = JSON.parse(frame.body);
|
||||
if (conv.id === convId) setConversation(conv);
|
||||
setConversations((prev) => prev.map((c) => c.id === conv.id ? conv : c));
|
||||
} catch { /* silent */ }
|
||||
});
|
||||
};
|
||||
stompRef.current = client;
|
||||
client.activate();
|
||||
}, [token]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -188,15 +179,15 @@ function ChatPage() {
|
||||
fetchConversations(),
|
||||
]);
|
||||
setLoadingConv(false);
|
||||
startPolling(convId);
|
||||
connectStomp(convId);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
return () => {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||
};
|
||||
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, startPolling, fetchConversations]);
|
||||
}, [token, authLoading, conversationIdParam, fetchConversation, fetchMessages, connectStomp, fetchConversations]);
|
||||
|
||||
async function handleSend(e) {
|
||||
e?.preventDefault();
|
||||
@@ -340,7 +331,7 @@ function ChatPage() {
|
||||
setConversation(conv);
|
||||
await Promise.all([fetchMessages(conv.id), fetchConversations()]);
|
||||
setLoadingConv(false);
|
||||
startPolling(conv.id);
|
||||
connectStomp(conv.id);
|
||||
router.replace(`/chat?id=${conv.id}`, { scroll: false });
|
||||
} catch {
|
||||
setError("Network error. Please try again.");
|
||||
@@ -349,14 +340,14 @@ function ChatPage() {
|
||||
}
|
||||
|
||||
async function switchConversation(convId) {
|
||||
if (pollRef.current) clearInterval(pollRef.current);
|
||||
if (stompRef.current) { stompRef.current.deactivate(); stompRef.current = null; }
|
||||
setMessages([]);
|
||||
setError(null);
|
||||
setLoadingConv(true);
|
||||
await fetchConversation(convId);
|
||||
await fetchMessages(convId);
|
||||
setLoadingConv(false);
|
||||
startPolling(convId);
|
||||
connectStomp(convId);
|
||||
router.replace(`/chat?id=${convId}`, { scroll: false });
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user