Metadata ve çeviri ile ilgili düzeltmeler. UI değişiklikleri.

This commit is contained in:
2025-11-17 11:03:39 +03:00
parent daf39e35c0
commit b6c9fb795b
14 changed files with 859 additions and 226 deletions

View File

@@ -11,7 +11,6 @@ import { useNavigate } from 'react-router-dom';
import Tesseract from 'tesseract.js';
import { useAppStore } from '../store/useAppStore';
import { correctTurkishCharacters } from '../utils/ocrUtils';
import { translateChunkToTurkish } from '../utils/translationUtils';
const OcrStep = () => {
const navigate = useNavigate();
@@ -20,21 +19,12 @@ const OcrStep = () => {
const ocrText = useAppStore((state) => state.ocrText);
const setOcrText = useAppStore((state) => state.setOcrText);
const setError = useAppStore((state) => state.setError);
const translatedText = useAppStore((state) => state.translatedText);
const translationStatus = useAppStore((state) => state.translationStatus);
const translationError = useAppStore((state) => state.translationError);
const translationProgress = useAppStore((state) => state.translationProgress);
const setTranslatedText = useAppStore((state) => state.setTranslatedText);
const setTranslationStatus = useAppStore((state) => state.setTranslationStatus);
const setTranslationError = useAppStore((state) => state.setTranslationError);
const setTranslationProgress = useAppStore((state) => state.setTranslationProgress);
const clearTranslation = useAppStore((state) => state.clearTranslation);
const bookMetadata = useAppStore((state) => state.bookMetadata);
const [status, setStatus] = useState('idle');
const [translationTrigger, setTranslationTrigger] = useState(0);
const [currentIndex, setCurrentIndex] = useState(0);
const [previewText, setPreviewText] = useState('');
const total = croppedImages.length;
const hasResults = useMemo(() => Boolean(ocrText?.length), [ocrText]);
const abortRef = useRef(false);
const assetBase = useMemo(() => {
@@ -47,7 +37,7 @@ const OcrStep = () => {
const workerRef = useRef(null);
const [workerReady, setWorkerReady] = useState(false);
const previewRef = useRef(null);
const translationPreviewRef = useRef(null);
// removed auto navigation to translation
const orderedImages = useMemo(
() => [...croppedImages].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
@@ -140,12 +130,6 @@ const OcrStep = () => {
previewRef.current.scrollTop = previewRef.current.scrollHeight;
}
}, [previewText]);
useEffect(() => {
if (translationPreviewRef.current) {
translationPreviewRef.current.scrollTop = translationPreviewRef.current.scrollHeight;
}
}, [translatedText]);
useEffect(() => {
if (!total || status === 'done' || !workerReady) return;
abortRef.current = false;
@@ -189,58 +173,6 @@ const OcrStep = () => {
};
}, [orderedImages, setError, setOcrText, status, total, workerReady]);
useEffect(() => {
if (status !== 'done') return;
if (!ocrText?.trim()) return;
if (translationStatus === 'running' || translationStatus === 'done') return;
let cancelled = false;
const sections = segmentOcrText(ocrText);
if (!sections.length) return;
const runTranslation = async () => {
setTranslationStatus('running');
setTranslationError(null);
setTranslationProgress(0);
setTranslatedText('');
try {
const translatedChunks = [];
for (let index = 0; index < sections.length; index += 1) {
if (cancelled) return;
const chunk = sections[index];
// eslint-disable-next-line no-await-in-loop
const translated = await translateChunkToTurkish(chunk);
if (cancelled) return;
translatedChunks[index] = translated;
const combined = translatedChunks.filter(Boolean).join('\n\n');
setTranslatedText(combined);
setTranslationProgress(Math.round(((index + 1) / sections.length) * 100));
}
if (!cancelled) {
setTranslationStatus('done');
}
} catch (error) {
if (!cancelled) {
setTranslationStatus('error');
setTranslationError(error.message || 'Çeviri tamamlanamadı.');
}
}
};
runTranslation();
return () => {
cancelled = true;
};
}, [
ocrText,
setTranslatedText,
setTranslationError,
setTranslationProgress,
setTranslationStatus,
status,
translationTrigger,
]);
if (!orderedImages.length) {
return (
@@ -264,6 +196,12 @@ const OcrStep = () => {
return (
<Stack spacing={4}>
{bookMetadata && (
<Typography variant="body2" color="success.main">
Seçilen kitap: <strong>{bookMetadata.title}</strong>
{bookMetadata.authors?.length ? `${bookMetadata.authors.join(', ')}` : ''}
</Typography>
)}
<Box textAlign="center">
<Typography variant="h5">OCR işlemi</Typography>
<Typography color="text.secondary">
@@ -280,99 +218,34 @@ const OcrStep = () => {
{progressText}
</Typography>
</Box>
<Stack spacing={1}>
<Box sx={{ p: 2, borderRadius: 2, bgcolor: 'background.default' }}>
<Typography variant="subtitle1">Ön izleme</Typography>
<Box
ref={previewRef}
sx={{
mt: 1,
maxHeight: '8.5em',
overflowY: 'auto',
whiteSpace: 'pre-wrap',
lineHeight: 1.5,
fontSize: '0.95rem',
color: 'text.secondary',
pr: 1,
}}
>
{previewText || 'Metin bekleniyor'}
</Box>
<Box sx={{ p: 2, borderRadius: 2, bgcolor: 'background.default' }}>
<Typography variant="subtitle1">Ön izleme</Typography>
<Box
ref={previewRef}
sx={{
mt: 1,
maxHeight: '8.5em',
overflowY: 'auto',
whiteSpace: 'pre-wrap',
lineHeight: 1.5,
fontSize: '0.95rem',
color: 'text.secondary',
pr: 1,
}}
>
{previewText || 'Metin bekleniyor'}
</Box>
<Box sx={{ p: 2, borderRadius: 2, bgcolor: 'background.default' }}>
<Stack direction={{ xs: 'column', sm: 'row' }} alignItems="flex-start" justifyContent="space-between">
<Box>
<Typography variant="subtitle1">Türkçe çeviriler</Typography>
<Typography variant="body2" color="text.secondary">
OCR metni parçalara ayrılıp GLM 4.6 ile çevriliyor.
</Typography>
</Box>
{translationStatus === 'error' && (
<Button
size="small"
variant="outlined"
onClick={() => {
clearTranslation();
setTranslationTrigger((prev) => prev + 1);
}}
>
Tekrar dene
</Button>
)}
</Stack>
{translationStatus === 'running' && (
<Box mt={2}>
<LinearProgress
variant="determinate"
value={translationProgress}
sx={{ height: 8, borderRadius: 3 }}
/>
<Typography mt={1} color="text.secondary" variant="caption">
%{translationProgress} tamamlandı
</Typography>
</Box>
)}
{translationStatus === 'done' && translatedText && (
<Alert severity="success" sx={{ mt: 2 }}>
Çeviri tamamlandı. EPUB üretiminde Türkçe içerik kullanılacak.
</Alert>
)}
{translationStatus === 'error' && translationError && (
<Alert severity="error" sx={{ mt: 2 }}>
{translationError}
</Alert>
)}
<Box
ref={translationPreviewRef}
sx={{
mt: 2,
maxHeight: '8.5em',
overflowY: 'auto',
whiteSpace: 'pre-wrap',
lineHeight: 1.5,
fontSize: '0.95rem',
color: 'text.secondary',
pr: 1,
border: '1px solid',
borderColor: 'divider',
borderRadius: 1.5,
p: 1.5,
}}
>
{translatedText || 'Çeviri bekleniyor...'}
</Box>
</Box>
</Stack>
</Box>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} justifyContent="space-between">
<Button variant="contained" onClick={() => navigate('/bulk-crop')}>
Geri dön
</Button>
<Button
variant="contained"
onClick={() => navigate('/epub')}
disabled={!hasResults || translationStatus === 'running'}
onClick={() => navigate('/translate')}
disabled={status !== 'done'}
>
EPUB oluştur
Çeviri adımına geç
</Button>
</Stack>
</Stack>
@@ -380,37 +253,3 @@ const OcrStep = () => {
};
export default OcrStep;
const MAX_CHUNK_LENGTH = 800;
const segmentOcrText = (text) => {
if (!text) return [];
const normalized = text.replace(/\r\n/g, '\n');
const paragraphs = normalized.split(/\n{2,}/).map((part) => part.trim()).filter(Boolean);
const chunks = [];
paragraphs.forEach((paragraph) => {
if (paragraph.length <= MAX_CHUNK_LENGTH) {
chunks.push(paragraph);
return;
}
let remaining = paragraph;
while (remaining.length > MAX_CHUNK_LENGTH) {
let sliceIndex = remaining.lastIndexOf(' ', MAX_CHUNK_LENGTH);
if (sliceIndex === -1 || sliceIndex < MAX_CHUNK_LENGTH * 0.6) {
sliceIndex = MAX_CHUNK_LENGTH;
}
const chunk = remaining.slice(0, sliceIndex).trim();
if (chunk) {
chunks.push(chunk);
}
remaining = remaining.slice(sliceIndex).trim();
}
if (remaining.length) {
chunks.push(remaining);
}
});
return chunks;
};