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"
|
"$AUTOSTART/nexus-popup.desktop"
|
||||||
cp -f "$NEXUS/management/autostart/nm-tray.desktop" "$AUTOSTART/nm-tray.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"
|
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
|
# Suppress blueman-applet autostart (both the system blueman.desktop and the
|
||||||
# user blueman-applet.desktop). blueman always forces its own tray icon, which
|
# 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
|
# 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 = [
|
const navItems = [
|
||||||
{ key: "chatbot", icon: "💬", label: "Chat" },
|
{ key: "chatbot", icon: "💬", label: "Chat" },
|
||||||
{ key: "playbook", icon: "📖", label: "Playbooks" },
|
{ 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" }}>
|
<div style={{ background: "#111", color: "#eee", height: "100vh", overflow: "hidden", fontFamily: "system-ui", display: "flex" }}>
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside style={{
|
<aside style={{
|
||||||
width: "220px",
|
width: "400px",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
background: "#0a0a0a",
|
background: "#0a0a0a",
|
||||||
borderRight: "1px solid #333",
|
borderRight: "1px solid #333",
|
||||||
@@ -201,7 +216,7 @@ function App() {
|
|||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
background: status === "Offline" || ollamaStatus === "unavailable" ? "#f44336"
|
background: status === "Offline" || ollamaStatus === "unavailable" ? "#f44336"
|
||||||
: ollamaStatus === "running" ? "#4caf50" : "#ff9800",
|
: ollamaStatus === "running" ? "#4caf50" : "#ff9800",
|
||||||
cursor: "pointer",
|
cursor: (ollamaBusy || ollamaStatus === "unavailable" || status === "Offline") ? "not-allowed" : "pointer",
|
||||||
transition: "all 0.3s"
|
transition: "all 0.3s"
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
@@ -209,7 +224,11 @@ function App() {
|
|||||||
setShowStatusTooltip(true);
|
setShowStatusTooltip(true);
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => setShowStatusTooltip(false)}
|
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 && (
|
{showStatusTooltip && (
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -230,35 +249,15 @@ function App() {
|
|||||||
Synapse: <strong>{status}</strong>
|
Synapse: <strong>{status}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: ollamaStatus === "unavailable" ? "#f44336" : ollamaStatus === "running" ? "#4caf50" : "#aaa" }}>
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Manual AI start/stop — Ollama is off until the user turns it on */}
|
{/* Start/Stop AI moved onto the status dot above — click it to toggle. */}
|
||||||
<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>
|
|
||||||
|
|
||||||
<nav style={{ display: "flex", flexDirection: "column", width: "100%", marginTop: "0.1rem" }}>
|
<nav style={{ display: "flex", flexDirection: "column", width: "100%", marginTop: "0.1rem" }}>
|
||||||
{navItems.map(item => {
|
{navItems.map(item => {
|
||||||
@@ -269,9 +268,11 @@ function App() {
|
|||||||
onClick={() => setCurrentPage(item.key)}
|
onClick={() => setCurrentPage(item.key)}
|
||||||
style={{
|
style={{
|
||||||
padding: "0.32rem 0.5rem",
|
padding: "0.32rem 0.5rem",
|
||||||
background: isActive ? "#1c2733" : "transparent",
|
background: "transparent",
|
||||||
color: isActive ? "#fff" : "#bbb",
|
color: isActive ? "#4aa3e0" : "#bbb",
|
||||||
|
fontWeight: isActive ? 600 : 400,
|
||||||
border: "none",
|
border: "none",
|
||||||
|
borderLeft: `2px solid ${isActive ? "#007acc" : "transparent"}`,
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
fontSize: "0.8rem",
|
fontSize: "0.8rem",
|
||||||
@@ -330,6 +331,22 @@ function App() {
|
|||||||
>
|
>
|
||||||
⤓ Export
|
⤓ Export
|
||||||
</button>
|
</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
|
<button
|
||||||
onClick={startNewChat}
|
onClick={startNewChat}
|
||||||
title="New chat"
|
title="New chat"
|
||||||
@@ -385,8 +402,9 @@ function App() {
|
|||||||
marginBottom: "0.3rem",
|
marginBottom: "0.3rem",
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
background: isActive ? "#1c2a3a" : (isHover ? "#161616" : "transparent"),
|
background: isHover ? "#161616" : "transparent",
|
||||||
border: `1px solid ${isActive ? "#2a4a6a" : "transparent"}`,
|
border: "1px solid transparent",
|
||||||
|
borderLeft: `2px solid ${isActive ? "#007acc" : "transparent"}`,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "0.4rem",
|
gap: "0.4rem",
|
||||||
@@ -395,7 +413,8 @@ function App() {
|
|||||||
<div style={{ flexGrow: 1, minWidth: 0 }}>
|
<div style={{ flexGrow: 1, minWidth: 0 }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: "0.82rem",
|
fontSize: "0.82rem",
|
||||||
color: isActive ? "#eee" : "#ccc",
|
color: isActive ? "#4aa3e0" : "#ccc",
|
||||||
|
fontWeight: isActive ? 600 : 400,
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
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