All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 1m2s
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>
164 lines
4.2 KiB
TypeScript
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,
|
|
};
|
|
});
|