bolt.diy/app/components/chat/LoadProblemButton.tsx
2025-03-18 19:18:12 -07:00

141 lines
4.0 KiB
TypeScript

import React, { useState } from 'react';
import { toast } from 'react-toastify';
import { createChatFromFolder } from '~/utils/folderImport';
import { logStore } from '~/lib/stores/logs';
import { assert } from '~/lib/replay/ReplayProtocolClient';
import type { BoltProblem } from '~/lib/replay/Problems';
import { getProblem } from '~/lib/replay/Problems';
import { createRepositoryImported } from '~/lib/replay/Repository';
import type { Message } from '~/lib/persistence/message';
interface LoadProblemButtonProps {
className?: string;
importChat?: (description: string, messages: Message[]) => Promise<void>;
}
export function setLastLoadedProblem(problem: BoltProblem) {
const problemSerialized = JSON.stringify(problem);
try {
localStorage.setItem('loadedProblemId', problem.problemId);
localStorage.setItem('loadedProblem', problemSerialized);
} catch (error: any) {
// Remove loadedProblem, so we don't accidentally associate (e.g. reproduction) data with the wrong problem.
localStorage.removeItem('loadedProblem');
console.error(
`Failed to set last loaded problem (size=${(problemSerialized.length / 1024).toFixed(2)}kb):`,
error.stack || error,
);
}
}
export async function getOrFetchLastLoadedProblem(): Promise<BoltProblem | null> {
const problemJSON = localStorage.getItem('loadedProblem');
let problem: BoltProblem | null = null;
if (problemJSON) {
problem = JSON.parse(problemJSON);
} else {
/*
* Problem might not have fit into localStorage.
* Try to re-load it from server.
*/
const problemId = localStorage.getItem('loadedProblemId');
if (!problemId) {
return null;
}
problem = await getProblem(problemId);
if (!problem) {
return null;
}
setLastLoadedProblem(problem);
}
return problem;
}
export async function loadProblem(
problemId: string,
importChat: (description: string, messages: Message[]) => Promise<void>,
) {
const problem = await getProblem(problemId);
if (!problem) {
return;
}
setLastLoadedProblem(problem);
const { repositoryContents, title: problemTitle } = problem;
try {
const repositoryId = await createRepositoryImported(`ImportProblem:${problemId}`, repositoryContents);
const messages = createChatFromFolder('problem', repositoryId);
await importChat(`Problem: ${problemTitle}`, [...messages]);
logStore.logSystem('Problem loaded successfully', {
problemId,
});
toast.success('Problem loaded successfully');
} catch (error) {
logStore.logError('Failed to load problem', error);
console.error('Failed to load problem:', error);
toast.error('Failed to load problem');
}
}
export const LoadProblemButton: React.FC<LoadProblemButtonProps> = ({ className, importChat }) => {
const [isLoading, setIsLoading] = useState(false);
const [isInputOpen, setIsInputOpen] = useState(false);
const handleSubmit = async (_e: React.ChangeEvent<HTMLInputElement>) => {
setIsLoading(true);
setIsInputOpen(false);
const problemId = (document.getElementById('problem-input') as HTMLInputElement)?.value;
assert(importChat, 'importChat is required');
await loadProblem(problemId, importChat);
setIsLoading(false);
};
return (
<>
{isInputOpen && (
<input
id="problem-input"
type="text"
webkitdirectory=""
directory=""
onChange={(_e) => {
/* Input change handled by onKeyDown */
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSubmit(e as any);
}
}}
className="border border-gray-300 rounded px-2 py-1"
{...({} as any)}
/>
)}
{!isInputOpen && (
<button
onClick={() => {
setIsInputOpen(true);
}}
className={className}
disabled={isLoading}
>
<div className="i-ph:globe" />
{isLoading ? 'Loading...' : 'Load Problem'}
</button>
)}
</>
);
};