- Бэкенд: FastAPI + SQLAlchemy async + PostgreSQL, JWT-аутентификация, Qdrant multi-tenant векторное хранилище, Celery воркер - Фронтенд: React + Vite + TypeScript SPA (логин, регистрация, дашборд, поиск по коллекциям, настройки) - Инфраструктура: docker-compose, Helm-чарт, GitHub Actions CI, Prometheus/Grafana мониторинг - Embedding через OpenRouter (nvidia/llama-nemotron-embed-vl-1b-v2:free) - LLM: google/gemma-4-31b-it:free через OpenRouter
56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
import { useState } from 'react'
|
||
import { useNavigate, Link } from 'react-router-dom'
|
||
import { api, setToken } from '../api/client'
|
||
|
||
export default function Login() {
|
||
const [email, setEmail] = useState('')
|
||
const [password, setPassword] = useState('')
|
||
const [error, setError] = useState('')
|
||
const navigate = useNavigate()
|
||
|
||
async function handleSubmit(e: React.FormEvent) {
|
||
e.preventDefault()
|
||
try {
|
||
const res = await api.login(email, password)
|
||
setToken(res.access_token)
|
||
navigate('/dashboard')
|
||
} catch (err: any) {
|
||
setError(err.message)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div style={{ maxWidth: 400, margin: '100px auto', padding: 20 }}>
|
||
<h1>Вход</h1>
|
||
<form onSubmit={handleSubmit}>
|
||
<div>
|
||
<input
|
||
type="email"
|
||
placeholder="Email"
|
||
value={email}
|
||
onChange={e => setEmail(e.target.value)}
|
||
required
|
||
style={{ width: '100%', padding: 8, marginBottom: 8 }}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<input
|
||
type="password"
|
||
placeholder="Пароль"
|
||
value={password}
|
||
onChange={e => setPassword(e.target.value)}
|
||
required
|
||
style={{ width: '100%', padding: 8, marginBottom: 8 }}
|
||
/>
|
||
</div>
|
||
{error && <p style={{ color: 'red' }}>{error}</p>}
|
||
<button type="submit" style={{ width: '100%', padding: 10 }}>
|
||
Войти
|
||
</button>
|
||
</form>
|
||
<p style={{ marginTop: 12 }}>
|
||
Нет аккаунта? <Link to="/register">Регистрация</Link>
|
||
</p>
|
||
</div>
|
||
)
|
||
}
|