Compare commits
2
Commits
f46e53de15
...
fe5d18afa7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe5d18afa7 | ||
|
|
4bfa979ee8 |
@@ -26,6 +26,9 @@ ln -sf "$NEXUS/management/autostart/nexus-popup.desktop" \
|
||||
"$AUTOSTART/nexus-popup.desktop"
|
||||
cp -f "$NEXUS/management/autostart/nm-tray.desktop" "$AUTOSTART/nm-tray.desktop"
|
||||
cp -f "$NEXUS/management/autostart/nm-tray-autostart.desktop" "$AUTOSTART/nm-tray-autostart.desktop"
|
||||
# Same for nm-applet (network-manager-gnome), which ships its own systray icon
|
||||
# from /etc/xdg/autostart — the network genmon (plugin-13) replaces it.
|
||||
cp -f "$NEXUS/management/autostart/nm-applet.desktop" "$AUTOSTART/nm-applet.desktop"
|
||||
# Suppress blueman-applet autostart (both the system blueman.desktop and the
|
||||
# user blueman-applet.desktop). blueman always forces its own tray icon, which
|
||||
# the panel renders too small; the Bluetooth genmon (plugin-16) replaces it and
|
||||
|
||||
+51
-32
@@ -154,6 +154,21 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
// Bulk delete of everything currently listed (respects the search filter).
|
||||
const clearConversations = async () => {
|
||||
const n = conversations.length;
|
||||
if (!n) return;
|
||||
const what = search ? `${n} matching conversation${n > 1 ? "s" : ""}` : `all ${n} conversation${n > 1 ? "s" : ""}`;
|
||||
if (!window.confirm(`Delete ${what}? This cannot be undone.`)) return;
|
||||
const ids = conversations.map(c => c.id);
|
||||
await Promise.all(
|
||||
ids.map(id => fetch(`${API_BASE}/conversations/${id}`, { method: "DELETE" }).catch(err =>
|
||||
console.error("Failed to delete conversation:", err)))
|
||||
);
|
||||
if (ids.includes(activeConversationId)) setActiveConversationId(crypto.randomUUID());
|
||||
loadConversations(search);
|
||||
};
|
||||
|
||||
const navItems = [
|
||||
{ key: "chatbot", icon: "💬", label: "Chat" },
|
||||
{ key: "playbook", icon: "📖", label: "Playbooks" },
|
||||
@@ -168,7 +183,7 @@ function App() {
|
||||
<div style={{ background: "#111", color: "#eee", height: "100vh", overflow: "hidden", fontFamily: "system-ui", display: "flex" }}>
|
||||
{/* Sidebar */}
|
||||
<aside style={{
|
||||
width: "220px",
|
||||
width: "400px",
|
||||
flexShrink: 0,
|
||||
background: "#0a0a0a",
|
||||
borderRight: "1px solid #333",
|
||||
@@ -201,7 +216,7 @@ function App() {
|
||||
borderRadius: "50%",
|
||||
background: status === "Offline" || ollamaStatus === "unavailable" ? "#f44336"
|
||||
: ollamaStatus === "running" ? "#4caf50" : "#ff9800",
|
||||
cursor: "pointer",
|
||||
cursor: (ollamaBusy || ollamaStatus === "unavailable" || status === "Offline") ? "not-allowed" : "pointer",
|
||||
transition: "all 0.3s"
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
@@ -209,7 +224,11 @@ function App() {
|
||||
setShowStatusTooltip(true);
|
||||
}}
|
||||
onMouseLeave={() => setShowStatusTooltip(false)}
|
||||
title="Hover to refresh"
|
||||
onClick={() => {
|
||||
if (ollamaBusy || ollamaStatus === "unavailable" || status === "Offline") return;
|
||||
toggleOllama();
|
||||
}}
|
||||
title={ollamaStatus === "unavailable" ? "Ollama binary not found" : "Click to start/stop the AI"}
|
||||
/>
|
||||
{showStatusTooltip && (
|
||||
<div style={{
|
||||
@@ -230,35 +249,15 @@ function App() {
|
||||
Synapse: <strong>{status}</strong>
|
||||
</div>
|
||||
<div style={{ color: ollamaStatus === "unavailable" ? "#f44336" : ollamaStatus === "running" ? "#4caf50" : "#aaa" }}>
|
||||
Ollama: <strong>{ollamaStatus}</strong>
|
||||
{/* Naming the phase matters: nearly all of the wait is the model
|
||||
being read off disk (GBs), not the server booting. */}
|
||||
Ollama: <strong>{ollamaBusy ? (ollamaStatus === "running" ? "stopping…" : "loading model…") : ollamaStatus}</strong>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Manual AI start/stop — Ollama is off until the user turns it on */}
|
||||
<button
|
||||
onClick={toggleOllama}
|
||||
disabled={ollamaBusy || ollamaStatus === "unavailable" || status === "Offline"}
|
||||
title={ollamaStatus === "unavailable" ? "Ollama binary not found" : "Start or stop the AI"}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "0.4rem 0.6rem",
|
||||
background: ollamaStatus === "running" ? "#2a1a1a" : "#152a15",
|
||||
color: ollamaStatus === "running" ? "#ff8a80" : "#8aff8a",
|
||||
border: "1px solid " + (ollamaStatus === "running" ? "#5a2a2a" : "#2a5a2a"),
|
||||
borderRadius: "6px",
|
||||
cursor: (ollamaBusy || ollamaStatus === "unavailable" || status === "Offline") ? "not-allowed" : "pointer",
|
||||
fontSize: "0.78rem",
|
||||
opacity: (ollamaStatus === "unavailable" || status === "Offline") ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{/* Naming the phase matters: nearly all of the wait is the model
|
||||
being read off disk (GBs), not the server booting. "…" for a
|
||||
minute reads as hung. */}
|
||||
{ollamaBusy ? (ollamaStatus === "running" ? "Stopping…" : "Loading model…")
|
||||
: ollamaStatus === "running" ? "⏹ Stop AI" : "▶ Start AI"}
|
||||
</button>
|
||||
{/* Start/Stop AI moved onto the status dot above — click it to toggle. */}
|
||||
|
||||
<nav style={{ display: "flex", flexDirection: "column", width: "100%", marginTop: "0.1rem" }}>
|
||||
{navItems.map(item => {
|
||||
@@ -269,9 +268,11 @@ function App() {
|
||||
onClick={() => setCurrentPage(item.key)}
|
||||
style={{
|
||||
padding: "0.32rem 0.5rem",
|
||||
background: isActive ? "#1c2733" : "transparent",
|
||||
color: isActive ? "#fff" : "#bbb",
|
||||
background: "transparent",
|
||||
color: isActive ? "#4aa3e0" : "#bbb",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
border: "none",
|
||||
borderLeft: `2px solid ${isActive ? "#007acc" : "transparent"}`,
|
||||
borderRadius: "5px",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.8rem",
|
||||
@@ -330,6 +331,22 @@ function App() {
|
||||
>
|
||||
⤓ Export
|
||||
</button>
|
||||
<button
|
||||
onClick={clearConversations}
|
||||
title={search ? "Delete the conversations matching this search" : "Delete all conversations"}
|
||||
disabled={conversations.length === 0}
|
||||
style={{
|
||||
padding: "0.2rem 0.5rem",
|
||||
background: "#161616",
|
||||
color: conversations.length === 0 ? "#555" : "#c66",
|
||||
border: "1px solid #2a2a2a",
|
||||
borderRadius: "6px",
|
||||
cursor: conversations.length === 0 ? "default" : "pointer",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
🗑 Clear
|
||||
</button>
|
||||
<button
|
||||
onClick={startNewChat}
|
||||
title="New chat"
|
||||
@@ -385,8 +402,9 @@ function App() {
|
||||
marginBottom: "0.3rem",
|
||||
borderRadius: "6px",
|
||||
cursor: "pointer",
|
||||
background: isActive ? "#1c2a3a" : (isHover ? "#161616" : "transparent"),
|
||||
border: `1px solid ${isActive ? "#2a4a6a" : "transparent"}`,
|
||||
background: isHover ? "#161616" : "transparent",
|
||||
border: "1px solid transparent",
|
||||
borderLeft: `2px solid ${isActive ? "#007acc" : "transparent"}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "0.4rem",
|
||||
@@ -395,7 +413,8 @@ function App() {
|
||||
<div style={{ flexGrow: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
fontSize: "0.82rem",
|
||||
color: isActive ? "#eee" : "#ccc",
|
||||
color: isActive ? "#4aa3e0" : "#ccc",
|
||||
fontWeight: isActive ? 600 : 400,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Network
|
||||
Comment=Manage your network connections
|
||||
Icon=nm-device-wireless
|
||||
TryExec=nm-applet
|
||||
Exec=nm-applet
|
||||
Hidden=true
|
||||
X-XFCE-Autostart-Override=true
|
||||
Reference in New Issue
Block a user