source: lan-frontend/src/components/AIAssistantDrawer.jsx@ e4c61dd

Last change on this file since e4c61dd was e4c61dd, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Prototype 1.1

  • Property mode set to 100644
File size: 6.4 KB
Line 
1import React, {useEffect, useRef, useState} from "react";
2
3export default function AIAssistantDrawer({
4 open,
5 onClose,
6 computers,
7 scope,
8 setScope,
9 apiBase = "", // ако сакаш можеш да пратиш VITE_API_BASE тука
10 }) {
11 const [messages, setMessages] = useState([
12 {
13 role: "assistant",
14 text: "Постави прашање за Sysmon, процеси, мрежа. Можеш да избереш компјутер (scope) горе.",
15 },
16 ]);
17 const [input, setInput] = useState("");
18 const [sending, setSending] = useState(false);
19 const bottomRef = useRef(null);
20
21 useEffect(() => {
22 if (open) {
23 // затвори со Escape
24 const onKey = (e) => {
25 if (e.key === "Escape") onClose?.();
26 };
27 window.addEventListener("keydown", onKey);
28 return () => window.removeEventListener("keydown", onKey);
29 }
30 }, [open, onClose]);
31
32 useEffect(() => {
33 if (bottomRef.current) bottomRef.current.scrollIntoView({behavior: "smooth"});
34 }, [messages, open]);
35
36 async function send() {
37 const q = input.trim();
38 if (!q || sending) return;
39
40 setInput("");
41 setMessages((m) => [...m, {role: "user", text: q}]);
42 setSending(true);
43
44 try {
45 const base = (apiBase || "").replace(/\/$/, ""); // тргни trailing /
46 const url = `${base}/api/chat`;
47
48 const r = await fetch(url, {
49 method: "POST",
50 headers: {"Content-Type": "application/json"},
51 credentials: "include",
52 body: JSON.stringify({question: q, computer_name: scope || null}),
53 });
54
55
56 const data = await r.json().catch(() => ({}));
57 // if (!r.ok) {
58 // setMessages((m) => [...m, {
59 // role: "assistant",
60 // text: `Error ${r.status}: ${data.error || "Request failed"}`
61 // }]);
62 // } else {
63 // setMessages((m) => [...m, {role: "assistant", text: data.answer || "(no answer)"}]);
64 // }
65 // setMessages((m) => [
66 // ...m,
67 // {role: "assistant", text: data.answer || "(no answer)"},
68 // ]);
69 if (!r.ok) {
70 setMessages((m) => [
71 ...m,
72 {role: "assistant", text: `Error ${r.status}: ${data.error || "Request failed"}`},
73 ]);
74 } else {
75 const qs = Array.isArray(data.queries) ? data.queries : [];
76 const notes = data.notes || "";
77
78 const text =
79 qs.length
80 ? `SQL queries:\n\n${qs.map((q, i) => `${i + 1}) ${q}`).join("\n\n")}\n\nNotes: ${notes}`
81 : `Notes: ${notes || "No queries returned."}`;
82
83 setMessages((m) => [...m, {role: "assistant", text}]);
84 }
85
86
87 } catch (e) {
88 setMessages((m) => [
89 ...m,
90 {role: "assistant", text: `Грешка: ${String(e?.message || e)}`},
91 ]);
92 } finally {
93 setSending(false);
94 }
95 }
96
97 if (!open) return null;
98
99 return (
100 <>
101 <div className="aiDrawer-backdrop" onMouseDown={onClose}/>
102
103 <aside
104 className="aiDrawer"
105 role="dialog"
106 aria-modal="true"
107 onMouseDown={(e) => e.stopPropagation()}
108 >
109 <header className="aiDrawer-head">
110 <div>
111 <div className="aiDrawer-title">🤖 AI Assistant</div>
112 <div className="aiDrawer-sub">RAG over your logs</div>
113 </div>
114
115 <button className="aiBtn" onClick={onClose} aria-label="Close">
116
117 </button>
118 </header>
119
120 <div className="aiDrawer-toolbar">
121 <select
122 className="aiSelect"
123 value={scope || ""}
124 onChange={(e) => setScope(e.target.value || null)}
125 >
126 <option value="">All computers</option>
127 {(computers || []).map((c) => (
128 <option key={c.name} value={c.name}>
129 {c.name}
130 </option>
131 ))}
132 </select>
133
134 <div className="aiHint">
135 Tip: “Top процеси по RAM” / “сомнителни sysmon events”
136 </div>
137 </div>
138
139 <main className="aiDrawer-body">
140 {messages.map((m, i) => (
141 <div
142 key={i}
143 className={`aiMsg ${m.role === "user" ? "user" : "assistant"}`}
144 >
145 <div className="aiMsg-meta">{m.role === "user" ? "You" : "Assistant"}</div>
146 <div className="aiMsg-text">{m.text}</div>
147 </div>
148 ))}
149 <div ref={bottomRef}/>
150 </main>
151
152 <footer className="aiDrawer-foot">
153 <textarea
154 className="aiTextarea"
155 rows={2}
156 value={input}
157 placeholder="Постави прашање… (Enter = send, Shift+Enter нов ред)"
158 onChange={(e) => setInput(e.target.value)}
159 onKeyDown={(e) => {
160 if (e.key === "Enter" && !e.shiftKey) {
161 e.preventDefault();
162 send();
163 }
164 }}
165 />
166 <button
167 className="aiBtn aiBtnPrimary"
168 onClick={send}
169 disabled={sending || !input.trim()}
170 >
171 {sending ? "Sending…" : "Send"}
172 </button>
173 </footer>
174 </aside>
175 </>
176 );
177}
Note: See TracBrowser for help on using the repository browser.