Add MindSpace page live edit, chat skills, and H5 deploy tooling.
Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { fetchMindSpacePageDraftPreview } from '../api/client';
|
||||
import { applyPageChangeHighlight } from '../utils/mindspacePageChangeHighlight';
|
||||
|
||||
export type MindSpacePreviewChangeHighlight = {
|
||||
revision: number;
|
||||
previousTitle: string;
|
||||
previousContent: string;
|
||||
};
|
||||
|
||||
export function MindSpacePageDraftPreviewFrame({
|
||||
pageId,
|
||||
title,
|
||||
summary,
|
||||
content,
|
||||
templateId,
|
||||
reloadKey = 0,
|
||||
minHeight = 680,
|
||||
maxHeight = 4800,
|
||||
className = 'mindspace-page-preview-frame',
|
||||
changeHighlight = null,
|
||||
}: {
|
||||
pageId: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
content: string;
|
||||
templateId: string;
|
||||
reloadKey?: number;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
className?: string;
|
||||
changeHighlight?: MindSpacePreviewChangeHighlight | null;
|
||||
}) {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(null);
|
||||
const [srcdoc, setSrcdoc] = useState('');
|
||||
const [height, setHeight] = useState(minHeight);
|
||||
const [initialLoading, setInitialLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const requestRef = useRef(0);
|
||||
const hasRenderedRef = useRef(false);
|
||||
const appliedHighlightRevisionRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const requestId = requestRef.current + 1;
|
||||
requestRef.current = requestId;
|
||||
if (!hasRenderedRef.current) setInitialLoading(true);
|
||||
setError(null);
|
||||
|
||||
const timer = window.setTimeout(() => {
|
||||
void fetchMindSpacePageDraftPreview(pageId, { title, summary, content, templateId })
|
||||
.then((html) => {
|
||||
if (requestRef.current !== requestId) return;
|
||||
setSrcdoc(html);
|
||||
hasRenderedRef.current = true;
|
||||
setInitialLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (requestRef.current !== requestId) return;
|
||||
setError(err instanceof Error ? err.message : '预览加载失败');
|
||||
setInitialLoading(false);
|
||||
});
|
||||
}, hasRenderedRef.current ? 450 : 120);
|
||||
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [pageId, title, summary, content, templateId, reloadKey]);
|
||||
|
||||
useEffect(() => {
|
||||
setHeight(minHeight);
|
||||
}, [srcdoc, reloadKey, minHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
const iframe = iframeRef.current;
|
||||
if (!iframe || !srcdoc) return;
|
||||
|
||||
const syncHeight = () => {
|
||||
try {
|
||||
const doc = iframe.contentDocument;
|
||||
if (!doc) return;
|
||||
const body = doc.body;
|
||||
const root = doc.documentElement;
|
||||
const next = Math.ceil(
|
||||
Math.max(
|
||||
body?.scrollHeight ?? 0,
|
||||
body?.offsetHeight ?? 0,
|
||||
root?.scrollHeight ?? 0,
|
||||
root?.offsetHeight ?? 0,
|
||||
minHeight,
|
||||
),
|
||||
);
|
||||
setHeight(Math.min(next, maxHeight));
|
||||
|
||||
if (
|
||||
changeHighlight &&
|
||||
appliedHighlightRevisionRef.current !== changeHighlight.revision
|
||||
) {
|
||||
appliedHighlightRevisionRef.current = changeHighlight.revision;
|
||||
applyPageChangeHighlight(doc, {
|
||||
previousTitle: changeHighlight.previousTitle,
|
||||
nextTitle: title,
|
||||
previousContent: changeHighlight.previousContent,
|
||||
nextContent: content,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
setHeight(minHeight);
|
||||
}
|
||||
};
|
||||
|
||||
iframe.addEventListener('load', syncHeight);
|
||||
return () => iframe.removeEventListener('load', syncHeight);
|
||||
}, [changeHighlight, content, maxHeight, minHeight, srcdoc, title]);
|
||||
|
||||
if (error) {
|
||||
return <div className="mindspace-state mindspace-error">{error}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{initialLoading && !srcdoc ? (
|
||||
<div className="mindspace-state mindspace-page-preview-loading">正在加载预览…</div>
|
||||
) : null}
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
title={`${title || '页面'} 预览`}
|
||||
srcDoc={srcdoc}
|
||||
sandbox="allow-same-origin"
|
||||
className={className}
|
||||
style={{ height: `${height}px`, visibility: srcdoc ? 'visible' : 'hidden' }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user