diff --git a/apps/studio/src/components/pipeline/stages/QuizzesSettings.tsx b/apps/studio/src/components/pipeline/stages/QuizzesSettings.tsx index ac27e75..121625c 100644 --- a/apps/studio/src/components/pipeline/stages/QuizzesSettings.tsx +++ b/apps/studio/src/components/pipeline/stages/QuizzesSettings.tsx @@ -54,7 +54,9 @@ export function QuizzesSettings({ bookLabel, headerTarget, tab = "general" }: { } } if (m.section_types && typeof m.section_types === "object") { - setSectionTypes(m.section_types as Record) + const all = m.section_types as Record + const disabled = new Set(Array.isArray(m.disabled_section_types) ? m.disabled_section_types as string[] : []) + setSectionTypes(Object.fromEntries(Object.entries(all).filter(([k]) => !disabled.has(k)))) } }, [activeConfigData]) diff --git a/apps/studio/src/components/pipeline/stages/StoryboardSectionDetail.tsx b/apps/studio/src/components/pipeline/stages/StoryboardSectionDetail.tsx index 1f36bd7..5169ecb 100644 --- a/apps/studio/src/components/pipeline/stages/StoryboardSectionDetail.tsx +++ b/apps/studio/src/components/pipeline/stages/StoryboardSectionDetail.tsx @@ -411,7 +411,11 @@ export function StoryboardSectionDetail({ }) const textTypes = configQuery.data?.merged?.text_types as Record | undefined - const sectionTypes = configQuery.data?.merged?.section_types as Record | undefined + const allSectionTypes = configQuery.data?.merged?.section_types as Record | undefined + const disabledSectionTypes = new Set(configQuery.data?.merged?.disabled_section_types as string[] ?? []) + const sectionTypes = allSectionTypes + ? Object.fromEntries(Object.entries(allSectionTypes).filter(([key]) => !disabledSectionTypes.has(key))) + : undefined // Abort in-flight requests when the component unmounts useEffect(() => { diff --git a/apps/studio/src/components/pipeline/stages/StoryboardSettings.tsx b/apps/studio/src/components/pipeline/stages/StoryboardSettings.tsx index 06abea2..7d9299e 100644 --- a/apps/studio/src/components/pipeline/stages/StoryboardSettings.tsx +++ b/apps/studio/src/components/pipeline/stages/StoryboardSettings.tsx @@ -69,6 +69,7 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: // Form state const [sectionTypes, setSectionTypes] = useState>({}) const [prunedSectionTypes, setPrunedSectionTypes] = useState>(new Set()) + const [disabledSectionTypes, setDisabledSectionTypes] = useState>(new Set()) const [sectionRenderStrategies, setSectionRenderStrategies] = useState>({}) const [defaultRenderStrategy, setDefaultRenderStrategy] = useState("") const [allStrategyNames, setAllStrategyNames] = useState([]) @@ -161,6 +162,9 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: if (Array.isArray(merged.pruned_section_types)) { setPrunedSectionTypes(new Set(merged.pruned_section_types as string[])) } + if (Array.isArray(merged.disabled_section_types)) { + setDisabledSectionTypes(new Set(merged.disabled_section_types as string[])) + } if (merged.default_render_strategy) { setDefaultRenderStrategy(String(merged.default_render_strategy)) } @@ -228,23 +232,12 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: }) } - const removeSectionType = (key: string) => { - markDirty("section_types") - markDirty("pruned_section_types") - markDirty("section_render_strategies") - setSectionTypes((prev) => { - const next = { ...prev } - delete next[key] - return next - }) - setPrunedSectionTypes((prev) => { + const toggleDisabled = (key: string) => { + markDirty("disabled_section_types") + setDisabledSectionTypes((prev) => { const next = new Set(prev) - next.delete(key) - return next - }) - setSectionRenderStrategies((prev) => { - const next = { ...prev } - delete next[key] + if (next.has(key)) next.delete(key) + else next.add(key) return next }) } @@ -284,6 +277,9 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: if (shouldWrite("pruned_section_types")) { overrides.pruned_section_types = Array.from(prunedSectionTypes) } + if (shouldWrite("disabled_section_types")) { + overrides.disabled_section_types = Array.from(disabledSectionTypes) + } if (shouldWrite("default_render_strategy")) { overrides.default_render_strategy = defaultRenderStrategy || undefined } @@ -463,7 +459,7 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: Section Types

- Types used during page sectioning. Pruned types are excluded from rendering. + Types used during page sectioning. Pruned types are classified but excluded from rendering. Disabled types are hidden from the LLM entirely.

