Setting to automatically skip blank pages, on by default
This commit is contained in:
parent
02bf20e137
commit
96e499cc58
7 changed files with 96 additions and 18 deletions
|
|
@ -44,7 +44,8 @@ export const metadata: Metadata = {
|
|||
},
|
||||
};
|
||||
|
||||
const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production';
|
||||
//const isDev = process.env.NEXT_PUBLIC_NODE_ENV !== 'production' || process.env.NODE_ENV == null;
|
||||
const isDev = false;
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
|
||||
import { GithubIcon } from './icons/Icons'
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="m-8 text-sm text-muted">
|
||||
<div className="flex flex-col items-center space-y-2">
|
||||
<div className="flex flex-wrap sm:flex-nowrap items-center justify-center text-center sm:space-x-3">
|
||||
<a
|
||||
href="https://github.com/richardr1126/OpenReader-WebUI"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:text-foreground transition-colors"
|
||||
>
|
||||
<GithubIcon className="w-5 h-5" />
|
||||
</a>
|
||||
<span className='w-full sm:w-fit'>•</span>
|
||||
<Popover className="flex">
|
||||
<PopoverButton className="hover:text-foreground transition-colors">
|
||||
<PopoverButton className="hover:text-foreground transition-colors flex items-center gap-1">
|
||||
Privacy info
|
||||
</PopoverButton>
|
||||
<PopoverPanel anchor="top" className="bg-base p-4 rounded-lg shadow-lg w-64">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const viewTypes = [
|
|||
];
|
||||
|
||||
export function PDFViewSettings({ isOpen, setIsOpen }: PDFViewSettingsProps) {
|
||||
const { viewType, updateConfigKey } = useConfig();
|
||||
const { viewType, skipBlank, updateConfigKey } = useConfig();
|
||||
const selectedView = viewTypes.find(v => v.id === viewType) || viewTypes[0];
|
||||
|
||||
return (
|
||||
|
|
@ -109,6 +109,20 @@ export function PDFViewSettings({ isOpen, setIsOpen }: PDFViewSettingsProps) {
|
|||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={skipBlank}
|
||||
onChange={(e) => updateConfigKey('skipBlank', e.target.checked)}
|
||||
className="form-checkbox h-4 w-4 text-accent rounded border-muted"
|
||||
/>
|
||||
<span className="text-sm font-medium text-foreground">Skip blank pages</span>
|
||||
</label>
|
||||
<p className="text-sm text-muted pl-6">
|
||||
Automatically skip pages with no text content
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -182,3 +182,35 @@ export function UploadIcon(props: React.SVGProps<SVGSVGElement>) {
|
|||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function InfoIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className={props.className}
|
||||
width={props.width || "1.5em"}
|
||||
height={props.height || "1.5em"}
|
||||
{...props}
|
||||
>
|
||||
<path fillRule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function GithubIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
className={props.className}
|
||||
width={props.width || "1.5em"}
|
||||
height={props.height || "1.5em"}
|
||||
{...props}
|
||||
>
|
||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface ConfigContextType {
|
|||
viewType: ViewType;
|
||||
voiceSpeed: number;
|
||||
voice: string;
|
||||
skipBlank: boolean;
|
||||
updateConfig: (newConfig: Partial<{ apiKey: string; baseUrl: string; viewType: ViewType }>) => Promise<void>;
|
||||
updateConfigKey: <K extends keyof ConfigValues>(key: K, value: ConfigValues[K]) => Promise<void>;
|
||||
isLoading: boolean;
|
||||
|
|
@ -23,6 +24,7 @@ type ConfigValues = {
|
|||
viewType: ViewType;
|
||||
voiceSpeed: number;
|
||||
voice: string;
|
||||
skipBlank: boolean;
|
||||
};
|
||||
|
||||
const ConfigContext = createContext<ConfigContextType | undefined>(undefined);
|
||||
|
|
@ -34,7 +36,7 @@ export function ConfigProvider({ children }: { children: React.ReactNode }) {
|
|||
const [viewType, setViewType] = useState<ViewType>('single');
|
||||
const [voiceSpeed, setVoiceSpeed] = useState<number>(1);
|
||||
const [voice, setVoice] = useState<string>('af_sarah');
|
||||
|
||||
const [skipBlank, setSkipBlank] = useState<boolean>(true);
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isDBReady, setIsDBReady] = useState(false);
|
||||
|
|
@ -52,12 +54,14 @@ export function ConfigProvider({ children }: { children: React.ReactNode }) {
|
|||
const cachedViewType = await getItem('viewType');
|
||||
const cachedVoiceSpeed = await getItem('voiceSpeed');
|
||||
const cachedVoice = await getItem('voice');
|
||||
const cachedSkipBlank = await getItem('skipBlank');
|
||||
|
||||
if (cachedApiKey) console.log('Cached API key found:', cachedApiKey);
|
||||
if (cachedBaseUrl) console.log('Cached base URL found:', cachedBaseUrl);
|
||||
if (cachedViewType) console.log('Cached view type found:', cachedViewType);
|
||||
if (cachedVoiceSpeed) console.log('Cached voice speed found:', cachedVoiceSpeed);
|
||||
if (cachedVoice) console.log('Cached voice found:', cachedVoice);
|
||||
if (cachedSkipBlank) console.log('Cached skip blank found:', cachedSkipBlank);
|
||||
|
||||
// If not in cache, use env variables
|
||||
const defaultApiKey = process.env.NEXT_PUBLIC_OPENAI_API_KEY || '';
|
||||
|
|
@ -69,6 +73,7 @@ export function ConfigProvider({ children }: { children: React.ReactNode }) {
|
|||
setViewType((cachedViewType || 'single') as ViewType);
|
||||
setVoiceSpeed(parseFloat(cachedVoiceSpeed || '1'));
|
||||
setVoice(cachedVoice || 'af_sarah');
|
||||
setSkipBlank(cachedSkipBlank === 'false' ? false : true);
|
||||
|
||||
// If not in cache, save to cache
|
||||
if (!cachedApiKey) {
|
||||
|
|
@ -80,6 +85,9 @@ export function ConfigProvider({ children }: { children: React.ReactNode }) {
|
|||
if (!cachedViewType) {
|
||||
await setItem('viewType', 'single');
|
||||
}
|
||||
if (cachedSkipBlank === null) {
|
||||
await setItem('skipBlank', 'true');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing:', error);
|
||||
|
|
@ -126,6 +134,9 @@ export function ConfigProvider({ children }: { children: React.ReactNode }) {
|
|||
case 'voice':
|
||||
setVoice(value as string);
|
||||
break;
|
||||
case 'skipBlank':
|
||||
setSkipBlank(value as boolean);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error updating config key ${key}:`, error);
|
||||
|
|
@ -140,6 +151,7 @@ export function ConfigProvider({ children }: { children: React.ReactNode }) {
|
|||
viewType,
|
||||
voiceSpeed,
|
||||
voice,
|
||||
skipBlank,
|
||||
updateConfig,
|
||||
updateConfigKey,
|
||||
isLoading,
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export function PDFProvider({ children }: { children: ReactNode }) {
|
|||
const text = await extractTextFromPDF(currDocURL, currDocPage);
|
||||
setCurrDocText(text);
|
||||
setTTSText(text);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading PDF text:', error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
isLoading: configIsLoading,
|
||||
voiceSpeed,
|
||||
voice: configVoice,
|
||||
updateConfigKey
|
||||
updateConfigKey,
|
||||
skipBlank,
|
||||
} = useConfig();
|
||||
|
||||
// OpenAI client reference
|
||||
|
|
@ -112,6 +113,17 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
const [currDocPages, setCurrDocPages] = useState<number>();
|
||||
const [nextPageLoading, setNextPageLoading] = useState(false);
|
||||
|
||||
/**
|
||||
* Changes the current page by a specified amount
|
||||
*
|
||||
* @param {number} [num=1] - The number of pages to increment by
|
||||
* @returns {void}
|
||||
*/
|
||||
const incrementPage = useCallback((num = 1) => {
|
||||
setNextPageLoading(true);
|
||||
setCurrDocPage(currDocPage + num);
|
||||
}, [currDocPage]);
|
||||
|
||||
/**
|
||||
* Sets the current text and splits it into sentences
|
||||
*
|
||||
|
|
@ -121,10 +133,17 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
const setText = useCallback((text: string) => {
|
||||
console.log('Setting page text:', text);
|
||||
const newSentences = splitIntoSentences(text);
|
||||
setSentences(newSentences);
|
||||
|
||||
// If skipBlank is enabled and there's no text and we are playing audio, automatically move to next page
|
||||
if (isPlaying && skipBlank && newSentences.length === 0 && currDocPage < currDocPages!) {
|
||||
console.log('Skipping blank page:', currDocPage);
|
||||
incrementPage();
|
||||
return;
|
||||
}
|
||||
|
||||
setSentences(newSentences);
|
||||
setNextPageLoading(false);
|
||||
}, []);
|
||||
}, [isPlaying, skipBlank, currDocPage, currDocPages, incrementPage]);
|
||||
|
||||
/**
|
||||
* Stops the current audio playback and clears the active Howl instance
|
||||
|
|
@ -168,17 +187,6 @@ export function TTSProvider({ children }: { children: React.ReactNode }) {
|
|||
setCurrDocPage(page);
|
||||
}, [abortAudio]);
|
||||
|
||||
/**
|
||||
* Changes the current page by a specified amount
|
||||
*
|
||||
* @param {number} [num=1] - The number of pages to increment by
|
||||
* @returns {void}
|
||||
*/
|
||||
const incrementPage = useCallback((num = 1) => {
|
||||
setNextPageLoading(true);
|
||||
setCurrDocPage(currDocPage + num);
|
||||
}, [currDocPage]);
|
||||
|
||||
/**
|
||||
* Moves to the next or previous sentence
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in a new issue