diff --git a/src/app/(app)/pdf/[id]/page.tsx b/src/app/(app)/pdf/[id]/page.tsx
index 4cfb922..bad2b1e 100644
--- a/src/app/(app)/pdf/[id]/page.tsx
+++ b/src/app/(app)/pdf/[id]/page.tsx
@@ -337,7 +337,6 @@ export default function PDFViewerPage() {
parseStatus,
parsedOverlayEnabled,
skipBlockKinds: documentSettings.pdf?.skipBlockKinds ?? [],
- chaptersFromSections: documentSettings.pdf?.chaptersFromSections ?? true,
onToggleOverlay: (enabled) => setParsedOverlayEnabled(enabled),
onToggleSkipKind: (kind, enabled) => {
const current = new Set(documentSettings.pdf?.skipBlockKinds ?? []);
@@ -347,20 +346,8 @@ export default function PDFViewerPage() {
...documentSettings,
schemaVersion: 1,
pdf: {
- ...(documentSettings.pdf ?? { chaptersFromSections: true }),
+ ...(documentSettings.pdf ?? {}),
skipBlockKinds: Array.from(current),
- chaptersFromSections: documentSettings.pdf?.chaptersFromSections ?? true,
- },
- });
- },
- onToggleChaptersFromSections: (enabled) => {
- void updateDocumentSettings({
- ...documentSettings,
- schemaVersion: 1,
- pdf: {
- ...(documentSettings.pdf ?? { skipBlockKinds: [] }),
- skipBlockKinds: documentSettings.pdf?.skipBlockKinds ?? [],
- chaptersFromSections: enabled,
},
});
},
diff --git a/src/app/(app)/pdf/[id]/usePdfDocument.ts b/src/app/(app)/pdf/[id]/usePdfDocument.ts
index f8f5cca..5aafa0a 100644
--- a/src/app/(app)/pdf/[id]/usePdfDocument.ts
+++ b/src/app/(app)/pdf/[id]/usePdfDocument.ts
@@ -275,7 +275,7 @@ export function usePdfDocument(): PdfDocumentState {
text: block.text,
locator: {
readerType: 'pdf',
- page: block.fragments[0]?.page ?? pageNum,
+ page: pageNum,
blockId: block.id,
} as TTSSegmentLocator,
}))
@@ -328,11 +328,13 @@ export function usePdfDocument(): PdfDocumentState {
...upcomingPageNumbers.map((pageNum) => getPageText(pageNum, true)),
]);
const nextText = upcomingTexts[0];
+ const nextSourceUnits = nextPageNumber ? sourceUnitsFromParsedPage(nextPageNumber) : [];
const additionalUpcoming = upcomingPageNumbers
.slice(1)
.map((pageNum, idx) => ({
location: pageNum,
text: upcomingTexts[idx + 1] || '',
+ sourceUnits: sourceUnitsFromParsedPage(pageNum),
}))
.filter((item) => item.text.trim().length > 0);
@@ -351,6 +353,7 @@ export function usePdfDocument(): PdfDocumentState {
previousText: prevText,
nextLocation: nextPageNumber,
nextText: nextText,
+ nextSourceUnits,
upcomingLocations: additionalUpcoming,
...(sourceUnits.length > 0 ? { sourceUnits } : {}),
});
diff --git a/src/components/documents/DocumentSettings.tsx b/src/components/documents/DocumentSettings.tsx
index 9a5ec8a..27d1576 100644
--- a/src/components/documents/DocumentSettings.tsx
+++ b/src/components/documents/DocumentSettings.tsx
@@ -103,10 +103,8 @@ export function DocumentSettings({ isOpen, setIsOpen, epub, html, pdf }: {
parseStatus: PdfParseStatus | null;
parsedOverlayEnabled: boolean;
skipBlockKinds: ParsedPdfBlockKind[];
- chaptersFromSections: boolean;
onToggleOverlay: (enabled: boolean) => void;
onToggleSkipKind: (kind: ParsedPdfBlockKind, enabled: boolean) => void;
- onToggleChaptersFromSections: (enabled: boolean) => void;
onForceReparse: () => void;
}
}) {
@@ -204,13 +202,6 @@ export function DocumentSettings({ isOpen, setIsOpen, epub, html, pdf }: {
onChange={(checked) => updateConfigKey('pdfWordHighlightEnabled', checked)}
variant="flat"
/>
-
)}
diff --git a/src/components/reader/SegmentsSidebar.tsx b/src/components/reader/SegmentsSidebar.tsx
index 1e79ccc..5c9b2f1 100644
--- a/src/components/reader/SegmentsSidebar.tsx
+++ b/src/components/reader/SegmentsSidebar.tsx
@@ -249,6 +249,8 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
const segmentsQuery = useInfiniteQuery({
queryKey: segmentsQueryKey,
enabled: isOpen && !!documentId,
+ refetchInterval: isOpen ? 2500 : false,
+ refetchIntervalInBackground: false,
initialPageParam: null as string | null,
queryFn: async ({ pageParam, signal }) => {
if (!documentId) {
diff --git a/src/contexts/TTSContext.tsx b/src/contexts/TTSContext.tsx
index d96d23e..b380bcd 100644
--- a/src/contexts/TTSContext.tsx
+++ b/src/contexts/TTSContext.tsx
@@ -163,8 +163,9 @@ interface SetTextOptions {
previousLocation?: TTSLocation;
nextLocation?: TTSLocation;
nextText?: string;
+ nextSourceUnits?: CanonicalTtsSourceUnit[];
previousText?: string;
- upcomingLocations?: Array<{ location: TTSLocation; text: string }>;
+ upcomingLocations?: Array<{ location: TTSLocation; text: string; sourceUnits?: CanonicalTtsSourceUnit[] }>;
}
type TTSSegmentPlaybackSource = {
@@ -245,12 +246,16 @@ const buildCacheKey = (
speed: number,
provider: string,
model: string,
+ providerType: string,
+ instructions: string,
) => {
return [
`provider=${provider || ''}`,
+ `providerType=${providerType || ''}`,
`model=${model || ''}`,
`voice=${voice || ''}`,
`speed=${Number.isFinite(speed) ? speed : ''}`,
+ `instructions=${instructions || ''}`,
`text=${sentence}`,
].join('|');
};
@@ -263,10 +268,12 @@ const buildScopedSegmentCacheKey = (
speed: number,
provider: string,
model: string,
+ providerType: string,
+ instructions: string,
segmentKey?: string | null,
) => {
return [
- buildCacheKey(sentence, voice, speed, provider, model),
+ buildCacheKey(sentence, voice, speed, provider, model, providerType, instructions),
`segmentKey=${segmentKey || ''}`,
`locator=${segmentKey ? '' : buildLocatorRequestKey(locator)}`,
`segmentIndex=${segmentKey ? '' : segmentIndex}`,
@@ -306,9 +313,11 @@ const buildSegmentRequestKey = (
segmentIndex: number,
sentence: string,
segmentKey?: string | null,
-): string => segmentKey
- ? `${segmentKey}::${sentence}`
- : `${buildLocatorRequestKey(locator)}::${segmentIndex}::${sentence}`;
+): string => {
+ return segmentKey
+ ? `${segmentKey}::${sentence}`
+ : `${buildLocatorRequestKey(locator)}::${segmentIndex}::${sentence}`;
+};
const resolveJumpIndex = (input: JumpResolutionInput): JumpResolution => {
if (input.newSentenceCount <= 0) {
@@ -1172,29 +1181,48 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
plannedSegmentsByLocationRef.current.clear();
pendingNextLocationRef.current = normalizedOptions.nextLocation;
- const pendingPrefetches: Array = [];
+ const pendingPrefetches: Array<{
+ location: TTSLocation;
+ sourceUnits: CanonicalTtsSourceUnit[];
+ }> = [];
if (normalizedOptions.nextLocation !== undefined && normalizedOptions.nextText?.trim()) {
- pendingPrefetches.push({
- sourceKey: sourceKeyForLocation(normalizedOptions.nextLocation, currDocPage),
- location: normalizedOptions.nextLocation,
- text: normalizedOptions.nextText,
- locator: locatorForLocation(normalizedOptions.nextLocation, activeReaderType),
- });
+ const provided = normalizedOptions.nextSourceUnits?.filter((unit) => unit.text.trim().length > 0) ?? [];
+ pendingPrefetches.push(provided.length > 0
+ ? {
+ location: normalizedOptions.nextLocation,
+ sourceUnits: provided,
+ }
+ : {
+ location: normalizedOptions.nextLocation,
+ sourceUnits: [{
+ sourceKey: sourceKeyForLocation(normalizedOptions.nextLocation, currDocPage),
+ text: normalizedOptions.nextText,
+ locator: locatorForLocation(normalizedOptions.nextLocation, activeReaderType),
+ }],
+ });
}
if (Array.isArray(normalizedOptions.upcomingLocations)) {
for (const item of normalizedOptions.upcomingLocations) {
if (item.location === undefined || !item.text?.trim()) continue;
- pendingPrefetches.push({
- sourceKey: sourceKeyForLocation(item.location, currDocPage),
- location: item.location,
- text: item.text,
- locator: locatorForLocation(item.location, activeReaderType),
- });
+ const provided = item.sourceUnits?.filter((unit) => unit.text.trim().length > 0) ?? [];
+ pendingPrefetches.push(provided.length > 0
+ ? {
+ location: item.location,
+ sourceUnits: provided,
+ }
+ : {
+ location: item.location,
+ sourceUnits: [{
+ sourceKey: sourceKeyForLocation(item.location, currDocPage),
+ text: item.text,
+ locator: locatorForLocation(item.location, activeReaderType),
+ }],
+ });
}
}
for (const item of pendingPrefetches) {
if (smartSentenceSplitting) {
- sourceUnits.push(item);
+ sourceUnits.push(...item.sourceUnits);
}
}
@@ -1216,9 +1244,10 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
const newSentences = currentSegments.map((segment) => segment.text);
for (const item of pendingPrefetches) {
+ const sourceKeys = new Set(item.sourceUnits.map((unit) => unit.sourceKey));
const planned = smartSentenceSplitting
- ? plan.segments.filter((segment) => segment.ownerSourceKey === item.sourceKey)
- : planCanonicalTtsSegments([item], {
+ ? plan.segments.filter((segment) => sourceKeys.has(segment.ownerSourceKey))
+ : planCanonicalTtsSegments(item.sourceUnits, {
readerType: activeReaderType,
maxBlockLength: ttsSegmentMaxBlockLength,
keyPrefix: buildSegmentKeyPrefix(documentId, activeReaderType),
@@ -1554,6 +1583,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
effectiveNativeSpeed,
configProviderRef,
ttsModel,
+ configProviderType,
+ providerModelPolicy.supportsInstructions ? ttsInstructions : '',
segmentKey,
);
@@ -2216,6 +2247,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
effectiveNativeSpeed,
configProviderRef,
ttsModel,
+ configProviderType,
+ providerModelPolicy.supportsInstructions ? ttsInstructions : '',
playbackSegment?.key,
);
const cachedAlignment = sentenceAlignmentCacheRef.current.get(alignmentKey);
@@ -2346,6 +2379,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
effectiveNativeSpeed,
configProviderRef,
ttsModel,
+ configProviderType,
+ providerModelPolicy.supportsInstructions ? ttsInstructions : '',
nextSegment?.key,
);
if (segmentManifestCacheRef.current.has(cacheKey)) {
@@ -2477,6 +2512,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
effectiveNativeSpeed,
configProviderRef,
ttsModel,
+ configProviderType,
+ providerModelPolicy.supportsInstructions ? ttsInstructions : '',
segment.key,
);
if (seenCandidates.has(requestKey)) continue;
@@ -2615,6 +2652,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
effectiveNativeSpeed,
configProviderRef,
ttsModel,
+ configProviderType,
+ providerModelPolicy.supportsInstructions ? ttsInstructions : '',
plannedSegment?.key,
);
const requestKey = buildSegmentRequestKey(locator, sentenceIndex, sentence, plannedSegment?.key);
@@ -2644,6 +2683,8 @@ export function TTSProvider({ children }: { children: ReactNode }): ReactElement
effectiveNativeSpeed,
configProviderRef,
ttsModel,
+ configProviderType,
+ providerModelPolicy.supportsInstructions ? ttsInstructions : '',
segment.key,
);
const requestKey = buildSegmentRequestKey(segmentLocator, index, sentence, segment.key);
diff --git a/src/lib/client/audiobooks/adapters/pdf.ts b/src/lib/client/audiobooks/adapters/pdf.ts
index 9aff5ac..a462c32 100644
--- a/src/lib/client/audiobooks/adapters/pdf.ts
+++ b/src/lib/client/audiobooks/adapters/pdf.ts
@@ -41,12 +41,6 @@ function prepareParsedChapters({
.filter((block) => !skip.has(block.kind));
if (!allBlocks.length) return [];
- const chaptersFromSections = settings.pdf?.chaptersFromSections ?? true;
- if (!chaptersFromSections) {
- const text = chapterTextFromBlocks(allBlocks, smartSentenceSplitting, maxBlockLength);
- return text ? [{ index: 0, title: 'Document', text }] : [];
- }
-
const chapters: PreparedAudiobookChapter[] = [];
let currentTitle = 'Introduction';
let currentBlocks: ParsedPdfBlock[] = [];
diff --git a/src/lib/shared/document-settings.ts b/src/lib/shared/document-settings.ts
index 323a039..26b45e0 100644
--- a/src/lib/shared/document-settings.ts
+++ b/src/lib/shared/document-settings.ts
@@ -24,7 +24,6 @@ export function mergeDocumentSettings(
schemaVersion: 1,
pdf: {
skipBlockKinds: [...(defaults.pdf?.skipBlockKinds ?? [])],
- chaptersFromSections: defaults.pdf?.chaptersFromSections ?? true,
},
};
@@ -38,10 +37,6 @@ export function mergeDocumentSettings(
schemaVersion: 1,
pdf: {
skipBlockKinds: normalizeSkipKinds(pdfRec.skipBlockKinds),
- chaptersFromSections:
- typeof pdfRec.chaptersFromSections === 'boolean'
- ? pdfRec.chaptersFromSections
- : (base.pdf?.chaptersFromSections ?? true),
},
};
}
diff --git a/src/types/document-settings.ts b/src/types/document-settings.ts
index 1bfd1ba..6908f33 100644
--- a/src/types/document-settings.ts
+++ b/src/types/document-settings.ts
@@ -4,7 +4,6 @@ export interface DocumentSettings {
schemaVersion: 1;
pdf?: {
skipBlockKinds: ParsedPdfBlockKind[];
- chaptersFromSections: boolean;
};
}
@@ -12,6 +11,5 @@ export const DEFAULT_DOCUMENT_SETTINGS: DocumentSettings = {
schemaVersion: 1,
pdf: {
skipBlockKinds: ['header', 'footer', 'footnote', 'vision_footnote'],
- chaptersFromSections: true,
},
};
diff --git a/tests/unit/pdf-audiobook-adapter.spec.ts b/tests/unit/pdf-audiobook-adapter.spec.ts
index 20c7114..4acdeaa 100644
--- a/tests/unit/pdf-audiobook-adapter.spec.ts
+++ b/tests/unit/pdf-audiobook-adapter.spec.ts
@@ -55,7 +55,6 @@ test.describe('pdf audiobook adapter', () => {
schemaVersion: 1,
pdf: {
skipBlockKinds: ['header'],
- chaptersFromSections: true,
},
};
@@ -120,7 +119,6 @@ test.describe('pdf audiobook adapter', () => {
schemaVersion: 1,
pdf: {
skipBlockKinds: [],
- chaptersFromSections: true,
},
};