"use client"; import { useCallback, useRef, useState } from "react"; import { api } from "@/lib/api/client"; import { useToast } from "@/components/Toast"; // 进来即写(延迟落库):落地页「直接开始写」直达空白编辑器,**只有敲入实质正文后** // 才懒建占位项目并把已敲入正文种进首章草稿。空进空出不落库,杜绝孤儿「未命名草稿」。 export const DRAFT_PLACEHOLDER_TITLE = "未命名草稿"; export const DRAFT_FIRST_CHAPTER = 1; export type DeferredStatus = "idle" | "creating" | "created" | "error"; export interface UseDeferredDraft { status: DeferredStatus; // 首次有实质正文时调用:懒建占位项目 + 落首章草稿,返回新项目 id(调用方据此跳转编辑器)。 // 幂等:已建/在建时复用同一结果,绝不重复 POST。失败返回 null(不跳转)。 ensureCreated: (text: string) => Promise; } export function useDeferredDraft(): UseDeferredDraft { const [status, setStatus] = useState("idle"); const toast = useToast(); // 已建项目 id 与在途 Promise:双重幂等闸,防抖多次触发只落一次库。 const createdIdRef = useRef(null); const inflightRef = useRef | null>(null); const ensureCreated = useCallback( (text: string): Promise => { if (createdIdRef.current) return Promise.resolve(createdIdRef.current); if (inflightRef.current) return inflightRef.current; const run = createDraftProject(text, toast, setStatus).then((id) => { inflightRef.current = null; if (id) createdIdRef.current = id; return id; }); inflightRef.current = run; return run; }, [toast], ); return { status, ensureCreated }; } type Toast = ReturnType; // 懒建项目 + 落首章草稿(草稿失败为非致命:项目已建,正文仍在前端,进编辑器后自动重存)。 async function createDraftProject( text: string, toast: Toast, setStatus: (s: DeferredStatus) => void, ): Promise { setStatus("creating"); try { const { data, error } = await api.POST("/projects", { body: { title: DRAFT_PLACEHOLDER_TITLE }, }); if (error || !data) { setStatus("error"); toast("创建草稿失败,请重试", "error"); return null; } await seedFirstChapter(data.id, text, toast); setStatus("created"); return data.id; } catch { setStatus("error"); toast("创建草稿异常,请检查网络", "error"); return null; } } // 把已敲入正文种进首章草稿;空白正文跳过。落库失败仅提示、不抛(项目已建,不阻断进入编辑器)。 async function seedFirstChapter( projectId: string, text: string, toast: Toast, ): Promise { if (text.trim().length === 0) return; const { error } = await api.PUT( "/projects/{project_id}/chapters/{chapter_no}/draft", { params: { path: { project_id: projectId, chapter_no: DRAFT_FIRST_CHAPTER }, }, body: { text }, }, ); if (error) { toast("首段草稿保存失败,进入编辑器后会自动重试", "info"); } }