mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-02 18:45:21 +00:00
change requests
This commit is contained in:
parent
1f6b0fea9f
commit
59515f4183
89
frontend/src/components/tools/ocr/AdvancedOCRSettings.tsx
Normal file
89
frontend/src/components/tools/ocr/AdvancedOCRSettings.tsx
Normal 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;
|
@ -64,6 +64,16 @@
|
|||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.languagePickerOptionWithCheckbox {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.languagePickerCheckbox {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.languagePickerOption:hover {
|
.languagePickerOption:hover {
|
||||||
background-color: var(--mantine-color-gray-0); /* Light gray on hover for light mode */
|
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);
|
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 */
|
/* Additional helper classes for the component */
|
||||||
.languagePickerTarget {
|
.languagePickerTarget {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import { tempOcrLanguages } from '../../../utils/tempOcrLanguages';
|
import { tempOcrLanguages } from '../../../utils/tempOcrLanguages';
|
||||||
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore';
|
||||||
@ -11,8 +11,8 @@ export interface LanguageOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface LanguagePickerProps {
|
export interface LanguagePickerProps {
|
||||||
value: string;
|
value: string[];
|
||||||
onChange: (value: string) => void;
|
onChange: (value: string[]) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -22,7 +22,7 @@ export interface LanguagePickerProps {
|
|||||||
const LanguagePicker: React.FC<LanguagePickerProps> = ({
|
const LanguagePicker: React.FC<LanguagePickerProps> = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = 'Select language',
|
placeholder = 'Select languages',
|
||||||
disabled = false,
|
disabled = false,
|
||||||
label,
|
label,
|
||||||
languagesEndpoint = '/api/v1/ui-data/ocr-pdf'
|
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 (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@ -105,7 +121,7 @@ const LanguagePicker: React.FC<LanguagePickerProps> = ({
|
|||||||
>
|
>
|
||||||
<div className={styles.languagePickerContent}>
|
<div className={styles.languagePickerContent}>
|
||||||
<Text size="sm" className={styles.languagePickerText}>
|
<Text size="sm" className={styles.languagePickerText}>
|
||||||
{selectedLanguage?.label || placeholder}
|
{getDisplayText()}
|
||||||
</Text>
|
</Text>
|
||||||
<UnfoldMoreIcon className={styles.languagePickerIcon} />
|
<UnfoldMoreIcon className={styles.languagePickerIcon} />
|
||||||
</div>
|
</div>
|
||||||
@ -117,10 +133,16 @@ const LanguagePicker: React.FC<LanguagePickerProps> = ({
|
|||||||
{availableLanguages.map((lang) => (
|
{availableLanguages.map((lang) => (
|
||||||
<Box
|
<Box
|
||||||
key={lang.value}
|
key={lang.value}
|
||||||
className={`${styles.languagePickerOption} ${value === lang.value ? styles.selected : ''}`}
|
className={`${styles.languagePickerOption} ${styles.languagePickerOptionWithCheckbox}`}
|
||||||
onClick={() => onChange(lang.value)}
|
onClick={() => handleLanguageToggle(lang.value)}
|
||||||
>
|
>
|
||||||
<Text size="sm">{lang.label}</Text>
|
<Text size="sm">{lang.label}</Text>
|
||||||
|
<Checkbox
|
||||||
|
checked={value.includes(lang.value)}
|
||||||
|
onChange={() => {}} // Handled by parent onClick
|
||||||
|
className={styles.languagePickerCheckbox}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
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 { useTranslation } from 'react-i18next';
|
||||||
import LanguagePicker from './LanguagePicker';
|
import LanguagePicker from './LanguagePicker';
|
||||||
|
|
||||||
@ -23,18 +23,9 @@ const OCRSettings: React.FC<OCRSettingsProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Text size="sm" fw={500}>{t('ocr.settings.title', 'OCR Configuration')}</Text>
|
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
label={t('ocr.settings.ocrMode.label', 'OCR Mode')}
|
label={t('ocr.settings.ocrMode.label', 'OCR Mode')}
|
||||||
value={parameters.ocrType}
|
value={parameters.ocrType}
|
||||||
@ -50,38 +41,12 @@ const OCRSettings: React.FC<OCRSettingsProps> = ({
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<LanguagePicker
|
<LanguagePicker
|
||||||
value={parameters.languages[0] || ''}
|
value={parameters.languages || []}
|
||||||
onChange={(value) => onParameterChange('languages', [value])}
|
onChange={(value) => onParameterChange('languages', value)}
|
||||||
placeholder={t('ocr.settings.languages.placeholder', 'Select primary language for OCR')}
|
placeholder={t('ocr.settings.languages.placeholder', 'Select languages')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
label={t('ocr.settings.languages.label', 'Languages')}
|
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>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React, { createContext, useContext, useMemo, useRef } from 'react';
|
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 {
|
interface ToolStepContextType {
|
||||||
visibleStepCount: number;
|
visibleStepCount: number;
|
||||||
@ -48,15 +50,45 @@ const ToolStep = ({
|
|||||||
p="md"
|
p="md"
|
||||||
withBorder
|
withBorder
|
||||||
style={{
|
style={{
|
||||||
cursor: isCollapsed && onCollapsedClick ? 'pointer' : 'default',
|
|
||||||
opacity: isCollapsed ? 0.8 : 1,
|
opacity: isCollapsed ? 0.8 : 1,
|
||||||
transition: 'opacity 0.2s ease'
|
transition: 'opacity 0.2s ease'
|
||||||
}}
|
}}
|
||||||
onClick={isCollapsed && onCollapsedClick ? onCollapsedClick : undefined}
|
|
||||||
>
|
>
|
||||||
<Text fw={500} size="lg" mb="sm">
|
{/* Chevron icon to collapse/expand the step */}
|
||||||
{shouldShowNumber ? `${stepNumber}. ` : ''}{title}
|
<Flex
|
||||||
</Text>
|
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 ? (
|
{isCollapsed ? (
|
||||||
<Box>
|
<Box>
|
||||||
|
@ -11,7 +11,7 @@ export interface OCRParametersHook {
|
|||||||
const defaultParameters: OCRParameters = {
|
const defaultParameters: OCRParameters = {
|
||||||
languages: ['eng'],
|
languages: ['eng'],
|
||||||
ocrType: 'skip-text',
|
ocrType: 'skip-text',
|
||||||
ocrRenderType: 'sandwich',
|
ocrRenderType: 'hocr',
|
||||||
additionalOptions: [],
|
additionalOptions: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo, useState } from "react";
|
||||||
import { Button, Stack, Text } from "@mantine/core";
|
import { Button, Stack, Text, Box } from "@mantine/core";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||||
@ -13,6 +13,7 @@ import FileStatusIndicator from "../components/tools/shared/FileStatusIndicator"
|
|||||||
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
import ResultsPreview from "../components/tools/shared/ResultsPreview";
|
||||||
|
|
||||||
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
import OCRSettings from "../components/tools/ocr/OCRSettings";
|
||||||
|
import AdvancedOCRSettings from "../components/tools/ocr/AdvancedOCRSettings";
|
||||||
|
|
||||||
import { useOCRParameters } from "../hooks/tools/ocr/useOCRParameters";
|
import { useOCRParameters } from "../hooks/tools/ocr/useOCRParameters";
|
||||||
import { useOCROperation } from "../hooks/tools/ocr/useOCROperation";
|
import { useOCROperation } from "../hooks/tools/ocr/useOCROperation";
|
||||||
@ -26,14 +27,37 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
const ocrParams = useOCRParameters();
|
const ocrParams = useOCRParameters();
|
||||||
const ocrOperation = useOCROperation();
|
const ocrOperation = useOCROperation();
|
||||||
|
|
||||||
|
// Step expansion state management
|
||||||
|
const [expandedStep, setExpandedStep] = useState<'files' | 'settings' | 'advanced' | null>('files');
|
||||||
|
const [hasAccessedAdvanced, setHasAccessedAdvanced] = useState(false);
|
||||||
|
|
||||||
// Endpoint validation
|
// Endpoint validation
|
||||||
const { enabled: endpointEnabled, loading: endpointLoading } = useEndpointEnabled("ocr-pdf");
|
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(() => {
|
useEffect(() => {
|
||||||
ocrOperation.resetResults();
|
ocrOperation.resetResults();
|
||||||
onPreviewFile?.(null);
|
onPreviewFile?.(null);
|
||||||
}, [ocrParams.parameters, selectedFiles]);
|
}, [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 () => {
|
const handleOCR = async () => {
|
||||||
try {
|
try {
|
||||||
await ocrOperation.executeOperation(
|
await ocrOperation.executeOperation(
|
||||||
@ -60,12 +84,34 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
ocrOperation.resetResults();
|
ocrOperation.resetResults();
|
||||||
onPreviewFile?.(null);
|
onPreviewFile?.(null);
|
||||||
setCurrentMode('ocr');
|
setCurrentMode('ocr');
|
||||||
|
setExpandedStep('settings');
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasFiles = selectedFiles.length > 0;
|
// Step navigation handlers
|
||||||
const hasResults = ocrOperation.files.length > 0 || ocrOperation.downloadUrl !== null;
|
const handleStepClick = (step: 'files' | 'settings' | 'advanced') => {
|
||||||
const filesCollapsed = hasFiles;
|
// Prevent expanding steps that aren't ready
|
||||||
const settingsCollapsed = hasResults;
|
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(() =>
|
const previewResults = useMemo(() =>
|
||||||
ocrOperation.files?.map((file: File, index: number) => ({
|
ocrOperation.files?.map((file: File, index: number) => ({
|
||||||
@ -81,10 +127,11 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
{/* Files Step */}
|
{/* Files Step */}
|
||||||
<ToolStep
|
<ToolStep
|
||||||
title="Files"
|
title="Files"
|
||||||
isVisible={true}
|
isVisible={filesVisible}
|
||||||
isCollapsed={filesCollapsed}
|
isCollapsed={hasFiles ? filesCollapsed : false}
|
||||||
isCompleted={filesCollapsed}
|
isCompleted={hasFiles}
|
||||||
completedMessage={hasFiles ?
|
onCollapsedClick={undefined}
|
||||||
|
completedMessage={hasFiles && filesCollapsed ?
|
||||||
selectedFiles.length === 1
|
selectedFiles.length === 1
|
||||||
? `Selected: ${selectedFiles[0].name}`
|
? `Selected: ${selectedFiles[0].name}`
|
||||||
: `Selected: ${selectedFiles.length} files`
|
: `Selected: ${selectedFiles.length} files`
|
||||||
@ -99,11 +146,14 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
{/* Settings Step */}
|
{/* Settings Step */}
|
||||||
<ToolStep
|
<ToolStep
|
||||||
title="Settings"
|
title="Settings"
|
||||||
isVisible={hasFiles}
|
isVisible={settingsVisible}
|
||||||
isCollapsed={settingsCollapsed}
|
isCollapsed={settingsCollapsed}
|
||||||
isCompleted={settingsCollapsed}
|
isCompleted={hasFiles && hasValidSettings}
|
||||||
onCollapsedClick={settingsCollapsed ? handleSettingsReset : undefined}
|
onCollapsedClick={() => {
|
||||||
completedMessage={settingsCollapsed ? "OCR processing completed" : undefined}
|
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">
|
<Stack gap="sm">
|
||||||
<OCRSettings
|
<OCRSettings
|
||||||
@ -112,6 +162,33 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
disabled={endpointLoading}
|
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
|
<OperationButton
|
||||||
onClick={handleOCR}
|
onClick={handleOCR}
|
||||||
isLoading={ocrOperation.isLoading}
|
isLoading={ocrOperation.isLoading}
|
||||||
@ -119,13 +196,13 @@ const OCR = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
|||||||
loadingText={t("loading")}
|
loadingText={t("loading")}
|
||||||
submitText="Process OCR and Review"
|
submitText="Process OCR and Review"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Box>
|
||||||
</ToolStep>
|
)}
|
||||||
|
|
||||||
{/* Results Step */}
|
{/* Results Step */}
|
||||||
<ToolStep
|
<ToolStep
|
||||||
title="Results"
|
title="Results"
|
||||||
isVisible={hasResults}
|
isVisible={resultsVisible}
|
||||||
>
|
>
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
{ocrOperation.status && (
|
{ocrOperation.status && (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user