mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Avoid top level await to fix execution and full module initialization race condition (#9)
This commit is contained in:
parent
cd66283c8a
commit
dabbfc8bd7
@ -8,17 +8,24 @@ import { workbenchStore } from '~/lib/stores/workbench';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { cubicEasingFn } from '~/utils/easings';
|
||||
import { WORK_DIR } from '~/utils/constants';
|
||||
import { createAsyncSuspenseValue } from '~/lib/asyncSuspenseValue';
|
||||
|
||||
const highlighterOptions = {
|
||||
langs: ['shell'],
|
||||
themes: ['light-plus', 'dark-plus'],
|
||||
};
|
||||
|
||||
const shellHighlighter: HighlighterGeneric<BundledLanguage, BundledTheme> =
|
||||
import.meta.hot?.data.shellHighlighter ?? (await createHighlighter(highlighterOptions));
|
||||
const shellHighlighter = createAsyncSuspenseValue(async () => {
|
||||
const shellHighlighterPromise: Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> =
|
||||
import.meta.hot?.data.shellHighlighterPromise ?? createHighlighter(highlighterOptions);
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.data.shellHighlighterPromise = shellHighlighterPromise;
|
||||
}
|
||||
return shellHighlighterPromise;
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.data.shellHighlighter = shellHighlighter;
|
||||
if (typeof document !== 'undefined') {
|
||||
shellHighlighter.preload();
|
||||
}
|
||||
|
||||
interface ArtifactProps {
|
||||
@ -134,7 +141,7 @@ function ShellCodeBlock({ classsName, code }: ShellCodeBlockProps) {
|
||||
<div
|
||||
className={classNames('text-xs', classsName)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: shellHighlighter.codeToHtml(code, {
|
||||
__html: shellHighlighter.read().codeToHtml(code, {
|
||||
lang: 'shell',
|
||||
theme: 'dark-plus',
|
||||
}),
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Message } from 'ai';
|
||||
import React, { useState } from 'react';
|
||||
import React, { Suspense, useState } from 'react';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import { AssistantMessage, getAnnotationsTokensUsage } from './AssistantMessage';
|
||||
import { UserMessage } from './UserMessage';
|
||||
@ -8,7 +8,7 @@ import { db, chatId } from '~/lib/persistence/useChatHistory';
|
||||
import { forkChat } from '~/lib/persistence/db';
|
||||
import { toast } from 'react-toastify';
|
||||
import WithTooltip from '~/components/ui/Tooltip';
|
||||
import { assert, sendCommandDedicatedClient } from "~/lib/replay/ReplayProtocolClient";
|
||||
import { assert, sendCommandDedicatedClient } from '~/lib/replay/ReplayProtocolClient';
|
||||
|
||||
interface MessagesProps {
|
||||
id?: string;
|
||||
@ -64,35 +64,42 @@ export const Messages = React.forwardRef<HTMLDivElement, MessagesProps>((props:
|
||||
'mt-4': !isFirst,
|
||||
})}
|
||||
>
|
||||
{isUserMessage && (
|
||||
<div className="flex items-center justify-center w-[34px] h-[34px] overflow-hidden bg-white text-gray-600 rounded-full shrink-0 self-start">
|
||||
<div className="i-ph:user-fill text-xl"></div>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-col-1 w-full">
|
||||
{isUserMessage ? (
|
||||
<UserMessage content={content} />
|
||||
) : (
|
||||
<AssistantMessage content={content} annotations={message.annotations} />
|
||||
<Suspense
|
||||
fallback={
|
||||
// TODO: this fallback could be improved
|
||||
<div className="text-center w-full text-bolt-elements-textSecondary i-svg-spinners:3-dots-fade text-4xl mt-4"></div>
|
||||
}
|
||||
>
|
||||
{isUserMessage && (
|
||||
<div className="flex items-center justify-center w-[34px] h-[34px] overflow-hidden bg-white text-gray-600 rounded-full shrink-0 self-start">
|
||||
<div className="i-ph:user-fill text-xl"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isUserMessage && messageId && getLastMessageProjectContents(index) && EnableRewindButton && (
|
||||
<div className="flex gap-2 flex-col lg:flex-row">
|
||||
<WithTooltip tooltip="Rewind to this message">
|
||||
<button
|
||||
onClick={() => {
|
||||
const contents = getLastMessageProjectContents(index);
|
||||
assert(contents);
|
||||
}}
|
||||
key="i-ph:arrow-u-up-left"
|
||||
className={classNames(
|
||||
'i-ph:arrow-u-up-left',
|
||||
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
|
||||
)}
|
||||
/>
|
||||
</WithTooltip>
|
||||
<div className="grid grid-col-1 w-full">
|
||||
{isUserMessage ? (
|
||||
<UserMessage content={content} />
|
||||
) : (
|
||||
<AssistantMessage content={content} annotations={message.annotations} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isUserMessage && messageId && getLastMessageProjectContents(index) && EnableRewindButton && (
|
||||
<div className="flex gap-2 flex-col lg:flex-row">
|
||||
<WithTooltip tooltip="Rewind to this message">
|
||||
<button
|
||||
onClick={() => {
|
||||
const contents = getLastMessageProjectContents(index);
|
||||
assert(contents);
|
||||
}}
|
||||
key="i-ph:arrow-u-up-left"
|
||||
className={classNames(
|
||||
'i-ph:arrow-u-up-left',
|
||||
'text-xl text-bolt-elements-textSecondary hover:text-bolt-elements-textPrimary transition-colors',
|
||||
)}
|
||||
/>
|
||||
</WithTooltip>
|
||||
</div>
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
|
58
app/lib/asyncSuspenseValue.ts
Normal file
58
app/lib/asyncSuspenseValue.ts
Normal file
@ -0,0 +1,58 @@
|
||||
type SuspenseRecord<T> =
|
||||
| {
|
||||
status: 'resolved';
|
||||
value: T;
|
||||
}
|
||||
| {
|
||||
status: 'rejected';
|
||||
error: Error;
|
||||
}
|
||||
| {
|
||||
status: 'pending';
|
||||
promise: Promise<T>;
|
||||
};
|
||||
|
||||
export function createAsyncSuspenseValue<T>(getValue: () => Promise<T>) {
|
||||
let record: SuspenseRecord<T> | undefined;
|
||||
|
||||
const load = () => {
|
||||
const promise = getValue().then(
|
||||
(value) => {
|
||||
record = { status: 'resolved', value };
|
||||
return value;
|
||||
},
|
||||
(error) => {
|
||||
record = { status: 'rejected', error };
|
||||
throw error;
|
||||
},
|
||||
);
|
||||
|
||||
record = { status: 'pending', promise };
|
||||
return promise;
|
||||
};
|
||||
|
||||
const asyncValue = {
|
||||
read() {
|
||||
if (!record) {
|
||||
throw load();
|
||||
}
|
||||
|
||||
switch (record.status) {
|
||||
case 'pending':
|
||||
throw record.promise;
|
||||
case 'resolved':
|
||||
return record.value;
|
||||
case 'rejected':
|
||||
throw record.error;
|
||||
}
|
||||
},
|
||||
preload() {
|
||||
if (record) {
|
||||
return;
|
||||
}
|
||||
load().catch(() => {});
|
||||
},
|
||||
};
|
||||
|
||||
return asyncValue;
|
||||
}
|
Loading…
Reference in New Issue
Block a user