import { defineStore } from "pinia"; import { ref } from "vue"; export interface ToolCall { id: string; name: string; input: Record; result?: unknown; status: "pending" | "done" | "error"; } export interface Message { id: string; role: "user" | "assistant"; content: string; toolCalls?: ToolCall[]; timestamp: number; } export interface ConversationSummary { id: string; title: string; model: string; createdAt: string; tokensIn: number; tokensOut: number; } /** * Copilot (XO) store. * Manages the AI assistant panel state, messages, and streaming. * * Backend calls are handled by the useCopilot composable; * this store manages purely reactive UI state. */ export const useCopilotStore = defineStore("copilot", () => { const isPanelOpen = ref(false); const isAuthenticated = ref(false); const isStreaming = ref(false); const activeConversationId = ref(null); const messages = ref([]); const conversations = ref([]); const model = ref("claude-sonnet-4-20250514"); const tokenUsage = ref({ input: 0, output: 0 }); const showSettings = ref(false); /** Toggle the copilot panel open/closed. */ function togglePanel(): void { isPanelOpen.value = !isPanelOpen.value; } /** Start a new conversation (resets local state). */ function newConversation(): void { activeConversationId.value = null; messages.value = []; tokenUsage.value = { input: 0, output: 0 }; } /** Add a user message to the local message list. */ function sendMessage(text: string): void { const userMsg: Message = { id: `msg-${Date.now()}`, role: "user", content: text, timestamp: Date.now(), }; messages.value.push(userMsg); // Rough token estimate for display purposes tokenUsage.value.input += Math.ceil(text.length / 4); } /** Append a streaming text delta to the latest assistant message. */ function appendStreamDelta(text: string): void { const last = messages.value[messages.value.length - 1]; if (last && last.role === "assistant") { last.content += text; } } /** Create a new assistant message (for streaming start). */ function createAssistantMessage(): Message { const msg: Message = { id: `msg-${Date.now()}`, role: "assistant", content: "", toolCalls: [], timestamp: Date.now(), }; messages.value.push(msg); return msg; } /** Add a tool call to the latest assistant message. */ function addToolCall(call: ToolCall): void { const last = messages.value[messages.value.length - 1]; if (last && last.role === "assistant") { if (!last.toolCalls) last.toolCalls = []; last.toolCalls.push(call); } } /** Complete a pending tool call with a result. */ function completeToolCall(callId: string, result: unknown): void { for (const msg of messages.value) { if (!msg.toolCalls) continue; const tc = msg.toolCalls.find((t) => t.id === callId); if (tc) { tc.result = result; tc.status = "done"; break; } } } /** Mark a tool call as errored. */ function failToolCall(callId: string, error: unknown): void { for (const msg of messages.value) { if (!msg.toolCalls) continue; const tc = msg.toolCalls.find((t) => t.id === callId); if (tc) { tc.result = error; tc.status = "error"; break; } } } /** Load conversation list from backend. */ async function loadConversations(): Promise { // TODO: wire to AIService.ListConversations when conversation history UI is built conversations.value = []; } /** Clear all messages in the current conversation. */ function clearHistory(): void { messages.value = []; activeConversationId.value = null; tokenUsage.value = { input: 0, output: 0 }; } return { isPanelOpen, isAuthenticated, isStreaming, activeConversationId, messages, conversations, model, tokenUsage, showSettings, togglePanel, newConversation, sendMessage, appendStreamDelta, createAssistantMessage, addToolCall, completeToolCall, failToolCall, loadConversations, clearHistory, }; });