Nahrát soubory do „website/frontend/src/components“
This commit is contained in:
parent
e68bc8c966
commit
5a85a68fcd
67
website/frontend/src/components/ChatInput.jsx
Normal file
67
website/frontend/src/components/ChatInput.jsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Send } from 'lucide-react';
|
||||||
|
|
||||||
|
const ChatInput = ({ onSubmit }) => {
|
||||||
|
const [input, setInput] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setError(""); // reset chyby
|
||||||
|
|
||||||
|
if (input.trim()) {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/predict", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ text: input }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
setError(data.error || "Chyba pri analýze textu.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onSubmit) {
|
||||||
|
onSubmit(input, data.prediction);
|
||||||
|
}
|
||||||
|
|
||||||
|
setInput('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Chyba požiadavky:", error);
|
||||||
|
setError("Nepodarilo sa spojiť so serverom.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setError("Text nesmie byť prázdny.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="input-container">
|
||||||
|
<form onSubmit={handleSubmit} className="input-wrapper">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
placeholder="Zadajte text na analýzu"
|
||||||
|
className="text-input"
|
||||||
|
/>
|
||||||
|
<button type="submit" className="submit-button" title="Spustiť analýzu">
|
||||||
|
<Send size={24} />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mt-8 text-red-500 font-semibold text-center">
|
||||||
|
⚠️ {error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatInput;
|
||||||
|
|
||||||
|
|
16
website/frontend/src/components/ChatWindow.jsx
Normal file
16
website/frontend/src/components/ChatWindow.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
|
||||||
|
const ChatWindow = ({ messages }) => {
|
||||||
|
return (
|
||||||
|
<div className="chat-window">
|
||||||
|
{messages.map((msg, index) => (
|
||||||
|
<div key={index} className={`message ${msg.sender}`}>
|
||||||
|
{msg.text}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatWindow;
|
20
website/frontend/src/components/Footer.jsx
Normal file
20
website/frontend/src/components/Footer.jsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FaTelegram, FaGooglePlay } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const Footer = () => {
|
||||||
|
return (
|
||||||
|
<footer className="footer-container">
|
||||||
|
<p>Created by Tetiana Mohorian</p>
|
||||||
|
<div className="footer-icons">
|
||||||
|
<a href="https://t.me/hate_speech_sk_bot" target="_blank" rel="noopener noreferrer">
|
||||||
|
<FaTelegram size={32} />
|
||||||
|
</a>
|
||||||
|
<a href="https://play.google.com/store" target="_blank" rel="noopener noreferrer">
|
||||||
|
<FaGooglePlay size={28} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
9
website/frontend/src/components/Header.jsx
Normal file
9
website/frontend/src/components/Header.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Header = () => (
|
||||||
|
<header className="header-container">
|
||||||
|
<h1 className="app-title">Detektor nenávistného jazyka</h1>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Header;
|
35
website/frontend/src/components/Historia.jsx
Normal file
35
website/frontend/src/components/Historia.jsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Historia = ({ history }) => {
|
||||||
|
if (!history || history.length === 0) {
|
||||||
|
return <p className="text-center text-gray-400 p-4">Zatiaľ žiadna história</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex justify-center w-full">
|
||||||
|
<div className="overflow-x-auto w-[70%] min-w-[400px]">
|
||||||
|
<table className="table-fixed border-collapse text-gray-300 w-full" style={{ borderSpacing: "20px 0" }}>
|
||||||
|
<tbody>
|
||||||
|
{history.map((item, index) => (
|
||||||
|
<tr key={index} className={index % 2 === 0 ? 'bg-gray-800/40' : 'bg-gray-800/20'}>
|
||||||
|
<td className="px-6 py-3 text-left">{item.text}</td>
|
||||||
|
<td className="px-6 py-3 text-center">
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-medium ${
|
||||||
|
item.prediction.includes("toxický")
|
||||||
|
? "bg-red-500/40 text-red-100"
|
||||||
|
: "bg-green-500/40 text-green-100"
|
||||||
|
}`}>
|
||||||
|
{item.prediction}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-6 py-3 text-right text-gray-400 text-xs">{item.timestamp}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Historia;
|
148
website/frontend/src/components/InfoBox.jsx
Normal file
148
website/frontend/src/components/InfoBox.jsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import ChatInput from './ChatInput.jsx';
|
||||||
|
import Historia from './Historia.jsx';
|
||||||
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
|
||||||
|
const InfoBox = () => {
|
||||||
|
const [headerText, setHeaderText] = useState('Analyzujte text na nenávistný jazyk');
|
||||||
|
const [paragraphText, setParagraphText] = useState('Tento nástroj využíva umelú inteligenciu na identifikáciu toxického obsahu v textoch. Stačí zadať text a zistiť, či obsahuje nenávistný jazyk.');
|
||||||
|
const [history, setHistory] = useState([]);
|
||||||
|
const [showHistory, setShowHistory] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const fetchHistory = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/history");
|
||||||
|
const data = await response.json();
|
||||||
|
setHistory(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Nepodarilo sa načítať históriu:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSendMessage = async (userMessage) => {
|
||||||
|
setHeaderText("Analyzujem text...");
|
||||||
|
setParagraphText("Analyzujeme váš text, prosím čakajte...");
|
||||||
|
setIsLoading(true);
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/predict", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ text: userMessage }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
const elapsed = Date.now() - startTime;
|
||||||
|
const minimumDelay = 5000;
|
||||||
|
|
||||||
|
if (elapsed < minimumDelay) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, minimumDelay - elapsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
setHeaderText(data.prediction);
|
||||||
|
setParagraphText(`Váš text bol: "${userMessage}"`);
|
||||||
|
fetchHistory();
|
||||||
|
} else {
|
||||||
|
console.error("Server vrátil chybu:", data.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Chyba pri odosielaní:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchHistory();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="info-container text-center w-full pb-16">
|
||||||
|
<h2 className={isLoading ? "pulse" : ""}>{isLoading ? 'Analyzujem text...' : headerText}</h2>
|
||||||
|
<p>{isLoading ? 'Prosím čakajte, prebieha analýza.' : paragraphText}</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center w-full">
|
||||||
|
<ChatInput onSubmit={handleSendMessage} />
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex justify-center items-center mt-4">
|
||||||
|
<div className="w-6 h-6 border-4 border-blue-500 border-t-transparent border-solid rounded-full animate-spin"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="relative w-full mt-8 max-w-3x">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowHistory(!showHistory)}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="button-historia w-full py-3 px-5 rounded-lg transition font-semibold text-white bg-gray-800/70 hover:bg-gray-800/90"
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
width: '100%',
|
||||||
|
textAlign: 'center',
|
||||||
|
height: '48px',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{
|
||||||
|
position: 'relative',
|
||||||
|
display: 'inline-block',
|
||||||
|
width: '100%'
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
paddingTop: '-7px',
|
||||||
|
paddingBottom: '7px'
|
||||||
|
}}>
|
||||||
|
🕘 História analýz
|
||||||
|
</span>
|
||||||
|
<span style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: '0',
|
||||||
|
top: '50%',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
paddingTop: '7px',
|
||||||
|
paddingBottom: '7px'
|
||||||
|
}}>
|
||||||
|
{showHistory ? <ChevronUp size={24}/> : <ChevronDown size={24}/>}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showHistory && (
|
||||||
|
<div
|
||||||
|
className={`w-full
|
||||||
|
mt-2 p-4 rounded-lg
|
||||||
|
bg-gray-900/80 backdrop-blur-md
|
||||||
|
overflow-y-auto max-h-40
|
||||||
|
transition-all duration-500 ease-in-out
|
||||||
|
mx-auto
|
||||||
|
${showHistory ? 'max-h-40 opacity-100' : 'max-h-0 opacity-0'} `}
|
||||||
|
|
||||||
|
style={{
|
||||||
|
maxHeight: '80px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
scrollbarWidth: 'thin',
|
||||||
|
scrollbarColor: '#4B5563 transparent'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="history-container" style={{minHeight: '150px', minHeight: window.innerWidth <= 426 ? '20px' : '140px',}}>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<Historia history={history}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoBox;
|
Loading…
Reference in New Issue
Block a user