feat: land page aggregate and phase7 document ai mainline

- 收口 page aggregate 读取、本地状态与命令客户端\n- 接入 phase7 document ai sidecar 与前端编排入口\n- 更新 architecture 与 design 状态迁移
This commit is contained in:
lix-2026
2026-04-23 07:38:34 +08:00
parent 8353aea2f9
commit 41e958769e
93 changed files with 8778 additions and 2222 deletions
@@ -9,13 +9,15 @@ import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import { useEditorBridgeStore } from "@/store/editor-bridge";
import type { EditorReferenceBridge } from "@/store/editor-bridge";
import type { Json } from "@/types/supabase";
import type { DocumentStats, PageOptionsState } from "@/types/page-options";
import type { DocumentStats } from "@/types/page-options";
import { pickLeptosTiptapRuntimePageOptions } from "@/lib/documents/page-option-semantics";
import {
blocksFromTiptapDoc,
editorBlockDocumentFromTiptapDoc,
tiptapDocFromBlocks,
} from "@/lib/documents/tiptap-content-converter";
import { buildDocumentSavePayload } from "@/lib/documents/save-contract";
import { executePageBodySavePayload } from "@/lib/documents/page-command-client";
import {
loadLeptosTiptapIslandAssets,
} from "@/components/editor/leptos-tiptap-island-loader";
@@ -116,21 +118,7 @@ type IslandBootstrapPayload = {
readOnly: boolean;
pageOptions: RuntimePageOptionsPayload;
};
type RuntimePageOptionsPayload = Pick<
PageOptionsState,
"wideLayout" | "smallText" | "layoutDensity" | "showHeadingNumbers" | "embedDefaultBlockId"
>;
function buildRuntimePageOptions(pageOptions: PageOptionsState): RuntimePageOptionsPayload {
return {
wideLayout: pageOptions.wideLayout,
smallText: pageOptions.smallText,
layoutDensity: pageOptions.layoutDensity,
showHeadingNumbers: pageOptions.showHeadingNumbers,
embedDefaultBlockId: pageOptions.embedDefaultBlockId,
};
}
type RuntimePageOptionsPayload = ReturnType<typeof pickLeptosTiptapRuntimePageOptions>;
function toIsoNow(): string {
return new Date().toISOString();
@@ -316,7 +304,7 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
revision: props.initialRevision ?? null,
conflictDetectionKey: props.initialConflictDetectionKey ?? null,
readOnly: Boolean(props.readOnly),
pageOptions: buildRuntimePageOptions(props.pageOptions),
pageOptions: pickLeptosTiptapRuntimePageOptions(props.pageOptions),
});
const [editorHeight, setEditorHeight] = useState(FALLBACK_MIN_HEIGHT);
const [bridgeState, setBridgeState] = useState<RuntimeBridgeState>({
@@ -375,7 +363,7 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
revision: props.initialRevision ?? null,
conflictDetectionKey: props.initialConflictDetectionKey ?? null,
readOnly: Boolean(props.readOnly),
pageOptions: buildRuntimePageOptions(props.pageOptions),
pageOptions: pickLeptosTiptapRuntimePageOptions(props.pageOptions),
};
}, [
mountIdentity,
@@ -418,35 +406,22 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
const persistDocument = useCallback(async () => {
const normalizedDoc = latestDocRef.current;
const normalizedBlocks = blocksFromTiptapDoc(normalizedDoc) as Json;
const response = await fetch("/api/documents/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(
buildDocumentSavePayload({
documentId: props.documentId,
workspaceId: props.workspaceId,
revision: revisionRef.current,
editorDocument: editorBlockDocumentFromTiptapDoc(normalizedDoc, props.documentId),
content: normalizedBlocks,
tiptapDocument: normalizedDoc,
conflictDetectionKey: conflictDetectionKeyRef.current,
snapshotCapturedAt: toIsoNow(),
blockCount: Array.isArray(normalizedBlocks) ? normalizedBlocks.length : 0,
}),
),
});
const payload = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(
typeof payload?.error === "string" ? payload.error : `保存失败(${response.status}`,
);
}
revisionRef.current =
typeof payload?.revision === "number" ? payload.revision : revisionRef.current;
const persistedMeta = await executePageBodySavePayload(
buildDocumentSavePayload({
documentId: props.documentId,
workspaceId: props.workspaceId,
revision: revisionRef.current,
editorDocument: editorBlockDocumentFromTiptapDoc(normalizedDoc, props.documentId),
content: normalizedBlocks,
tiptapDocument: normalizedDoc,
conflictDetectionKey: conflictDetectionKeyRef.current,
snapshotCapturedAt: toIsoNow(),
blockCount: Array.isArray(normalizedBlocks) ? normalizedBlocks.length : 0,
}),
);
revisionRef.current = persistedMeta.revision ?? revisionRef.current;
conflictDetectionKeyRef.current =
typeof payload?.conflictDetectionKey === "string"
? payload.conflictDetectionKey
: conflictDetectionKeyRef.current;
persistedMeta.conflictDetectionKey ?? conflictDetectionKeyRef.current;
onPersistedMetaChangeRef.current?.({
revision: revisionRef.current,
conflictDetectionKey: conflictDetectionKeyRef.current,
@@ -717,7 +692,7 @@ export function LeptosTiptapIslandEditorHost(props: DocumentEditorHostProps) {
}
dispatchRuntimeCommand(target, {
command: "setPageOptions",
pageOptions: buildRuntimePageOptions(props.pageOptions),
pageOptions: pickLeptosTiptapRuntimePageOptions(props.pageOptions),
});
}, [props.pageOptions]);