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,117 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
|
||||
export type PageDraftSnapshot = {
|
||||
title: string;
|
||||
summary: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
const MAX_HISTORY = 50;
|
||||
const HISTORY_DEBOUNCE_MS = 600;
|
||||
|
||||
export function usePageDraftHistory(initial: PageDraftSnapshot) {
|
||||
const [snapshot, setSnapshot] = useState(initial);
|
||||
const stableRef = useRef(initial);
|
||||
const pastRef = useRef<PageDraftSnapshot[]>([]);
|
||||
const futureRef = useRef<PageDraftSnapshot[]>([]);
|
||||
const debounceRef = useRef<number | null>(null);
|
||||
const [historyRevision, setHistoryRevision] = useState(0);
|
||||
const [canUndo, setCanUndo] = useState(false);
|
||||
const [canRedo, setCanRedo] = useState(false);
|
||||
|
||||
const bump = () => {
|
||||
setCanUndo(pastRef.current.length > 0);
|
||||
setCanRedo(futureRef.current.length > 0);
|
||||
setHistoryRevision((value) => value + 1);
|
||||
};
|
||||
|
||||
const flushDebounced = useCallback(() => {
|
||||
if (debounceRef.current != null) {
|
||||
window.clearTimeout(debounceRef.current);
|
||||
debounceRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const recordStable = useCallback((next: PageDraftSnapshot) => {
|
||||
if (JSON.stringify(stableRef.current) === JSON.stringify(next)) return;
|
||||
pastRef.current = [...pastRef.current.slice(-(MAX_HISTORY - 1)), stableRef.current];
|
||||
futureRef.current = [];
|
||||
stableRef.current = next;
|
||||
bump();
|
||||
}, []);
|
||||
|
||||
const update = useCallback(
|
||||
(next: PageDraftSnapshot) => {
|
||||
setSnapshot(next);
|
||||
bump();
|
||||
flushDebounced();
|
||||
debounceRef.current = window.setTimeout(() => {
|
||||
debounceRef.current = null;
|
||||
recordStable(next);
|
||||
}, HISTORY_DEBOUNCE_MS);
|
||||
},
|
||||
[flushDebounced, recordStable],
|
||||
);
|
||||
|
||||
const replace = useCallback(
|
||||
(next: PageDraftSnapshot, options?: { recordHistory?: boolean }) => {
|
||||
flushDebounced();
|
||||
if (options?.recordHistory) {
|
||||
pastRef.current = [...pastRef.current.slice(-(MAX_HISTORY - 1)), snapshot];
|
||||
futureRef.current = [];
|
||||
}
|
||||
stableRef.current = next;
|
||||
setSnapshot(next);
|
||||
bump();
|
||||
},
|
||||
[flushDebounced, snapshot],
|
||||
);
|
||||
|
||||
const undo = useCallback(() => {
|
||||
flushDebounced();
|
||||
const previous = pastRef.current[pastRef.current.length - 1];
|
||||
if (!previous) return false;
|
||||
pastRef.current = pastRef.current.slice(0, -1);
|
||||
futureRef.current = [snapshot, ...futureRef.current];
|
||||
stableRef.current = previous;
|
||||
setSnapshot(previous);
|
||||
bump();
|
||||
return true;
|
||||
}, [flushDebounced, snapshot]);
|
||||
|
||||
const redo = useCallback(() => {
|
||||
flushDebounced();
|
||||
const next = futureRef.current[0];
|
||||
if (!next) return false;
|
||||
futureRef.current = futureRef.current.slice(1);
|
||||
pastRef.current = [...pastRef.current, snapshot];
|
||||
stableRef.current = next;
|
||||
setSnapshot(next);
|
||||
bump();
|
||||
return true;
|
||||
}, [flushDebounced, snapshot]);
|
||||
|
||||
const reset = useCallback(
|
||||
(next: PageDraftSnapshot) => {
|
||||
flushDebounced();
|
||||
pastRef.current = [];
|
||||
futureRef.current = [];
|
||||
stableRef.current = next;
|
||||
setSnapshot(next);
|
||||
bump();
|
||||
},
|
||||
[flushDebounced],
|
||||
);
|
||||
|
||||
return {
|
||||
snapshot,
|
||||
update,
|
||||
replace,
|
||||
undo,
|
||||
redo,
|
||||
reset,
|
||||
canUndo,
|
||||
canRedo,
|
||||
historyRevision,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user