385 lines
11 KiB
HTML
385 lines
11 KiB
HTML
<!doctype html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Агент по охране труда</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: -apple-system, "Segoe UI", Roboto, sans-serif;
|
||
background: linear-gradient(135deg, #0f172a 0%, #1e3a5f 50%, #0f172a 100%);
|
||
min-height: 100vh;
|
||
color: #e2e8f0;
|
||
}
|
||
.container {
|
||
max-width: 900px;
|
||
margin: 0 auto;
|
||
padding: 40px 20px;
|
||
}
|
||
header {
|
||
text-align: center;
|
||
margin-bottom: 40px;
|
||
}
|
||
.logo {
|
||
font-size: 64px;
|
||
margin-bottom: 16px;
|
||
}
|
||
h1 {
|
||
font-size: clamp(28px, 5vw, 42px);
|
||
font-weight: 700;
|
||
color: #ffffff;
|
||
margin-bottom: 12px;
|
||
}
|
||
.subtitle {
|
||
font-size: 18px;
|
||
color: #94a3b8;
|
||
max-width: 600px;
|
||
margin: 0 auto;
|
||
}
|
||
.card {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 20px;
|
||
padding: 32px;
|
||
margin-bottom: 24px;
|
||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||
}
|
||
.input-section {
|
||
margin-bottom: 24px;
|
||
}
|
||
label {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: #cbd5e1;
|
||
margin-bottom: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
textarea {
|
||
width: 100%;
|
||
min-height: 140px;
|
||
padding: 16px;
|
||
font-size: 16px;
|
||
font-family: inherit;
|
||
background: rgba(15, 23, 42, 0.6);
|
||
border: 2px solid rgba(148, 163, 184, 0.2);
|
||
border-radius: 12px;
|
||
color: #ffffff;
|
||
resize: vertical;
|
||
transition: border-color 0.3s, box-shadow 0.3s;
|
||
}
|
||
textarea:focus {
|
||
outline: none;
|
||
border-color: #38bdf8;
|
||
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.2);
|
||
}
|
||
textarea::placeholder {
|
||
color: #64748b;
|
||
}
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 16px 32px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
border: none;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: transform 0.2s, box-shadow 0.2s;
|
||
}
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, #0ea5e9 0%, #0284c7 100%);
|
||
color: #ffffff;
|
||
box-shadow: 0 4px 14px rgba(14, 165, 233, 0.4);
|
||
}
|
||
.btn-primary:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 20px rgba(14, 165, 233, 0.5);
|
||
}
|
||
.btn-primary:disabled {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
}
|
||
.btn-icon {
|
||
font-size: 20px;
|
||
}
|
||
.quick-questions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 16px;
|
||
}
|
||
.quick-btn {
|
||
padding: 10px 18px;
|
||
font-size: 14px;
|
||
background: rgba(56, 189, 248, 0.15);
|
||
border: 1px solid rgba(56, 189, 248, 0.3);
|
||
border-radius: 20px;
|
||
color: #7dd3fc;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.quick-btn:hover {
|
||
background: rgba(56, 189, 248, 0.25);
|
||
border-color: #38bdf8;
|
||
}
|
||
.response-section {
|
||
display: none;
|
||
}
|
||
.response-section.active {
|
||
display: block;
|
||
}
|
||
.response-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 16px;
|
||
border-bottom: 1px solid rgba(148, 163, 184, 0.2);
|
||
}
|
||
.response-icon {
|
||
font-size: 28px;
|
||
}
|
||
.response-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #ffffff;
|
||
}
|
||
.response-content {
|
||
font-size: 16px;
|
||
line-height: 1.7;
|
||
color: #cbd5e1;
|
||
white-space: pre-wrap;
|
||
}
|
||
.response-content h1,
|
||
.response-content h2,
|
||
.response-content h3 {
|
||
color: #ffffff;
|
||
margin: 24px 0 12px;
|
||
font-weight: 600;
|
||
}
|
||
.response-content h1 { font-size: 24px; }
|
||
.response-content h2 { font-size: 20px; }
|
||
.response-content h3 { font-size: 18px; }
|
||
.response-content ul,
|
||
.response-content ol {
|
||
margin: 12px 0;
|
||
padding-left: 24px;
|
||
}
|
||
.response-content li {
|
||
margin-bottom: 8px;
|
||
}
|
||
.response-content strong {
|
||
color: #ffffff;
|
||
font-weight: 600;
|
||
}
|
||
.response-content code {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-family: "Consolas", "Monaco", monospace;
|
||
font-size: 14px;
|
||
}
|
||
.response-content pre {
|
||
background: rgba(15, 23, 42, 0.6);
|
||
padding: 16px;
|
||
border-radius: 8px;
|
||
overflow-x: auto;
|
||
margin: 16px 0;
|
||
}
|
||
.response-content pre code {
|
||
background: none;
|
||
padding: 0;
|
||
}
|
||
.loading {
|
||
display: none;
|
||
text-align: center;
|
||
padding: 40px;
|
||
}
|
||
.loading.active {
|
||
display: block;
|
||
}
|
||
.spinner {
|
||
width: 48px;
|
||
height: 48px;
|
||
border: 4px solid rgba(56, 189, 248, 0.2);
|
||
border-top-color: #38bdf8;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto 16px;
|
||
}
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
.loading-text {
|
||
color: #94a3b8;
|
||
font-size: 14px;
|
||
}
|
||
.error-message {
|
||
background: rgba(239, 68, 68, 0.15);
|
||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||
border-radius: 12px;
|
||
padding: 16px 20px;
|
||
color: #fca5a5;
|
||
margin-top: 16px;
|
||
display: none;
|
||
}
|
||
.error-message.active {
|
||
display: block;
|
||
}
|
||
footer {
|
||
text-align: center;
|
||
padding: 32px 20px;
|
||
color: #64748b;
|
||
font-size: 14px;
|
||
}
|
||
@media (max-width: 640px) {
|
||
.container { padding: 20px 16px; }
|
||
.card { padding: 24px 20px; }
|
||
.btn { width: 100%; justify-content: center; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<header>
|
||
<div class="logo">🛡️</div>
|
||
<h1>Агент по охране труда</h1>
|
||
<p class="subtitle">Эксперт по технике безопасности и охране труда. Задайте вопрос — получите профессиональный ответ.</p>
|
||
</header>
|
||
|
||
<div class="card">
|
||
<div class="input-section">
|
||
<label for="question">Ваш вопрос</label>
|
||
<textarea
|
||
id="question"
|
||
placeholder="Например: Какие требования охраны труда предъявляются к работе на высоте? Или: Составьте инструкцию по технике безопасности для офиса..."
|
||
></textarea>
|
||
<div class="quick-questions">
|
||
<button class="quick-btn" onclick="setQuestion('Составь инструкцию по охране труда для офиса')">📋 Инструкция для офиса</button>
|
||
<button class="quick-btn" onclick="setQuestion('Какие виды инструктажей по охране труда существуют?')">📚 Виды инструктажей</button>
|
||
<button class="quick-btn" onclick="setQuestion('Как провести оценку профессиональных рисков?')">⚠️ Оценка рисков</button>
|
||
<button class="quick-btn" onclick="setQuestion('Требования к работе на высоте')">🏗️ Работа на высоте</button>
|
||
<button class="quick-btn" onclick="setQuestion('Как оформить журнал инструктажей?')">📖 Журнал инструктажей</button>
|
||
</div>
|
||
</div>
|
||
<button class="btn btn-primary" id="askBtn" onclick="askAgent()">
|
||
<span class="btn-icon">💬</span>
|
||
<span>Задать вопрос агенту</span>
|
||
</button>
|
||
<div class="error-message" id="errorMessage"></div>
|
||
</div>
|
||
|
||
<div class="loading" id="loading">
|
||
<div class="spinner"></div>
|
||
<p class="loading-text">Агент анализирует ваш вопрос и готовит ответ...</p>
|
||
</div>
|
||
|
||
<div class="card response-section" id="responseSection">
|
||
<div class="response-header">
|
||
<span class="response-icon">✅</span>
|
||
<span class="response-title">Ответ агента</span>
|
||
</div>
|
||
<div class="response-content" id="responseContent"></div>
|
||
</div>
|
||
|
||
<footer>
|
||
<p>Приоритет в работе — безопасность людей</p>
|
||
</footer>
|
||
</div>
|
||
|
||
<script>
|
||
let sessionId = null;
|
||
|
||
function setQuestion(text) {
|
||
document.getElementById('question').value = text;
|
||
}
|
||
|
||
async function createSession() {
|
||
const resp = await fetch('/api/session', { method: 'POST' });
|
||
const data = await resp.json();
|
||
if (!data.ok) throw new Error('Не удалось создать сессию');
|
||
sessionId = data.sessionId;
|
||
return sessionId;
|
||
}
|
||
|
||
async function askAgent() {
|
||
const question = document.getElementById('question').value.trim();
|
||
if (!question) {
|
||
showError('Введите вопрос');
|
||
return;
|
||
}
|
||
|
||
const btn = document.getElementById('askBtn');
|
||
const loading = document.getElementById('loading');
|
||
const responseSection = document.getElementById('responseSection');
|
||
const errorMessage = document.getElementById('errorMessage');
|
||
|
||
btn.disabled = true;
|
||
loading.classList.add('active');
|
||
responseSection.classList.remove('active');
|
||
errorMessage.classList.remove('active');
|
||
|
||
try {
|
||
if (!sessionId) {
|
||
await createSession();
|
||
}
|
||
|
||
const resp = await fetch('/api/ask', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ question, sessionId })
|
||
});
|
||
|
||
const data = await resp.json();
|
||
|
||
if (!data.ok) {
|
||
throw new Error(data.error || 'Ошибка при получении ответа');
|
||
}
|
||
|
||
document.getElementById('responseContent').innerHTML = formatResponse(data.answer);
|
||
responseSection.classList.add('active');
|
||
|
||
} catch (err) {
|
||
showError(err.message);
|
||
} finally {
|
||
btn.disabled = false;
|
||
loading.classList.remove('active');
|
||
}
|
||
}
|
||
|
||
function formatResponse(text) {
|
||
if (!text) return '';
|
||
let html = text
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||
.replace(/`(.+?)`/g, '<code>$1</code>')
|
||
.replace(/\n/g, '<br>');
|
||
return '<ul>' + html.replace(/<li>/g, '</li><li>').replace(/^<\/li>/, '') + '</ul>';
|
||
}
|
||
|
||
function showError(message) {
|
||
const el = document.getElementById('errorMessage');
|
||
el.textContent = message;
|
||
el.classList.add('active');
|
||
}
|
||
|
||
document.getElementById('question').addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter' && e.ctrlKey) {
|
||
askAgent();
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|