Mercurial
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/love/epi/src/hooks/useChat.ts Mon Dec 01 20:35:56 2025 -0800 @@ -0,0 +1,93 @@ +import { useAtom, useSetAtom } from 'jotai'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { messagesAtom, addMessageAtom, updateChatTitleAtom } from '@/atoms/chatAtoms'; + +import { sendMessageChatId, subscribeToChat } from '@/hooks/useChatWebsocket'; +import type { Payload } from '@/hooks/useChatWebsocket'; +import { useComposer } from './useComposer'; + +export function useChat(chatId: string) { + const [isLoading, setIsLoading] = useState<boolean>(false); + + const messagesAtomForChat = useMemo(() => messagesAtom(chatId), [chatId]); + const [messages, setMessages] = useAtom(messagesAtomForChat); + const { setIdle } = useComposer(); + + const addMessage = useSetAtom(addMessageAtom); + const updateChatTitle = useSetAtom(updateChatTitleAtom); + + const streamingMessageIdRef = useRef<string | null>(null); + + useEffect(() => { + const unsubscribe = subscribeToChat(chatId, (payload: Payload) => { + if (payload.action === 'done') { + setIdle(); + streamingMessageIdRef.current = null; + return; + } + + if (payload.action === 'title_updated' && streamingMessageIdRef.current) { + updateChatTitle({ chatId, title: payload.title }); + } + + if (payload.action === 'image' && payload.url && streamingMessageIdRef.current) { + setMessages(prev => + prev.map(m => + m.id === streamingMessageIdRef.current + ? { ...m, image_url: payload.url } + : m, + ), + ); + } + + if (payload.action === 'append' && payload.content && streamingMessageIdRef.current) { + setMessages(prev => + prev.map(m => + m.id === streamingMessageIdRef.current + ? { ...m, content: m.content + payload.content } + : m, + ), + ); + } + }); + + return unsubscribe; + }, [chatId, setIdle, setMessages]); + + const sendUserMessage = useCallback( + (content: string, overrideChatID?: string) => { + if (!(chatId || overrideChatID) || !content.trim()) return; + + const effectiveChatId = (chatId === 'new' || !chatId) && overrideChatID ? overrideChatID : chatId; + + addMessage({ + chatId: effectiveChatId, + message: { role: 'user', content: content.trim(), type: 'text' }, + }); + + const id = crypto.randomUUID(); + streamingMessageIdRef.current = id; + + addMessage({ + chatId: effectiveChatId, + message: { id, role: 'assistant', content: '', type: 'text' }, + }); + + sendMessageChatId(content, effectiveChatId); + }, + [chatId, addMessage], + ); + + const stopStreaming = useCallback(() => { + setIsLoading(false); + streamingMessageIdRef.current = null; + }, []); + + return { + messages: messages ?? [], + sendUserMessage, + setMessages, + isLoading, + stopStreaming, + }; +}