mirror of
https://github.com/ItzCrazyKns/Perplexica.git
synced 2025-12-23 20:18:15 +00:00
feat(chat-hook): handle reconnect
This commit is contained in:
@@ -401,6 +401,50 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
});
|
});
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
|
const checkReconnect = async () => {
|
||||||
|
if (messages.length > 0) {
|
||||||
|
const lastMsg = messages[messages.length - 1];
|
||||||
|
|
||||||
|
if (lastMsg.status === 'answering') {
|
||||||
|
setLoading(true);
|
||||||
|
setResearchEnded(false);
|
||||||
|
setMessageAppeared(false);
|
||||||
|
|
||||||
|
const res = await fetch(`/api/reconnect/${lastMsg.backendId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.body) throw new Error('No response body');
|
||||||
|
|
||||||
|
const reader = res.body?.getReader();
|
||||||
|
const decoder = new TextDecoder('utf-8');
|
||||||
|
|
||||||
|
let partialChunk = '';
|
||||||
|
|
||||||
|
const messageHandler = getMessageHandler(lastMsg);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { value, done } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
|
||||||
|
partialChunk += decoder.decode(value, { stream: true });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const messages = partialChunk.split('\n');
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (!msg.trim()) continue;
|
||||||
|
const json = JSON.parse(msg);
|
||||||
|
messageHandler(json);
|
||||||
|
}
|
||||||
|
partialChunk = '';
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Incomplete JSON, waiting for next chunk...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkConfig(
|
checkConfig(
|
||||||
setChatModelProvider,
|
setChatModelProvider,
|
||||||
@@ -454,13 +498,22 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMessagesLoaded && isConfigReady) {
|
if (isMessagesLoaded && isConfigReady && newChatCreated) {
|
||||||
setIsReady(true);
|
setIsReady(true);
|
||||||
console.debug(new Date(), 'app:ready');
|
console.debug(new Date(), 'app:ready');
|
||||||
|
} else if (isMessagesLoaded && isConfigReady && !newChatCreated) {
|
||||||
|
checkReconnect()
|
||||||
|
.then(() => {
|
||||||
|
setIsReady(true);
|
||||||
|
console.debug(new Date(), 'app:ready');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Error during reconnect:', err);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setIsReady(false);
|
setIsReady(false);
|
||||||
}
|
}
|
||||||
}, [isMessagesLoaded, isConfigReady]);
|
}, [isMessagesLoaded, isConfigReady, newChatCreated]);
|
||||||
|
|
||||||
const rewrite = (messageId: string) => {
|
const rewrite = (messageId: string) => {
|
||||||
const index = messages.findIndex((msg) => msg.messageId === messageId);
|
const index = messages.findIndex((msg) => msg.messageId === messageId);
|
||||||
@@ -488,38 +541,10 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [isConfigReady, isReady, initialMessage]);
|
}, [isConfigReady, isReady, initialMessage]);
|
||||||
|
|
||||||
const sendMessage: ChatContext['sendMessage'] = async (
|
const getMessageHandler = (message: Message) => {
|
||||||
message,
|
const messageId = message.messageId;
|
||||||
messageId,
|
|
||||||
rewrite = false,
|
|
||||||
) => {
|
|
||||||
if (loading || !message) return;
|
|
||||||
setLoading(true);
|
|
||||||
setResearchEnded(false);
|
|
||||||
setMessageAppeared(false);
|
|
||||||
|
|
||||||
if (messages.length <= 1) {
|
return async (data: any) => {
|
||||||
window.history.replaceState(null, '', `/c/${chatId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageId = messageId ?? crypto.randomBytes(7).toString('hex');
|
|
||||||
const backendId = crypto.randomBytes(20).toString('hex');
|
|
||||||
|
|
||||||
const newMessage: Message = {
|
|
||||||
messageId,
|
|
||||||
chatId: chatId!,
|
|
||||||
backendId,
|
|
||||||
query: message,
|
|
||||||
responseBlocks: [],
|
|
||||||
status: 'answering',
|
|
||||||
createdAt: new Date(),
|
|
||||||
};
|
|
||||||
|
|
||||||
setMessages((prevMessages) => [...prevMessages, newMessage]);
|
|
||||||
|
|
||||||
const receivedTextRef = { current: '' };
|
|
||||||
|
|
||||||
const messageHandler = async (data: any) => {
|
|
||||||
if (data.type === 'error') {
|
if (data.type === 'error') {
|
||||||
toast.error(data.data);
|
toast.error(data.data);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -536,7 +561,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
if (data.type === 'researchComplete') {
|
if (data.type === 'researchComplete') {
|
||||||
setResearchEnded(true);
|
setResearchEnded(true);
|
||||||
if (
|
if (
|
||||||
newMessage.responseBlocks.find(
|
message.responseBlocks.find(
|
||||||
(b) => b.type === 'source' && b.data.length > 0,
|
(b) => b.type === 'source' && b.data.length > 0,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -556,6 +581,13 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return msg;
|
return msg;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(data.block.type === 'source' && data.block.data.length > 0) ||
|
||||||
|
data.block.type === 'text'
|
||||||
|
) {
|
||||||
|
setMessageAppeared(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === 'updateBlock') {
|
if (data.type === 'updateBlock') {
|
||||||
@@ -577,72 +609,19 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.type === 'sources') {
|
|
||||||
const sourceBlock: Block = {
|
|
||||||
id: crypto.randomBytes(7).toString('hex'),
|
|
||||||
type: 'source',
|
|
||||||
data: data.data,
|
|
||||||
};
|
|
||||||
|
|
||||||
setMessages((prev) =>
|
|
||||||
prev.map((msg) => {
|
|
||||||
if (msg.messageId === messageId) {
|
|
||||||
return {
|
|
||||||
...msg,
|
|
||||||
responseBlocks: [...msg.responseBlocks, sourceBlock],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
if (data.data.length > 0) {
|
|
||||||
setMessageAppeared(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'message') {
|
|
||||||
receivedTextRef.current += data.data;
|
|
||||||
|
|
||||||
setMessages((prev) =>
|
|
||||||
prev.map((msg) => {
|
|
||||||
if (msg.messageId === messageId) {
|
|
||||||
const existingTextBlockIndex = msg.responseBlocks.findIndex(
|
|
||||||
(b) => b.type === 'text',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingTextBlockIndex >= 0) {
|
|
||||||
const updatedBlocks = [...msg.responseBlocks];
|
|
||||||
const existingBlock = updatedBlocks[
|
|
||||||
existingTextBlockIndex
|
|
||||||
] as Block & { type: 'text' };
|
|
||||||
updatedBlocks[existingTextBlockIndex] = {
|
|
||||||
...existingBlock,
|
|
||||||
data: existingBlock.data + data.data,
|
|
||||||
};
|
|
||||||
return { ...msg, responseBlocks: updatedBlocks };
|
|
||||||
} else {
|
|
||||||
const textBlock: Block = {
|
|
||||||
id: crypto.randomBytes(7).toString('hex'),
|
|
||||||
type: 'text',
|
|
||||||
data: data.data,
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
...msg,
|
|
||||||
responseBlocks: [...msg.responseBlocks, textBlock],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return msg;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
setMessageAppeared(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.type === 'messageEnd') {
|
if (data.type === 'messageEnd') {
|
||||||
|
const currentMsg = messagesRef.current.find(
|
||||||
|
(msg) => msg.messageId === messageId,
|
||||||
|
);
|
||||||
|
|
||||||
const newHistory: [string, string][] = [
|
const newHistory: [string, string][] = [
|
||||||
...chatHistory,
|
...chatHistory,
|
||||||
['human', message],
|
['human', message.query],
|
||||||
['assistant', receivedTextRef.current],
|
[
|
||||||
|
'assistant',
|
||||||
|
currentMsg?.responseBlocks.find((b) => b.type === 'text')?.data ||
|
||||||
|
'',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
setChatHistory(newHistory);
|
setChatHistory(newHistory);
|
||||||
@@ -672,9 +651,6 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if there are sources and no suggestions
|
// Check if there are sources and no suggestions
|
||||||
const currentMsg = messagesRef.current.find(
|
|
||||||
(msg) => msg.messageId === messageId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const hasSourceBlocks = currentMsg?.responseBlocks.some(
|
const hasSourceBlocks = currentMsg?.responseBlocks.some(
|
||||||
(block) => block.type === 'source' && block.data.length > 0,
|
(block) => block.type === 'source' && block.data.length > 0,
|
||||||
@@ -705,6 +681,36 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendMessage: ChatContext['sendMessage'] = async (
|
||||||
|
message,
|
||||||
|
messageId,
|
||||||
|
rewrite = false,
|
||||||
|
) => {
|
||||||
|
if (loading || !message) return;
|
||||||
|
setLoading(true);
|
||||||
|
setResearchEnded(false);
|
||||||
|
setMessageAppeared(false);
|
||||||
|
|
||||||
|
if (messages.length <= 1) {
|
||||||
|
window.history.replaceState(null, '', `/c/${chatId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageId = messageId ?? crypto.randomBytes(7).toString('hex');
|
||||||
|
const backendId = crypto.randomBytes(20).toString('hex');
|
||||||
|
|
||||||
|
const newMessage: Message = {
|
||||||
|
messageId,
|
||||||
|
chatId: chatId!,
|
||||||
|
backendId,
|
||||||
|
query: message,
|
||||||
|
responseBlocks: [],
|
||||||
|
status: 'answering',
|
||||||
|
createdAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
setMessages((prevMessages) => [...prevMessages, newMessage]);
|
||||||
|
|
||||||
const messageIndex = messages.findIndex((m) => m.messageId === messageId);
|
const messageIndex = messages.findIndex((m) => m.messageId === messageId);
|
||||||
|
|
||||||
@@ -746,6 +752,8 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
|
|||||||
|
|
||||||
let partialChunk = '';
|
let partialChunk = '';
|
||||||
|
|
||||||
|
const messageHandler = getMessageHandler(newMessage);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
|
|||||||
Reference in New Issue
Block a user