diff --git a/src/modules/apps/builder/AppBuilder.tsx b/src/modules/apps/builder/AppBuilder.tsx index cf6cace..e9dc34e 100644 --- a/src/modules/apps/builder/AppBuilder.tsx +++ b/src/modules/apps/builder/AppBuilder.tsx @@ -236,7 +236,7 @@ function AppBuilderContent() { totalCount, ]); - const handleReportError = useCallback( + const handleFixError = useCallback( async (errorText: string) => { await sendMessage( `I have encountered the following error:\n\n\`\`\`error\n${errorText}\n\`\`\`\n\nFix this error please.`, @@ -352,7 +352,7 @@ function AppBuilderContent() { diff --git a/src/modules/apps/builder/ArtifactSharedIframe.tsx b/src/modules/apps/builder/ArtifactSharedIframe.tsx index 58ab9d5..1c82505 100644 --- a/src/modules/apps/builder/ArtifactSharedIframe.tsx +++ b/src/modules/apps/builder/ArtifactSharedIframe.tsx @@ -19,7 +19,7 @@ import { createChatCompletion, modulesToPackages } from '@/app/api/apps'; import { ChatCompletionCreateBody } from '@/app/api/apps/types'; import { ApiError } from '@/app/api/errors'; import { useProjectContext } from '@/layout/providers/ProjectProvider'; -import { Theme, useTheme } from '@/layout/providers/ThemeProvider'; +import { useTheme } from '@/layout/providers/ThemeProvider'; import { USERCONTENT_SITE_URL } from '@/utils/constants'; import { removeTrailingSlash } from '@/utils/helpers'; import { Loading } from '@carbon/react'; @@ -29,7 +29,7 @@ import AppPlaceholder from './Placeholder.svg'; interface Props { sourceCode: string | null; - onReportError?: (errorText: string) => void; + onFixError?: (errorText: string) => void; } function getErrorMessage(error: unknown) { @@ -42,39 +42,35 @@ function getErrorMessage(error: unknown) { return 'Unknown error when calling LLM function.'; } -export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { +export function ArtifactSharedIframe({ sourceCode, onFixError }: Props) { const iframeRef = useRef(null); const [state, setState] = useState(State.LOADING); const { appliedTheme: theme } = useTheme(); const { project, organization } = useProjectContext(); + const [iframeLoadCount, setIframeLoadCount] = useState(0); - const postMessage = (message: PostMessage) => { + const postMessage = useCallback((message: PostMessage) => { + if(iframeLoadCount === 0) return; iframeRef.current?.contentWindow?.postMessage( message, USERCONTENT_SITE_URL, ); - }; + }, [iframeLoadCount]) - const updateTheme = useCallback((theme: Theme) => { - postMessage({ type: PostMessageType.UPDATE_THEME, theme }); - }, []); - - const updateCode = useCallback( - (code: string | null) => { - if (!code) { - return; - } - - postMessage({ - type: PostMessageType.UPDATE_CODE, + useEffect(() => { + postMessage({ + type: PostMessageType.UPDATE_STATE, + stateChange: { + code: sourceCode ?? undefined, config: { - can_fix_error: Boolean(onReportError), + canFixError: Boolean(onFixError) }, - code, - }); - }, - [onReportError], - ); + theme: theme ?? 'system', + fullscreen: false, + ancestorOrigin: window.location.origin, + } + }) + }, [sourceCode, onFixError, theme, postMessage]); const handleMessage = useCallback( async (event: MessageEvent) => { @@ -84,15 +80,19 @@ export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { return; } - if ( - data.type === RecieveMessageType.SCRIPT_RUN_STATE_CHANGED && - data.scriptRunState === ScriptRunState.RUNNING - ) { + if (data.type === RecieveMessageType.READY) { setState(State.READY); return; } if (data.type === RecieveMessageType.REQUEST) { + const respond = (payload: unknown = undefined) => + postMessage({ + type: PostMessageType.RESPONSE, + request_id: data.request_id, + payload, + }); + try { switch (data.request_type) { case 'modules_to_packages': @@ -101,12 +101,9 @@ export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { project.id, data.payload.modules, ); - postMessage({ - type: PostMessageType.RESPONSE, - request_id: data.request_id, - payload: packagesResponse, - }); + respond(packagesResponse); break; + case 'chat_completion': const response = await createChatCompletion( organization.id, @@ -115,49 +112,23 @@ export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { ); const message = response?.choices[0]?.message?.content; if (!message) throw new Error(); // missing completion - postMessage({ - type: PostMessageType.RESPONSE, - request_id: data.request_id, - payload: { message }, - }); + respond({ message }); + break; + + case 'fix_error': + onFixError?.(data.payload.errorText); + respond(); break; } } catch (err) { - postMessage({ - type: PostMessageType.RESPONSE, - request_id: data.request_id, - payload: { error: getErrorMessage(err) }, - }); + respond({ error: getErrorMessage(err) }); } return; } - - if (data.type === RecieveMessageType.REPORT_ERROR) { - onReportError?.(data.errorText); - return; - } }, - [project, organization, onReportError], + [project, organization, onFixError, postMessage], ); - const handleIframeLoad = useCallback(() => { - if (theme) { - updateTheme(theme); - } - }, [theme, updateTheme]); - - useEffect(() => { - if (theme) { - updateTheme(theme); - } - }, [theme, updateTheme]); - - useEffect(() => { - if (state === State.READY) { - updateCode(sourceCode); - } - }, [state, theme, sourceCode, updateCode]); - useEffect(() => { window.addEventListener('message', handleMessage); @@ -180,7 +151,7 @@ export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { 'allow-popups-to-escape-sandbox', ].join(' ')} className={classes.app} - onLoad={handleIframeLoad} + onLoad={() => setIframeLoadCount(i => i + 1)} /> {!sourceCode ? ( @@ -188,7 +159,7 @@ export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { ) : ( - state === State.LOADING && sourceCode && + state === State.LOADING && )} ); @@ -196,15 +167,16 @@ export function ArtifactSharedIframe({ sourceCode, onReportError }: Props) { type PostMessage = | { - type: PostMessageType.UPDATE_CODE; - code: string; - config: { - can_fix_error?: boolean; - }; - } - | { - type: PostMessageType.UPDATE_THEME; - theme: Theme; + type: PostMessageType.UPDATE_STATE; + stateChange: Partial<{ + fullscreen: boolean, + theme: 'light' | 'dark' | 'system', + code: string, + config: { + canFixError: boolean + }, + ancestorOrigin: string, + }>; } | { type: PostMessageType.RESPONSE; @@ -213,15 +185,8 @@ type PostMessage = }; enum PostMessageType { - UPDATE_CODE = 'bee:updateCode', - UPDATE_THEME = 'bee:updateTheme', RESPONSE = 'bee:response', -} - -enum ScriptRunState { - INITIAL = 'initial', - NOT_RUNNING = 'notRunning', - RUNNING = 'running', + UPDATE_STATE = 'bee:updateState' } enum State { @@ -230,15 +195,13 @@ enum State { } enum RecieveMessageType { - SCRIPT_RUN_STATE_CHANGED = 'SCRIPT_RUN_STATE_CHANGED', + READY = 'bee:ready', REQUEST = 'bee:request', - REPORT_ERROR = 'bee:reportError', } export type StliteMessage = | { - type: RecieveMessageType.SCRIPT_RUN_STATE_CHANGED; - scriptRunState: ScriptRunState; + type: RecieveMessageType.READY; } | { type: RecieveMessageType.REQUEST; @@ -253,6 +216,8 @@ export type StliteMessage = payload: ChatCompletionCreateBody; } | { - type: RecieveMessageType.REPORT_ERROR; - errorText: string; + type: RecieveMessageType.REQUEST; + request_type: 'fix_error'; + request_id: string; + payload: { errorText: string }; };