diff --git a/src/editor/popup/components/ai-response/index.js b/src/editor/popup/components/ai-response/index.js index f57429c..11342c4 100644 --- a/src/editor/popup/components/ai-response/index.js +++ b/src/editor/popup/components/ai-response/index.js @@ -1,4 +1,5 @@ import { isEqual } from 'lodash'; +import clsx from 'clsx'; /** * Styles @@ -8,49 +9,123 @@ import './style.scss'; /** * WordPress dependencies */ -import { memo } from '@wordpress/element'; +import { memo, useState, useEffect, useRef } from '@wordpress/element'; import { BlockPreview } from '@wordpress/block-editor'; +function RenderPreview({ response, className, style }) { + return ( +
+ div { + margin-top: 0; + } + `, + }, + ]} + /> +
+ ); +} + const AIResponse = memo( function AIResponse({ response, loading }) { + const [activePreview, setActivePreview] = useState(1); + const [preview1Data, setPreview1Data] = useState([]); + const [preview2Data, setPreview2Data] = useState([]); + const transitionTimeoutRef = useRef(null); + + // This implementation make me cry, but it works for now. + // In short, when we have a single preview and update the response, + // it rerenders and we see a blink. To avoid this, we have two previews + // and we switch between them on each update. + useEffect(() => { + if (!response.length) { + return; + } + + // Clear any existing timeout + if (transitionTimeoutRef.current) { + clearTimeout(transitionTimeoutRef.current); + } + + // Update the inactive preview with new data + if (activePreview === 1) { + setPreview2Data(response); + } else { + setPreview1Data(response); + } + + // Wait for the next frame to start transition. + // Small delay to ensure new content is rendered. + transitionTimeoutRef.current = setTimeout(() => { + setActivePreview(activePreview === 1 ? 2 : 1); + }, 50); + + return () => { + if (transitionTimeoutRef.current) { + clearTimeout(transitionTimeoutRef.current); + } + }; + }, [response]); + if (!response.length && !loading) { return null; } + const hiddenPreviewStyles = { + position: 'absolute', + top: 0, + left: 0, + right: 0, + }; + return (
- {response.length > 0 && ( -
- div { - margin-top: 0; - } - `, - }, - ]} + {(preview1Data.length > 0 || preview2Data.length > 0) && ( +
+ +
)} diff --git a/src/editor/popup/components/ai-response/style.scss b/src/editor/popup/components/ai-response/style.scss index a1e270c..44352ac 100644 --- a/src/editor/popup/components/ai-response/style.scss +++ b/src/editor/popup/components/ai-response/style.scss @@ -12,6 +12,13 @@ } } +.mind-popup-response__preview { + /* GPU acceleration */ + will-change: opacity; + backface-visibility: hidden; + transform: translateZ(0); +} + @keyframes mind-cursor-blink { 0%, 100% { diff --git a/src/editor/processors/blocks-stream-processor/index.js b/src/editor/processors/blocks-stream-processor/index.js index e1915d0..5f3e637 100644 --- a/src/editor/processors/blocks-stream-processor/index.js +++ b/src/editor/processors/blocks-stream-processor/index.js @@ -13,7 +13,7 @@ export default class BlocksStreamProcessor { // Add throttled dispatch this.throttledDispatch = this.throttle( this.performDispatch.bind(this), - 150 + 200 ); }