Merge pull request #70 from replayio/dominik/pro-1053-rework-save-solution-in-nut-ui-update-docs-on-the-solve

`Save Reproduction` button
This commit is contained in:
Domi 2025-03-18 23:33:46 +08:00 committed by GitHub
commit bd9d13ca5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 41 additions and 74 deletions

View File

@ -20,7 +20,7 @@ export function setLastLoadedProblem(problem: BoltProblem) {
localStorage.setItem('loadedProblemId', problem.problemId); localStorage.setItem('loadedProblemId', problem.problemId);
localStorage.setItem('loadedProblem', problemSerialized); localStorage.setItem('loadedProblem', problemSerialized);
} catch (error: any) { } catch (error: any) {
// Remove loadedProblem, so we don't accidentally associate a solution with the wrong problem. // Remove loadedProblem, so we don't accidentally associate (e.g. reproduction) data with the wrong problem.
localStorage.removeItem('loadedProblem'); localStorage.removeItem('loadedProblem');
console.error( console.error(
`Failed to set last loaded problem (size=${(problemSerialized.length / 1024).toFixed(2)}kb):`, `Failed to set last loaded problem (size=${(problemSerialized.length / 1024).toFixed(2)}kb):`,

View File

@ -12,7 +12,7 @@ import { HistoryItem } from './HistoryItem';
import { binDates } from './date-binning'; import { binDates } from './date-binning';
import { useSearchFilter } from '~/lib/hooks/useSearchFilter'; import { useSearchFilter } from '~/lib/hooks/useSearchFilter';
import { SaveProblem } from './SaveProblem'; import { SaveProblem } from './SaveProblem';
import { SaveSolution } from './SaveSolution'; import { SaveReproductionModal } from './SaveReproduction';
import { getNutIsAdmin } from '~/lib/replay/Problems'; import { getNutIsAdmin } from '~/lib/replay/Problems';
const menuVariants = { const menuVariants = {
@ -140,7 +140,7 @@ export const Menu = () => {
Problems Problems
</a> </a>
<SaveProblem /> <SaveProblem />
{getNutIsAdmin() && <SaveSolution />} {getNutIsAdmin() && <SaveReproductionModal />}
<a <a
href="/about" 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" 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

@ -1,8 +1,8 @@
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import ReactModal from 'react-modal'; import ReactModal from 'react-modal';
import { useState } from 'react'; import { useState } from 'react';
import { BoltProblemStatus, updateProblem } from '~/lib/replay/Problems'; import { updateProblem } from '~/lib/replay/Problems';
import type { BoltProblem, BoltProblemInput } from '~/lib/replay/Problems'; import type { BoltProblem, BoltProblemInput, BoltProblemSolution } from '~/lib/replay/Problems';
import { getOrFetchLastLoadedProblem } from '~/components/chat/LoadProblemButton'; import { getOrFetchLastLoadedProblem } from '~/components/chat/LoadProblemButton';
import { import {
getLastUserSimulationData, getLastUserSimulationData,
@ -18,52 +18,38 @@ ReactModal.setAppElement('#root');
* the problem the current chat was loaded from. * the problem the current chat was loaded from.
*/ */
export function SaveSolution() { export function SaveReproductionModal() {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [formData, setFormData] = useState({ const [savedReproduction, setSavedReproduction] = useState<boolean>(false);
evaluator: '',
});
const [savedSolution, setSavedSolution] = useState<boolean>(false);
const [problem, setProblem] = useState<BoltProblem | null>(null); const [problem, setProblem] = useState<BoltProblem | null>(null);
const handleSaveSolution = async () => { const handleSaveReproduction = async () => {
const loadId = toast.loading('Loading problem...'); const loadId = toast.loading('Loading problem...');
try { try {
const savedProblem = await getOrFetchLastLoadedProblem(); const lastProblem = await getOrFetchLastLoadedProblem();
if (!savedProblem) { if (!lastProblem) {
toast.error('No problem loaded'); toast.error('No problem loaded');
return; return;
} }
setProblem(savedProblem); setProblem(lastProblem);
setFormData({
evaluator: savedProblem.solution?.evaluator || '',
});
} finally { } finally {
toast.dismiss(loadId); toast.dismiss(loadId);
} }
setSavedSolution(false); setSavedReproduction(false);
setIsModalOpen(true); setIsModalOpen(true);
}; };
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleSubmitReproduction = async () => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const handleSubmitSolution = async () => {
if (!problem) { if (!problem) {
toast.error('No problem loaded'); toast.error('No problem loaded');
return; return;
} }
if (!isSimulatingOrHasFinished()) { if (!isSimulatingOrHasFinished()) {
toast.error('No simulation found (neither in progress nor finished)'); toast.error('No simulation data found (neither in progress nor finished)');
return; return;
} }
@ -80,9 +66,8 @@ export function SaveSolution() {
toast.dismiss(loadId); toast.dismiss(loadId);
} }
toast.info('Submitting solution...'); toast.info('Submitting reproduction...');
console.log('SubmitReproduction');
console.log('SubmitSolution', formData);
const simulationData = getLastUserSimulationData(); const simulationData = getLastUserSimulationData();
@ -98,40 +83,33 @@ export function SaveSolution() {
return; return;
} }
/* const reproData = { simulationData, messages };
* The evaluator is only present when the problem has been solved.
* We still create a "solution" object even if it hasn't been /**
* solved quite yet, which is used for working on the problem.
*
* TODO: Split `solution` into `reproData` and `evaluator`. * TODO: Split `solution` into `reproData` and `evaluator`.
*/ */
const evaluator = formData.evaluator.length ? formData.evaluator : undefined; const solution: BoltProblemSolution = {
evaluator: problem.solution?.evaluator,
...reproData,
/*
* TODO: Also store recordingId for easier debugging.
* recordingId,
*/
};
const problemUpdatePacket: BoltProblemInput = { const problemUpdatePacket: BoltProblemInput = {
...problem,
version: 2, version: 2,
title: problem.title, solution,
description: problem.description,
username: problem.username,
repositoryContents: problem.repositoryContents,
status: evaluator ? BoltProblemStatus.Solved : BoltProblemStatus.Unsolved,
solution: {
simulationData,
messages,
evaluator,
/*
* TODO: Also store recordingId for easier debugging.
* recordingId,
*/
},
}; };
await updateProblem(problem.problemId, problemUpdatePacket); await updateProblem(problem.problemId, problemUpdatePacket);
setSavedSolution(true); setSavedReproduction(true);
} catch (error: any) { } catch (error: any) {
console.error('Error saving solution', error?.stack || error); console.error('Error saving reproduction', error?.stack || error);
toast.error(`Error saving solution: ${error?.message}`); toast.error(`Error saving reproduction: ${error?.message}`);
} }
}; };
@ -140,9 +118,9 @@ export function SaveSolution() {
<a <a
href="#" 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" 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={handleSaveSolution} onClick={handleSaveReproduction}
> >
Save Solution Save Reproduction
</a> </a>
<ReactModal <ReactModal
isOpen={isModalOpen} isOpen={isModalOpen}
@ -150,9 +128,9 @@ export function SaveSolution() {
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" 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" overlayClassName="fixed inset-0 bg-black bg-opacity-50 z-40"
> >
{savedSolution && ( {savedReproduction && (
<> <>
<div className="text-center mb-2">Solution Saved</div> <div className="text-center mb-2">Reproduction Saved</div>
<div className="text-center"> <div className="text-center">
<div className="flex justify-center gap-2 mt-4"> <div className="flex justify-center gap-2 mt-4">
<button <button
@ -165,27 +143,16 @@ export function SaveSolution() {
</div> </div>
</> </>
)} )}
{!savedSolution && ( {!savedReproduction && (
<> <>
<div className="text-center">Save solution for loaded problem from last prompt and recording.</div>
<div className="text-center">Evaluator describes a condition the explanation must satisfy.</div>
<div className="text-center"> <div className="text-center">
Leave the evaluator blank if the API explanation is not right and the problem isn't solved yet. Save reproduction data (prompt, user annotations + simulationData) for the currently loaded problem:{' '}
{problem?.problemId}
</div> </div>
<div style={{ marginTop: '10px' }}> <div style={{ marginTop: '10px' }}>
<div className="grid grid-cols-[auto_1fr] gap-4 max-w-md mx-auto">
<div className="flex items-center">Evaluator:</div>
<input
type="text"
name="evaluator"
className="bg-bolt-elements-background-depth-1 text-bolt-elements-textPrimary rounded px-2 w-full border border-gray-300"
value={formData.evaluator}
onChange={handleInputChange}
/>
</div>
<div className="flex justify-center gap-2 mt-4"> <div className="flex justify-center gap-2 mt-4">
<button <button
onClick={handleSubmitSolution} onClick={handleSubmitReproduction}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
> >
Submit Submit