106 lines
3.6 KiB
TypeScript
106 lines
3.6 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
|
|
import type {
|
|
CanvasCodeSelection,
|
|
CanvasSelectionState,
|
|
CanvasTextSelection,
|
|
WritingSelection,
|
|
} from "../types/selection";
|
|
|
|
export interface CanvasUIContextValue {
|
|
isChatCollapsed: boolean;
|
|
isEditing: boolean;
|
|
isWebSearchOpen: boolean;
|
|
/** AI 写作:右侧草稿是否正在流式生成。生成中右侧走只读流式视图(自动下滑、禁用编辑/润色/滑词),生成完才可编辑 */
|
|
isDraftStreaming: boolean;
|
|
/** AI 写作:流式生成中的草稿 Markdown。生成期间走这条独立通道(不写全局 artifact),避免高频刷新触发编辑器更新风暴 */
|
|
draftStreamingMarkdown: string;
|
|
/** AI 写作:草稿是否进入"生成完成的稳定态"(可编辑)。仅此时才挂载带编辑器的渲染器;生成/审核中一律只读 */
|
|
isDraftEditable: boolean;
|
|
selection: CanvasSelectionState;
|
|
/** Tiptap editor selection for AI rewrite flow */
|
|
writingSelection: WritingSelection | null;
|
|
setIsChatCollapsed: (v: boolean) => void;
|
|
setIsEditing: (v: boolean) => void;
|
|
setIsWebSearchOpen: (v: boolean) => void;
|
|
setIsDraftStreaming: (v: boolean) => void;
|
|
setDraftStreamingMarkdown: (v: string) => void;
|
|
setIsDraftEditable: (v: boolean) => void;
|
|
/** Set text selection; clears any existing code selection. */
|
|
setTextSelection: (s: CanvasTextSelection | null) => void;
|
|
/** Set code selection; clears any existing text selection. */
|
|
setCodeSelection: (s: CanvasCodeSelection | null) => void;
|
|
clearSelection: () => void;
|
|
setWritingSelection: (s: WritingSelection | null) => void;
|
|
}
|
|
|
|
const EMPTY_SELECTION: CanvasSelectionState = {
|
|
textSelection: null,
|
|
codeSelection: null,
|
|
};
|
|
|
|
const CanvasUIContext = createContext<CanvasUIContextValue | null>(null);
|
|
|
|
export function CanvasUIProvider({ children }: { children: ReactNode }) {
|
|
const [isChatCollapsed, setIsChatCollapsed] = useState(false);
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [isWebSearchOpen, setIsWebSearchOpen] = useState(false);
|
|
const [isDraftStreaming, setIsDraftStreaming] = useState(false);
|
|
const [draftStreamingMarkdown, setDraftStreamingMarkdown] = useState('');
|
|
const [isDraftEditable, setIsDraftEditable] = useState(false);
|
|
const [selection, setSelection] =
|
|
useState<CanvasSelectionState>(EMPTY_SELECTION);
|
|
const [writingSelection, setWritingSelection] =
|
|
useState<WritingSelection | null>(null);
|
|
|
|
const setTextSelection = useCallback((s: CanvasTextSelection | null) => {
|
|
setSelection({ textSelection: s, codeSelection: null });
|
|
}, []);
|
|
|
|
const setCodeSelection = useCallback((s: CanvasCodeSelection | null) => {
|
|
setSelection({ textSelection: null, codeSelection: s });
|
|
}, []);
|
|
|
|
return (
|
|
<CanvasUIContext.Provider
|
|
value={{
|
|
isChatCollapsed,
|
|
isEditing,
|
|
isWebSearchOpen,
|
|
isDraftStreaming,
|
|
draftStreamingMarkdown,
|
|
isDraftEditable,
|
|
selection,
|
|
writingSelection,
|
|
setIsChatCollapsed,
|
|
setIsEditing,
|
|
setIsWebSearchOpen,
|
|
setIsDraftStreaming,
|
|
setDraftStreamingMarkdown,
|
|
setIsDraftEditable,
|
|
setTextSelection,
|
|
setCodeSelection,
|
|
clearSelection: () => setSelection(EMPTY_SELECTION),
|
|
setWritingSelection,
|
|
}}
|
|
>
|
|
{children}
|
|
</CanvasUIContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useCanvasUIContext() {
|
|
const ctx = useContext(CanvasUIContext);
|
|
if (!ctx)
|
|
throw new Error(
|
|
"useCanvasUIContext must be used within CanvasUIProvider",
|
|
);
|
|
return ctx;
|
|
}
|