change requests

This commit is contained in:
EthanHealy01 2025-07-30 17:25:13 +01:00
parent 1f6b0fea9f
commit 59515f4183
7 changed files with 267 additions and 90 deletions

View File

@ -0,0 +1,89 @@
import React from 'react';
import { Stack, Text, Divider, Switch, Group, Checkbox } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { OCRParameters } from './OCRSettings';
export interface AdvancedOCRParameters {
ocrRenderType: string;
advancedOptions: string[];
}
interface AdvancedOCRSettingsProps {
ocrRenderType: string;
advancedOptions: string[];
onParameterChange: (key: keyof OCRParameters, value: any) => void;
disabled?: boolean;
}
const AdvancedOCRSettings: React.FC<AdvancedOCRSettingsProps> = ({
ocrRenderType,
advancedOptions,
onParameterChange,
disabled = false
}) => {
const { t } = useTranslation();
// Define the advanced options available
const advancedOptionsData = [
{ value: 'sidecar', label: t('ocr.settings.advancedOptions.sidecar', 'Create a text file') },
{ value: 'deskew', label: t('ocr.settings.advancedOptions.deskew', 'Deskew pages') },
{ value: 'clean', label: t('ocr.settings.advancedOptions.clean', 'Clean input file') },
{ value: 'cleanFinal', label: t('ocr.settings.advancedOptions.cleanFinal', 'Clean final output') },
];
// Handle individual checkbox changes
const handleCheckboxChange = (optionValue: string, checked: boolean) => {
const newOptions = checked
? [...advancedOptions, optionValue]
: advancedOptions.filter(option => option !== optionValue);
onParameterChange('additionalOptions', newOptions);
};
return (
<Stack gap="md">
<div>
<Text size="sm" fw={500} mb="sm" mt="md">
{t('ocr.settings.output.label', 'Output Render Type ')}
</Text>
<Group justify="space-between" align="center" gap="xs" wrap="nowrap">
<Text size="xs" style={{ flex: '0 1 auto', lineHeight: 1.3, textAlign: 'left' }}>
{t('ocr.settings.output.hocr', 'HOCR (Auto)')}
</Text>
<Switch
checked={ocrRenderType === 'sandwich'}
onChange={(event) => onParameterChange('ocrRenderType', event.currentTarget.checked ? 'sandwich' : 'hocr')}
disabled={disabled}
size="sm"
style={{ flexShrink: 0 }}
/>
<Text size="xs" style={{ flex: '0 1 auto', lineHeight: 1.3, textAlign: 'right' }}>
{t('ocr.settings.output.sandwich', 'Searchable PDF')}
</Text>
</Group>
</div>
<Divider />
<div>
<Text size="sm" fw={500} mb="md">
{t('ocr.settings.advancedOptions.label', 'Processing Options')}
</Text>
<Stack gap="sm">
{advancedOptionsData.map((option) => (
<Checkbox
key={option.value}
checked={advancedOptions.includes(option.value)}
onChange={(event) => handleCheckboxChange(option.value, event.currentTarget.checked)}
label={option.label}
disabled={disabled}
size="sm"
/>
))}
</Stack>
</div>
</Stack>
);
};
export default AdvancedOCRSettings;

View File

