summaryrefslogblamecommitdiff
path: root/makima/frontend/src/hooks/useMeshChatHistory.ts
blob: 82c576da6da4366e513980e2fdd55eb712e46219 (plain) (tree)




































































































































                                                                               
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,
  };
}