"use client"; import { useState, useEffect, useCallback } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Flag, Clock, Hash, ArrowRight, Bot } from "lucide-react"; import { useInference } from "@/lib/inference"; import { API_BASE } from "@/lib/constants"; type Message = { role: "user" | "assistant"; content: string; }; const buildPrompt = ( current: string, target: string, path_so_far: string[], links: string[] ) => { const formatted_links = links .map((link, index) => `${index + 1}. ${link}`) .join("\n"); const path_so_far_str = path_so_far.join(" -> "); return `You are playing WikiRun, trying to navigate from one Wikipedia article to another using only links. IMPORTANT: You MUST put your final answer in NUMBER tags, where NUMBER is the link number. For example, if you want to choose link 3, output 3. Current article: ${current} Target article: ${target} You have ${links.length} link(s) to choose from: ${formatted_links} Your path so far: ${path_so_far_str} Think about which link is most likely to lead you toward the target article. First, analyze each link briefly and how it connects to your goal, then select the most promising one. Remember to format your final answer by explicitly writing out the xml number tags like this: NUMBER`; }; interface GameComponentProps { player: "me" | "model"; model?: string; maxHops: number; startPage: string; targetPage: string; onReset: () => void; maxTokens: number; maxLinks: number; } export default function GameComponent({ player, model, maxHops, startPage, targetPage, onReset, maxTokens, maxLinks, }: GameComponentProps) { const [currentPage, setCurrentPage] = useState(startPage); const [currentPageLinks, setCurrentPageLinks] = useState([]); const [linksLoading, setLinksLoading] = useState(false); const [hops, setHops] = useState(0); const [timeElapsed, setTimeElapsed] = useState(0); const [visitedNodes, setVisitedNodes] = useState([startPage]); const [gameStatus, setGameStatus] = useState<"playing" | "won" | "lost">( "playing" ); const [convo, setConvo] = useState([]); const { status: modelStatus, partialText, inferenceResult, inference } = useInference({ apiKey: window.localStorage.getItem("huggingface_access_token") || undefined, }); const fetchCurrentPageLinks = useCallback(async () => { setLinksLoading(true); const response = await fetch( `${API_BASE}/get_article_with_links/${currentPage}` ); const data = await response.json(); setCurrentPageLinks(data.links.slice(0, maxLinks)); setLinksLoading(false); }, [currentPage, maxLinks]); useEffect(() => { fetchCurrentPageLinks(); }, [fetchCurrentPageLinks]); useEffect(() => { if (gameStatus === "playing") { const timer = setInterval(() => { setTimeElapsed((prev) => prev + 1); }, 1000); return () => clearInterval(timer); } }, [gameStatus]); // Check win condition useEffect(() => { if (currentPage === targetPage) { setGameStatus("won"); } else if (hops >= maxHops) { setGameStatus("lost"); } }, [currentPage, targetPage, hops, maxHops]); const handleLinkClick = (link: string) => { if (gameStatus !== "playing") return; setCurrentPage(link); setHops((prev) => prev + 1); setVisitedNodes((prev) => [...prev, link]); }; const makeModelMove = async () => { const prompt = buildPrompt( currentPage, targetPage, visitedNodes, currentPageLinks ); pushConvo({ role: "user", content: prompt, }); const modelResponse = await inference({ model: model, prompt, maxTokens: maxTokens, }); pushConvo({ role: "assistant", content: modelResponse, }); console.log("Model response", modelResponse); const answer = modelResponse.match(/(.*?)<\/answer>/)?.[1]; if (!answer) { console.error("No answer found in model response"); return; } // try parsing the answer as an integer const answerInt = parseInt(answer); if (isNaN(answerInt)) { console.error("Invalid answer found in model response"); return; } if (answerInt < 1 || answerInt > currentPageLinks.length) { console.error( "Selected link out of bounds", answerInt, "from ", currentPageLinks.length, "links" ); return; } const selectedLink = currentPageLinks[answerInt - 1]; console.log( "Model picked selectedLink", selectedLink, "from ", currentPageLinks ); handleLinkClick(selectedLink); }; const handleGiveUp = () => { setGameStatus("lost"); }; const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs < 10 ? "0" : ""}${secs}`; }; const pushConvo = (message: Message) => { setConvo((prev) => [...prev, message]); }; return (

Game Status

Current
{currentPage}
Target
{targetPage}
Hops
{hops} / {maxHops}
Time
{formatTime(timeElapsed)}
{player === "model" && (
{model} {modelStatus === "thinking" ? "is thinking..." : "is playing"}
)}
{/* Left pane - Current page and available links */}

Available Links

{gameStatus !== "playing" && ( )}
{/* Available links */} {gameStatus === "playing" && ( <>
{currentPageLinks .sort((a, b) => a.localeCompare(b)) .map((link) => ( ))}
{player === "model" && ( )} )} {player === "model" && modelStatus === "thinking" && gameStatus === "playing" && (
{model} is thinking...
)} {gameStatus === "playing" && player === "me" && ( )} {gameStatus === "won" && (

{player === "model" ? `${model} won!` : "You won!"}

{player === "model" ? "It" : "You"} reached {targetPage} in {hops}{" "} hops.

)} {gameStatus === "lost" && (

Game Over

{player === "model" ? `${model} didn't` : "You didn't"} reach{" "} {targetPage} within {maxHops} hops.

)}

LLM Reasoning

{ convo.map((message, index) => (

{message.role}

{message.content}


)) } { modelStatus === "thinking" && (

{partialText}

)}
{/*