import { listen } from "@tauri-apps/api/event"; import { invoke } from "@tauri-apps/api/core"; import { claudeStore } from "$lib/stores/claude"; import { characterState } from "$lib/stores/character"; import { configStore } from "$lib/stores/config"; import { initStatsListener, resetSessionStats } from "$lib/stores/stats"; import { initAchievementsListener } from "$lib/stores/achievements"; import type { ConnectionStatus, PermissionPromptEvent } from "$lib/types/messages"; import type { CharacterState } from "$lib/types/states"; import { initializeNotificationRules, cleanupNotificationRules, handleConnectionStatusChange, handleNewUserMessage, } from "$lib/notifications/rules"; interface StateChangePayload { state: CharacterState; tool_name: string | null; } let hasConnectedThisSession = false; let unlisteners: Array<() => void> = []; let skipNextGreeting = false; export function setSkipNextGreeting(skip: boolean) { skipNextGreeting = skip; } function getTimeOfDay(): string { const hour = new Date().getHours(); if (hour >= 5 && hour < 12) { return "morning"; } else if (hour >= 12 && hour < 17) { return "afternoon"; } else if (hour >= 17 && hour < 21) { return "evening"; } else { return "late night"; } } function generateGreetingPrompt(): string { const timeOfDay = getTimeOfDay(); return `[System: A new session has started. It's currently ${timeOfDay}. Please greet the user warmly and briefly. Keep it short - just 1-2 sentences.]`; } async function sendGreeting() { // Check if we should skip this greeting if (skipNextGreeting) { skipNextGreeting = false; // Reset the flag return; } const config = configStore.getConfig(); if (!config.greeting_enabled) { return; } const greetingPrompt = config.greeting_custom_prompt?.trim() || generateGreetingPrompt(); // Don't show the system prompt in the UI - just trigger Claude to respond characterState.setState("thinking"); // Reset notification state for greeting handleNewUserMessage(); try { await invoke("send_prompt", { message: greetingPrompt }); } catch (error) { console.error("Failed to send greeting:", error); claudeStore.addLine("error", `Failed to send greeting: ${error}`); characterState.setTemporaryState("error", 3000); } } interface OutputPayload { line_type: string; content: string; tool_name: string | null; } export async function initializeTauriListeners() { // Cleanup any existing listeners first cleanupTauriListeners(); // Initialize notification rules initializeNotificationRules(); // Initialize stats listener await initStatsListener(); // Initialize achievements listener await initAchievementsListener(); const connectionUnlisten = await listen("claude:connection", async (event) => { const status = event.payload as ConnectionStatus; claudeStore.setConnectionStatus(status); // Handle notification for connection status handleConnectionStatusChange(status); if (status === "connected") { claudeStore.addLine("system", "Connected to Claude Code"); characterState.setState("idle"); if (!hasConnectedThisSession) { hasConnectedThisSession = true; resetSessionStats(); // Reset session stats on new connection await sendGreeting(); } } else if (status === "disconnected") { // Only reset session flag if we're not about to reconnect if (!skipNextGreeting) { hasConnectedThisSession = false; } // Don't add system message if we're about to reconnect if (!skipNextGreeting) { claudeStore.addLine("system", "Disconnected from Claude Code"); } characterState.setState("idle"); } else if (status === "error") { hasConnectedThisSession = false; claudeStore.addLine("error", "Connection error"); characterState.setTemporaryState("error", 3000); } }); unlisteners.push(connectionUnlisten); const stateUnlisten = await listen("claude:state", (event) => { const { state } = event.payload; const stateMap: Record = { idle: "idle", thinking: "thinking", typing: "typing", searching: "searching", coding: "coding", mcp: "mcp", permission: "permission", success: "success", error: "error", }; const mappedState = stateMap[state.toLowerCase()] || "idle"; if (mappedState === "success" || mappedState === "error") { characterState.setTemporaryState(mappedState, 3000); } else { characterState.setState(mappedState); } }); unlisteners.push(stateUnlisten); const outputUnlisten = await listen("claude:output", (event) => { const { line_type, content, tool_name } = event.payload; claudeStore.addLine( line_type as "user" | "assistant" | "system" | "tool" | "error", content, tool_name || undefined ); }); unlisteners.push(outputUnlisten); const streamUnlisten = await listen("claude:stream", () => { // no-op }); unlisteners.push(streamUnlisten); const sessionUnlisten = await listen("claude:session", (event) => { claudeStore.setSessionId(event.payload); claudeStore.addLine("system", `Session: ${event.payload.substring(0, 8)}...`); }); unlisteners.push(sessionUnlisten); const cwdUnlisten = await listen("claude:cwd", (event) => { claudeStore.setWorkingDirectory(event.payload); }); unlisteners.push(cwdUnlisten); const permissionUnlisten = await listen("claude:permission", (event) => { const { id, tool_name, tool_input, description } = event.payload; claudeStore.requestPermission({ id, tool: tool_name, description, input: tool_input, }); claudeStore.addLine("system", `Permission requested for: ${tool_name}`); }); unlisteners.push(permissionUnlisten); console.log("Tauri event listeners initialized"); } export function cleanupTauriListeners() { // Cleanup all event listeners unlisteners.forEach((unlisten) => unlisten()); unlisteners = []; // Cleanup notification rules cleanupNotificationRules(); console.log("Tauri event listeners cleaned up"); }