bolt.diy/app/components/sidebar/SaveSolution.tsx
2025-03-12 22:19:53 +08:00

207 lines
6.4 KiB
TypeScript

import { toast } from 'react-toastify';
import ReactModal from 'react-modal';
import { useState } from 'react';
import { BoltProblemStatus, updateProblem } from '~/lib/replay/Problems';
import type { BoltProblem, BoltProblemInput } from '~/lib/replay/Problems';
import { getOrFetchLastLoadedProblem } from '~/components/chat/LoadProblemButton';
import {
getLastUserSimulationData,
getLastSimulationChatMessages,
getSimulationRecordingId,
isSimulatingOrHasFinished,
} 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 SaveSolution() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [formData, setFormData] = useState({
evaluator: '',
});
const [savedSolution, setSavedSolution] = useState<boolean>(false);
const [problem, setProblem] = useState<BoltProblem | null>(null);
const handleSaveSolution = async () => {
const loadId = toast.loading('Loading problem...');
try {
const savedProblem = await getOrFetchLastLoadedProblem();
if (!savedProblem) {
toast.error('No problem loaded');
return;
}
setProblem(savedProblem);
setFormData({
evaluator: savedProblem.solution?.evaluator || '',
});
} finally {
toast.dismiss(loadId);
}
setSavedSolution(false);
setIsModalOpen(true);
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
const handleSubmitSolution = async () => {
if (!problem) {
toast.error('No problem loaded');
return;
}
if (!isSimulatingOrHasFinished()) {
toast.error('No simulation found (neither in progress nor finished)');
return;
}
try {
const loadId = toast.loading('Waiting for recording...');
try {
/*
* Wait for simulation to finish.
* const recordingId =
*/
await getSimulationRecordingId();
} finally {
toast.dismiss(loadId);
}
toast.info('Submitting solution...');
console.log('SubmitSolution', formData);
const simulationData = getLastUserSimulationData();
if (!simulationData) {
toast.error('No simulation data found');
return;
}
const messages = getLastSimulationChatMessages();
if (!messages) {
toast.error('No user prompt found');
return;
}
/*
* 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`.
*/
const evaluator = formData.evaluator.length ? formData.evaluator : undefined;
const problemUpdatePacket: BoltProblemInput = {
version: 2,
title: problem.title,
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);
setSavedSolution(true);
} catch (error: any) {
console.error('Error saving solution', error?.stack || error);
toast.error(`Error saving solution: ${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={handleSaveSolution}
>
Save Solution
</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"
>
{savedSolution && (
<>
<div className="text-center mb-2">Solution 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>
</>
)}
{!savedSolution && (
<>
<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">
Leave the evaluator blank if the API explanation is not right and the problem isn't solved yet.
</div>
<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">
<button
onClick={handleSubmitSolution}
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>
</>
);
}