"use client"; import { useState, useEffect, useCallback, useMemo } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Flag, Clock, Hash, BarChart, ArrowRight, Bot } from "lucide-react"; import inference from "@/lib/inference"; import ReasoningTrace, { Run, Step } from "./reasoning-trace"; import ForceDirectedGraph from "./force-directed-graph"; const mockRun: Run = { steps: [ { type: "start", article: "Dogs", metadata: { message: "Starting Node", }, }, { type: "step", article: "Dogs", links: ["Dogs", "Cats", "Birds"], metadata: { conversation: [ { role: "user", content: "I want to go to the moon", }, { role: "assistant", content: "I want to go to the moon", }, ], }, }, ], }; 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`; }; const API_BASE = "http://localhost:8000"; 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 [reasoningTrace, setReasoningTrace] = useState({ start_article: startPage, destination_article: targetPage, steps: [ { type: "start", article: startPage, metadata: { message: "Starting Node", }, }, ], }); const [isModelThinking, setIsModelThinking] = useState(false); const runs = useMemo(() => { return reasoningTrace ? [reasoningTrace] : []; }, [reasoningTrace]); 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 addStepToReasoningTrace = (step: Step) => { setReasoningTrace((prev) => { if (!prev) return { steps: [step], start_article: startPage, destination_article: targetPage }; return { steps: [...prev.steps, step], start_article: startPage, destination_article: targetPage }; }); }; const handleLinkClick = (link: string, userClicked: boolean = true) => { if (gameStatus !== "playing") return; setCurrentPage(link); setHops((prev) => prev + 1); setVisitedNodes((prev) => [...prev, link]); if (userClicked) { addStepToReasoningTrace({ type: "step", article: link, links: currentPageLinks, metadata: { message: "User clicked link", }, }); } }; const makeModelMove = async () => { setIsModelThinking(true); const prompt = buildPrompt( currentPage, targetPage, visitedNodes, currentPageLinks ); const modelResponse = await inference({ apiKey: window.localStorage.getItem("huggingface_access_token") || undefined, model: model, prompt, maxTokens: maxTokens, }); console.log("Model response", modelResponse.content); const answer = modelResponse.content?.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 ); addStepToReasoningTrace({ type: "step", article: selectedLink, links: currentPageLinks, metadata: { message: "Model picked link", conversation: [ { role: "user", content: prompt, }, { role: "assistant", content: modelResponse.content || "", }, ], }, }); handleLinkClick(selectedLink, false); setIsModelThinking(false); }; const handleGiveUp = () => { setGameStatus("lost"); }; const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins}:${secs < 10 ? "0" : ""}${secs}`; }; return (

Game Status

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

Available Links

{gameStatus !== "playing" && ( )}
{/* Wikipedia iframe (mocked) */} {/*

{currentPage}

https://en.wikipedia.org/wiki/{currentPage.replace(/\s+/g, "_")}

This is a mock Wikipedia page for {currentPage}. In the actual implementation, this would be an iframe showing the real Wikipedia page.

*/} {/* Available links */} {gameStatus === "playing" && ( <>
{currentPageLinks .sort((a, b) => a.localeCompare(b)) .map((link) => ( ))}
{player === "model" && ( )} )} {player === "model" && isModelThinking && 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.

)}
{/* Right pane - Game stats and graph */}
Path Visualization
); }