trigo / trigo-web /app /src /composables /useTrigoAgent.ts
k-l-lambda's picture
updated
502af73
/**
* Vue Composable for Trigo AI Agent
*
* Provides reactive interface to the ONNX-based AI agent for move generation.
* Features:
* - Lazy initialization (only loads when needed)
* - Reactive state tracking (ready, thinking, errors)
* - Automatic cleanup on unmount
* - Move generation with error handling
* - Uses tree agent with evaluation mode for efficient parallel move evaluation
*/
import { ref, onUnmounted } from "vue";
import { TrigoTreeAgent } from "@inc/trigoTreeAgent";
import { OnnxInferencer } from "@/services/onnxInferencer";
import type { TrigoGame } from "@inc/trigo/game";
import type { Position } from "@inc/trigo/types";
/**
* Composable for using the Trigo AI agent in Vue components
*/
export function useTrigoAgent() {
// Reactive state
const isReady = ref(false);
const isThinking = ref(false);
const error = ref<string | null>(null);
const lastMoveTime = ref<number>(0);
// Agent instance (created lazily)
let agent: TrigoTreeAgent | null = null;
let inferencer: OnnxInferencer | null = null;
/**
* Initialize the AI agent
* - Loads ONNX evaluation model and prepares for inference
* - Call this when entering VS AI mode
* - Safe to call multiple times (won't re-initialize)
*/
const initialize = async (): Promise<void> => {
if (isReady.value) {
console.log("[useTrigoAgent] Already initialized");
return;
}
error.value = null;
try {
console.log("[useTrigoAgent] Initializing tree agent with evaluation mode...");
// Create OnnxInferencer with evaluation model
inferencer = new OnnxInferencer({
modelPath: "/onnx/GPT2CausalLM_ep0015_evaluation.onnx",
vocabSize: 259,
seqLen: 2048 // Evaluation models support longer sequences
});
// Initialize ONNX model
await inferencer.initialize();
// Create tree agent with the inferencer
agent = new TrigoTreeAgent(inferencer);
isReady.value = true;
console.log("[useTrigoAgent] ✓ Tree agent ready");
} catch (err) {
const errorMessage =
err instanceof Error ? err.message : "Failed to initialize AI agent";
error.value = errorMessage;
console.error("[useTrigoAgent] Initialization failed:", err);
throw err;
}
};
/**
* Generate the next move for the AI player
*
* @param game - Current game instance
* @returns The selected move position, or null if no valid moves or pass move
* @throws Error if agent is not initialized
*/
const generateMove = async (game: TrigoGame): Promise<Position | null> => {
if (!agent) {
throw new Error("AI agent not initialized. Call initialize() first.");
}
if (!isReady.value) {
throw new Error("AI agent is not ready yet.");
}
if (isThinking.value) {
console.warn("[useTrigoAgent] Already generating a move, ignoring request");
return null;
}
error.value = null;
isThinking.value = true;
try {
console.log("[useTrigoAgent] Generating move with tree agent...");
const startTime = performance.now();
// Use tree agent to select best move
const move = await agent.selectBestMove(game);
const elapsed = performance.now() - startTime;
lastMoveTime.value = elapsed;
console.log(`[useTrigoAgent] ✓ Move generated in ${elapsed.toFixed(2)}ms`);
// Convert Move to Position (return null if pass move)
if (!move || move.isPass) {
console.log("[useTrigoAgent] AI chose to pass");
return null;
}
if (move.x !== undefined && move.y !== undefined && move.z !== undefined) {
return { x: move.x, y: move.y, z: move.z };
}
console.warn("[useTrigoAgent] Move has undefined coordinates:", move);
return null;
} catch (err) {
const errorMessage = err instanceof Error ? err.message : "Failed to generate move";
error.value = errorMessage;
console.error("[useTrigoAgent] Move generation failed:", err);
throw err;
} finally {
isThinking.value = false;
}
};
/**
* Check if the agent is initialized and ready
*/
const checkIsReady = (): boolean => {
return agent !== null && inferencer !== null;
};
/**
* Clean up resources when component unmounts
*/
const cleanup = (): void => {
if (agent || inferencer) {
console.log("[useTrigoAgent] Cleaning up...");
agent = null;
inferencer = null;
isReady.value = false;
isThinking.value = false;
error.value = null;
lastMoveTime.value = 0;
}
};
// Automatic cleanup on component unmount
onUnmounted(() => {
cleanup();
});
return {
// State
isReady,
isThinking,
error,
lastMoveTime,
// Methods
initialize,
generateMove,
checkIsReady,
cleanup
};
}