import { ref, computed } from "vue"; import { defineStore } from "pinia"; import { apiGet, apiPost, apiPatch, apiDelete, apiStreamPost, } from "@/api/client"; import type { Conversation, ConversationDetail, Message, OllamaModel, OllamaStatus, } from "@/types/chat"; export const useChatStore = defineStore("chat", () => { const conversations = ref([]); const currentConversation = ref(null); const total = ref(0); const loading = ref(false); const streaming = ref(false); const streamingContent = ref(""); const models = ref([]); const ollamaStatus = ref<"checking" | "available" | "unavailable">("checking"); const modelStatus = ref<"checking" | "ready" | "not_found">("checking"); const defaultModel = ref(""); let statusPollTimer: ReturnType | null = null; const chatReady = computed( () => ollamaStatus.value === "available" && modelStatus.value === "ready" ); async function fetchConversations(limit = 50, offset = 0) { loading.value = true; try { const data = await apiGet<{ conversations: Conversation[]; total: number }>( `/api/chat/conversations?limit=${limit}&offset=${offset}` ); conversations.value = data.conversations; total.value = data.total; } finally { loading.value = false; } } async function createConversation( title = "", model = "" ): Promise { const conv = await apiPost("/api/chat/conversations", { title, model, }); conversations.value.unshift(conv); return conv; } async function fetchConversation(id: number) { loading.value = true; try { currentConversation.value = await apiGet( `/api/chat/conversations/${id}` ); } finally { loading.value = false; } } async function deleteConversation(id: number) { await apiDelete(`/api/chat/conversations/${id}`); conversations.value = conversations.value.filter((c) => c.id !== id); if (currentConversation.value?.id === id) { currentConversation.value = null; } } async function updateTitle(id: number, title: string) { const updated = await apiPatch( `/api/chat/conversations/${id}`, { title } ); const idx = conversations.value.findIndex((c) => c.id === id); if (idx !== -1) { conversations.value[idx] = { ...conversations.value[idx], ...updated }; } if (currentConversation.value?.id === id) { currentConversation.value.title = updated.title; } } async function sendMessage(content: string, contextNoteId?: number | null) { if (!currentConversation.value) return; const convId = currentConversation.value.id; // Add optimistic user message const userMsg: Message = { id: -Date.now(), // Temporary ID conversation_id: convId, role: "user", content, context_note_id: contextNoteId ?? null, created_at: new Date().toISOString(), }; currentConversation.value.messages.push(userMsg); streaming.value = true; streamingContent.value = ""; try { await apiStreamPost( `/api/chat/conversations/${convId}/messages`, { content, context_note_id: contextNoteId }, (data) => { if (data.chunk) { streamingContent.value += data.chunk as string; } if (data.done) { // Add the final assistant message const assistantMsg: Message = { id: data.message_id as number, conversation_id: convId, role: "assistant", content: streamingContent.value, context_note_id: null, created_at: new Date().toISOString(), }; if (currentConversation.value?.id === convId) { currentConversation.value.messages.push(assistantMsg); } streamingContent.value = ""; streaming.value = false; // Update conversation in list const idx = conversations.value.findIndex((c) => c.id === convId); if (idx !== -1) { conversations.value[idx].message_count += 2; conversations.value[idx].updated_at = new Date().toISOString(); } } if (data.error) { streaming.value = false; streamingContent.value = ""; } } ); } catch { streaming.value = false; streamingContent.value = ""; } } async function saveMessageAsNote(messageId: number) { return await apiPost>( `/api/chat/messages/${messageId}/save-as-note`, {} ); } async function summarizeAsNote(conversationId: number) { return await apiPost>( `/api/chat/conversations/${conversationId}/summarize`, {} ); } async function fetchStatus() { try { const data = await apiGet("/api/chat/status"); ollamaStatus.value = data.ollama; modelStatus.value = data.model; defaultModel.value = data.default_model; } catch { ollamaStatus.value = "unavailable"; modelStatus.value = "not_found"; } } function startStatusPolling() { fetchStatus(); stopStatusPolling(); statusPollTimer = setInterval(fetchStatus, 30_000); } function stopStatusPolling() { if (statusPollTimer !== null) { clearInterval(statusPollTimer); statusPollTimer = null; } } async function fetchModels() { try { const data = await apiGet<{ models: OllamaModel[] }>("/api/chat/models"); models.value = data.models; } catch { models.value = []; } } return { conversations, currentConversation, total, loading, streaming, streamingContent, models, ollamaStatus, modelStatus, defaultModel, chatReady, fetchConversations, createConversation, fetchConversation, deleteConversation, updateTitle, sendMessage, saveMessageAsNote, summarizeAsNote, fetchModels, fetchStatus, startStatusPolling, stopStatusPolling, }; });