{/* Header */} @@ -476,14 +472,15 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }:
{Object.entries(sectionTypes).map(([key, description]) => { const pruned = prunedSectionTypes.has(key) + const disabled = disabledSectionTypes.has(key) const renderOverride = sectionRenderStrategies[key] ?? "" return (
togglePruned(key)} /> - + {key} @@ -786,9 +783,8 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: {tab === "activity-prompts" && (() => { const activityNames = Object.keys(activityStrategies) // Activities are enabled when their section types are NOT pruned and render strategies are mapped - const allEnabled = activityNames.length > 0 && - activityNames.every((name) => sectionRenderStrategies[name] === name) && - !activityNames.some((name) => prunedSectionTypes.has(name)) + const anyEnabled = activityNames.length > 0 && + activityNames.some((name) => !disabledSectionTypes.has(name)) return (
@@ -799,24 +795,10 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: {/* Universal enable/disable toggle */}
{ - // 1. Toggle section_render_strategies — maps activity section types to their strategies - markDirty("section_render_strategies") - setSectionRenderStrategies((prev) => { - const next = { ...prev } - for (const name of activityNames) { - if (checked) { - next[name] = name - } else { - delete next[name] - } - } - return next - }) - // 2. Toggle pruned_section_types — hides activity types from the page classifier - markDirty("pruned_section_types") - setPrunedSectionTypes((prev) => { + markDirty("disabled_section_types") + setDisabledSectionTypes((prev) => { const next = new Set(prev) for (const name of activityNames) { if (checked) { @@ -830,10 +812,10 @@ export function StoryboardSettings({ bookLabel, headerTarget, tab = "general" }: }} />

- {allEnabled + {anyEnabled ? "Activity section types are available for classification and rendering." : "Activity section types are hidden from the classifier and skipped during rendering."}

diff --git a/packages/pipeline/src/__tests__/page-sectioning.test.ts b/packages/pipeline/src/__tests__/page-sectioning.test.ts index 9759017..98059da 100644 --- a/packages/pipeline/src/__tests__/page-sectioning.test.ts +++ b/packages/pipeline/src/__tests__/page-sectioning.test.ts @@ -35,6 +35,41 @@ describe("buildSectioningConfig", () => { expect(config.prunedSectionTypes).toEqual(["credits"]) }) + it("excludes disabled section types from sectionTypes", () => { + const appConfig: AppConfig = { + text_types: { heading: "Heading" }, + text_group_types: { paragraph: "Paragraph" }, + section_types: { + text_only: "Reading section with only text", + images_only: "Section with only images", + credits: "Credits section", + }, + pruned_section_types: ["credits"], + disabled_section_types: ["images_only"], + } + + const config = buildSectioningConfig(appConfig) + expect(config.sectionTypes).toEqual([ + { key: "text_only", description: "Reading section with only text" }, + { key: "credits", description: "Credits section" }, + ]) + expect(config.prunedSectionTypes).toEqual(["credits"]) + }) + + it("handles empty disabled_section_types", () => { + const appConfig: AppConfig = { + text_types: { heading: "Heading" }, + text_group_types: { paragraph: "Paragraph" }, + section_types: { text_only: "Text" }, + disabled_section_types: [], + } + + const config = buildSectioningConfig(appConfig) + expect(config.sectionTypes).toEqual([ + { key: "text_only", description: "Text" }, + ]) + }) + it("defaults prompt and model when not specified", () => { const appConfig: AppConfig = { text_types: { heading: "Heading" }, diff --git a/packages/pipeline/src/page-sectioning.ts b/packages/pipeline/src/page-sectioning.ts index f230bb8..560471e 100644 --- a/packages/pipeline/src/page-sectioning.ts +++ b/packages/pipeline/src/page-sectioning.ts @@ -265,9 +265,12 @@ function validatePageSectioning( * Build SectioningConfig from AppConfig. */ export function buildSectioningConfig(appConfig: AppConfig): SectioningConfig { + const disabledSet = new Set(appConfig.disabled_section_types ?? []) const sectionTypes: TypeDef[] = Object.entries( appConfig.section_types ?? {} - ).map(([key, description]) => ({ key, description })) + ) + .filter(([key]) => !disabledSet.has(key)) + .map(([key, description]) => ({ key, description })) return { sectionTypes, diff --git a/packages/types/src/config.ts b/packages/types/src/config.ts index dbd12b9..59ff9bd 100644 --- a/packages/types/src/config.ts +++ b/packages/types/src/config.ts @@ -83,6 +83,7 @@ export const AppConfig = z section_types: z.record(z.string(), z.string()).optional(), pruned_text_types: z.array(z.string()).optional(), pruned_section_types: z.array(z.string()).optional(), + disabled_section_types: z.array(z.string()).optional(), text_classification: StepConfig.optional(), translation: StepConfig.optional(), metadata: StepConfig.optional(),