'use client'; import { useState, useEffect } from 'react'; import { useConfig, ViewType } from '@/contexts/ConfigContext'; import { ReaderSidebarShell } from '@/components/reader/ReaderSidebarShell'; import { SEGMENT_PRELOAD_DEPTH_MIN, SEGMENT_PRELOAD_DEPTH_MAX, SEGMENT_PRELOAD_SENTENCE_LOOKAHEAD_MIN, SEGMENT_PRELOAD_SENTENCE_LOOKAHEAD_MAX, TTS_SEGMENT_MAX_BLOCK_LENGTH_MIN, TTS_SEGMENT_MAX_BLOCK_LENGTH_MAX, TTS_SEGMENT_MAX_BLOCK_LENGTH_STEP, clampSegmentPreloadDepth, clampSegmentPreloadSentenceLookahead, clampTtsSegmentMaxBlockLength, } from '@/types/config'; import { IconButton, RangeInput, Section, ToggleRow, CheckItem, SegmentedControl } from '@/components/ui'; import { RefreshIcon, SparkleIcon } from '@/components/icons/Icons'; import type { ParsedPdfBlockKind, PdfParseStatus } from '@/types/parsed-pdf'; import { isForceReparseDisabled } from '@/lib/client/pdf/force-reparse'; const PDF_SKIP_KIND_OPTIONS: Array<{ kind: ParsedPdfBlockKind; label: string }> = [ { kind: 'header', label: 'Header' }, { kind: 'footer', label: 'Footer' }, { kind: 'footnote', label: 'Footnote' }, { kind: 'vision_footnote', label: 'Vision footnote' }, { kind: 'figure_title', label: 'Figure title' }, { kind: 'doc_title', label: 'Document title' }, { kind: 'paragraph_title', label: 'Paragraph title' }, { kind: 'abstract', label: 'Abstract' }, { kind: 'algorithm', label: 'Algorithm' }, { kind: 'aside_text', label: 'Aside text' }, { kind: 'content', label: 'Content' }, { kind: 'reference', label: 'Reference' }, { kind: 'reference_content', label: 'Reference content' }, { kind: 'text', label: 'Text' }, { kind: 'number', label: 'Number' }, { kind: 'formula', label: 'Formula' }, { kind: 'formula_number', label: 'Formula number' }, { kind: 'table', label: 'Table' }, { kind: 'chart', label: 'Chart' }, { kind: 'image', label: 'Image' }, { kind: 'seal', label: 'Seal' }, ]; const viewTypeTextMapping = [ { id: 'single', name: 'Single Page' }, { id: 'dual', name: 'Two Pages' }, { id: 'scroll', name: 'Continuous Scroll' }, ]; type RangeSettingProps = { label: string; value: number; min: number; max: number; step: number; description: string; valueWidth?: string; formatter?: (value: number) => string; onChange: (value: number) => void; }; function RangeSetting({ label, value, min, max, step, description, valueWidth = 'w-10', formatter = (next) => String(next), onChange, }: RangeSettingProps) { return (
onChange(Number(event.target.value))} className="flex-1" /> {formatter(value)}

{description}

); } export function DocumentSettings({ isOpen, setIsOpen, epub, html, pdf }: { isOpen: boolean, setIsOpen: (isOpen: boolean) => void, epub?: boolean, html?: boolean, pdf?: { parseStatus: PdfParseStatus | null; parsedOverlayEnabled: boolean; skipBlockKinds: ParsedPdfBlockKind[]; onToggleOverlay: (enabled: boolean) => void; onToggleSkipKind: (kind: ParsedPdfBlockKind, enabled: boolean) => void; onForceReparse: () => void; } }) { const canWordHighlight = true; const { viewType, skipBlank, epubTheme, segmentPreloadDepthPages, segmentPreloadSentenceLookahead, ttsSegmentMaxBlockLength, updateConfigKey, pdfHighlightEnabled, epubHighlightEnabled, pdfWordHighlightEnabled, epubWordHighlightEnabled, htmlHighlightEnabled, htmlWordHighlightEnabled, } = useConfig(); const selectedView = viewTypeTextMapping.find(v => v.id === viewType) || viewTypeTextMapping[0]; const isPdfMode = !epub && !html && !!pdf; const [localPreloadDepth, setLocalPreloadDepth] = useState(segmentPreloadDepthPages); const [localSentenceLookahead, setLocalSentenceLookahead] = useState(segmentPreloadSentenceLookahead); const [localMaxBlockLength, setLocalMaxBlockLength] = useState(ttsSegmentMaxBlockLength); useEffect(() => { setLocalPreloadDepth(segmentPreloadDepthPages); }, [segmentPreloadDepthPages]); useEffect(() => { setLocalSentenceLookahead(segmentPreloadSentenceLookahead); }, [segmentPreloadSentenceLookahead]); useEffect(() => { setLocalMaxBlockLength(ttsSegmentMaxBlockLength); }, [ttsSegmentMaxBlockLength]); return ( setIsOpen(false)} ariaLabel="Document settings" title="Reader Settings" subtitle="Tune layout, preloading, and playback." bodyClassName="flex-1 overflow-y-auto px-4 py-4 bg-[radial-gradient(circle_at_top_right,color-mix(in_srgb,var(--accent),transparent_92%),transparent_35%)]" panelClassName="w-full sm:w-[30rem]" >
{isPdfMode && pdf && (
({ value: view.id as ViewType, label: view.name }))} onChange={(nextViewType) => updateConfigKey('viewType', nextViewType)} ariaLabel="Page mode" className="grid-cols-3" /> {selectedView.id === 'scroll' ? (

Scroll mode may be slower on large PDFs.

) : null}
updateConfigKey('pdfHighlightEnabled', checked)} variant="flat" /> updateConfigKey('pdfWordHighlightEnabled', checked)} variant="flat" />
)} {isPdfMode && pdf && (
PP-DocLayout-V3 {pdf.parseStatus ?? 'pending'}
} >
Skip Block Kinds While Reading Aloud
{PDF_SKIP_KIND_OPTIONS.map((option) => ( pdf.onToggleSkipKind(option.kind, enabled)} /> ))}
)}
{!html && ( updateConfigKey('skipBlank', checked)} variant="flat" /> )}
String(value)} onChange={(value) => { const next = clampSegmentPreloadDepth(value); setLocalPreloadDepth(next); void updateConfigKey('segmentPreloadDepthPages', next); }} /> String(value)} onChange={(value) => { const next = clampSegmentPreloadSentenceLookahead(value); setLocalSentenceLookahead(next); void updateConfigKey('segmentPreloadSentenceLookahead', next); }} /> String(value)} onChange={(value) => { const next = clampTtsSegmentMaxBlockLength(value); setLocalMaxBlockLength(next); void updateConfigKey('ttsSegmentMaxBlockLength', next); }} />
{epub && (
updateConfigKey('epubTheme', checked)} variant="flat" /> updateConfigKey('epubHighlightEnabled', checked)} variant="flat" /> updateConfigKey('epubWordHighlightEnabled', checked)} variant="flat" />
)} {html && (
updateConfigKey('htmlHighlightEnabled', checked)} variant="flat" /> updateConfigKey('htmlWordHighlightEnabled', checked)} variant="flat" />
)}
); }