Mercurial
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 } |