klg-asutk-app/components/AIAgentModal.tsx
Yuriy aa052763f6 Безопасность и качество: 8 исправлений + обновления
- .env.example: полный шаблон, защита секретов
- .gitignore: явное исключение .env.* и секретов
- layout.tsx: XSS — заменён dangerouslySetInnerHTML на next/script для SW
- ESLint: no-console error (allow warn/error), ignore scripts/
- scripts/remove-console-logs.js: очистка console.log без glob
- backend/routes/modules: README с планом рефакторинга крупных файлов
- SECURITY.md: гид по секретам, XSS, CORS, auth, линту
- .husky/pre-commit: запуск npm run lint

+ прочие правки приложения и бэкенда

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-14 21:29:16 +03:00

59 lines
3.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useRef, useEffect } from 'react';
import { Modal } from '@/components/ui';
interface Props { isOpen: boolean; onClose: () => void; }
interface Message { role: 'user' | 'assistant' | 'system'; content: string; ts: number; }
export default function AIAgentModal({ isOpen, onClose }: Props) {
const [messages, setMessages] = useState<Message[]>([{ role: 'system', content: 'AI-ассистент КЛГ АСУ ТК готов к работе. Задайте вопрос о лётной годности, нормативных документах или данных в системе.', ts: Date.now() }]);
const [input, setInput] = useState('');
const [loading, setLoading] = useState(false);
const endRef = useRef<HTMLDivElement>(null);
useEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
const send = async () => {
if (!input.trim() || loading) return;
const userMsg: Message = { role: 'user', content: input.trim(), ts: Date.now() };
setMessages(m => [...m, userMsg]);
setInput(''); setLoading(true);
try {
const res = await fetch('/api/ai-chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [...messages, userMsg].map(m => ({ role: m.role, content: m.content })) }) });
const data = await res.json();
setMessages(m => [...m, { role: 'assistant', content: data.content || data.message || 'Нет ответа', ts: Date.now() }]);
} catch (e: any) {
setMessages(m => [...m, { role: 'assistant', content: `Ошибка: ${e.message}. Проверьте подключение к AI API.`, ts: Date.now() }]);
} finally { setLoading(false); }
};
return (
<Modal isOpen={isOpen} onClose={onClose} title="🤖 AI Ассистент" size="lg">
<div className="flex flex-col h-[60vh]">
<div className="flex-1 overflow-y-auto space-y-3 mb-4 pr-2">
{messages.map((m, i) => (
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] px-4 py-3 rounded-xl text-sm leading-relaxed ${
m.role === 'user' ? 'bg-primary-500 text-white rounded-br-sm' :
m.role === 'system' ? 'bg-gray-100 text-gray-500 italic' :
'bg-gray-100 text-gray-800 rounded-bl-sm'}`}>
<div className="whitespace-pre-wrap">{m.content}</div>
<div className={`text-[10px] mt-1 ${m.role === 'user' ? 'text-white/60' : 'text-gray-400'}`}>{new Date(m.ts).toLocaleTimeString('ru-RU')}</div>
</div>
</div>
))}
{loading && <div className="flex justify-start"><div className="bg-gray-100 px-4 py-3 rounded-xl text-gray-400">Думаю...</div></div>}
<div ref={endRef} />
</div>
<div className="flex gap-2 shrink-0">
<input value={input} onChange={e => setInput(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
placeholder="Задайте вопрос..." className="input-field flex-1" disabled={loading} />
<button onClick={send} disabled={loading || !input.trim()} className="btn-primary disabled:opacity-50">Отправить</button>
</div>
</div>
</Modal>
);
}