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

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

Added OAuth/prototype

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