// Accessors for the API to access saved problems. import { toast } from "react-toastify"; import { sendCommandDedicatedClient } from "./ReplayProtocolClient"; import type { ProtocolMessage } from "./SimulationPrompt"; import Cookies from 'js-cookie'; import JSZip from 'jszip'; import type { FileArtifact } from "~/utils/folderImport"; export interface BoltProblemComment { username?: string; content: string; timestamp: number; } export interface BoltProblemSolution { simulationData: any; messages: ProtocolMessage[]; evaluator?: string; } export enum BoltProblemStatus { // Problem has been submitted but not yet reviewed. Pending = "Pending", // Problem has been reviewed and has not been solved yet. Unsolved = "Unsolved", // Nut automatically produces a suitable explanation for solving the problem. Solved = "Solved", } // Information about each problem stored in the index file. export interface BoltProblemDescription { version: number; problemId: string; timestamp: number; title: string; description: string; status?: BoltProblemStatus; keywords?: string[]; } export interface BoltProblem extends BoltProblemDescription { username?: string; repositoryContents: string; comments?: BoltProblemComment[]; solution?: BoltProblemSolution; } export type BoltProblemInput = Omit; export async function listAllProblems(): Promise { try { const rv = await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "listBoltProblems", }, }); console.log("ListProblemsRval", rv); return (rv as any).rval.problems.reverse(); } catch (error) { console.error("Error fetching problems", error); toast.error("Failed to fetch problems"); return []; } } export async function getProblem(problemId: string): Promise { try { const rv = await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "fetchBoltProblem", params: { problemId }, }, }); console.log("FetchProblemRval", rv); const problem = (rv as { rval: { problem: BoltProblem } }).rval.problem; if ("prompt" in problem) { // 2/11/2025: Update obsolete data format for older problems. problem.repositoryContents = (problem as any).prompt.content; delete problem.prompt; } return problem; } catch (error) { console.error("Error fetching problem", error); toast.error("Failed to fetch problem"); } return null; } export async function submitProblem(problem: BoltProblemInput): Promise { try { const rv = await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "submitBoltProblem", params: { problem }, }, }); console.log("SubmitProblemRval", rv); return (rv as any).rval.problemId; } catch (error) { console.error("Error submitting problem", error); toast.error("Failed to submit problem"); return null; } } export async function updateProblem(problemId: string, problem: BoltProblemInput) { try { if (!getNutIsAdmin()) { toast.error("Admin user required"); return; } const loginKey = Cookies.get(nutLoginKeyCookieName); await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "updateBoltProblem", params: { problemId, problem, loginKey }, }, }); } catch (error) { console.error("Error updating problem", error); toast.error("Failed to update problem"); } } const nutLoginKeyCookieName = 'nutLoginKey'; const nutIsAdminCookieName = 'nutIsAdmin'; export function getNutLoginKey(): string | undefined { const cookieValue = Cookies.get(nutLoginKeyCookieName); return cookieValue?.length ? cookieValue : undefined; } export function getNutIsAdmin(): boolean { return Cookies.get(nutIsAdminCookieName) === 'true'; } interface UserInfo { username: string; loginKey: string; details: string; admin: boolean; } export async function saveNutLoginKey(key: string) { const { rval: { userInfo } } = await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "getUserInfo", params: { loginKey: key }, }, }) as { rval: { userInfo: UserInfo } }; console.log("UserInfo", userInfo); Cookies.set(nutLoginKeyCookieName, key); Cookies.set(nutIsAdminCookieName, userInfo.admin ? 'true' : 'false'); } export function setNutIsAdmin(isAdmin: boolean) { Cookies.set(nutIsAdminCookieName, isAdmin ? 'true' : 'false'); } const nutProblemsUsernameCookieName = 'nutProblemsUsername'; export function getProblemsUsername(): string | undefined { const cookieValue = Cookies.get(nutProblemsUsernameCookieName); return cookieValue?.length ? cookieValue : undefined; } export function saveProblemsUsername(username: string) { Cookies.set(nutProblemsUsernameCookieName, username); } export async function extractFileArtifactsFromRepositoryContents(repositoryContents: string): Promise { const zip = new JSZip(); await zip.loadAsync(repositoryContents, { base64: true }); const fileArtifacts: FileArtifact[] = []; for (const [key, object] of Object.entries(zip.files)) { if (object.dir) continue; fileArtifacts.push({ content: await object.async('text'), path: key, }); } return fileArtifacts; } export async function submitFeedback(feedback: any) { try { const rv = await sendCommandDedicatedClient({ method: "Recording.globalExperimentalCommand", params: { name: "submitFeedback", params: { feedback }, }, }); console.log("SubmitFeedbackRval", rv); return true; } catch (error) { console.error("Error submitting feedback", error); toast.error("Failed to submit feedback"); return false; } }