wraith/frontend/src/stores/copilot.store.ts
Vantz Stockwell fbd2fd4f80
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m2s
feat: wire real Claude API — OAuth login + live chat via Wails bindings
Replace mock responses in the XO copilot panel with real Wails binding
calls to the Go AIService backend:

- StartLogin now opens the browser via pkg/browser.OpenURL
- SendMessage returns ChatResponse (text + tool call results) instead of
  bare error, fixing the tool-call accumulation bug in messageLoop
- Add GetModel/SetModel methods for frontend model switching
- Frontend useCopilot composable calls Go via Call.ByName from
  @wailsio/runtime, with conversation auto-creation, auth checks, and
  error display in the chat panel
- Store defaults to isAuthenticated=false; panel checks auth on mount
- CopilotSettings syncs model changes and logout to the backend

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 10:22:07 -04:00

164 lines
4.2 KiB
TypeScript

import { defineStore } from "pinia";
import { ref } from "vue";
export interface ToolCall {
id: string;
name: string;
input: Record<string, unknown>;
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<string | null>(null);
const messages = ref<Message[]>([]);
const conversations = ref<ConversationSummary[]>([]);
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<void> {
// 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,
};
});