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

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

Added history mmanage

  • Property mode set to 100644
File size: 6.2 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 setMessages((m) => [
76 ...m,
77 {role: "assistant", text: data.answer || "(no answer)"},
78 ]);
79 }
80
81 } catch (e) {
82 setMessages((m) => [
83 ...m,
84 {role: "assistant", text: `Грешка: ${String(e?.message || e)}`},
85 ]);
86 } finally {
87 setSending(false);
88 }
89 }
90
91 if (!open) return null;
92
93 return (
94 <>
95 <div className="aiDrawer-backdrop" onMouseDown={onClose}/>
96
97 <aside
98 className="aiDrawer"
99 role="dialog"
100 aria-modal="true"
101 onMouseDown={(e) => e.stopPropagation()}
102 >
103 <header className="aiDrawer-head">
104 <div>
105 <div className="aiDrawer-title">🤖 AI Assistant</div>
106 <div className="aiDrawer-sub">RAG over your logs</div>
107 </div>
108
109 <button className="aiBtn" onClick={onClose} aria-label="Close">
110
111 </button>
112 </header>
113
114 <div className="aiDrawer-toolbar">
115 <select
116 className="aiSelect"
117 value={scope || ""}
118 onChange={(e) => setScope(e.target.value || null)}
119 >
120 <option value="">All computers</option>
121 {(computers || []).map((c) => (
122 <option key={c.name} value={c.name}>
123 {c.name}
124 </option>
125 ))}
126 </select>
127
128 <div className="aiHint">
129 Tip: “Top процеси по RAM” / “сомнителни sysmon events”
130 </div>
131 </div>
132
133 <main className="aiDrawer-body">
134 {messages.map((m, i) => (
135 <div
136 key={i}
137 className={`aiMsg ${m.role === "user" ? "user" : "assistant"}`}
138 >
139 <div className="aiMsg-meta">{m.role === "user" ? "You" : "Assistant"}</div>
140 <div className="aiMsg-text">{m.text}</div>
141 </div>
142 ))}
143 <div ref={bottomRef}/>
144 </main>
145
146 <footer className="aiDrawer-foot">
147 <textarea
148 className="aiTextarea"
149 rows={2}
150 value={input}
151 placeholder="Постави прашање… (Enter = send, Shift+Enter нов ред)"
152 onChange={(e) => setInput(e.target.value)}
153 onKeyDown={(e) => {
154 if (e.key === "Enter" && !e.shiftKey) {
155 e.preventDefault();
156 send();
157 }
158 }}
159 />
160 <button
161 className="aiBtn aiBtnPrimary"
162 onClick={send}
163 disabled={sending || !input.trim()}
164 >
165 {sending ? "Sending…" : "Send"}
166 </button>
167 </footer>
168 </aside>
169 </>
170 );
171}
Note: See TracBrowser for help on using the repository browser.