Ignore:
Timestamp:
01/21/26 13:35:44 (6 months ago)
Author:
istevanoska <ilinastevanoska@…>
Branches:
master
Children:
d42aac3
Parents:
2058e5c
Message:

Added OAuth/prototype

File:
1 edited

Legend:

Unmodified
Added
Removed
  • lan-frontend/src/components/AIAssistantDrawer.jsx

    r2058e5c r505f39a  
    1 import React from "react";
     1import React, {useEffect, useRef, useState} from "react";
    22
    3 import { useEffect, useRef, useState } from "react";
     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);
    420
    5 export default function AIAssistantDrawer({ open, onClose, computers, scope, setScope }) {
    6   const [messages, setMessages] = useState([
    7     { role: "assistant", text: "Постави прашање за Sysmon, процеси, мрежа. Можеш да избереш компјутер (scope) горе." },
    8   ]);
    9   const [input, setInput] = useState("");
    10   const [sending, setSending] = useState(false);
    11   const bottomRef = useRef(null);
     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]);
    1231
    13   useEffect(() => {
    14     if (bottomRef.current) bottomRef.current.scrollIntoView({ behavior: "smooth" });
    15   }, [messages, open]);
     32    useEffect(() => {
     33        if (bottomRef.current) bottomRef.current.scrollIntoView({behavior: "smooth"});
     34    }, [messages, open]);
    1635
    17   async function send() {
    18     const q = input.trim();
    19     if (!q || sending) return;
     36    async function send() {
     37        const q = input.trim();
     38        if (!q || sending) return;
    2039
    21     setInput("");
    22     setMessages((m) => [...m, { role: "user", text: q }]);
    23     setSending(true);
     40        setInput("");
     41        setMessages((m) => [...m, {role: "user", text: q}]);
     42        setSending(true);
    2443
    25     try {
    26       const r = await fetch("/api/chat", {
    27         method: "POST",
    28         headers: { "Content-Type": "application/json" },
    29         body: JSON.stringify({ question: q, computer_name: scope || null }),
    30       });
    31       const data = await r.json();
    32       setMessages((m) => [...m, { role: "assistant", text: data.answer || "(no answer)" }]);
    33     } catch (e) {
    34       setMessages((m) => [...m, { role: "assistant", text: `Грешка: ${String(e.message || e)}` }]);
    35     } finally {
    36       setSending(false);
     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        }
    3777    }
    38   }
    3978
    40   if (!open) return null;
     79    if (!open) return null;
    4180
    42   return (
    43     <>
    44       <div className="drawer-backdrop" onMouseDown={onClose} />
    45       <div className="drawer" onMouseDown={(e) => e.stopPropagation()}>
    46         <div className="drawer-head">
    47           <div>
    48             <div className="drawer-title">🤖 AI Assistant</div>
    49             <div className="drawer-sub">Uses /api/chat (RAG over your logs)</div>
    50           </div>
    51           <button className="btn" onClick={onClose}>Close</button>
    52         </div>
     81    return (
     82        <>
     83            <div className="aiDrawer-backdrop" onMouseDown={onClose}/>
    5384
    54         <div style={{ padding: "12px 16px", borderBottom: "1px solid var(--border)" }}>
    55           <div className="row">
    56             <select className="select" value={scope || ""} onChange={(e) => setScope(e.target.value || null)}>
    57               <option value="">All computers</option>
    58               {(computers || []).map((c) => (
    59                 <option key={c.name} value={c.name}>{c.name}</option>
    60               ))}
    61             </select>
    62           </div>
    63           <div className="small">Tip: “Top процеси по RAM” или “сомнителни sysmon events”</div>
    64         </div>
     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>
    6596
    66         <div className="drawer-body">
    67           {messages.map((m, i) => (
    68             <div key={i} className={`bubble ${m.role === "user" ? "user" : ""}`}>
    69               <div style={{ fontSize: 11, color: "var(--dim)", marginBottom: 6 }}>
    70                 {m.role === "user" ? "You" : "Assistant"}
    71               </div>
    72               <div style={{ whiteSpace: "pre-wrap" }}>{m.text}</div>
    73             </div>
    74           ))}
    75           <div ref={bottomRef} />
    76         </div>
     97                    <button className="aiBtn" onClick={onClose} aria-label="Close">
     98                        ✕
     99                    </button>
     100                </header>
    77101
    78         <div className="drawer-foot">
    79           <div className="row">
    80             <textarea
    81               className="textarea"
     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"
    82137              rows={2}
    83138              value={input}
     
    85140              onChange={(e) => setInput(e.target.value)}
    86141              onKeyDown={(e) => {
    87                 if (e.key === "Enter" && !e.shiftKey) {
    88                   e.preventDefault();
    89                   send();
    90                 }
     142                  if (e.key === "Enter" && !e.shiftKey) {
     143                      e.preventDefault();
     144                      send();
     145                  }
    91146              }}
    92             />
    93             <button className="btn primary" onClick={send} disabled={sending || !input.trim()}>
    94               {sending ? "Sending…" : "Send"}
    95             </button>
    96           </div>
    97         </div>
    98       </div>
    99     </>
    100   );
     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    );
    101159}
Note: See TracChangeset for help on using the changeset viewer.