Files
anti-plagiarism/services/frontend/src/pages/Check.tsx
jze9 7758315632 feat: initial microservices project structure
Services:
- api: FastAPI gateway with JWT auth, async endpoints, WebSocket
- worker-gpu: CUDA sentence-transformers, FAISS IVFFlat, Ollama LLM
- worker-indexer: Winnowing+MinHash plagiarism detection, PDF/DOCX extraction
- worker-notifier: SMTP email notifications
- worker-gost: GOST 7.1-2003 and GOST R 7.0.5-2008 formatting

Infrastructure:
- docker-compose.yml (production) + docker-compose.dev.yml (hot reload)
- Nginx reverse proxy + WebSocket support
- PostgreSQL 16 with Alembic migrations
- Elasticsearch 8 with Russian/English analyzers
- MinIO, RabbitMQ, Redis, Ollama

Frontend:
- React 18 + Vite + TypeScript + TailwindCSS + Zustand + React Query v5
- 9 pages: Home, Search, Cabinet, Task, Check, Bibliography, Pricing, Login, Register

Scripts:
- Parser stubs: OpenAlex, КиберЛенинка, arXiv (Phase 0 - to be filled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:42:39 +05:00

138 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import { Shield, LayoutDashboard } from 'lucide-react';
import toast from 'react-hot-toast';
import { DropZone } from '../components/DropZone';
import { documentsApi } from '../api/client';
import { useAuthStore } from '../store/auth';
import type { Task } from '../types';
export function Check() {
const { isAuthenticated } = useAuthStore();
const navigate = useNavigate();
const [file, setFile] = useState<File | null>(null);
const [submittedTask, setSubmittedTask] = useState<Task | null>(null);
const upload = useMutation({
mutationFn: () => documentsApi.uploadForCheck(file!),
onSuccess: (response) => {
const task: Task = response.data;
setSubmittedTask(task);
toast.success('Файл загружен, проверка начата!');
},
onError: (error: any) => {
const msg = error.response?.data?.detail || 'Ошибка загрузки';
if (error.response?.status === 401) {
toast.error('Войдите для проверки плагиата');
navigate('/login');
} else {
toast.error(msg);
}
},
});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!file) return;
if (!isAuthenticated) {
toast.error('Необходима авторизация');
navigate('/login');
return;
}
upload.mutate();
};
if (submittedTask) {
return (
<div className="max-w-xl mx-auto pt-8">
<div className="bg-white rounded-2xl border border-gray-100 p-8 text-center">
<div className="w-16 h-16 bg-green-50 rounded-2xl flex items-center justify-center mx-auto mb-4">
<Shield className="w-8 h-8 text-green-500" />
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2">Проверка запущена</h2>
<p className="text-gray-500 mb-2">
Файл <strong>{(submittedTask.input_data as any).filename}</strong> передан на проверку.
</p>
<p className="text-sm text-gray-400 mb-6">
Вы получите email когда проверка завершится. Обычно это занимает 1-3 минуты.
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<Link
to={`/tasks/${submittedTask.id}`}
className="flex items-center justify-center gap-2 px-5 py-2.5 bg-brand-600 text-white rounded-lg text-sm font-medium hover:bg-brand-700 transition-colors"
>
Следить за прогрессом
</Link>
<Link
to="/cabinet"
className="flex items-center justify-center gap-2 px-5 py-2.5 border border-gray-200 text-gray-600 rounded-lg text-sm font-medium hover:bg-gray-50 transition-colors"
>
<LayoutDashboard className="w-4 h-4" />
Личный кабинет
</Link>
</div>
</div>
</div>
);
}
return (
<div className="max-w-2xl mx-auto">
<div className="mb-8">
<h1 className="text-2xl font-bold text-gray-900 mb-2">Проверка плагиата</h1>
<p className="text-gray-500">
Загрузите документ для проверки на 4 уровнях: точные совпадения, нечёткий поиск,
семантика и LLM-анализ парафраза.
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<DropZone
onFile={setFile}
file={file}
onClear={() => setFile(null)}
/>
{!isAuthenticated && (
<div className="bg-amber-50 border border-amber-200 rounded-xl p-4 text-sm text-amber-700">
<Link to="/login" className="underline font-medium">Войдите</Link> или{' '}
<Link to="/register" className="underline font-medium">зарегистрируйтесь</Link>{' '}
для проверки плагиата
</div>
)}
<button
type="submit"
disabled={!file || upload.isPending || !isAuthenticated}
className="w-full flex items-center justify-center gap-2 py-3 bg-brand-600 text-white rounded-xl font-medium hover:bg-brand-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
{upload.isPending ? (
<>
<Shield className="w-5 h-5 animate-pulse" />
Загрузка...
</>
) : (
<>
<Shield className="w-5 h-5" />
Проверить на плагиат
</>
)}
</button>
</form>
{/* Описание уровней */}
<div className="mt-8 bg-gray-50 rounded-xl p-5">
<h3 className="text-sm font-semibold text-gray-700 mb-3">4 уровня проверки:</h3>
<div className="space-y-2 text-sm text-gray-500">
<div>1. <strong>Winnowing + MinHash</strong> точные и нечёткие совпадения (~мс)</div>
<div>2. <strong>n-граммы + Jaccard</strong> перестановки слов (~сек)</div>
<div>3. <strong>FAISS GPU cosine</strong> семантическая близость (~мс)</div>
<div>4. <strong>Ollama Llama3</strong> анализ парафраза (~2 сек)</div>
</div>
</div>
</div>
);
}