import { defineStore } from 'pinia'; import { ref, computed, watch } from 'vue'; // Import watch import axios from 'boot/axios'; export const useChatStore = defineStore('chat', () => { const isVisible = ref(false); const currentThreadId = ref(null); const messages = ref([]); // Array of { sender: 'user' | 'bot', content: string, createdAt?: Date, loading?: boolean } const isLoading = ref(false); const error = ref(null); const pollingIntervalId = ref(null); // To store the interval ID // --- Getters --- const chatMessages = computed(() => messages.value); const isChatVisible = computed(() => isVisible.value); const activeThreadId = computed(() => currentThreadId.value); // --- Actions --- // New action to create a thread if it doesn't exist async function createThreadIfNotExists() { if (currentThreadId.value) return; // Already have a thread isLoading.value = true; error.value = null; try { // Call the endpoint without content to just create the thread const response = await axios.post('/api/chat/threads', {}); currentThreadId.value = response.data.threadId; messages.value = []; // Start with an empty message list for the new thread console.log('Created new chat thread:', currentThreadId.value); // Start polling now that we have a thread ID startPolling(); } catch (err) { console.error('Error creating chat thread:', err); error.value = 'Failed to start chat.'; // Don't set isVisible to false, let the user see the error } finally { isLoading.value = false; } } function toggleChat() { isVisible.value = !isVisible.value; if (isVisible.value) { if (!currentThreadId.value) { // If opening and no thread exists, create one createThreadIfNotExists(); } else { // If opening and thread exists, fetch messages if empty and start polling if (messages.value.length === 0) { fetchMessages(); } startPolling(); } } else { // If closing, stop polling stopPolling(); } } async function fetchMessages() { if (!currentThreadId.value) { console.log('No active thread to fetch messages for.'); // Don't try to fetch if no thread ID yet. createThreadIfNotExists handles the initial state. return; } // Avoid setting isLoading if polling, maybe use a different flag? For now, keep it simple. // isLoading.value = true; // Might cause flickering during polling error.value = null; // Clear previous errors on fetch attempt try { const response = await axios.get(`/api/chat/threads/${currentThreadId.value}/messages`); const newMessages = response.data.map(msg => ({ sender: msg.sender, content: msg.content, createdAt: new Date(msg.createdAt), loading: msg.content === 'Loading...' })).sort((a, b) => a.createdAt - b.createdAt); // Only update if messages have actually changed to prevent unnecessary re-renders if (JSON.stringify(messages.value) !== JSON.stringify(newMessages)) { messages.value = newMessages; } } catch (err) { console.error('Error fetching messages:', err); error.value = 'Failed to load messages.'; // Don't clear messages on polling error, keep the last known state // messages.value = []; stopPolling(); // Stop polling if there's an error fetching } finally { // isLoading.value = false; } } // Function to start polling function startPolling() { if (pollingIntervalId.value) return; // Already polling if (!currentThreadId.value) return; // No thread to poll for console.log('Starting chat polling for thread:', currentThreadId.value); pollingIntervalId.value = setInterval(fetchMessages, 5000); // Poll every 5 seconds } // Function to stop polling function stopPolling() { if (pollingIntervalId.value) { console.log('Stopping chat polling.'); clearInterval(pollingIntervalId.value); pollingIntervalId.value = null; } } async function sendMessage(content) { if (!content.trim()) return; if (!currentThreadId.value) { error.value = 'Cannot send message: No active chat thread.'; console.error('Attempted to send message without a thread ID.'); return; // Should not happen if UI waits for thread creation } const userMessage = { sender: 'user', content: content.trim(), createdAt: new Date(), }; messages.value.push(userMessage); const loadingMessage = { sender: 'bot', content: '...', loading: true, createdAt: new Date(Date.now() + 1) }; // Ensure unique key/time messages.value.push(loadingMessage); // Stop polling temporarily while sending a message to avoid conflicts stopPolling(); isLoading.value = true; // Indicate activity error.value = null; try { const payload = { content: userMessage.content }; // Always post to the existing thread once it's created const response = await axios.post(`/api/chat/threads/${currentThreadId.value}/messages`, payload); // Remove loading indicator messages.value = messages.value.filter(m => !m.loading); // The POST might return the new message, but we'll rely on the next fetchMessages call // triggered by startPolling to get the latest state including any potential bot response. // Immediately fetch messages after sending to get the updated list await fetchMessages(); } catch (err) { console.error('Error sending message:', err); error.value = 'Failed to send message.'; // Remove loading indicator on error messages.value = messages.value.filter(m => !m.loading); // Optionally add an error message to the chat // Ensure the object is correctly formatted messages.value.push({ sender: 'bot', content: "Sorry, I couldn't send that message.", createdAt: new Date() }); } finally { isLoading.value = false; // Restart polling after sending attempt is complete startPolling(); } } // Call this when the user logs out or the app closes if you want to clear state function resetChat() { stopPolling(); // Ensure polling stops on reset isVisible.value = false; currentThreadId.value = null; messages.value = []; isLoading.value = false; error.value = null; } // Watch for visibility changes to manage polling (alternative to putting logic in toggleChat) // watch(isVisible, (newValue) => { // if (newValue && currentThreadId.value) { // startPolling(); // } else { // stopPolling(); // } // }); // Watch for thread ID changes (e.g., after creation) // watch(currentThreadId, (newId) => { // if (newId && isVisible.value) { // messages.value = []; // Clear old messages if any // fetchMessages(); // Fetch messages for the new thread // startPolling(); // Start polling for the new thread // } else { // stopPolling(); // Stop polling if thread ID becomes null // } // }); return { // State refs isVisible, currentThreadId, messages, isLoading, error, // Computed getters chatMessages, isChatVisible, activeThreadId, // Actions toggleChat, sendMessage, fetchMessages, // Expose if needed externally resetChat, // Expose polling control if needed externally, though typically managed internally // startPolling, // stopPolling, }; });