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>
This commit is contained in:
jze9
2026-05-24 19:42:39 +05:00
commit 7758315632
120 changed files with 9500 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
import React from 'react';
import { Check } from 'lucide-react';
import { clsx } from 'clsx';
import { PLAN_LIMITS } from '../types';
const PLAN_ORDER = ['free', 'student', 'premium', 'science'] as const;
const PLAN_FEATURES = {
free: [
'10 поисков в день',
'3 краткие изложения в месяц',
'1 проверка плагиата в месяц',
'1 задача одновременно',
'ГОСТ библиография',
],
student: [
'Безлимитный поиск',
'30 кратких изложений в месяц',
'10 проверок плагиата в месяц',
'2 задачи одновременно',
'ГОСТ библиография',
'Email уведомления',
],
premium: [
'Безлимитный поиск',
'Безлимитные изложения',
'50 проверок плагиата в месяц',
'5 задач одновременно',
'ГОСТ библиография',
'Приоритетная очередь',
],
science: [
'Безлимитный поиск',
'Безлимитные изложения',
'Безлимитные проверки плагиата',
'10 задач одновременно',
'ГОСТ библиография',
'Максимальный приоритет',
'API доступ',
],
};
const POPULAR_PLAN = 'student';
export function Pricing() {
return (
<div className="space-y-8">
<div className="text-center">
<h1 className="text-3xl font-bold text-gray-900 mb-3">Тарифные планы</h1>
<p className="text-gray-500 max-w-xl mx-auto">
Начните бесплатно. Обновите план для расширенных возможностей.
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{PLAN_ORDER.map((planKey) => {
const plan = PLAN_LIMITS[planKey];
const features = PLAN_FEATURES[planKey];
const isPopular = planKey === POPULAR_PLAN;
return (
<div
key={planKey}
className={clsx(
'relative bg-white rounded-2xl border-2 p-6 flex flex-col',
isPopular ? 'border-brand-500 shadow-lg shadow-brand-100' : 'border-gray-100'
)}
>
{isPopular && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<span className="px-3 py-1 bg-brand-600 text-white text-xs font-medium rounded-full">
Популярный
</span>
</div>
)}
<div className="mb-4">
<h3 className="text-base font-semibold text-gray-900">{plan.name}</h3>
<div className="mt-2">
{plan.price === 0 ? (
<span className="text-3xl font-bold text-gray-900">Бесплатно</span>
) : (
<>
<span className="text-3xl font-bold text-gray-900">{plan.price}₽</span>
<span className="text-gray-400 text-sm">/мес</span>
</>
)}
</div>
</div>
<ul className="space-y-2.5 flex-1 mb-6">
{features.map((feature) => (
<li key={feature} className="flex items-start gap-2">
<Check className="w-4 h-4 text-green-500 flex-shrink-0 mt-0.5" />
<span className="text-sm text-gray-600">{feature}</span>
</li>
))}
</ul>
<button
className={clsx(
'w-full py-2.5 rounded-xl text-sm font-medium transition-colors',
isPopular
? 'bg-brand-600 text-white hover:bg-brand-700'
: plan.price === 0
? 'bg-gray-100 text-gray-700 hover:bg-gray-200'
: 'border border-brand-200 text-brand-700 hover:bg-brand-50'
)}
>
{plan.price === 0 ? 'Начать бесплатно' : 'Выбрать план'}
</button>
</div>
);
})}
</div>
<div className="bg-gray-50 rounded-xl p-6 text-center text-sm text-gray-500">
Оплата через российские системы. При вопросах пишите на noreply@jze9.ru
</div>
</div>
);
}