import {
addMessage,
modifyLatestMessage,
removeLatestMessage,
setError,
setStatus,
store,
} from "@/store";
import { io, Socket } from "socket.io-client";
const messageChunkSize = 50;
class SocketService {
private socket: Socket | null = null;
private messageBuffer = "";
connectSocket = (sessionToken: string) => {
if (!this.socket) {
this.socket = io("https://sakkarai-backend.azurewebsites.net", {
path: "/sockets/v1",
auth: { token: sessionToken },
transports: ["websocket"], // Ensure WebSocket transport is prioritized
reconnectionAttempts: 5,
timeout: 10000,
});
this.socket.on("connect", () => {
console.log("Socket connected");
store.dispatch(setStatus("CONNECTED"));
});
this.socket.on("disconnect", () => {
console.log("Socket disconnected");
store.dispatch(setStatus("DISCONNECTED"));
});
this.socket.on("receiveMessage", (message) => {
console.log("Message received at:", new Date().toISOString(), message);
let updateMessage = false;
let payload: any = {};
if (message.message) {
this.messageBuffer += message.message;
// if (this.messageBuffer.length >= messageChunkSize) {
// store.dispatch(
// modifyLatestMessage({ message: this.messageBuffer })
// );
// this.messageBuffer = "";
// }
}
if (message.id) {
store.dispatch(
modifyLatestMessage({ message: this.messageBuffer, id: message.id })
);
this.messageBuffer = "";
}
if (message.commit) {
store.dispatch(modifyLatestMessage({ commit: message.commit }));
}
});
this.socket.on("error", (error: any) => {
console.error("Socket error:", error);
store.dispatch(setStatus("IDLE"));
if (error.code === "insufficient-quota") {
store.dispatch(removeLatestMessage("assistant"));
store.dispatch(removeLatestMessage("user"));
store.dispatch(setError(error.message));
}
});
}
};
emitMessage = (message: string) => {
if (this.socket && this.socket.connected) {
store.dispatch(
addMessage({
content: message,
createdAt: new Date(),
role: "user",
canCommit: false,
user: null,
})
);
store.dispatch(
addMessage({
content: "",
createdAt: new Date(),
role: "assistant",
canCommit: false,
user: null,
})
);
this.socket.emit("sendMessage", { message });
store.dispatch(setStatus("STREAMING"));
} else {
console.error("Socket is not connected!");
}
};
disconnect() {
if (this.socket) {
this.socket.disconnect();
this.socket = null;
store.dispatch(setStatus("DISCONNECTED"));
}
}
}
export const socketService = new SocketService();
Hello I am building a chat app something like chat gpt and i want some help right now I first implemented a re render after message buffer means if it is exceed 50 characters then the UI will re render but there is a laginess in generating a response in that case then i remove the buffer condition and only re render when my whole response comes means when message.id is recieved only then i will update the redux state and then this will cause re render so this will reduce the re rendering and message.id is only generated at the end of the response but the issue in this implementation is that is fasten the response more but the user will see … untill complete message is recieved.
Now what i want is, it should display the words as soon as received instead of waiting for whole messgae, just like how ChatGPT displays the response and also it will very fast so I want something similar like that.
because if i have to achieve this i guess i have to implement again buffer alogorithm and re render ui after each word or something like that but that cause a lot of re renders so how can i achieve both fast response and hand to hand message recieving like gpt in only one re render..
I am attaching my chatSlice as well
export const chatSlice = createSlice({
name: "chat",
initialState,
reducers: {
setMessages: (state, action) => {
state.messages = action.payload;
},
addMessage: (state, action: { payload: Message }) => {
console.log("Adding Message", action.payload)
state.messages.unshift(action.payload);
},
setStatus: (state, action) => {
state.status = action.payload;
},
modifyLatestMessage: (
state,
action: {
payload: { id?: string; message?: string; commit?: boolean };
}
) => {
if (state.messages.length > 0) {
if (action.payload.message) {
state.status = "STREAMING";
state.messages[0].content += action.payload.message;
}
if (action.payload.commit) {
state.messages[0].canCommit = action.payload.commit;
}
if (action.payload.id) {
state.status = "IDLE";
if (state.messageBuffer.length > 0) {
state.messages[0].content += state.messageBuffer;
state.messageBuffer = "";
}
state.messages[0].id = action.payload.id;
}
}
},
removeLatestMessage: (state, action: { payload: string }) => {
const role = action.payload;
const lastUserMessageIndex = state.messages.findIndex(
(msg) => msg.role === role
);
if (lastUserMessageIndex !== -1) {
state.messages.splice(lastUserMessageIndex, 1);
}
},
resetChat: (state) => {
state.messages = [];
state.metadata = undefined;
},
setError: (state, action) => {
state.error = action.payload;
},
clearError: (state) => {
state.error = null;
},
},
extraReducers: (builder) => {
builder.addCase(fetchMessages.pending, (state) => {
state.status = "LOADING_MESSAGES";
});
builder.addCase(fetchMessages.fulfilled, (state, action) => {
state.messages.push(...action.payload.data);
state.metadata = action.payload.metadata;
state.status = "IDLE";
});
builder.addCase(fetchMessages.rejected, (state) => {
state.status = "IDLE";
});
// builder.addCase(sendMessage.pending, (state) => {
// state.status = "STREAMING_MESSAGE";
// });
// builder.addCase(sendMessage.fulfilled, (state) => {
// if (commitmentParser.test(state.messages[0].content)) {
// state.messages[0].canCommit = true;
// }
// state.status = "IDLE";
// });
// builder.addCase(sendMessage.rejected, (state, action) => {
// state.status = "IDLE";
// state.messages.shift();
// if (action.payload === "insufficient-quota") {
// state.error = "You have exceeded your message quota.";
// }
// });
builder.addCase(clearConversation.pending, (state) => {
state.status = "CLEARING_CHAT";
});
builder.addCase(clearConversation.fulfilled, (state) => {
state.status = "IDLE";
});
builder.addCase(clearConversation.rejected, (state) => {
state.status = "IDLE";
});
},
});