Spaces:
Running
Running
update
Browse files- src/App.tsx +40 -13
- src/api/auth/authApi.ts +14 -0
- src/api/auth/hooks.ts +24 -0
- src/api/auth/types.ts +9 -0
- src/components/common/navbar/Navbar.tsx +55 -33
- src/components/pages/auth/Login.scss +73 -0
- src/components/pages/auth/Login.tsx +51 -0
- src/components/pages/llmConfigs/LlmConfigList.tsx +1 -1
- src/components/pages/llmConfigs/LlmConfigModal.tsx +1 -1
- src/context/AuthContext.tsx +63 -0
- src/index.scss +1 -1
- src/main.tsx +2 -2
- src/shared/api/query.ts +34 -59
- tsconfig.tsbuildinfo +1 -1
src/App.tsx
CHANGED
|
@@ -3,31 +3,58 @@ import { Routes, Route, BrowserRouter as Router, Navigate } from "react-router-d
|
|
| 3 |
import Navbar from '@/components/common/navbar/Navbar';
|
| 4 |
import Page from "@/components/pages/main/Page";
|
| 5 |
import Logs from "@/components/pages/logsPage/Logs";
|
| 6 |
-
import Vectorization from "
|
| 7 |
import { pdfjs } from "react-pdf";
|
| 8 |
-
import LLMConfigList from "
|
| 9 |
-
import LlmPromptList from "
|
|
|
|
|
|
|
| 10 |
|
| 11 |
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
| 12 |
"pdfjs-dist/build/pdf.worker.min.mjs",
|
| 13 |
import.meta.url
|
| 14 |
).toString();
|
| 15 |
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
return (
|
| 18 |
<Router>
|
| 19 |
-
<Navbar
|
|
|
|
| 20 |
<Routes>
|
| 21 |
-
|
| 22 |
-
<Route path="/logs" element={
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
<Route path="/
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
</Routes>
|
|
|
|
| 29 |
</Router>
|
| 30 |
);
|
| 31 |
}
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import Navbar from '@/components/common/navbar/Navbar';
|
| 4 |
import Page from "@/components/pages/main/Page";
|
| 5 |
import Logs from "@/components/pages/logsPage/Logs";
|
| 6 |
+
import Vectorization from "@/components/pages/vectorizationPage/Vectorization";
|
| 7 |
import { pdfjs } from "react-pdf";
|
| 8 |
+
import LLMConfigList from "@/components/pages/llmConfigs/LlmConfigList";
|
| 9 |
+
import LlmPromptList from "@/components/pages/llmPrompts/LlmPromptList";
|
| 10 |
+
import LoginPage from "@/components/pages/auth/Login";
|
| 11 |
+
import { AuthProvider, useAuth } from "@/context/AuthContext";
|
| 12 |
|
| 13 |
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
| 14 |
"pdfjs-dist/build/pdf.worker.min.mjs",
|
| 15 |
import.meta.url
|
| 16 |
).toString();
|
| 17 |
|
| 18 |
+
const App: React.FC = () => {
|
| 19 |
+
const { isAuthenticated } = useAuth();
|
| 20 |
+
|
| 21 |
+
const PrivateRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
| 22 |
+
return isAuthenticated ? <>{children}</> : <Navigate to="/login" />;
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
return (
|
| 26 |
<Router>
|
| 27 |
+
<Navbar/>
|
| 28 |
+
<div className="app-content">
|
| 29 |
<Routes>
|
| 30 |
+
<Route path="/login" element={<LoginPage />} />
|
| 31 |
+
<Route path="/logs" element={
|
| 32 |
+
<PrivateRoute>
|
| 33 |
+
<Logs />
|
| 34 |
+
</PrivateRoute>} />
|
| 35 |
+
<Route path="/docs" element={
|
| 36 |
+
<PrivateRoute>
|
| 37 |
+
<Vectorization />
|
| 38 |
+
</PrivateRoute>} />
|
| 39 |
+
<Route path="/llmconfig" element={
|
| 40 |
+
<PrivateRoute>
|
| 41 |
+
<LLMConfigList />
|
| 42 |
+
</PrivateRoute>
|
| 43 |
+
} />
|
| 44 |
+
<Route path="/llmprompt" element={
|
| 45 |
+
<PrivateRoute>
|
| 46 |
+
<LlmPromptList />
|
| 47 |
+
</PrivateRoute>} />
|
| 48 |
</Routes>
|
| 49 |
+
</div>
|
| 50 |
</Router>
|
| 51 |
);
|
| 52 |
}
|
| 53 |
|
| 54 |
+
const RootApp: React.FC = () => (
|
| 55 |
+
<AuthProvider>
|
| 56 |
+
<App />
|
| 57 |
+
</AuthProvider>
|
| 58 |
+
);
|
| 59 |
+
|
| 60 |
+
export default RootApp;
|
src/api/auth/authApi.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { query } from '@/shared/api/query';
|
| 2 |
+
import { AuthResponse, LoginRequest } from './types';
|
| 3 |
+
|
| 4 |
+
export const login = async (data: LoginRequest): Promise<AuthResponse> => {
|
| 5 |
+
const response = await query<AuthResponse>({
|
| 6 |
+
url: '/auth/login',
|
| 7 |
+
method: 'post',
|
| 8 |
+
data,
|
| 9 |
+
});
|
| 10 |
+
if ('error' in response) {
|
| 11 |
+
throw new Error(`Ошибка авторизации: ${response.error.status}`);
|
| 12 |
+
}
|
| 13 |
+
return response.data;
|
| 14 |
+
};
|
src/api/auth/hooks.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect } from 'react';
|
| 2 |
+
import { useMutation } from '@tanstack/react-query';
|
| 3 |
+
import { login } from './authApi';
|
| 4 |
+
import { LoginRequest } from './types';
|
| 5 |
+
|
| 6 |
+
export const useAuth = () => {
|
| 7 |
+
|
| 8 |
+
const loginMutation = useMutation({
|
| 9 |
+
mutationFn: (data: LoginRequest) => login(data),
|
| 10 |
+
onSuccess: (data) => {
|
| 11 |
+
console.log(data)
|
| 12 |
+
return data;
|
| 13 |
+
},
|
| 14 |
+
onError: (error) => {
|
| 15 |
+
console.error('Login Error:', error);
|
| 16 |
+
},
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
return {
|
| 20 |
+
login: loginMutation.mutateAsync,
|
| 21 |
+
isLoading: loginMutation.isPending,
|
| 22 |
+
error: loginMutation.error,
|
| 23 |
+
};
|
| 24 |
+
};
|
src/api/auth/types.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export interface LoginRequest {
|
| 2 |
+
username: string;
|
| 3 |
+
password: string;
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
export interface AuthResponse {
|
| 7 |
+
access_token: string;
|
| 8 |
+
token_type: string;
|
| 9 |
+
}
|
src/components/common/navbar/Navbar.tsx
CHANGED
|
@@ -1,43 +1,65 @@
|
|
| 1 |
import React from 'react';
|
| 2 |
-
import { NavLink } from 'react-router-dom';
|
| 3 |
import './Navbar.scss';
|
|
|
|
| 4 |
|
| 5 |
const Navbar: React.FC = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
return (
|
| 7 |
<nav className="navbar">
|
| 8 |
<ul>
|
| 9 |
-
|
| 10 |
-
<
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
</ul>
|
| 42 |
</nav>
|
| 43 |
);
|
|
|
|
| 1 |
import React from 'react';
|
| 2 |
+
import { NavLink, useNavigate } from 'react-router-dom';
|
| 3 |
import './Navbar.scss';
|
| 4 |
+
import { useAuth } from '@/context/AuthContext';
|
| 5 |
|
| 6 |
const Navbar: React.FC = () => {
|
| 7 |
+
const { isAuthenticated, logout } = useAuth();
|
| 8 |
+
const navigate = useNavigate();
|
| 9 |
+
|
| 10 |
+
const handleLogout = () => {
|
| 11 |
+
logout();
|
| 12 |
+
navigate('/login');
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
return (
|
| 16 |
<nav className="navbar">
|
| 17 |
<ul>
|
| 18 |
+
{isAuthenticated ? (
|
| 19 |
+
<>
|
| 20 |
+
<li>
|
| 21 |
+
<NavLink
|
| 22 |
+
to="/logs"
|
| 23 |
+
className={({ isActive }) => (isActive ? 'active' : '')}
|
| 24 |
+
>
|
| 25 |
+
Логи
|
| 26 |
+
</NavLink>
|
| 27 |
+
</li>
|
| 28 |
+
<li>
|
| 29 |
+
<NavLink
|
| 30 |
+
to="/docs"
|
| 31 |
+
className={({ isActive }) => (isActive ? 'active' : '')}
|
| 32 |
+
>
|
| 33 |
+
Датасеты
|
| 34 |
+
</NavLink>
|
| 35 |
+
</li>
|
| 36 |
+
<li>
|
| 37 |
+
<NavLink
|
| 38 |
+
to="/llmconfig"
|
| 39 |
+
className={({ isActive }) => (isActive ? 'active' : '')}
|
| 40 |
+
>
|
| 41 |
+
Настройки LLM
|
| 42 |
+
</NavLink>
|
| 43 |
+
</li>
|
| 44 |
+
<li>
|
| 45 |
+
<NavLink
|
| 46 |
+
to="/llmprompt"
|
| 47 |
+
className={({ isActive }) => (isActive ? 'active' : '')}
|
| 48 |
+
>
|
| 49 |
+
Системные промпты
|
| 50 |
+
</NavLink>
|
| 51 |
+
</li>
|
| 52 |
+
<li>
|
| 53 |
+
<button onClick={handleLogout}>Выход</button>
|
| 54 |
+
</li>
|
| 55 |
+
</>
|
| 56 |
+
) : (
|
| 57 |
+
<li>
|
| 58 |
+
<NavLink to="/login" className={({ isActive }) => (isActive ? 'active' : '')}>
|
| 59 |
+
Вход
|
| 60 |
+
</NavLink>
|
| 61 |
+
</li>
|
| 62 |
+
)}
|
| 63 |
</ul>
|
| 64 |
</nav>
|
| 65 |
);
|
src/components/pages/auth/Login.scss
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.login-page {
|
| 2 |
+
display: flex;
|
| 3 |
+
flex-direction: column;
|
| 4 |
+
align-items: center;
|
| 5 |
+
justify-content: center;
|
| 6 |
+
height: 100vh;
|
| 7 |
+
background-color: #f5f5f5;
|
| 8 |
+
|
| 9 |
+
h2 {
|
| 10 |
+
margin-bottom: 20px;
|
| 11 |
+
font-size: 24px;
|
| 12 |
+
color: #333;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
form {
|
| 16 |
+
display: flex;
|
| 17 |
+
flex-direction: column;
|
| 18 |
+
gap: 15px;
|
| 19 |
+
width: 300px;
|
| 20 |
+
padding: 20px;
|
| 21 |
+
background-color: white;
|
| 22 |
+
border-radius: 8px;
|
| 23 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
| 24 |
+
|
| 25 |
+
div {
|
| 26 |
+
display: flex;
|
| 27 |
+
flex-direction: column;
|
| 28 |
+
|
| 29 |
+
label {
|
| 30 |
+
margin-bottom: 5px;
|
| 31 |
+
font-size: 14px;
|
| 32 |
+
color: #555;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
input {
|
| 36 |
+
padding: 8px;
|
| 37 |
+
font-size: 16px;
|
| 38 |
+
border: 1px solid #ddd;
|
| 39 |
+
border-radius: 4px;
|
| 40 |
+
outline: none;
|
| 41 |
+
|
| 42 |
+
&:focus {
|
| 43 |
+
border-color: #28a745;
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.error {
|
| 49 |
+
color: #dc3545;
|
| 50 |
+
font-size: 14px;
|
| 51 |
+
text-align: center;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
button {
|
| 55 |
+
padding: 10px;
|
| 56 |
+
font-size: 16px;
|
| 57 |
+
background-color: #28a745;
|
| 58 |
+
color: white;
|
| 59 |
+
border: none;
|
| 60 |
+
border-radius: 4px;
|
| 61 |
+
cursor: pointer;
|
| 62 |
+
|
| 63 |
+
&:hover {
|
| 64 |
+
background-color: #218838;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
&:disabled {
|
| 68 |
+
background-color: #6c757d;
|
| 69 |
+
cursor: not-allowed;
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
}
|
src/components/pages/auth/Login.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { useState } from 'react';
|
| 2 |
+
import { useNavigate } from 'react-router-dom';
|
| 3 |
+
import './Login.scss';
|
| 4 |
+
import { useAuth } from '@/context/AuthContext';
|
| 5 |
+
|
| 6 |
+
const LoginPage: React.FC = () => {
|
| 7 |
+
const [username, setUsername] = useState('');
|
| 8 |
+
const [password, setPassword] = useState('');
|
| 9 |
+
const { login, isLoading, error } = useAuth();
|
| 10 |
+
const navigate = useNavigate();
|
| 11 |
+
|
| 12 |
+
const handleSubmit = async (e: React.FormEvent) => {
|
| 13 |
+
e.preventDefault();
|
| 14 |
+
try {
|
| 15 |
+
await login({ username, password });
|
| 16 |
+
navigate('/logs', { replace: true });
|
| 17 |
+
} catch {
|
| 18 |
+
// Ошибка уже логируется в хуке
|
| 19 |
+
}
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
return (
|
| 23 |
+
<div className="login-page">
|
| 24 |
+
<h2>Вход в админку</h2>
|
| 25 |
+
<form onSubmit={handleSubmit}>
|
| 26 |
+
<div>
|
| 27 |
+
<label>Логин:</label>
|
| 28 |
+
<input
|
| 29 |
+
type="text"
|
| 30 |
+
value={username}
|
| 31 |
+
onChange={(e) => setUsername(e.target.value)}
|
| 32 |
+
/>
|
| 33 |
+
</div>
|
| 34 |
+
<div>
|
| 35 |
+
<label>Пароль:</label>
|
| 36 |
+
<input
|
| 37 |
+
type="password"
|
| 38 |
+
value={password}
|
| 39 |
+
onChange={(e) => setPassword(e.target.value)}
|
| 40 |
+
/>
|
| 41 |
+
</div>
|
| 42 |
+
{error && <p className="error">Неверный логин или пароль</p>}
|
| 43 |
+
<button type="submit" disabled={isLoading}>
|
| 44 |
+
{isLoading ? 'Вход...' : 'Войти'}
|
| 45 |
+
</button>
|
| 46 |
+
</form>
|
| 47 |
+
</div>
|
| 48 |
+
);
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
export default LoginPage;
|
src/components/pages/llmConfigs/LlmConfigList.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { GoTrash, GoStar, GoStarFill } from 'react-icons/go';
|
|
| 4 |
import { useQuery } from '@tanstack/react-query';
|
| 5 |
import { useLLMConfigs, } from "@/api/llmConfigs/hooks";
|
| 6 |
import { LLMConfig } from "@/api/llmConfigs/types";
|
| 7 |
-
import './
|
| 8 |
import LLMConfigModal from './LlmConfigModal';
|
| 9 |
import { fetchDefaultLLMConfig } from '@/api/llmConfigs/llmConfigApi';
|
| 10 |
|
|
|
|
| 4 |
import { useQuery } from '@tanstack/react-query';
|
| 5 |
import { useLLMConfigs, } from "@/api/llmConfigs/hooks";
|
| 6 |
import { LLMConfig } from "@/api/llmConfigs/types";
|
| 7 |
+
import './LLMConfigList.scss';
|
| 8 |
import LLMConfigModal from './LlmConfigModal';
|
| 9 |
import { fetchDefaultLLMConfig } from '@/api/llmConfigs/llmConfigApi';
|
| 10 |
|
src/components/pages/llmConfigs/LlmConfigModal.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { GoX } from 'react-icons/go';
|
|
| 4 |
import { LLMConfig } from '@/api/llmConfigs/types';
|
| 5 |
import { useQuery } from '@tanstack/react-query';
|
| 6 |
import { fetchLLMConfigById, fetchDefaultLLMConfig } from '@/api/llmConfigs/llmConfigApi';
|
| 7 |
-
import './
|
| 8 |
|
| 9 |
interface LLMConfigModalProps {
|
| 10 |
isOpen: boolean;
|
|
|
|
| 4 |
import { LLMConfig } from '@/api/llmConfigs/types';
|
| 5 |
import { useQuery } from '@tanstack/react-query';
|
| 6 |
import { fetchLLMConfigById, fetchDefaultLLMConfig } from '@/api/llmConfigs/llmConfigApi';
|
| 7 |
+
import './LLMConfigModal.scss';
|
| 8 |
|
| 9 |
interface LLMConfigModalProps {
|
| 10 |
isOpen: boolean;
|
src/context/AuthContext.tsx
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
| 2 |
+
import { useMutation } from '@tanstack/react-query';
|
| 3 |
+
import { login } from '@/api/auth/authApi';
|
| 4 |
+
import { LoginRequest } from '@/api/auth/types';
|
| 5 |
+
|
| 6 |
+
interface AuthContextType {
|
| 7 |
+
isAuthenticated: boolean;
|
| 8 |
+
login: (data: LoginRequest) => Promise<void>;
|
| 9 |
+
logout: () => void;
|
| 10 |
+
isLoading: boolean;
|
| 11 |
+
error: Error | null;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
| 15 |
+
|
| 16 |
+
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
| 17 |
+
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(() => {
|
| 18 |
+
// Инициализируем состояние сразу из localStorage
|
| 19 |
+
return !!localStorage.getItem('authToken');
|
| 20 |
+
});
|
| 21 |
+
|
| 22 |
+
const loginMutation = useMutation({
|
| 23 |
+
mutationFn: (data: LoginRequest) => login(data),
|
| 24 |
+
onSuccess: (data) => {
|
| 25 |
+
localStorage.setItem('authToken', data.access_token);
|
| 26 |
+
setIsAuthenticated(true);
|
| 27 |
+
},
|
| 28 |
+
onError: (error) => {
|
| 29 |
+
console.error('Login Error:', error);
|
| 30 |
+
},
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
const loginHandler = async (data: LoginRequest) => {
|
| 34 |
+
await loginMutation.mutateAsync(data);
|
| 35 |
+
};
|
| 36 |
+
|
| 37 |
+
const logout = () => {
|
| 38 |
+
localStorage.removeItem('authToken');
|
| 39 |
+
setIsAuthenticated(false);
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
return (
|
| 43 |
+
<AuthContext.Provider
|
| 44 |
+
value={{
|
| 45 |
+
isAuthenticated,
|
| 46 |
+
login: loginHandler,
|
| 47 |
+
logout,
|
| 48 |
+
isLoading: loginMutation.isPending,
|
| 49 |
+
error: loginMutation.error,
|
| 50 |
+
}}
|
| 51 |
+
>
|
| 52 |
+
{children}
|
| 53 |
+
</AuthContext.Provider>
|
| 54 |
+
);
|
| 55 |
+
};
|
| 56 |
+
|
| 57 |
+
export const useAuth = () => {
|
| 58 |
+
const context = useContext(AuthContext);
|
| 59 |
+
if (!context) {
|
| 60 |
+
throw new Error('useAuth must be used within an AuthProvider');
|
| 61 |
+
}
|
| 62 |
+
return context;
|
| 63 |
+
};
|
src/index.scss
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
@
|
| 2 |
|
| 3 |
:root {
|
| 4 |
--font-body: Arial, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
|
|
|
| 1 |
+
@use "@fontsource/fira-mono";
|
| 2 |
|
| 3 |
:root {
|
| 4 |
--font-body: Arial, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
src/main.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
| 3 |
import { Provider } from "react-redux";
|
| 4 |
import { store } from "./store/index.js";
|
| 5 |
import Modal from 'react-modal';
|
| 6 |
-
import
|
| 7 |
import "./index.scss";
|
| 8 |
|
| 9 |
export const queryClient = new QueryClient();
|
|
@@ -11,7 +11,7 @@ export const queryClient = new QueryClient();
|
|
| 11 |
createRoot(document.getElementById("root")!).render(
|
| 12 |
<QueryClientProvider client={queryClient}>
|
| 13 |
<Provider store={store}>
|
| 14 |
-
<
|
| 15 |
</Provider>
|
| 16 |
</QueryClientProvider>
|
| 17 |
);
|
|
|
|
| 3 |
import { Provider } from "react-redux";
|
| 4 |
import { store } from "./store/index.js";
|
| 5 |
import Modal from 'react-modal';
|
| 6 |
+
import RootApp from "./App";
|
| 7 |
import "./index.scss";
|
| 8 |
|
| 9 |
export const queryClient = new QueryClient();
|
|
|
|
| 11 |
createRoot(document.getElementById("root")!).render(
|
| 12 |
<QueryClientProvider client={queryClient}>
|
| 13 |
<Provider store={store}>
|
| 14 |
+
<RootApp />
|
| 15 |
</Provider>
|
| 16 |
</QueryClientProvider>
|
| 17 |
);
|
src/shared/api/query.ts
CHANGED
|
@@ -1,23 +1,14 @@
|
|
| 1 |
-
import axios, {
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
AxiosResponse,
|
| 5 |
-
AxiosResponseHeaders,
|
| 6 |
-
InternalAxiosRequestConfig,
|
| 7 |
-
RawAxiosResponseHeaders,
|
| 8 |
-
ResponseType,
|
| 9 |
-
} from "axios";
|
| 10 |
-
|
| 11 |
-
import { apiBaseUrl } from "../constants";
|
| 12 |
-
import { queryClient } from "@/main";
|
| 13 |
|
| 14 |
export interface IAxiosParams {
|
| 15 |
-
method: AxiosRequestConfig[
|
| 16 |
url: string;
|
| 17 |
-
params?: AxiosRequestConfig[
|
| 18 |
-
data?: AxiosRequestConfig[
|
| 19 |
-
headers?: AxiosRequestConfig[
|
| 20 |
-
responseType?:
|
| 21 |
notCauseError?: boolean;
|
| 22 |
signal?: AbortSignal;
|
| 23 |
}
|
|
@@ -26,50 +17,47 @@ export interface IQueryErrorResponse {
|
|
| 26 |
error: {
|
| 27 |
status: unknown;
|
| 28 |
data: unknown;
|
| 29 |
-
headers?:
|
| 30 |
notCauseError?: boolean;
|
| 31 |
};
|
| 32 |
}
|
| 33 |
|
| 34 |
export interface GenericIdentityFn<Type> {
|
| 35 |
data: Type;
|
| 36 |
-
headers?:
|
| 37 |
}
|
| 38 |
|
| 39 |
-
/**
|
| 40 |
-
* Инстанс аксиоса
|
| 41 |
-
*/
|
| 42 |
export const instance = axios.create({
|
| 43 |
-
baseURL: apiBaseUrl ? `${apiBaseUrl}` :
|
| 44 |
timeout: 600000,
|
| 45 |
});
|
| 46 |
|
| 47 |
-
/**
|
| 48 |
-
* Интерцептор на запрос
|
| 49 |
-
*/
|
| 50 |
const requestInterceptors = (req: InternalAxiosRequestConfig) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
return req;
|
| 52 |
};
|
| 53 |
|
| 54 |
-
/**
|
| 55 |
-
* Интерцептор на успешный ответ сервера
|
| 56 |
-
*/
|
| 57 |
const successInterceptors = (response: AxiosResponse) => response;
|
| 58 |
|
| 59 |
-
/**
|
| 60 |
-
* Интерцептор на ошибку сервера
|
| 61 |
-
*/
|
| 62 |
const errorInterceptors = async (error: AxiosError) => {
|
| 63 |
const message = error.message;
|
| 64 |
-
if (!axios.isCancel(error) && !message.includes("409")) {
|
| 65 |
-
alert(`${message} Произошла ошибка`);
|
| 66 |
-
}
|
| 67 |
|
| 68 |
-
if (message.includes(
|
| 69 |
-
console.log(
|
| 70 |
-
queryClient.invalidateQueries({ queryKey: [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
}
|
| 72 |
-
|
| 73 |
return Promise.reject(error);
|
| 74 |
};
|
| 75 |
|
|
@@ -84,38 +72,25 @@ export const query = async <T>(
|
|
| 84 |
const requestSignal = signal || controller.signal;
|
| 85 |
|
| 86 |
try {
|
| 87 |
-
console.log(
|
| 88 |
const result = await instance({
|
| 89 |
url: `${baseUrl}${url}`,
|
| 90 |
signal: requestSignal,
|
| 91 |
...requestOptions,
|
| 92 |
});
|
| 93 |
-
console.log(
|
| 94 |
return { data: result.data as T, headers: result.headers };
|
| 95 |
} catch (axiosError) {
|
| 96 |
if (axios.isCancel(axiosError)) {
|
| 97 |
-
console.log(
|
| 98 |
-
return {
|
| 99 |
-
error: {
|
| 100 |
-
status: "Cancelled",
|
| 101 |
-
data: "Request was cancelled",
|
| 102 |
-
notCauseError: true,
|
| 103 |
-
},
|
| 104 |
-
};
|
| 105 |
}
|
| 106 |
const err = axiosError as AxiosError;
|
| 107 |
-
console.log(
|
| 108 |
-
|
| 109 |
-
return {
|
| 110 |
-
error: {
|
| 111 |
-
status: err.response?.status,
|
| 112 |
-
data: err.response?.data,
|
| 113 |
-
headers: err.response?.headers,
|
| 114 |
-
},
|
| 115 |
-
};
|
| 116 |
} finally {
|
| 117 |
if (!signal) {
|
| 118 |
controller.abort();
|
| 119 |
}
|
| 120 |
}
|
| 121 |
-
};
|
|
|
|
| 1 |
+
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
| 2 |
+
import { apiBaseUrl } from '../constants';
|
| 3 |
+
import { queryClient } from '@/main';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
export interface IAxiosParams {
|
| 6 |
+
method: AxiosRequestConfig['method'];
|
| 7 |
url: string;
|
| 8 |
+
params?: AxiosRequestConfig['params'];
|
| 9 |
+
data?: AxiosRequestConfig['data'];
|
| 10 |
+
headers?: AxiosRequestConfig['headers'];
|
| 11 |
+
responseType?: AxiosRequestConfig['responseType'];
|
| 12 |
notCauseError?: boolean;
|
| 13 |
signal?: AbortSignal;
|
| 14 |
}
|
|
|
|
| 17 |
error: {
|
| 18 |
status: unknown;
|
| 19 |
data: unknown;
|
| 20 |
+
headers?: AxiosResponse['headers'];
|
| 21 |
notCauseError?: boolean;
|
| 22 |
};
|
| 23 |
}
|
| 24 |
|
| 25 |
export interface GenericIdentityFn<Type> {
|
| 26 |
data: Type;
|
| 27 |
+
headers?: AxiosResponse['headers'];
|
| 28 |
}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
| 30 |
export const instance = axios.create({
|
| 31 |
+
baseURL: apiBaseUrl ? `${apiBaseUrl}` : '',
|
| 32 |
timeout: 600000,
|
| 33 |
});
|
| 34 |
|
|
|
|
|
|
|
|
|
|
| 35 |
const requestInterceptors = (req: InternalAxiosRequestConfig) => {
|
| 36 |
+
// Добавляем jwt токен для всех маршрутов
|
| 37 |
+
const token = localStorage.getItem('authToken');
|
| 38 |
+
if (token) {
|
| 39 |
+
req.headers.Authorization = `Bearer ${token}`;
|
| 40 |
+
}
|
| 41 |
return req;
|
| 42 |
};
|
| 43 |
|
|
|
|
|
|
|
|
|
|
| 44 |
const successInterceptors = (response: AxiosResponse) => response;
|
| 45 |
|
|
|
|
|
|
|
|
|
|
| 46 |
const errorInterceptors = async (error: AxiosError) => {
|
| 47 |
const message = error.message;
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
if (message.includes('409')) {
|
| 50 |
+
console.log('409');
|
| 51 |
+
queryClient.invalidateQueries({ queryKey: ['processing'] });
|
| 52 |
+
}
|
| 53 |
+
if (error.response?.status === 401) {
|
| 54 |
+
localStorage.removeItem('authToken');
|
| 55 |
+
window.location.href = '/login'; // Редирект на логин при 401
|
| 56 |
+
} else {
|
| 57 |
+
if (!axios.isCancel(error) && !message.includes('409')) {
|
| 58 |
+
alert(`${message} Произошла ошибка`);
|
| 59 |
+
}
|
| 60 |
}
|
|
|
|
| 61 |
return Promise.reject(error);
|
| 62 |
};
|
| 63 |
|
|
|
|
| 72 |
const requestSignal = signal || controller.signal;
|
| 73 |
|
| 74 |
try {
|
| 75 |
+
console.log('Попытка отправки запроса');
|
| 76 |
const result = await instance({
|
| 77 |
url: `${baseUrl}${url}`,
|
| 78 |
signal: requestSignal,
|
| 79 |
...requestOptions,
|
| 80 |
});
|
| 81 |
+
console.log('Запрос отправлен, результат:', result);
|
| 82 |
return { data: result.data as T, headers: result.headers };
|
| 83 |
} catch (axiosError) {
|
| 84 |
if (axios.isCancel(axiosError)) {
|
| 85 |
+
console.log('Запрос отменен');
|
| 86 |
+
return { error: { status: 'Cancelled', data: 'Request was cancelled', notCauseError: true } };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
}
|
| 88 |
const err = axiosError as AxiosError;
|
| 89 |
+
console.log('Ошибка при отправке запроса');
|
| 90 |
+
return { error: { status: err.response?.status, data: err.response?.data, headers: err.response?.headers } };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
} finally {
|
| 92 |
if (!signal) {
|
| 93 |
controller.abort();
|
| 94 |
}
|
| 95 |
}
|
| 96 |
+
};
|
tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/documents/documentsapi.ts","./src/api/documents/hooks.ts","./src/api/documents/types.ts","./src/api/predictions/hooks.ts","./src/api/predictions/predictionsapi.ts","./src/api/predictions/types.ts","./src/components/generics/button/button.interface.ts","./src/components/generics/button/button.tsx","./src/components/generics/collapse/collapse.inreface.ts","./src/components/generics/collapse/collapse.tsx","./src/components/generics/dropdown/dropdown.interface.ts","./src/components/generics/dropdown/dropdown.tsx","./src/components/generics/editable/editable.interface.ts","./src/components/generics/editable/editable.tsx","./src/components/generics/input/input.interface.ts","./src/components/generics/input/input.tsx","./src/components/generics/loading/loading.tsx","./src/components/generics/modal/modal.interface.ts","./src/components/generics/modal/modal.tsx","./src/components/generics/sizechanger/sizechanger.tsx","./src/components/generics/sizechanger/sizechangerprops.ts","./src/components/generics/spinner/spinner.tsx","./src/components/generics/tag/tag.interface.ts","./src/components/generics/tag/tag.tsx","./src/components/generics/textarea/textarea.interface.ts","./src/components/generics/textarea/textarea.tsx","./src/components/generics/toggle/toggle.interface.ts","./src/components/generics/toggle/toggle.tsx","./src/components/generics/tooltip/tooltip.interface.ts","./src/components/generics/tooltip/tooltip.tsx","./src/components/pages/documentspage/documents.tsx","./src/components/pages/logspage/logs.tsx","./src/components/pages/main/page.tsx","./src/components/pages/vectorizationpage/vectorization.tsx","./src/components/views/abbreviationblock/abbreviationblock.interface.ts","./src/components/views/abbreviationblock/abbreviationsblock.tsx","./src/components/views/comment/comment.tsx","./src/components/views/comment/commentprops.ts","./src/components/views/documents/adddocuimentform/adddocumentform.interface.ts","./src/components/views/documents/adddocuimentform/adddocumentform.tsx","./src/components/views/documents/createdatasetform/createdatasetform.interface.ts","./src/components/views/documents/createdatasetform/createdatasetform.tsx","./src/components/views/documents/docslist/docslist.interface.ts","./src/components/views/documents/docslist/docslist.tsx","./src/components/views/llmanswer/llmanswer.tsx","./src/components/views/llmanswer/llmanswerprops.ts","./src/components/views/loginform/loginform.tsx","./src/components/views/pagination/pagination.tsx","./src/components/views/requesttextarea/requesttextarea.tsx","./src/components/views/requesttextarea/requesttextareaprops.ts","./src/components/views/search/documentmodal/documentmodal.tsx","./src/components/views/search/documentmodal/pdfviewer.tsx","./src/components/views/search/groupresults/groupresults.tsx","./src/components/views/search/groupresults/groupresultsprops.ts","./src/components/views/search/rocksnnresults/rocksnnresults.tsx","./src/components/views/search/rocksnnresults/rocksnnresultsprops.ts","./src/components/views/search/searchresults/searchresults.tsx","./src/components/views/search/searchresults/searchresultsprops.ts","./src/components/views/search/searchresultsitem/searchresultsitem.tsx","./src/components/views/search/searchresultsitem/searchresultsitemprops.ts","./src/components/views/search/segmentationresults/segmentationresult.tsx","./src/components/views/search/segmentationresults/segmentationsearch.ts","./src/components/views/search/staffresultsitem/staffresultsitem.tsx","./src/components/views/search/staffresultsitem/staffresultsitemprops.ts","./src/shared/constants.ts","./src/shared/types.ts","./src/shared/api/query.ts","./src/shared/hooks/useabbreviations/reducer.ts","./src/shared/hooks/useabbreviations/types.ts","./src/shared/hooks/useabbreviations/useabbreviation.ts","./src/shared/hooks/useauth/reducer.ts","./src/shared/hooks/useauth/types.ts","./src/shared/hooks/useauth/useauth.ts","./src/shared/utils/customparse.ts","./src/shared/utils/downloadexcel.ts","./src/shared/utils/downloadfile.ts","./src/shared/utils/escaperegexp.ts","./src/shared/utils/gettimezoneoffset.ts","./src/shared/utils/highlightmatches.ts","./src/shared/utils/parsedocumnetsinllmanswer.ts","./src/store/hooks.ts","./src/store/index.ts","./src/store/types.ts"],"version":"5.7.3"}
|
|
|
|
| 1 |
+
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/auth/authapi.ts","./src/api/auth/hooks.ts","./src/api/auth/types.ts","./src/api/documents/documentsapi.ts","./src/api/documents/hooks.ts","./src/api/documents/types.ts","./src/api/llmconfigs/hooks.ts","./src/api/llmconfigs/llmconfigapi.ts","./src/api/llmconfigs/types.ts","./src/api/llmprompts/hooks.ts","./src/api/llmprompts/llmpromptapi.ts","./src/api/llmprompts/types.ts","./src/api/predictions/hooks.ts","./src/api/predictions/predictionsapi.ts","./src/api/predictions/types.ts","./src/components/common/navbar/navbar.tsx","./src/components/generics/button/button.interface.ts","./src/components/generics/button/button.tsx","./src/components/generics/collapse/collapse.inreface.ts","./src/components/generics/collapse/collapse.tsx","./src/components/generics/dropdown/dropdown.interface.ts","./src/components/generics/dropdown/dropdown.tsx","./src/components/generics/editable/editable.interface.ts","./src/components/generics/editable/editable.tsx","./src/components/generics/input/input.interface.ts","./src/components/generics/input/input.tsx","./src/components/generics/loading/loading.tsx","./src/components/generics/modal/modal.interface.ts","./src/components/generics/modal/modal.tsx","./src/components/generics/sizechanger/sizechanger.tsx","./src/components/generics/sizechanger/sizechangerprops.ts","./src/components/generics/spinner/spinner.tsx","./src/components/generics/tag/tag.interface.ts","./src/components/generics/tag/tag.tsx","./src/components/generics/textarea/textarea.interface.ts","./src/components/generics/textarea/textarea.tsx","./src/components/generics/toggle/toggle.interface.ts","./src/components/generics/toggle/toggle.tsx","./src/components/generics/tooltip/tooltip.interface.ts","./src/components/generics/tooltip/tooltip.tsx","./src/components/pages/auth/login.tsx","./src/components/pages/documentspage/documents.tsx","./src/components/pages/llmconfigs/llmconfiglist.tsx","./src/components/pages/llmconfigs/llmconfigmodal.tsx","./src/components/pages/llmprompts/llmpromptlist.tsx","./src/components/pages/llmprompts/llmpromptmodal.tsx","./src/components/pages/logspage/logs.tsx","./src/components/pages/main/page.tsx","./src/components/pages/vectorizationpage/vectorization.tsx","./src/components/views/abbreviationblock/abbreviationblock.interface.ts","./src/components/views/abbreviationblock/abbreviationsblock.tsx","./src/components/views/comment/comment.tsx","./src/components/views/comment/commentprops.ts","./src/components/views/documents/adddocuimentform/adddocumentform.interface.ts","./src/components/views/documents/adddocuimentform/adddocumentform.tsx","./src/components/views/documents/createdatasetform/createdatasetform.interface.ts","./src/components/views/documents/createdatasetform/createdatasetform.tsx","./src/components/views/documents/docslist/docslist.interface.ts","./src/components/views/documents/docslist/docslist.tsx","./src/components/views/llmanswer/llmanswer.tsx","./src/components/views/llmanswer/llmanswerprops.ts","./src/components/views/loginform/loginform.tsx","./src/components/views/pagination/pagination.tsx","./src/components/views/requesttextarea/requesttextarea.tsx","./src/components/views/requesttextarea/requesttextareaprops.ts","./src/components/views/search/documentmodal/documentmodal.tsx","./src/components/views/search/documentmodal/pdfviewer.tsx","./src/components/views/search/groupresults/groupresults.tsx","./src/components/views/search/groupresults/groupresultsprops.ts","./src/components/views/search/rocksnnresults/rocksnnresults.tsx","./src/components/views/search/rocksnnresults/rocksnnresultsprops.ts","./src/components/views/search/searchresults/searchresults.tsx","./src/components/views/search/searchresults/searchresultsprops.ts","./src/components/views/search/searchresultsitem/searchresultsitem.tsx","./src/components/views/search/searchresultsitem/searchresultsitemprops.ts","./src/components/views/search/segmentationresults/segmentationresult.tsx","./src/components/views/search/segmentationresults/segmentationsearch.ts","./src/components/views/search/staffresultsitem/staffresultsitem.tsx","./src/components/views/search/staffresultsitem/staffresultsitemprops.ts","./src/context/authcontext.tsx","./src/shared/constants.ts","./src/shared/types.ts","./src/shared/api/query.ts","./src/shared/hooks/useabbreviations/reducer.ts","./src/shared/hooks/useabbreviations/types.ts","./src/shared/hooks/useabbreviations/useabbreviation.ts","./src/shared/hooks/useauth/reducer.ts","./src/shared/hooks/useauth/types.ts","./src/shared/hooks/useauth/useauth.ts","./src/shared/utils/customparse.ts","./src/shared/utils/downloadexcel.ts","./src/shared/utils/downloadfile.ts","./src/shared/utils/escaperegexp.ts","./src/shared/utils/gettimezoneoffset.ts","./src/shared/utils/highlightmatches.ts","./src/shared/utils/parsedocumnetsinllmanswer.ts","./src/store/hooks.ts","./src/store/index.ts","./src/store/types.ts"],"version":"5.7.3"}
|