comparison love/epi/src/hooks/useChat.ts @ 38:cf9caa4abc3e

[Love] FE and BE. Can chat and render images. Also created MCP for powerpoint generations.
author MrJuneJune <me@mrjunejune.com>
date Mon, 01 Dec 2025 20:35:56 -0800
parents
children
comparison
equal deleted inserted replaced
37:fb9bcd3145cb 38:cf9caa4abc3e
1 import { useAtom, useSetAtom } from 'jotai';
2 import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3 import { messagesAtom, addMessageAtom, updateChatTitleAtom } from '@/atoms/chatAtoms';
4
5 import { sendMessageChatId, subscribeToChat } from '@/hooks/useChatWebsocket';
6 import type { Payload } from '@/hooks/useChatWebsocket';
7 import { useComposer } from './useComposer';
8
9 export function useChat(chatId: string) {
10 const [isLoading, setIsLoading] = useState<boolean>(false);
11
12 const messagesAtomForChat = useMemo(() => messagesAtom(chatId), [chatId]);
13 const [messages, setMessages] = useAtom(messagesAtomForChat);
14 const { setIdle } = useComposer();
15
16 const addMessage = useSetAtom(addMessageAtom);
17 const updateChatTitle = useSetAtom(updateChatTitleAtom);
18
19 const streamingMessageIdRef = useRef<string | null>(null);
20
21 useEffect(() => {
22 const unsubscribe = subscribeToChat(chatId, (payload: Payload) => {
23 if (payload.action === 'done') {
24 setIdle();
25 streamingMessageIdRef.current = null;
26 return;
27 }
28
29 if (payload.action === 'title_updated' && streamingMessageIdRef.current) {
30 updateChatTitle({ chatId, title: payload.title });
31 }
32
33 if (payload.action === 'image' && payload.url && streamingMessageIdRef.current) {
34 setMessages(prev =>
35 prev.map(m =>
36 m.id === streamingMessageIdRef.current
37 ? { ...m, image_url: payload.url }
38 : m,
39 ),
40 );
41 }
42
43 if (payload.action === 'append' && payload.content && streamingMessageIdRef.current) {
44 setMessages(prev =>
45 prev.map(m =>
46 m.id === streamingMessageIdRef.current
47 ? { ...m, content: m.content + payload.content }
48 : m,
49 ),
50 );
51 }
52 });
53
54 return unsubscribe;
55 }, [chatId, setIdle, setMessages]);
56
57 const sendUserMessage = useCallback(
58 (content: string, overrideChatID?: string) => {
59 if (!(chatId || overrideChatID) || !content.trim()) return;
60
61 const effectiveChatId = (chatId === 'new' || !chatId) && overrideChatID ? overrideChatID : chatId;
62
63 addMessage({
64 chatId: effectiveChatId,
65 message: { role: 'user', content: content.trim(), type: 'text' },
66 });
67
68 const id = crypto.randomUUID();
69 streamingMessageIdRef.current = id;
70
71 addMessage({
72 chatId: effectiveChatId,
73 message: { id, role: 'assistant', content: '', type: 'text' },
74 });
75
76 sendMessageChatId(content, effectiveChatId);
77 },
78 [chatId, addMessage],
79 );
80
81 const stopStreaming = useCallback(() => {
82 setIsLoading(false);
83 streamingMessageIdRef.current = null;
84 }, []);
85
86 return {
87 messages: messages ?? [],
88 sendUserMessage,
89 setMessages,
90 isLoading,
91 stopStreaming,
92 };
93 }