feat(editor): save leptos island and page aggregate alignment progress
- switch main document flow toward leptos tiptap island host and generated runtime assets - align page aggregate loading, page head single-source updates, and AI tool result recovery - add tests and smoke scripts for title sync, AI route recovery, and editor host cutover
This commit is contained in:
@@ -20,15 +20,27 @@ import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DocumentToc } from "@/components/editor/document-toc";
|
||||
import { DocumentReadView } from "@/components/editor/document-read-view";
|
||||
import { emitDocumentsChanged } from "@/lib/events";
|
||||
import { extractPageBlocks, type PageSubtreeProjection } from "@/lib/documents/page-subtree";
|
||||
import {
|
||||
deleteDocumentCommand,
|
||||
embedDocumentCommand,
|
||||
moveDocumentCommand,
|
||||
renameDocumentCommand,
|
||||
updatePageOptionsCommand,
|
||||
updatePageTitleCommand,
|
||||
} from "@/lib/documents/tree-command-client";
|
||||
import { EditorHost } from "@/components/editor/editor-host";
|
||||
import type { EditorHostKind } from "@/components/editor/editor-host-config";
|
||||
import {
|
||||
DEFAULT_EDITOR_HOST_KIND,
|
||||
isLeptosTiptapHostKind,
|
||||
type EditorHostKind,
|
||||
} from "@/components/editor/editor-host-config";
|
||||
import type {
|
||||
EditorHostEvent,
|
||||
EditorHostFallbackReason,
|
||||
} from "@/components/editor/editor-host-types";
|
||||
import type { PageAggregateProjection } from "@/lib/documents/page-aggregate";
|
||||
import { usePageHeadTitle } from "@/components/editor/use-page-head-title";
|
||||
|
||||
const BlockNoteEditor = dynamic(
|
||||
() => import("@/components/editor/blocknote-editor").then((mod) => mod.BlockNoteEditor),
|
||||
@@ -78,21 +90,9 @@ const DocumentCommentsDrawer = dynamic(
|
||||
);
|
||||
|
||||
export interface DocumentContentProps {
|
||||
documentId: string;
|
||||
workspaceId: string;
|
||||
title: string | null;
|
||||
updatedAt: string | null;
|
||||
initialContent: unknown;
|
||||
initialContentRevision?: number | null;
|
||||
initialConflictDetectionKey?: string | null;
|
||||
initialPageSubtree?: PageSubtreeProjection | null;
|
||||
initialOptions: PageOptionsState;
|
||||
initialStats: DocumentStats | null;
|
||||
page: PageAggregateProjection;
|
||||
openTableId?: string | null;
|
||||
editorHostKind?: EditorHostKind;
|
||||
readOnly?: boolean;
|
||||
disableDownload?: boolean;
|
||||
disableCopy?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: PageOptionsState = {
|
||||
@@ -112,24 +112,43 @@ const defaultOptions: PageOptionsState = {
|
||||
};
|
||||
const defaultStats: DocumentStats = { wordCount: 0, characterCount: 0, blockCount: 0, todoTotal: 0, todoDone: 0 };
|
||||
const EDITOR_UNMOUNT_GRACE_MS = 1000;
|
||||
const FALLBACK_TRIGGER_HISTORY_LIMIT = 20;
|
||||
|
||||
export async function persistPageTitleAndNotifyDocumentsChanged(input: {
|
||||
documentId: string;
|
||||
workspaceId: string;
|
||||
title: string;
|
||||
persistTitleCommand: (payload: { documentId: string; workspaceId: string; title: string }) => Promise<unknown>;
|
||||
notifyDocumentsChanged: (documentId?: string) => void;
|
||||
}): Promise<string> {
|
||||
const payload = input.title.trim() || "无标题";
|
||||
await input.persistTitleCommand({
|
||||
documentId: input.documentId,
|
||||
workspaceId: input.workspaceId,
|
||||
title: payload,
|
||||
});
|
||||
input.notifyDocumentsChanged(input.documentId);
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function DocumentContent({
|
||||
documentId,
|
||||
workspaceId,
|
||||
title,
|
||||
updatedAt,
|
||||
initialContent,
|
||||
initialContentRevision = null,
|
||||
initialConflictDetectionKey = null,
|
||||
initialPageSubtree = null,
|
||||
initialOptions,
|
||||
initialStats,
|
||||
page,
|
||||
openTableId,
|
||||
editorHostKind = "blocknote",
|
||||
readOnly = false,
|
||||
disableDownload = false,
|
||||
disableCopy = false,
|
||||
editorHostKind = DEFAULT_EDITOR_HOST_KIND,
|
||||
}: DocumentContentProps) {
|
||||
const documentId = page.identity.documentId;
|
||||
const workspaceId = page.identity.workspaceId;
|
||||
const initialTitle = page.head.title;
|
||||
const updatedAt = page.head.updatedAt;
|
||||
const readOnly = page.head.permissions.readOnly;
|
||||
const disableDownload = page.head.permissions.disableDownload;
|
||||
const disableCopy = page.head.permissions.disableCopy;
|
||||
const initialOptions = page.layout.pageOptions;
|
||||
const initialContent = page.body.content;
|
||||
const initialContentRevision = page.body.revision;
|
||||
const initialConflictDetectionKey = page.body.conflictDetectionKey;
|
||||
const initialPageSubtree = page.tree.pageSubtree;
|
||||
const initialStats = page.stats;
|
||||
const setCurrentDocument = useCurrentDocumentStore((state) => state.setCurrent);
|
||||
const clearIfMatch = useCurrentDocumentStore((state) => state.clearIfMatch);
|
||||
const canEditDocument = !readOnly;
|
||||
@@ -143,22 +162,42 @@ export function DocumentContent({
|
||||
const openCommentsForPage = useCommentsUiStore((s) => s.openForPage);
|
||||
const spellCheck = useAppPreferencesStore((s) => s.spellCheck);
|
||||
const openMoveEmbedPicker = useMoveEmbedPickerStore((s) => s.openPicker);
|
||||
const [pageTitle, setPageTitle] = useState(title ?? "无标题");
|
||||
const {
|
||||
displayTitle: pageTitle,
|
||||
committedTitle: committedPageTitle,
|
||||
setDraftTitle: setPageTitleDraft,
|
||||
commitPersistedTitle,
|
||||
} = usePageHeadTitle({
|
||||
documentId,
|
||||
fallbackTitle: initialTitle,
|
||||
});
|
||||
const [content, setContent] = useState<unknown>(initialContent);
|
||||
const [serverContentSnapshot, setServerContentSnapshot] = useState<unknown>(initialContent);
|
||||
const [serverPageSubtreeSnapshot, setServerPageSubtreeSnapshot] = useState<PageSubtreeProjection | null>(
|
||||
initialPageSubtree,
|
||||
);
|
||||
const [serverPageSubtreeTitle, setServerPageSubtreeTitle] = useState<string>(title ?? "无标题");
|
||||
const [serverPageSubtreeTitle, setServerPageSubtreeTitle] = useState<string>(initialTitle);
|
||||
const [contentRevision, setContentRevision] = useState<number | null>(initialContentRevision);
|
||||
const [conflictDetectionKey, setConflictDetectionKey] = useState<string | null>(initialConflictDetectionKey);
|
||||
const [contentLoading, setContentLoading] = useState(() => initialContent == null);
|
||||
const [contentError, setContentError] = useState<string | null>(null);
|
||||
const [contentReloadKey, setContentReloadKey] = useState(0);
|
||||
const [showContentLoadingIndicator, setShowContentLoadingIndicator] = useState(false);
|
||||
const shouldUseExperimentalHost =
|
||||
editorHostKind === "leptos_tiptap_inline" ||
|
||||
editorHostKind === "leptos_tiptap_iframe_debug";
|
||||
const shouldUseRuntimeHost = isLeptosTiptapHostKind(editorHostKind);
|
||||
const requestedHostKind = shouldUseRuntimeHost ? editorHostKind : "blocknote";
|
||||
const [activeHostKind, setActiveHostKind] = useState<"blocknote" | EditorHostKind>(() =>
|
||||
requestedHostKind === "blocknote" ? "blocknote" : requestedHostKind,
|
||||
);
|
||||
const [hostRuntimeLoadFailure, setHostRuntimeLoadFailure] = useState<string | null>(null);
|
||||
const [hostInitFailure, setHostInitFailure] = useState<string | null>(null);
|
||||
const [hostCommandFailure, setHostCommandFailure] = useState<string | null>(null);
|
||||
const [hostSaveFailure, setHostSaveFailure] = useState<string | null>(null);
|
||||
const [hostFallbackCount, setHostFallbackCount] = useState<number>(0);
|
||||
const [lastFallbackReason, setLastFallbackReason] = useState<EditorHostFallbackReason | null>(null);
|
||||
const [lastFallbackAt, setLastFallbackAt] = useState<string | null>(null);
|
||||
const [hostStatus, setHostStatus] = useState<string>("idle");
|
||||
const [hostEventAt, setHostEventAt] = useState<string | null>(null);
|
||||
const fallbackTriggerHistoryRef = useRef<string[]>([]);
|
||||
const shouldStartEditing = canEditDocument;
|
||||
const [isEditing, setIsEditing] = useState(() => shouldStartEditing);
|
||||
const [keepEditorMounted, setKeepEditorMounted] = useState(() => shouldStartEditing);
|
||||
@@ -170,6 +209,51 @@ export function DocumentContent({
|
||||
const readViewRootRef = useRef<HTMLDivElement>(null);
|
||||
const pendingRestoreSnapshotRef = useRef<DocumentSnapshot | null>(null);
|
||||
const lastCopyBlockedAtRef = useRef<number>(0);
|
||||
const hasRequestedFallbackRef = useRef(false);
|
||||
|
||||
const resetHostObservability = useCallback((nextHost: "blocknote" | EditorHostKind) => {
|
||||
setHostStatus(nextHost === "blocknote" ? "blocknote_active" : "booting");
|
||||
setHostEventAt(new Date().toISOString());
|
||||
setHostRuntimeLoadFailure(null);
|
||||
setHostInitFailure(null);
|
||||
setHostCommandFailure(null);
|
||||
setHostSaveFailure(null);
|
||||
setHostFallbackCount(0);
|
||||
setLastFallbackReason(null);
|
||||
setLastFallbackAt(null);
|
||||
fallbackTriggerHistoryRef.current = [];
|
||||
hasRequestedFallbackRef.current = false;
|
||||
}, []);
|
||||
|
||||
const requestFallbackToBlockNote = useCallback(
|
||||
(reason: EditorHostFallbackReason, error?: string | null) => {
|
||||
if (hasRequestedFallbackRef.current) {
|
||||
return;
|
||||
}
|
||||
hasRequestedFallbackRef.current = true;
|
||||
const now = new Date().toISOString();
|
||||
setActiveHostKind("blocknote");
|
||||
setHostFallbackCount((prev) => prev + 1);
|
||||
setLastFallbackReason(reason);
|
||||
setLastFallbackAt(now);
|
||||
setHostStatus("blocknote_fallback");
|
||||
setHostEventAt(now);
|
||||
fallbackTriggerHistoryRef.current = [now, ...fallbackTriggerHistoryRef.current].slice(
|
||||
0,
|
||||
FALLBACK_TRIGGER_HISTORY_LIMIT,
|
||||
);
|
||||
if (reason === "runtime_load_failed") {
|
||||
setHostRuntimeLoadFailure(error ?? "runtime 加载失败");
|
||||
} else if (reason === "host_init_failed") {
|
||||
setHostInitFailure(error ?? "host 初始化失败");
|
||||
} else if (reason === "command_failed") {
|
||||
setHostCommandFailure(error ?? "命令执行失败");
|
||||
} else if (reason === "save_failed") {
|
||||
setHostSaveFailure(error ?? "保存失败");
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentDocument(documentId, readOnly, disableDownload, disableCopy);
|
||||
@@ -243,6 +327,11 @@ export function DocumentContent({
|
||||
};
|
||||
}, [disableCopy]);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveHostKind(requestedHostKind === "blocknote" ? "blocknote" : requestedHostKind);
|
||||
resetHostObservability(requestedHostKind === "blocknote" ? "blocknote" : requestedHostKind);
|
||||
}, [requestedHostKind, resetHostObservability]);
|
||||
|
||||
useEffect(() => {
|
||||
const tableId = (openTableId ?? "").trim();
|
||||
if (!tableId) return;
|
||||
@@ -264,12 +353,8 @@ export function DocumentContent({
|
||||
}, [documentId, editorBridge, openTableId, router]);
|
||||
|
||||
useEffect(() => {
|
||||
setPageTitle(title ?? "无标题");
|
||||
}, [title]);
|
||||
|
||||
useEffect(() => {
|
||||
setServerPageSubtreeTitle(title ?? "无标题");
|
||||
}, [title]);
|
||||
setServerPageSubtreeTitle(committedPageTitle);
|
||||
}, [committedPageTitle]);
|
||||
|
||||
useEffect(() => {
|
||||
setServerPageSubtreeSnapshot(initialPageSubtree);
|
||||
@@ -393,7 +478,7 @@ export function DocumentContent({
|
||||
typeof payload.pageSubtree?.rootNode.metadata.title === "string" &&
|
||||
payload.pageSubtree.rootNode.metadata.title.trim()
|
||||
? payload.pageSubtree.rootNode.metadata.title
|
||||
: title ?? "无标题",
|
||||
: committedPageTitle,
|
||||
);
|
||||
} catch (error) {
|
||||
if (canceled) return;
|
||||
@@ -421,19 +506,36 @@ export function DocumentContent({
|
||||
contentLoadingTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [documentId, initialContent, contentReloadKey, title, workspaceId]);
|
||||
}, [committedPageTitle, contentReloadKey, documentId, initialContent, workspaceId]);
|
||||
|
||||
const persistTitle = useCallback(
|
||||
async (nextTitle: string) => {
|
||||
if (readOnly) return;
|
||||
const payload = nextTitle.trim() || "无标题";
|
||||
try {
|
||||
await renameDocumentCommand({ documentId, workspaceId, title: payload });
|
||||
const payload = await persistPageTitleAndNotifyDocumentsChanged({
|
||||
documentId,
|
||||
workspaceId,
|
||||
title: nextTitle,
|
||||
persistTitleCommand: updatePageTitleCommand,
|
||||
notifyDocumentsChanged: emitDocumentsChanged,
|
||||
});
|
||||
commitPersistedTitle(payload);
|
||||
setServerPageSubtreeTitle(payload);
|
||||
} catch (error) {
|
||||
console.error("更新页面标题失败", error);
|
||||
}
|
||||
},
|
||||
[documentId, readOnly, workspaceId],
|
||||
[commitPersistedTitle, documentId, readOnly, workspaceId],
|
||||
);
|
||||
|
||||
const handleAiPageHeadTitleChange = useCallback(
|
||||
(nextTitle: string) => {
|
||||
setPageTitleDraft(nextTitle);
|
||||
commitPersistedTitle(nextTitle);
|
||||
setServerPageSubtreeTitle(nextTitle);
|
||||
emitDocumentsChanged(documentId);
|
||||
},
|
||||
[commitPersistedTitle, documentId, setPageTitleDraft],
|
||||
);
|
||||
|
||||
const debouncedPersistTitle = useDebouncedCallback((value: string) => {
|
||||
@@ -443,7 +545,7 @@ export function DocumentContent({
|
||||
const handleTitleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!canEditDocument) return;
|
||||
const value = event.target.value;
|
||||
setPageTitle(value);
|
||||
setPageTitleDraft(value);
|
||||
debouncedPersistTitle(value);
|
||||
};
|
||||
|
||||
@@ -463,15 +565,11 @@ export function DocumentContent({
|
||||
async (patch: Partial<PageOptionsState>) => {
|
||||
if (readOnly) return;
|
||||
try {
|
||||
const response = await fetch("/api/documents/options", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ documentId, workspaceId, options: patch }),
|
||||
await updatePageOptionsCommand({
|
||||
documentId,
|
||||
workspaceId,
|
||||
pageOptions: patch,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const payload = await response.json().catch(() => null);
|
||||
console.error(payload?.error ?? "更新页面选项失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -742,10 +840,10 @@ export function DocumentContent({
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = `${title ?? "未命名页面"}-${new Date().toISOString()}.json`;
|
||||
anchor.download = `${pageTitle ?? "未命名页面"}-${new Date().toISOString()}.json`;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}, [disableDownload, history, title]);
|
||||
}, [disableDownload, history, pageTitle]);
|
||||
|
||||
const pageRootClass = cn(
|
||||
"flex h-full overflow-hidden bg-wolai-bg",
|
||||
@@ -784,6 +882,16 @@ export function DocumentContent({
|
||||
})),
|
||||
[pageSubtree],
|
||||
);
|
||||
const getLatestBlocks = useCallback(() => latestBlocksRef.current, []);
|
||||
const getLatestPageSubtree = useCallback(() => pageSubtree, [pageSubtree]);
|
||||
const getLatestPersistedMeta = useCallback(
|
||||
() => ({
|
||||
workspaceId,
|
||||
revision: contentRevision,
|
||||
conflictDetectionKey,
|
||||
}),
|
||||
[conflictDetectionKey, contentRevision, workspaceId],
|
||||
);
|
||||
const inspectorCanUseEditorBridge = canEditDocument && isEditing;
|
||||
|
||||
const jumpToHeading = useCallback((headingId: string) => {
|
||||
@@ -861,6 +969,68 @@ export function DocumentContent({
|
||||
editorBridge.replaceWithSnapshot(pendingSnapshot.blocks);
|
||||
setHistoryOpen(false);
|
||||
}, [editorBridge, isEditing]);
|
||||
|
||||
const handleHostEvent = useCallback((event: EditorHostEvent) => {
|
||||
setHostEventAt(event.at);
|
||||
if (event.kind === "status_changed") {
|
||||
setHostStatus(event.status);
|
||||
return;
|
||||
}
|
||||
if (event.kind === "runtime_load_failed") {
|
||||
setHostRuntimeLoadFailure(event.message);
|
||||
return;
|
||||
}
|
||||
if (event.kind === "host_init_failed") {
|
||||
setHostInitFailure(event.message);
|
||||
return;
|
||||
}
|
||||
if (event.kind === "command_failed") {
|
||||
setHostCommandFailure(event.message);
|
||||
return;
|
||||
}
|
||||
if (event.kind === "save_failed") {
|
||||
setHostSaveFailure(event.message);
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const hostObservability = useMemo(
|
||||
() => ({
|
||||
requestedHostKind,
|
||||
activeHostKind,
|
||||
status: hostStatus,
|
||||
runtimeLoadFailed: hostRuntimeLoadFailure,
|
||||
hostInitFailed: hostInitFailure,
|
||||
commandFailed: hostCommandFailure,
|
||||
saveFailed: hostSaveFailure,
|
||||
fallbackCount: hostFallbackCount,
|
||||
lastFallbackReason,
|
||||
lastFallbackAt,
|
||||
lastEventAt: hostEventAt,
|
||||
fallbackTimestamps: fallbackTriggerHistoryRef.current,
|
||||
}),
|
||||
[
|
||||
activeHostKind,
|
||||
hostEventAt,
|
||||
hostFallbackCount,
|
||||
hostInitFailure,
|
||||
hostCommandFailure,
|
||||
hostRuntimeLoadFailure,
|
||||
hostSaveFailure,
|
||||
hostStatus,
|
||||
lastFallbackAt,
|
||||
lastFallbackReason,
|
||||
requestedHostKind,
|
||||
],
|
||||
);
|
||||
const activeHostFailureMessage =
|
||||
hostRuntimeLoadFailure ?? hostInitFailure ?? hostCommandFailure ?? hostSaveFailure;
|
||||
const showFallbackBanner =
|
||||
requestedHostKind !== "blocknote" && activeHostKind === "blocknote" && lastFallbackReason != null;
|
||||
const showFailureBanner =
|
||||
requestedHostKind !== "blocknote" &&
|
||||
activeHostKind !== "blocknote" &&
|
||||
activeHostFailureMessage != null;
|
||||
return (
|
||||
<ImagePickerProvider documentId={documentId} workspaceId={workspaceId}>
|
||||
<div className={pageRootClass} ref={pageRootRef}>
|
||||
@@ -935,15 +1105,55 @@ export function DocumentContent({
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative">
|
||||
{showFallbackBanner ? (
|
||||
<div className="mb-4 flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
默认 island 主编辑器已自动回退到 BlockNote
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-amber-700">
|
||||
原因:{lastFallbackReason}
|
||||
{lastFallbackAt ? `,时间:${lastFallbackAt}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setActiveHostKind(requestedHostKind);
|
||||
resetHostObservability(requestedHostKind);
|
||||
}}
|
||||
>
|
||||
重试 island 主链
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{showFailureBanner ? (
|
||||
<div className="mb-4 flex items-center justify-between rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
<div>
|
||||
<div className="font-medium">island 主链出现错误</div>
|
||||
<div className="mt-1 text-xs text-red-600">{activeHostFailureMessage}</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => requestFallbackToBlockNote("explicit_fallback", activeHostFailureMessage)}
|
||||
>
|
||||
切回 BlockNote
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{keepEditorMounted && (
|
||||
<div className={cn(!isEditing && "pointer-events-none absolute inset-0 opacity-0")} aria-hidden={!isEditing}>
|
||||
{shouldUseExperimentalHost ? (
|
||||
{activeHostKind !== "blocknote" ? (
|
||||
<EditorHost
|
||||
documentId={documentId}
|
||||
workspaceId={workspaceId}
|
||||
initialContent={content}
|
||||
title={title}
|
||||
hostKind={editorHostKind}
|
||||
title={pageTitle}
|
||||
hostKind={activeHostKind}
|
||||
initialRevision={contentRevision}
|
||||
initialConflictDetectionKey={conflictDetectionKey}
|
||||
pageOptions={options}
|
||||
@@ -955,13 +1165,17 @@ export function DocumentContent({
|
||||
setContentRevision(meta.revision);
|
||||
setConflictDetectionKey(meta.conflictDetectionKey);
|
||||
}}
|
||||
onHostEvent={handleHostEvent}
|
||||
onRequestFallback={(payload) => {
|
||||
requestFallbackToBlockNote(payload.reason, payload.error);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<BlockNoteEditor
|
||||
documentId={documentId}
|
||||
workspaceId={workspaceId}
|
||||
initialContent={content}
|
||||
title={title}
|
||||
title={pageTitle}
|
||||
initialRevision={contentRevision}
|
||||
initialConflictDetectionKey={conflictDetectionKey}
|
||||
pageOptions={options}
|
||||
@@ -977,6 +1191,21 @@ export function DocumentContent({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="sr-only"
|
||||
data-editor-host-observability={JSON.stringify(hostObservability)}
|
||||
data-editor-host-active={hostObservability.activeHostKind}
|
||||
data-editor-host-requested={hostObservability.requestedHostKind}
|
||||
data-editor-host-status={hostObservability.status}
|
||||
data-editor-host-runtime-load-failed={
|
||||
hostObservability.runtimeLoadFailed ? "1" : "0"
|
||||
}
|
||||
data-editor-host-init-failed={hostObservability.hostInitFailed ? "1" : "0"}
|
||||
data-editor-host-command-failed={hostObservability.commandFailed ? "1" : "0"}
|
||||
data-editor-host-save-failed={hostObservability.saveFailed ? "1" : "0"}
|
||||
data-editor-host-fallback-count={String(hostObservability.fallbackCount)}
|
||||
data-editor-host-last-fallback-reason={hostObservability.lastFallbackReason ?? ""}
|
||||
/>
|
||||
{!isEditing && (
|
||||
<div className="relative" ref={readViewRootRef}>
|
||||
<DocumentReadView
|
||||
@@ -1037,8 +1266,14 @@ export function DocumentContent({
|
||||
<DocumentCommentsDrawer />
|
||||
<DocumentAiAgentPanel
|
||||
documentId={documentId}
|
||||
getLatestBlocks={() => latestBlocksRef.current}
|
||||
getLatestPageSubtree={() => pageSubtree}
|
||||
getLatestBlocks={getLatestBlocks}
|
||||
getLatestPageSubtree={getLatestPageSubtree}
|
||||
getLatestPersistedMeta={getLatestPersistedMeta}
|
||||
onPersistedMetaChange={(meta) => {
|
||||
setContentRevision(meta.revision);
|
||||
setConflictDetectionKey(meta.conflictDetectionKey);
|
||||
}}
|
||||
onPageHeadTitleChange={handleAiPageHeadTitleChange}
|
||||
/>
|
||||
</ImagePickerProvider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user