import { useCopilotStore } from "@/stores/copilot.store"; import type { ToolCall } from "@/stores/copilot.store"; import { Call } from "@wailsio/runtime"; /** * Fully qualified Go method name prefix for AIService bindings. * Wails v3 ByName format: 'package.struct.method' */ const AI = "github.com/vstockwell/wraith/internal/ai.AIService"; /** Call a bound Go method on AIService by name. */ async function callAI(method: string, ...args: unknown[]): Promise { return Call.ByName(`${AI}.${method}`, ...args) as Promise; } /** Shape returned by Go AIService.SendMessage. */ interface ChatResponse { text: string; toolCalls?: { name: string; input: unknown; result: unknown; error?: string; }[]; } /** * Composable providing real Wails binding wrappers for the AI copilot. * * Calls the Go AIService via Wails v3 Call.ByName. SendMessage blocks * until the full response (including tool-use loops) is complete. */ export function useCopilot() { const store = useCopilotStore(); /** * Process a user message by calling the real Go backend. * The backend blocks until the full response is ready (no streaming yet). */ async function processMessage(text: string): Promise { store.isStreaming = true; // Ensure we have a conversation if (!store.activeConversationId) { try { const convId = await callAI("NewConversation"); store.activeConversationId = convId; } catch (err) { store.messages.push({ id: `msg-${Date.now()}`, role: "assistant", content: `Error creating conversation: ${err}`, timestamp: Date.now(), }); store.isStreaming = false; return; } } try { const response = await callAI( "SendMessage", store.activeConversationId, text, ); // Build the assistant message from the response const toolCalls: ToolCall[] | undefined = response.toolCalls?.map( (tc) => ({ id: `tc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, name: tc.name, input: (tc.input ?? {}) as Record, result: tc.result, status: (tc.error ? "error" : "done") as "done" | "error", }), ); store.messages.push({ id: `msg-${Date.now()}`, role: "assistant", content: response.text || "", toolCalls: toolCalls, timestamp: Date.now(), }); } catch (err) { store.messages.push({ id: `msg-${Date.now()}`, role: "assistant", content: `Error: ${err}`, timestamp: Date.now(), }); } finally { store.isStreaming = false; } } /** Begin the OAuth login flow (opens browser). */ async function startLogin(): Promise { try { await callAI("StartLogin"); } catch (err) { console.error("StartLogin failed:", err); } } /** Check whether the user is authenticated. */ async function checkAuth(): Promise { try { const authed = await callAI("IsAuthenticated"); store.isAuthenticated = authed; return authed; } catch { return false; } } /** Log out and clear tokens. */ async function logout(): Promise { try { await callAI("Logout"); store.isAuthenticated = false; store.clearHistory(); } catch (err) { console.error("Logout failed:", err); } } /** Sync the model setting to the Go backend. */ async function setModel(model: string): Promise { try { await callAI("SetModel", model); store.model = model; } catch (err) { console.error("SetModel failed:", err); } } /** Load the current model from the Go backend. */ async function getModel(): Promise { try { const m = await callAI("GetModel"); store.model = m; return m; } catch { return store.model; } } return { processMessage, startLogin, checkAuth, logout, setModel, getModel }; }