/* Assistant: AI-помощник проекта — чат-виджет с контекстом текущего модуля. Интеграция с backend AI API (заглушка). */ window.RD = window.RD || { core: {}, modules: {} }; window.RD.modules.Assistant = function Assistant() { var store = RD.core.useStore(); var h = React.createElement; var useState = React.useState; var useRef = React.useRef; var useEffect = React.useEffect; var messagesState = useState([ { role: 'assistant', text: 'Привет! Я Прораб-ИИ — твой AI-помощник по проекту. Спрашивай о задачах, сроках, бюджете, рисках.' } ]); var messages = messagesState[0], setMessages = messagesState[1]; var inputState = useState(''); var input = inputState[0], setInput = inputState[1]; var loadingState = useState(false); var loading = loadingState[0], setLoading = loadingState[1]; var scrollRef = useRef(null); // Auto-scroll при новых сообщениях useEffect(function () { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [messages]); // === Отправка сообщения === function sendMessage() { if (!input.trim() || loading) return; var userMessage = { role: 'user', text: input.trim() }; setMessages(messages.concat(userMessage)); setInput(''); setLoading(true); // TODO: реальный API вызов к AI backend // Заглушка: простые паттерн-ответы setTimeout(function () { var reply = generateReply(userMessage.text, store.raw); setMessages(function (prev) { return prev.concat({ role: 'assistant', text: reply }); }); setLoading(false); }, 800); } function handleKeyPress(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } } // === Простая логика ответов (заглушка) === function generateReply(text, raw) { var lower = text.toLowerCase(); var data = raw.data || {}; // Бюджет if (lower.match(/бюджет|сколько потрач|деньг|остал/)) { var smeta = data.smeta || []; var payments = data.payments || []; var totalBudget = smeta.reduce(function (s, item) { return s + ((item.qty || 0) * (item.price || 0)); }, 0); var totalSpent = payments.filter(function (p) { return p.status === 'paid'; }) .reduce(function (s, p) { return s + (p.amount || 0); }, 0); var remaining = totalBudget - totalSpent; return 'Бюджет проекта: ' + Math.round(totalBudget).toLocaleString('ru') + ' ₽\n' + 'Потрачено: ' + Math.round(totalSpent).toLocaleString('ru') + ' ₽ (' + Math.round((totalSpent / totalBudget) * 100) + '%)\n' + 'Осталось: ' + Math.round(remaining).toLocaleString('ru') + ' ₽'; } // График if (lower.match(/график|задач|сколько задач|работ|выполн/)) { var tasks = data.tasks || []; var completed = tasks.filter(function (t) { return t.done; }).length; var total = tasks.length; var percent = total > 0 ? Math.round((completed / total) * 100) : 0; return 'График работ:\n' + 'Всего задач: ' + total + '\n' + 'Завершено: ' + completed + ' (' + percent + '%)\n' + 'В работе: ' + (total - completed); } // Просрочки if (lower.match(/просроч|опозда|проблем|риск/)) { var tasks = data.tasks || []; var now = new Date(); var overdue = tasks.filter(function (t) { return !t.done && t.end && new Date(t.end) < now; }).length; if (overdue === 0) { return '✓ Просрочек нет! Все задачи выполняются в срок.'; } return '⚠ Внимание: ' + overdue + ' задач просрочено. Проверь модуль "График работ" и "Алерты".'; } // Поставки if (lower.match(/постав|матери|заказ|снаб/)) { var supplies = data.supplies || []; var pending = supplies.filter(function (s) { return s.status === 'pending' || s.status === 'ordered'; }).length; return 'Поставки:\n' + 'Ожидают: ' + pending + '\n' + 'Проверь модуль "Оплаты и поставки" для деталей.'; } // Команда if (lower.match(/команд|кто работ|исполнит|подрядчик/)) { var team = data.team || []; return 'В проекте работает ' + team.length + ' человек.\n' + 'Смотри модуль "Команда" для подробностей.'; } // Дефолт return 'Я пока в режиме обучения и отвечаю на базовые вопросы:\n' + '• Бюджет и остаток средств\n' + '• График работ и прогресс\n' + '• Просрочки и риски\n' + '• Поставки и заказы\n' + '• Команда проекта\n\n' + 'Спроси что-нибудь из этого!'; } // === Готовые вопросы (quick actions) === var quickQuestions = [ 'Сколько осталось денег?', 'Есть просрочки?', 'Какой прогресс по графику?', 'Что с поставками?' ]; function quickButton(q) { return h('button', { key: q, onClick: function () { setInput(q); }, style: { padding: '6px 12px', border: '1px solid var(--rd-border)', borderRadius: 8, background: 'var(--rd-surface-2)', color: 'var(--rd-text)', fontSize: 11, fontWeight: 500, cursor: 'pointer', whiteSpace: 'nowrap' } }, q); } // === Render === return h('div', { style: { flex: 1, display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' } }, // Header h('div', { style: { padding: 20, borderBottom: '1px solid var(--rd-border)' } }, h('h2', { style: { fontSize: 20, fontWeight: 700, color: 'var(--rd-text)', marginBottom: 6 } }, 'Прораб-ИИ'), h('p', { style: { fontSize: 13, color: 'var(--rd-text-muted)' } }, 'AI-помощник по проекту · спрашивай о задачах, бюджете, рисках' ) ), // Quick questions messages.length === 1 && h('div', { style: { padding: '12px 20px', borderBottom: '1px solid var(--rd-border)' } }, h('div', { style: { fontSize: 11, fontWeight: 600, color: 'var(--rd-text-muted)', marginBottom: 8, letterSpacing: .3 } }, 'БЫСТРЫЕ ВОПРОСЫ'), h('div', { style: { display: 'flex', gap: 8, flexWrap: 'wrap' } }, quickQuestions.map(quickButton) ) ), // Messages h('div', { ref: scrollRef, style: { flex: 1, overflow: 'auto', padding: 20, display: 'flex', flexDirection: 'column', gap: 14 } }, messages.map(function (msg, i) { var isUser = msg.role === 'user'; return h('div', { key: i, style: { display: 'flex', gap: 10, alignItems: 'flex-start', flexDirection: isUser ? 'row-reverse' : 'row' } }, // Avatar h('div', { style: { width: 34, height: 34, borderRadius: '50%', background: isUser ? 'var(--rd-accent)' : 'linear-gradient(135deg,#6a78f0,#3357e6)', color: '#fff', fontSize: 13, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 'none' } }, isUser ? (store.state.user || 'Вы')[0] : '🤖'), // Message bubble h('div', { style: { maxWidth: '70%', padding: 12, borderRadius: 12, background: isUser ? 'var(--rd-accent)' : 'var(--rd-surface-2)', color: isUser ? '#fff' : 'var(--rd-text)', fontSize: 13, lineHeight: 1.5, whiteSpace: 'pre-wrap', border: isUser ? 'none' : '1px solid var(--rd-border)' } }, msg.text) ); }), loading && h('div', { style: { display: 'flex', gap: 10, alignItems: 'center' } }, h('div', { style: { width: 34, height: 34, borderRadius: '50%', background: 'linear-gradient(135deg,#6a78f0,#3357e6)', color: '#fff', fontSize: 13, fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center' } }, '🤖'), h('div', { style: { padding: 12, borderRadius: 12, background: 'var(--rd-surface-2)', border: '1px solid var(--rd-border)', fontSize: 13, color: 'var(--rd-text-muted)' } }, 'Думаю...') ) ), // Input h('div', { style: { padding: 16, borderTop: '1px solid var(--rd-border)' } }, h('div', { style: { display: 'flex', gap: 10 } }, h('textarea', { value: input, onChange: function (e) { setInput(e.target.value); }, onKeyPress: handleKeyPress, placeholder: 'Спроси что-нибудь о проекте...', disabled: loading, style: { flex: 1, minHeight: 44, maxHeight: 120, padding: 12, border: '1px solid var(--rd-border)', borderRadius: 10, background: 'var(--rd-surface)', color: 'var(--rd-text)', fontSize: 13, fontFamily: 'inherit', resize: 'vertical' } }), h('button', { onClick: sendMessage, disabled: !input.trim() || loading, style: { width: 44, height: 44, border: 'none', borderRadius: 10, background: input.trim() && !loading ? 'var(--rd-accent)' : 'var(--rd-surface-2)', color: input.trim() && !loading ? '#fff' : 'var(--rd-text-muted)', fontSize: 18, fontWeight: 700, cursor: input.trim() && !loading ? 'pointer' : 'not-allowed', flex: 'none' } }, '→') ) ) ); };