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
);
}