@ -64,6 +64,16 @@
transition: background-color 0.2s ease;
}
.languagePickerOptionWithCheckbox {
display: flex;
align-items: center;
justify-content: space-between;
}
.languagePickerCheckbox {
margin-left: auto;
}
.languagePickerOption:hover {
background-color: var(--mantine-color-gray-0); /* Light gray on hover for light mode */
}
@ -73,25 +83,7 @@
background-color: var(--mantine-color-dark-5);
}
.languagePickerOption.selected {
background-color: var(--mantine-color-blue-1); /* Light blue for light mode */
color: var(--mantine-color-blue-9); /* Dark blue text for light mode */
}
/* Dark mode selected option */
[data-mantine-color-scheme="dark"] .languagePickerOption.selected {
background-color: var(--mantine-color-blue-8); /* Darker blue for better contrast */
color: var(--mantine-color-white); /* White text for dark mode selected items */
}
.languagePickerOption.selected:hover {
background-color: var(--mantine-color-blue-2); /* Slightly darker on hover for light mode */
}
/* Dark mode selected option hover */
[data-mantine-color-scheme="dark"] .languagePickerOption.selected:hover {
background-color: var(--mantine-color-blue-7); /* Slightly lighter on hover */
}
/* Additional helper classes for the component */
.languagePickerTarget {

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react';
import { Stack, Text, Loader, Popover, Box } from '@mantine/core';
import { Stack, Text, Loader, Popover, Box, Checkbox } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { tempOcrLanguages } from '../../../utils/tempOcrLanguages';
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
@ -11,8 +11,8 @@ export interface LanguageOption {
}
export interface LanguagePickerProps {
value: string;
onChange: (value: string) => void;
value: string[];
onChange: (value: string[]) => void;
placeholder?: string;
disabled?: boolean;
label?: string;
@ -22,7 +22,7 @@ export interface LanguagePickerProps {
const LanguagePicker: React.FC<LanguagePickerProps> = ({
value,
onChange,
placeholder = 'Select language',
placeholder = 'Select languages',
disabled = false,
label,
languagesEndpoint = '/api/v1/ui-data/ocr-pdf'
@ -85,7 +85,23 @@ const LanguagePicker: React.FC<LanguagePickerProps> = ({
);
}
const selectedLanguage = availableLanguages.find(lang => lang.value === value);
const handleLanguageToggle = (languageValue: string) => {
const newSelection = value.includes(languageValue)
? value.filter(v => v !== languageValue)
: [...value, languageValue];
onChange(newSelection);
};
const getDisplayText = () => {
if (value.length === 0) {
return placeholder;
} else if (value.length === 1) {
const selectedLanguage = availableLanguages.find(lang => lang.value === value[0]);
return selectedLanguage?.label || value[0];
} else {
return `${value.length} languages selected`;
}
};
return (
<Box>
@ -105,7 +121,7 @@ const LanguagePicker: React.FC<LanguagePickerProps> = ({
>
<div className={styles.languagePickerContent}>
<Text size="sm" className={styles.languagePickerText}>
{selectedLanguage?.label || placeholder}
{getDisplayText()}
</Text>
<UnfoldMoreIcon className={styles.languagePickerIcon} />
</div>
@ -117,10 +133,16 @@ const LanguagePicker: React.FC<LanguagePickerProps> = ({
{availableLanguages.map((lang) => (
<Box
key={lang.value}
className={`${styles.languagePickerOption} ${value === lang.value ? styles.selected : ''}`}
onClick={() => onChange(lang.value)}
className={`${styles.languagePickerOption} ${styles.languagePickerOptionWithCheckbox}`}
onClick={() => handleLanguageToggle(lang.value)}
>
<Text size="sm">{lang.label}</Text>
<Checkbox
checked={value.includes(lang.value)}
onChange={() => {}} // Handled by parent onClick
className={styles.languagePickerCheckbox}
size="sm"
/>
</Box>
))}
</Box>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Stack, Select, MultiSelect, Text, Divider } from '@mantine/core';
import { Stack, Select, Text, Divider } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import LanguagePicker from './LanguagePicker';
@ -23,17 +23,8 @@ const OCRSettings: React.FC<OCRSettingsProps> = ({
}) => {
const { t } = useTranslation();
// Define the additional options available
const additionalOptionsData = [
{ value: 'sidecar', label: t('ocr.settings.additionalOptions.sidecar', 'Create sidecar text file') },
{ value: 'deskew', label: t('ocr.settings.additionalOptions.deskew', 'Deskew pages') },
{ value: 'clean', label: t('ocr.settings.additionalOptions.clean', 'Clean input file') },
{ value: 'cleanFinal', label: t('ocr.settings.additionalOptions.cleanFinal', 'Clean final output') },
];
return (
<Stack gap="md">
<Text size="sm" fw={500}>{t('ocr.settings.title', 'OCR Configuration')}</Text>
<Select
label={t('ocr.settings.ocrMode.label', 'OCR Mode')}
@ -50,38 +41,12 @@ const OCRSettings: React.FC<OCRSettingsProps> = ({
<Divider />
<LanguagePicker
value={parameters.languages[0] || ''}
onChange={(value) => onParameterChange('languages', [value])}
placeholder={t('ocr.settings.languages.placeholder', 'Select primary language for OCR')}
value={parameters.languages || []}
onChange={(value) => onParameterChange('languages', value)}
placeholder={t('ocr.settings.languages.placeholder', 'Select languages')}
disabled={disabled}
label={t('ocr.settings.languages.label', 'Languages')}
/>
<Divider />
<Select
label={t('ocr.settings.output.label', 'Output')}
value={parameters.ocrRenderType}
onChange={(value) => onParameterChange('ocrRenderType', value || 'sandwich')}
data={[
{ value: 'sandwich', label: t('ocr.settings.output.sandwich', 'Searchable PDF (Sandwich)') },
{ value: 'hocr', label: t('ocr.settings.output.hocr', 'HOCR XML') }
]}
disabled={disabled}
/>
<Divider />
<MultiSelect
label={t('ocr.settings.additionalOptions.label', 'Additional Options')}
placeholder={t('ocr.settings.additionalOptions.placeholder', 'Select Options')}
value={parameters.additionalOptions}
onChange={(value) => onParameterChange('additionalOptions', value)}
data={additionalOptionsData}
disabled={disabled}
clearable
comboboxProps={{ position: 'top', middlewares: { flip: false, shift: false } }}
/>
</Stack>
);
};

View File

@ -1,5 +1,7 @@
import React, { createContext, useContext, useMemo, useRef } from 'react';
import { Paper, Text, Stack, Box } from '@mantine/core';
import { Paper, Text, Stack, Box, Flex } from '@mantine/core';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
interface ToolStepContextType {
visibleStepCount: number;
@ -48,15 +50,45 @@ const ToolStep = ({
p="md"
withBorder
style={{
cursor: isCollapsed && onCollapsedClick ? 'pointer' : 'default',
opacity: isCollapsed ? 0.8 : 1,
transition: 'opacity 0.2s ease'
}}
onClick={isCollapsed && onCollapsedClick ? onCollapsedClick : undefined}
>
<Text fw={500} size="lg" mb="sm">
{shouldShowNumber ? `${stepNumber}. ` : ''}{title}
</Text>
{/* Chevron icon to collapse/expand the step */}
<Flex
align="center"
justify="space-between"
mb="sm"
style={{
cursor: onCollapsedClick ? 'pointer' : 'default'
}}
onClick={onCollapsedClick}
>
<Flex align="center" gap="sm">
{shouldShowNumber && (
<Text fw={500} size="lg" c="dimmed">
{stepNumber}
</Text>
)}
<Text fw={500} size="lg">
{title}
</Text>
</Flex>
{isCollapsed ? (
<ChevronRightIcon style={{
fontSize: '1.2rem',
color: 'var(--mantine-color-dimmed)',
opacity: onCollapsedClick ? 1 : 0.5
}} />
) : (
<ExpandMoreIcon style={{
fontSize: '1.2rem',
color: 'var(--mantine-color-dimmed)',
opacity: onCollapsedClick ? 1 : 0.5
}} />
)}
</Flex>
{isCollapsed ? (
<Box>

View File

@ -11,7 +11,7 @@ export interface OCRParametersHook {
const defaultParameters: OCRParameters = {
languages: ['eng'],
ocrType: 'skip-text',
ocrRenderType: 'sandwich',
ocrRenderType: 'hocr',
additionalOptions: [],
};

View File

@ -1,5 +1,5 @@
import React, { useEffect, useMemo } from "react";
import { Button, Stack, Text } from "@mantine/core";
import React, { useEffect, useMemo, useState } from "react";
import { Button, Stack, Text, Box } from "@mantine/core";
import { useTranslation } from "react-i18next";
import DownloadIcon from "@mui/icons-material/Download";
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
@ -13,6 +13,7 @@ import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator"
import ResultsPreview from "../components/tools/shared/ResultsPreview";
import OCRSettings from "../components/tools/ocr/OCRSettings";
import AdvancedOCRSettings from "../components/tools/ocr/AdvancedOCRSettings";
import { useOCRParameters } from "../hooks/tools/ocr/useOCRParameters";
import { useOCROperation } from "../hooks/tools/ocr/useOCROperation";
@ -26,14 +27,37 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
const ocrParams = useOCRParameters();
const ocrOperation = useOCROperation();
// Step expansion state management
const [expandedStep, setExpandedStep] = useState<'files' | 'settings' | 'advanced' | null>('files');
const [hasAccessedAdvanced, setHasAccessedAdvanced] = useState(false);
// Endpoint validation
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("ocr-pdf");
// Calculate state variables
const hasFiles = selectedFiles.length > 0;
const hasResults = ocrOperation.files.length > 0 || ocrOperation.downloadUrl !== null;
const hasValidSettings = ocrParams.validateParameters();
useEffect(() => {
ocrOperation.resetResults();
onPreviewFile?.(null);
}, [ocrParams.parameters, selectedFiles]);
// Auto-advance logic - only auto-advance from files to settings when files are first selected
useEffect(() => {
if (selectedFiles.length > 0 && expandedStep === 'files') {
setExpandedStep('settings');
}
}, [selectedFiles.length, expandedStep]);
// Collapse all steps when results appear
useEffect(() => {
if (hasResults) {
setExpandedStep(null);
}
}, [hasResults]);
const handleOCR = async () => {
try {
await ocrOperation.executeOperation(
@ -60,12 +84,34 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
ocrOperation.resetResults();
onPreviewFile?.(null);
setCurrentMode('ocr');
setExpandedStep('settings');
};
const hasFiles = selectedFiles.length > 0;
const hasResults = ocrOperation.files.length > 0 || ocrOperation.downloadUrl !== null;
const filesCollapsed = hasFiles;
const settingsCollapsed = hasResults;
// Step navigation handlers
const handleStepClick = (step: 'files' | 'settings' | 'advanced') => {
// Prevent expanding steps that aren't ready
if (step === 'settings' && !hasFiles) {
return;
}
if (step === 'advanced' && !hasAccessedAdvanced) {
setHasAccessedAdvanced(true);
}
setExpandedStep(step);
};
const handleAdvanceToAdvanced = () => {
setHasAccessedAdvanced(true);
setExpandedStep('advanced');
};
// Step visibility and collapse logic
const filesVisible = true;
const settingsVisible = true;
const resultsVisible = hasResults;
const filesCollapsed = expandedStep !== 'files';
const settingsCollapsed = expandedStep !== 'settings';
const previewResults = useMemo(() =>
ocrOperation.files?.map((file: File, index: number) => ({
@ -81,10 +127,11 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
{/* Files Step */}
<ToolStep
title="Files"
isVisible={true}
isCollapsed={filesCollapsed}
isCompleted={filesCollapsed}
completedMessage={hasFiles ?
isVisible={filesVisible}
isCollapsed={hasFiles ? filesCollapsed : false}
isCompleted={hasFiles}
onCollapsedClick={undefined}
completedMessage={hasFiles && filesCollapsed ?
selectedFiles.length === 1
? `Selected: ${selectedFiles[0].name}`
: `Selected: ${selectedFiles.length} files`
@ -99,11 +146,14 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
{/* Settings Step */}
<ToolStep
title="Settings"
isVisible={hasFiles}
isVisible={settingsVisible}
isCollapsed={settingsCollapsed}
isCompleted={settingsCollapsed}
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
completedMessage={settingsCollapsed ? "OCR processing completed" : undefined}
isCompleted={hasFiles && hasValidSettings}
onCollapsedClick={() => {
if (!hasFiles) return; // Only allow if files are selected
setExpandedStep(expandedStep === 'settings' ? null : 'settings');
}}
completedMessage={hasFiles && hasValidSettings && settingsCollapsed ? "Basic settings configured" : undefined}
>
<Stack gap="sm">
<OCRSettings
@ -112,6 +162,33 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
disabled={endpointLoading}
/>
</Stack>
</ToolStep>
{/* Advanced Step */}
<ToolStep
title="Advanced"
isVisible={true}
isCollapsed={expandedStep !== 'advanced'}
isCompleted={hasFiles && hasResults}
onCollapsedClick={() => {
if (!hasFiles) return; // Only allow if files are selected
setHasAccessedAdvanced(true);
setExpandedStep(expandedStep === 'advanced' ? null : 'advanced');
}}
completedMessage={hasFiles && hasResults && expandedStep !== 'advanced' ? "OCR processing completed" : undefined}
>
<AdvancedOCRSettings
ocrRenderType={ocrParams.parameters.ocrRenderType}
advancedOptions={ocrParams.parameters.additionalOptions}
onParameterChange={ocrParams.updateParameter}
disabled={endpointLoading}
/>
</ToolStep>
{/* Process Button - Available after all configuration */}
{hasValidSettings && !hasResults && (
<Box mt="md">
<OperationButton
onClick={handleOCR}
isLoading={ocrOperation.isLoading}
@ -119,13 +196,13 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
loadingText={t("loading")}
submitText="Process OCR and Review"
/>
</Stack>
</ToolStep>
</Box>
)}
{/* Results Step */}
<ToolStep
title="Results"
isVisible={hasResults}
isVisible={resultsVisible}
>
<Stack gap="sm">
{ocrOperation.status && (