mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Fix assorted problems (#27)
This commit is contained in:
parent
b2efa3b56d
commit
20bb97c0c5
@ -424,8 +424,10 @@ export const ChatImpl = memo(
|
||||
// The project contents are associated with the last message present when
|
||||
// the user message is added.
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
const { contentBase64 } = await workbenchStore.generateZipBase64();
|
||||
saveProjectContents(lastMessage.id, { content: contentBase64 });
|
||||
if (lastMessage) {
|
||||
const { contentBase64 } = await workbenchStore.generateZipBase64();
|
||||
saveProjectContents(lastMessage.id, { content: contentBase64 });
|
||||
}
|
||||
};
|
||||
|
||||
const onRewind = async (messageId: string, contents: string) => {
|
||||
|
@ -113,7 +113,6 @@ async function restorePartialFile(
|
||||
existingContent: string,
|
||||
newContent: string,
|
||||
apiKey: string,
|
||||
mainResponseText: string,
|
||||
responseDescription: string
|
||||
) {
|
||||
const systemPrompt = `
|
||||
@ -171,24 +170,20 @@ ${responseDescription}
|
||||
const closeTag = restoreCall.responseText.indexOf(CloseTag);
|
||||
|
||||
if (openTag === -1 || closeTag === -1) {
|
||||
console.error("Invalid restored content");
|
||||
return { restoreCall, newResponseText: mainResponseText };
|
||||
console.error("Invalid restored content", restoreCall.responseText);
|
||||
return { restoreCall, restoredContent: newContent };
|
||||
}
|
||||
|
||||
const restoredContent = restoreCall.responseText.substring(openTag + OpenTag.length, closeTag);
|
||||
const newContentIndex = mainResponseText.indexOf(newContent);
|
||||
|
||||
if (newContentIndex === -1) {
|
||||
console.error("New content not found in response");
|
||||
return { restoreCall, newResponseText: mainResponseText };
|
||||
// Sometimes the model ignores its instructions and doesn't return the content if it hasn't
|
||||
// made any modifications. In this case we use the unmodified new content.
|
||||
if (restoredContent.length < existingContent.length && restoredContent.length < newContent.length) {
|
||||
console.error("Restored content too short", restoredContent);
|
||||
return { restoreCall, restoredContent: newContent };
|
||||
}
|
||||
|
||||
const newResponseText =
|
||||
mainResponseText.substring(0, newContentIndex) +
|
||||
restoredContent +
|
||||
mainResponseText.substring(newContentIndex + newContent.length);
|
||||
|
||||
return { restoreCall, newResponseText };
|
||||
return { restoreCall, restoredContent };
|
||||
}
|
||||
|
||||
// Return the english description in a model response, skipping over any artifacts.
|
||||
@ -214,12 +209,67 @@ function getMessageDescription(responseText: string): string {
|
||||
return responseText;
|
||||
}
|
||||
|
||||
async function getLatestPackageVersion(packageName: string) {
|
||||
try {
|
||||
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
||||
const data = await response.json() as any;
|
||||
if (typeof data.version == "string") {
|
||||
return data.version;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error getting latest package version", packageName, e);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function ignorePackageUpgrade(packageName: string) {
|
||||
// Don't upgrade react, our support for react 19 isn't complete yet.
|
||||
return packageName.startsWith("react");
|
||||
}
|
||||
|
||||
// Upgrade dependencies in package.json to the latest version, instead of the random
|
||||
// and sometimes ancient versions that the AI picks.
|
||||
async function upgradePackageJSON(content: string) {
|
||||
try {
|
||||
const packageJSON = JSON.parse(content);
|
||||
for (const key of Object.keys(packageJSON.dependencies)) {
|
||||
if (!ignorePackageUpgrade(key)) {
|
||||
const version = await getLatestPackageVersion(key);
|
||||
if (version) {
|
||||
packageJSON.dependencies[key] = version;
|
||||
}
|
||||
}
|
||||
}
|
||||
return JSON.stringify(packageJSON, null, 2);
|
||||
} catch (e) {
|
||||
console.error("Error upgrading package.json", e);
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
function replaceFileContents(responseText: string, oldContent: string, newContent: string) {
|
||||
let contentIndex = responseText.indexOf(oldContent);
|
||||
|
||||
if (contentIndex === -1) {
|
||||
// The old content may have a trailing newline which wasn't originally present in the response.
|
||||
oldContent = oldContent.trim();
|
||||
contentIndex = responseText.indexOf(oldContent);
|
||||
|
||||
console.error("Old content not found in response", JSON.stringify({ responseText, oldContent }));
|
||||
return responseText;
|
||||
}
|
||||
|
||||
return responseText.substring(0, contentIndex) +
|
||||
newContent +
|
||||
responseText.substring(contentIndex + oldContent.length);
|
||||
}
|
||||
|
||||
interface FileContents {
|
||||
filePath: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
async function restorePartialFiles(files: FileMap, apiKey: string, responseText: string) {
|
||||
async function fixupResponseFiles(files: FileMap, apiKey: string, responseText: string) {
|
||||
const fileContents: FileContents[] = [];
|
||||
|
||||
const messageParser = new StreamingMessageParser({
|
||||
@ -240,20 +290,23 @@ async function restorePartialFiles(files: FileMap, apiKey: string, responseText:
|
||||
const responseDescription = getMessageDescription(responseText);
|
||||
|
||||
const restoreCalls: AnthropicCall[] = [];
|
||||
for (const file of fileContents) {
|
||||
const existingContent = getFileContents(files, file.filePath);
|
||||
const newContent = file.content;
|
||||
for (const { filePath, content: newContent } of fileContents) {
|
||||
const existingContent = getFileContents(files, filePath);
|
||||
|
||||
if (shouldRestorePartialFile(existingContent, newContent)) {
|
||||
const { restoreCall, newResponseText } = await restorePartialFile(
|
||||
const { restoreCall, restoredContent } = await restorePartialFile(
|
||||
existingContent,
|
||||
newContent,
|
||||
apiKey,
|
||||
responseText,
|
||||
responseDescription
|
||||
);
|
||||
restoreCalls.push(restoreCall);
|
||||
responseText = newResponseText;
|
||||
responseText = replaceFileContents(responseText, newContent, restoredContent);
|
||||
}
|
||||
|
||||
if (filePath.includes("package.json")) {
|
||||
const newPackageJSON = await upgradePackageJSON(newContent);
|
||||
responseText = replaceFileContents(responseText, newContent, newPackageJSON);
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,7 +327,7 @@ export async function chatAnthropic(chatController: ChatStreamController, files:
|
||||
|
||||
const mainCall = await callAnthropic(apiKey, systemPrompt, messageParams);
|
||||
|
||||
const { responseText, restoreCalls } = await restorePartialFiles(files, apiKey, mainCall.responseText);
|
||||
const { responseText, restoreCalls } = await fixupResponseFiles(files, apiKey, mainCall.responseText);
|
||||
|
||||
chatController.writeText(responseText);
|
||||
|
||||
|
@ -37,7 +37,7 @@ export function useChatHistory() {
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [initialMessages, setInitialMessages] = useState<Message[]>([]);
|
||||
const [ready, setReady] = useState<boolean>(false);
|
||||
const [ready, setReady] = useState<boolean>(!mixedId && !problemId);
|
||||
const [urlId, setUrlId] = useState<string | undefined>();
|
||||
|
||||
const importChat = async (description: string, messages: Message[]) => {
|
||||
@ -100,7 +100,7 @@ export function useChatHistory() {
|
||||
}, []);
|
||||
|
||||
return {
|
||||
ready: ready || (!mixedId && !problemId),
|
||||
ready,
|
||||
initialMessages,
|
||||
storeMessageHistory: async (messages: Message[]) => {
|
||||
if (!db || messages.length === 0) {
|
||||
|
@ -37,7 +37,9 @@ function sendIframeRequest<K extends keyof RequestMap>(
|
||||
iframe: HTMLIFrameElement,
|
||||
request: Extract<Request, { request: K }>,
|
||||
) {
|
||||
assert(iframe.contentWindow);
|
||||
if (!iframe.contentWindow) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const target = iframe.contentWindow;
|
||||
const requestId = ++lastRequestId;
|
||||
@ -56,10 +58,12 @@ function sendIframeRequest<K extends keyof RequestMap>(
|
||||
});
|
||||
}
|
||||
|
||||
let gMessageCount = 0;
|
||||
|
||||
export async function getIFrameSimulationData(iframe: HTMLIFrameElement): Promise<SimulationData> {
|
||||
const buffer = await sendIframeRequest(iframe, { request: 'recording-data' });
|
||||
if (!buffer) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const jsonString = decoder.decode(new Uint8Array(buffer));
|
||||
|
||||
@ -75,7 +79,9 @@ export interface MouseData {
|
||||
}
|
||||
|
||||
export async function getMouseData(iframe: HTMLIFrameElement, position: { x: number; y: number }): Promise<MouseData> {
|
||||
return sendIframeRequest(iframe, { request: 'mouse-data', payload: position });
|
||||
const mouseData = await sendIframeRequest(iframe, { request: 'mouse-data', payload: position });
|
||||
assert(mouseData, "Expected to have mouse data");
|
||||
return mouseData;
|
||||
}
|
||||
|
||||
// Add handlers to the current iframe's window.
|
||||
|
Loading…
Reference in New Issue
Block a user