"use client"; import { useCallback, useMemo, useState } from "react"; // 极轻 genre 采集门(WFW-7,纯前端零契约)。首次写章前,若项目无 genre, // 让作者点一个题材,仅【临时并入本次写作指令】——MVP 不落库(无项目更新端点), // 避免无题材 slop。纯状态,无副作用;组件只负责渲染 chips + 触发 onWrite。 // 有 effectiveGenre 时的题材指令前缀(并进 composeDirective)。空题材 → ""。 export function toGenreDirective(genre: string | null): string { const trimmed = genre?.trim() ?? ""; return trimmed.length > 0 ? `本章题材:${trimmed}` : ""; } export interface GenreGate { // 项目缺 genre(需先采集)。为空串/纯空白亦视为缺失。 needsGenre: boolean; // 作者本次临时点选的题材(未选 = null)。 chosenGenre: string | null; setChosenGenre: (genre: string) => void; // 生效题材:项目 genre 优先,其次作者点选,都无 → null。 effectiveGenre: string | null; // 并入本次 directive 的题材串(形如「本章题材:玄幻」);无生效题材 → ""。 genreDirective: string; } export function useGenreGate(projectGenre: string | null): GenreGate { const [chosenGenre, setChosenGenreState] = useState(null); const setChosenGenre = useCallback((genre: string): void => { const trimmed = genre.trim(); setChosenGenreState(trimmed.length > 0 ? trimmed : null); }, []); const needsGenre = (projectGenre?.trim() ?? "").length === 0; const effectiveGenre = useMemo(() => { const fromProject = projectGenre?.trim() ?? ""; if (fromProject.length > 0) return fromProject; return chosenGenre; }, [projectGenre, chosenGenre]); const genreDirective = useMemo( () => toGenreDirective(effectiveGenre), [effectiveGenre], ); return { needsGenre, chosenGenre, setChosenGenre, effectiveGenre, genreDirective, }; }