diff options
Diffstat (limited to 'makima/frontend/src/hooks/useMeshChatHistory.ts')
| -rw-r--r-- | makima/frontend/src/hooks/useMeshChatHistory.ts | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/makima/frontend/src/hooks/useMeshChatHistory.ts b/makima/frontend/src/hooks/useMeshChatHistory.ts new file mode 100644 index 0000000..82c576d --- /dev/null +++ b/makima/frontend/src/hooks/useMeshChatHistory.ts @@ -0,0 +1,133 @@ +import { useState, useCallback, useEffect } from "react"; +import { + getMeshChatHistory, + clearMeshChatHistory, + chatWithMeshContext, + type MeshChatMessageRecord, + type MeshChatContext, + type MeshChatResponse, + type LlmModel, +} from "../lib/api"; + +export interface MeshChatState { + conversationId: string | null; + messages: MeshChatMessageRecord[]; + loading: boolean; + error: string | null; + sending: boolean; +} + +export function useMeshChatHistory() { + const [state, setState] = useState<MeshChatState>({ + conversationId: null, + messages: [], + loading: true, + error: null, + sending: false, + }); + + const fetchHistory = useCallback(async () => { + setState((prev) => ({ ...prev, loading: true, error: null })); + try { + const response = await getMeshChatHistory(); + setState((prev) => ({ + ...prev, + conversationId: response.conversationId, + messages: response.messages, + loading: false, + })); + } catch (e) { + setState((prev) => ({ + ...prev, + error: e instanceof Error ? e.message : "Failed to fetch chat history", + loading: false, + })); + } + }, []); + + const clearHistory = useCallback(async (): Promise<boolean> => { + setState((prev) => ({ ...prev, loading: true, error: null })); + try { + const response = await clearMeshChatHistory(); + setState({ + conversationId: response.conversationId, + messages: [], + loading: false, + error: null, + sending: false, + }); + return true; + } catch (e) { + setState((prev) => ({ + ...prev, + error: e instanceof Error ? e.message : "Failed to clear chat history", + loading: false, + })); + return false; + } + }, []); + + const sendMessage = useCallback( + async ( + message: string, + context: MeshChatContext, + model?: LlmModel + ): Promise<MeshChatResponse | null> => { + setState((prev) => ({ ...prev, sending: true, error: null })); + + // Optimistically add user message (will be refetched after response) + const tempUserMessage: MeshChatMessageRecord = { + id: `temp-${Date.now()}`, + conversationId: state.conversationId || "", + role: "user", + content: message, + contextType: context.type, + contextTaskId: context.taskId || null, + toolCalls: null, + pendingQuestions: null, + createdAt: new Date().toISOString(), + }; + + setState((prev) => ({ + ...prev, + messages: [...prev.messages, tempUserMessage], + })); + + try { + const response = await chatWithMeshContext(message, context, model); + + // Refetch to get the actual saved messages (with proper IDs) + await fetchHistory(); + + setState((prev) => ({ ...prev, sending: false })); + return response; + } catch (e) { + // Remove optimistic message on error + setState((prev) => ({ + ...prev, + messages: prev.messages.filter((m) => m.id !== tempUserMessage.id), + error: e instanceof Error ? e.message : "Failed to send message", + sending: false, + })); + return null; + } + }, + [state.conversationId, fetchHistory] + ); + + // Initial fetch on mount + useEffect(() => { + fetchHistory(); + }, [fetchHistory]); + + return { + conversationId: state.conversationId, + messages: state.messages, + loading: state.loading, + error: state.error, + sending: state.sending, + fetchHistory, + clearHistory, + sendMessage, + }; +} |
