[PRO-1078] fix problem saving + merge problems and submissions

This commit is contained in:
D. Seifert 2025-03-27 18:16:06 +08:00
parent dc21175300
commit 3a202eeb3d
3 changed files with 105 additions and 200 deletions

View File

@ -12,8 +12,9 @@ import { HistoryItem } from './HistoryItem';
import { binDates } from './date-binning';
import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
import { SaveProblem } from './SaveProblem';
import { SaveReproductionModal } from './SaveReproduction';
import { useAdminStatus } from '~/lib/stores/user';
import { authStatusStore } from '../../lib/stores/auth';
import { useStore } from '@nanostores/react';
const menuVariants = {
closed: {
@ -46,7 +47,7 @@ export const Menu = () => {
const [open, setOpen] = useState(false);
const [dialogContent, setDialogContent] = useState<DialogContent>(null);
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const { isAdmin } = useAdminStatus();
const isLoggedIn = useStore(authStatusStore.isLoggedIn);
const { filteredItems: filteredList, handleSearchChange } = useSearchFilter({
items: list,
@ -140,8 +141,7 @@ export const Menu = () => {
>
Problems
</a>
<SaveProblem />
{isAdmin && <SaveReproductionModal />}
{isLoggedIn && <SaveProblem />}
<a
href="/about"
className="flex gap-2 bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"

View File

@ -3,14 +3,102 @@ import ReactModal from 'react-modal';
import { useState, useEffect } from 'react';
import { workbenchStore } from '~/lib/stores/workbench';
import { submitProblem, BoltProblemStatus } from '~/lib/replay/Problems';
import type { BoltProblemInput } from '~/lib/replay/Problems';
import type { BoltProblemInput, BoltProblemSolution } from '~/lib/replay/Problems';
import { shouldUseSupabase, getCurrentUser } from '~/lib/supabase/client';
import { authModalStore } from '~/lib/stores/authModal';
import { authStatusStore } from '~/lib/stores/auth';
import { useStore } from '@nanostores/react';
import {
getLastUserSimulationData,
getLastSimulationChatMessages,
isSimulatingOrHasFinished,
getLastSimulationChatReferences,
} from '~/lib/replay/SimulationPrompt';
ReactModal.setAppElement('#root');
// External functions for problem storage
async function saveProblem(
title: string,
description: string,
username: string,
reproData: any,
): Promise<string | null> {
if (!title) {
toast.error('Please fill in title field');
return null;
}
if (!shouldUseSupabase() && !username) {
toast.error('Please enter a username');
return null;
}
toast.info('Submitting problem...');
const repositoryId = workbenchStore.repositoryId.get();
if (!repositoryId) {
toast.error('No repository ID found');
return null;
}
const solution: BoltProblemSolution = {
evaluator: undefined,
...reproData,
};
const problem: BoltProblemInput = {
version: 2,
title,
description,
username: shouldUseSupabase() ? (undefined as any) : username,
user_id: shouldUseSupabase() ? (await getCurrentUser())?.id || '' : undefined,
repositoryId,
status: BoltProblemStatus.Pending,
solution,
};
const problemId = await submitProblem(problem);
if (problemId) {
localStorage.setItem('loadedProblemId', problemId);
}
return problemId;
}
function getReproductionData(): any | null {
if (!isSimulatingOrHasFinished()) {
toast.error('No simulation data found (neither in progress nor finished)');
return null;
}
try {
const simulationData = getLastUserSimulationData();
if (!simulationData) {
toast.error('No simulation data found');
return null;
}
const messages = getLastSimulationChatMessages();
const references = getLastSimulationChatReferences();
if (!messages) {
toast.error('No user prompt found');
return null;
}
return { simulationData, messages, references };
} catch (error: any) {
console.error('Error getting reproduction data', error?.stack || error);
toast.error(`Error getting reproduction data: ${error?.message}`);
return null;
}
}
// Component for saving the current chat as a new problem.
export function SaveProblem() {
@ -21,6 +109,7 @@ export function SaveProblem() {
username: '',
});
const [problemId, setProblemId] = useState<string | null>(null);
const [reproData, setReproData] = useState<any>(null);
const isLoggedIn = useStore(authStatusStore.isLoggedIn);
const username = useStore(authStatusStore.username);
@ -34,6 +123,13 @@ export function SaveProblem() {
const handleSaveProblem = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const currentReproData = getReproductionData();
if (!currentReproData) {
return;
}
setReproData(currentReproData);
setIsModalOpen(true);
setProblemId(null);
};
@ -52,40 +148,13 @@ export function SaveProblem() {
};
const handleSubmitProblem = async () => {
if (!formData.title) {
toast.error('Please fill in title field');
if (!reproData) {
return;
}
if (!shouldUseSupabase() && !formData.username) {
toast.error('Please enter a username');
return;
}
toast.info('Submitting problem...');
const repositoryId = workbenchStore.repositoryId.get();
if (!repositoryId) {
toast.error('No repository ID found');
return;
}
const problem: BoltProblemInput = {
version: 2,
title: formData.title,
description: formData.description,
username: shouldUseSupabase() ? (undefined as any) : formData.username,
user_id: shouldUseSupabase() ? (await getCurrentUser())?.id || '' : undefined,
repositoryId,
status: BoltProblemStatus.Pending,
};
const problemId = await submitProblem(problem);
if (problemId) {
setProblemId(problemId);
localStorage.setItem('loadedProblemId', problemId);
const newProblemId = await saveProblem(formData.title, formData.description, formData.username, reproData);
if (newProblemId) {
setProblemId(newProblemId);
}
};

View File

@ -1,164 +0,0 @@
import { toast } from 'react-toastify';
import ReactModal from 'react-modal';
import { useState } from 'react';
import { updateProblem } from '~/lib/replay/Problems';
import type { BoltProblem, BoltProblemInput, BoltProblemSolution } from '~/lib/replay/Problems';
import { getOrFetchLastLoadedProblem } from '~/components/chat/LoadProblemButton';
import {
getLastUserSimulationData,
getLastSimulationChatMessages,
isSimulatingOrHasFinished,
getLastSimulationChatReferences,
} from '~/lib/replay/SimulationPrompt';
ReactModal.setAppElement('#root');
/*
* Component for saving input simulation and prompt information for
* the problem the current chat was loaded from.
*/
export function SaveReproductionModal() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [savedReproduction, setSavedReproduction] = useState<boolean>(false);
const [problem, setProblem] = useState<BoltProblem | null>(null);
const handleSaveReproduction = async (e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
const loadId = toast.loading('Loading problem...');
try {
const lastProblem = await getOrFetchLastLoadedProblem();
if (!lastProblem) {
toast.error('No problem loaded');
return;
}
setProblem(lastProblem);
} finally {
toast.dismiss(loadId);
}
setSavedReproduction(false);
setIsModalOpen(true);
};
const handleSubmitReproduction = async () => {
if (!problem) {
toast.error('No problem loaded');
return;
}
if (!isSimulatingOrHasFinished()) {
toast.error('No simulation data found (neither in progress nor finished)');
return;
}
try {
toast.info('Submitting reproduction...');
console.log('SubmitReproduction');
const simulationData = getLastUserSimulationData();
if (!simulationData) {
toast.error('No simulation data found');
return;
}
const messages = getLastSimulationChatMessages();
const references = getLastSimulationChatReferences();
if (!messages) {
toast.error('No user prompt found');
return;
}
const reproData = { simulationData, messages, references };
/**
* TODO: Split `solution` into `reproData` and `evaluator`.
*/
const solution: BoltProblemSolution = {
evaluator: problem.solution?.evaluator,
...reproData,
/*
* TODO: Also store recordingId for easier debugging.
* recordingId,
*/
};
const problemUpdatePacket: BoltProblemInput = {
...problem,
version: 2,
solution,
};
await updateProblem(problem.problemId, problemUpdatePacket);
setSavedReproduction(true);
} catch (error: any) {
console.error('Error saving reproduction', error?.stack || error);
toast.error(`Error saving reproduction: ${error?.message}`);
}
};
return (
<>
<a
href="#"
className="flex gap-2 bg-bolt-elements-sidebar-buttonBackgroundDefault text-bolt-elements-sidebar-buttonText hover:bg-bolt-elements-sidebar-buttonBackgroundHover rounded-md p-2 transition-theme"
onClick={handleSaveReproduction}
>
Save Reproduction
</a>
<ReactModal
isOpen={isModalOpen}
onRequestClose={() => setIsModalOpen(false)}
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg p-6 max-w-2xl w-full z-50"
overlayClassName="fixed inset-0 bg-black bg-opacity-50 z-40"
>
{savedReproduction && (
<>
<div className="text-center mb-2">Reproduction Saved</div>
<div className="text-center">
<div className="flex justify-center gap-2 mt-4">
<button
onClick={() => setIsModalOpen(false)}
className="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400"
>
Close
</button>
</div>
</div>
</>
)}
{!savedReproduction && (
<>
<div className="text-center">
Save reproduction data (prompt, user annotations + simulationData) for the currently loaded problem:{' '}
{problem?.problemId}
</div>
<div style={{ marginTop: '10px' }}>
<div className="flex justify-center gap-2 mt-4">
<button
onClick={handleSubmitReproduction}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Submit
</button>
<button
onClick={() => setIsModalOpen(false)}
className="px-4 py-2 bg-gray-300 rounded hover:bg-gray-400"
>
Cancel
</button>
</div>
</div>
</>
)}
</ReactModal>
</>
);
}