Feat: Hermes-Terminal (ttyd) statt AnythingLLM + AnythingLLM komplett raus
Paket B des Plans. AnythingLLM war nur ein Fallback-Chat zu Hermes; ersetzt durch das echte interaktive Agent-Terminal (`hermes chat`, mit Tools/PC) als eingebettetes Web-Terminal. Box: ttyd (apt) wrappt `hermes chat`; systemd-User-Unit deploy/hermes-terminal.service (LAN-Bind eno1:7681, apt-Default-ttyd-Dienst deaktiviert). In deploy.sh verankert. Backend: config HERMES_TERMINAL_URL statt ANYTHINGLLM_URL/_REPO; agent_status liefert terminal_url/terminal_reachable; maintenance ohne _anythingllm_update; system.py Dienst-Liste zeigt "Hermes-Terminal". Frontend: neue Terminal-Seite (iframe auf ttyd) + Nav-Tab; AgentView/AgentStatusCard/nav/api auf Terminal umgestellt; SystemDrawer toter hermes-dashboard raus, hermes-webui -> hermes-terminal; Guide-Texte aktualisiert. Cleanup: deploy/hermes-webui.service + deploy/lobechat/ entfernt (LobeChat-Migration hinfaellig), HERMES_WEBUI_URL-Env raus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+106
-101
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
||||
<link rel="manifest" href="/manifest.webmanifest" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<title>Mission Control 2.0</title>
|
||||
<script type="module" crossorigin src="/assets/index-BpalJKIe.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CeszaZ7k.css">
|
||||
<script type="module" crossorigin src="/assets/index-BtXN9NH2.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-SuFuJOVE.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ModelsView } from "@/views/ModelsView"
|
||||
import { ConnectView } from "@/views/ConnectView"
|
||||
import { MemoryView } from "@/views/MemoryView"
|
||||
import { AgentView } from "@/views/AgentView"
|
||||
import { TerminalView } from "@/views/TerminalView"
|
||||
import { GuideView } from "@/views/GuideView"
|
||||
import { Placeholder } from "@/views/Placeholder"
|
||||
import { SystemDrawer } from "@/components/SystemDrawer"
|
||||
@@ -180,8 +181,9 @@ export default function App() {
|
||||
{view === "connect" && <ConnectView />}
|
||||
{view === "memory" && <MemoryView />}
|
||||
{view === "agent" && <AgentView />}
|
||||
{view === "terminal" && <TerminalView />}
|
||||
{view === "guide" && <GuideView />}
|
||||
{!["dashboard", "models", "connect", "memory", "agent", "guide"].includes(view) && (
|
||||
{!["dashboard", "models", "connect", "memory", "agent", "terminal", "guide"].includes(view) && (
|
||||
<Placeholder title={active.label} hint={active.hint} />
|
||||
)}
|
||||
</main>
|
||||
|
||||
@@ -15,8 +15,7 @@ const SERVICES = [
|
||||
{ id: "llama-swap", label: "Llama Swap", type: "system" },
|
||||
{ id: "mission-control-2", label: "Mission Control 2", type: "user" },
|
||||
{ id: "hermes-gateway", label: "Hermes Gateway", type: "user" },
|
||||
{ id: "hermes-dashboard", label: "Hermes Dashboard", type: "user" },
|
||||
{ id: "hermes-webui", label: "Hermes WebUI", type: "user" }
|
||||
{ id: "hermes-terminal", label: "Hermes Terminal", type: "user" }
|
||||
]
|
||||
|
||||
function formatBytes(bytes?: number) {
|
||||
|
||||
@@ -33,19 +33,19 @@ export function AgentStatusCard() {
|
||||
<Bot className="h-4.5 w-4.5 text-primary" />
|
||||
<h2 className="text-sm font-semibold tracking-wide uppercase text-foreground">Hermes Agent</h2>
|
||||
</div>
|
||||
{agent?.webui_url && (
|
||||
{agent?.terminal_url && (
|
||||
<a
|
||||
href={resolveExternalUrl(agent.webui_url)}
|
||||
href={resolveExternalUrl(agent.terminal_url)}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className={cn(
|
||||
"flex items-center gap-1 px-3 py-1 rounded-lg text-xs font-medium transition-all",
|
||||
agent.webui_reachable
|
||||
agent.terminal_reachable
|
||||
? "bg-primary text-primary-foreground hover:opacity-90 shadow-md shadow-primary/20"
|
||||
: "border border-border text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
<ExternalLink className="h-3 w-3" /> AnythingLLM öffnen
|
||||
<ExternalLink className="h-3 w-3" /> Terminal öffnen
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -60,10 +60,10 @@ export function AgentStatusCard() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
||||
<div className="text-[10px] text-muted-foreground uppercase font-semibold">AnythingLLM</div>
|
||||
<div className="text-[10px] text-muted-foreground uppercase font-semibold">Terminal</div>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<span className={cn("h-2 w-2 rounded-full", agent.webui_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
||||
<span className="text-xs font-medium">{agent.webui_reachable ? "Online" : "Offline"}</span>
|
||||
<span className={cn("h-2 w-2 rounded-full", agent.terminal_reachable ? "bg-emerald-500 animate-pulse" : "bg-amber-500")} />
|
||||
<span className="text-xs font-medium">{agent.terminal_reachable ? "Online" : "Offline"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -227,15 +227,15 @@ export interface SystemStatus {
|
||||
|
||||
export interface ServicesResp {
|
||||
services: { name: string; url: string; ok: boolean }[]
|
||||
links: { engine_ui: string; gateway: string; hermes_webui: string }
|
||||
links: { engine_ui: string; gateway: string; hermes_terminal: string }
|
||||
}
|
||||
|
||||
export interface ComponentUpdate {
|
||||
key: string // "hermes_agent" | "anythingllm"
|
||||
key: string // z.B. "hermes_agent"
|
||||
name: string
|
||||
current: string | null
|
||||
latest: string | null
|
||||
update: boolean | null // null = unbestimmbar (z.B. AnythingLLM-Version remote nicht abfragbar)
|
||||
update: boolean | null // null = unbestimmbar (installierte Version remote nicht abfragbar)
|
||||
reachable: boolean | null
|
||||
}
|
||||
|
||||
@@ -278,9 +278,9 @@ export interface DedupeResult {
|
||||
|
||||
export interface AgentStatus {
|
||||
gateway_url: string
|
||||
webui_url: string
|
||||
terminal_url: string
|
||||
gateway_reachable: boolean
|
||||
webui_reachable: boolean
|
||||
terminal_reachable: boolean
|
||||
home_exists: boolean
|
||||
brain_model?: string
|
||||
has_config: boolean
|
||||
|
||||
+4
-2
@@ -4,11 +4,12 @@ import {
|
||||
Brain,
|
||||
Plug,
|
||||
Bot,
|
||||
TerminalSquare,
|
||||
HelpCircle,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
export type ViewId = "dashboard" | "models" | "memory" | "connect" | "agent" | "guide"
|
||||
export type ViewId = "dashboard" | "models" | "memory" | "connect" | "agent" | "terminal" | "guide"
|
||||
|
||||
export interface NavItem {
|
||||
id: ViewId
|
||||
@@ -23,7 +24,8 @@ export const NAV: NavItem[] = [
|
||||
{ id: "models", label: "Modell-Manager", hint: "Verwalten, laden & Gateway-Routing", icon: Boxes },
|
||||
{ id: "memory", label: "Gedächtnis", hint: "Geteiltes Memory verwalten", icon: Brain },
|
||||
{ id: "connect", label: "Verbinden", hint: "IDE-/Agent-Configs erzeugen", icon: Plug },
|
||||
{ id: "agent", label: "Hermes", hint: "Agent-Status & AnythingLLM öffnen", icon: Bot },
|
||||
{ id: "agent", label: "Hermes", hint: "Agent-Status & Verdrahtung", icon: Bot },
|
||||
{ id: "terminal", label: "Terminal", hint: "Interaktives Hermes-Agent-Terminal", icon: TerminalSquare },
|
||||
{ id: "guide", label: "Anleitung", hint: "Einrichten & Vibe-Coding", icon: HelpCircle },
|
||||
]
|
||||
|
||||
|
||||
@@ -135,20 +135,20 @@ export function AgentView() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{s?.webui_url && (
|
||||
{s?.terminal_url && (
|
||||
<a
|
||||
href={resolveExternalUrl(s.webui_url)}
|
||||
href={resolveExternalUrl(s.terminal_url)}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className={cn(
|
||||
"flex h-9 px-4 items-center gap-1.5 text-xs font-semibold rounded-lg transition-all cursor-pointer shadow-md shrink-0 self-start",
|
||||
s.webui_reachable
|
||||
s.terminal_reachable
|
||||
? "bg-primary text-primary-foreground hover:opacity-90 shadow-primary/10"
|
||||
: "border border-border/60 text-muted-foreground bg-background/20"
|
||||
)}
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
<span>AnythingLLM öffnen</span>
|
||||
<span>Terminal öffnen</span>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
@@ -170,9 +170,9 @@ export function AgentView() {
|
||||
icon={Bot}
|
||||
/>
|
||||
<Tile
|
||||
label="AnythingLLM"
|
||||
ok={s.webui_reachable}
|
||||
detail="Chat-UI (AnythingLLM)"
|
||||
label="Terminal"
|
||||
ok={s.terminal_reachable}
|
||||
detail="Web-Terminal (hermes chat)"
|
||||
icon={Activity}
|
||||
/>
|
||||
<Tile
|
||||
@@ -213,9 +213,9 @@ export function AgentView() {
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
{/* Connection: WebUI to Gateway */}
|
||||
{/* Connection: Terminal to Gateway */}
|
||||
<path d={getCurvePath(w * 0.15, h * 0.5, w * 0.5, h * 0.5)} stroke="url(#cyan-to-teal)" strokeWidth="1.5" fill="none" />
|
||||
{(hoveredNode === "webui" || s.webui_reachable) && (
|
||||
{(hoveredNode === "terminal" || s.terminal_reachable) && (
|
||||
<path d={getCurvePath(w * 0.15, h * 0.5, w * 0.5, h * 0.5)} stroke="#06b6d4" strokeWidth="2.5" fill="none" className="svg-flow-path" />
|
||||
)}
|
||||
|
||||
@@ -236,14 +236,14 @@ export function AgentView() {
|
||||
<div
|
||||
className="absolute cursor-pointer select-none z-10 w-36 h-9 -translate-y-1/2 -translate-x-1/2 rounded-xl border border-border/60 bg-card/75 backdrop-blur-md flex items-center justify-center gap-1.5 px-2.5 hover:border-primary transition-all text-[10px] font-semibold text-foreground shadow shadow-black/20"
|
||||
style={{ left: "15%", top: "50%" }}
|
||||
onMouseEnter={() => setHoveredNode("webui")}
|
||||
onMouseEnter={() => setHoveredNode("terminal")}
|
||||
onMouseLeave={() => setHoveredNode(null)}
|
||||
onClick={() => s.webui_reachable && window.open(resolveExternalUrl(s.webui_url), "_blank")}
|
||||
title={s.webui_reachable ? "Klicken um AnythingLLM zu öffnen" : "AnythingLLM offline"}
|
||||
onClick={() => s.terminal_reachable && window.open(resolveExternalUrl(s.terminal_url), "_blank")}
|
||||
title={s.terminal_reachable ? "Klicken um das Hermes-Terminal zu öffnen" : "Terminal offline"}
|
||||
>
|
||||
<Activity className={cn("h-3.5 w-3.5", s.webui_reachable ? "text-emerald-400" : "text-amber-500")} />
|
||||
<span>AnythingLLM</span>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full animate-pulse ml-auto", s.webui_reachable ? "bg-emerald-500" : "bg-amber-500")} />
|
||||
<Activity className={cn("h-3.5 w-3.5", s.terminal_reachable ? "text-emerald-400" : "text-amber-500")} />
|
||||
<span>Terminal</span>
|
||||
<span className={cn("w-1.5 h-1.5 rounded-full animate-pulse ml-auto", s.terminal_reachable ? "bg-emerald-500" : "bg-amber-500")} />
|
||||
</div>
|
||||
|
||||
{/* COLUMN 2: Gateway */}
|
||||
@@ -360,7 +360,7 @@ export function AgentView() {
|
||||
PC Executor verbunden
|
||||
</div>
|
||||
<p className="text-[10px]">
|
||||
Hermes kann jetzt per Telegram oder WebUI Befehle auf TobisNicerPC ausführen. Nutze <code className="text-foreground">pc_shell</code>, <code className="text-foreground">pc_screenshot</code> oder <code className="text-foreground">pc_open</code>.
|
||||
Hermes kann jetzt per Telegram oder Terminal Befehle auf TobisNicerPC ausführen. Nutze <code className="text-foreground">pc_shell</code>, <code className="text-foreground">pc_screenshot</code> oder <code className="text-foreground">pc_open</code>.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
@@ -395,7 +395,7 @@ export function AgentView() {
|
||||
<div className="p-3.5 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1">
|
||||
<div className="text-muted-foreground/60"># Dienste manuell auf der Box prüfen:</div>
|
||||
<div className="text-primary">systemctl --user status hermes-gateway</div>
|
||||
<div className="text-primary">systemctl --user status hermes-webui</div>
|
||||
<div className="text-primary">systemctl --user status hermes-terminal</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
|
||||
@@ -328,8 +328,8 @@ export function GuideView() {
|
||||
|
||||
<ul className="text-[11px] text-muted-foreground space-y-2 list-disc pl-4 leading-relaxed">
|
||||
<li><strong>Keine Verbindung?</strong> Überprüfe im Live-Verbindungsprüfer oben, ob die Box-IP erreichbar ist. Stelle sicher, dass dein lokaler PC im selben WLAN/LAN-Netzwerk wie die Box eingeloggt ist.</li>
|
||||
<li><strong>Modell antwortet nicht?</strong> Schaue unter <strong>Diagnose</strong>, ob der Dienst `llama-swap` aktiv (grün) is. Wenn nicht, klicke daneben auf <strong>Restart</strong>.</li>
|
||||
<li><strong>Hermes Agent reagiert merkwürdig?</strong> Starte in AnythingLLM einfach eine frische Session (neuen Chat). Ältere Chats sammeln Kontext-Müll an.</li>
|
||||
<li><strong>Modell antwortet nicht?</strong> Schaue in der <strong>Zentrale</strong> unter <strong>Dienste</strong>, ob `llama-swap` aktiv (grün) ist. Wenn nicht, klicke daneben auf <strong>Restart</strong>.</li>
|
||||
<li><strong>Hermes Agent reagiert merkwürdig?</strong> Starte im <strong>Terminal</strong> eine frische Session (neuer `hermes chat`). Ältere Sessions sammeln Kontext-Müll an.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
@@ -474,7 +474,7 @@ description: Drive development with strict TDD practices
|
||||
<ul className="list-disc pl-4 space-y-1">
|
||||
<li><strong>Chat-Kontext sauber halten:</strong> Starte regelmäßig neue Chats. Zu große Historien verlangsamen den Agenten und führen zu Halluzinationen.</li>
|
||||
<li><strong>Gehirn festlegen:</strong> Konfiguriere im Gateway die Modell-Rolle <code>brain</code> für Hermes, damit er automatisch das passende Modell per Llama Swap lädt.</li>
|
||||
<li><strong>Sandbox umgehen:</strong> Erweitere Hermes' System-Prompt (AnythingLLM-Einstellungen), um Befehle über SSH an deinen Windows-PC zu leiten.</li>
|
||||
<li><strong>Sandbox umgehen:</strong> Erweitere Hermes' System-Prompt (`~/.hermes/SOUL.md` / Config), um Befehle über SSH an deinen Windows-PC zu leiten.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { TerminalSquare, ExternalLink, AlertTriangle } from "lucide-react"
|
||||
import { useAgentStatus } from "@/lib/queries"
|
||||
import { resolveExternalUrl } from "@/lib/utils"
|
||||
|
||||
export function TerminalView() {
|
||||
const { data: agent } = useAgentStatus(5_000)
|
||||
const url = agent?.terminal_url ? resolveExternalUrl(agent.terminal_url) : undefined
|
||||
const ok = agent?.terminal_reachable
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-4">
|
||||
<div className="flex flex-wrap items-end justify-between gap-3">
|
||||
<div>
|
||||
<h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent">
|
||||
Hermes Terminal
|
||||
</h1>
|
||||
<p className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
Interaktiver Agent (<code className="rounded bg-background/40 px-1 font-mono text-[11px] text-primary">hermes chat</code>) mit Tools & PC-Steuerung — „mehr als Chatten".
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground">
|
||||
<span className={`h-2 w-2 rounded-full ${ok ? "bg-emerald-500 animate-pulse" : "bg-amber-500"}`} />
|
||||
{ok ? "online" : "offline"}
|
||||
</span>
|
||||
{url && (
|
||||
<a href={url} target="_blank" rel="noopener"
|
||||
className="flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground">
|
||||
<ExternalLink className="h-3.5 w-3.5" /> In neuem Tab
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{url ? (
|
||||
<div className="relative min-h-[68vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25">
|
||||
{ok === false && (
|
||||
<div className="absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center">
|
||||
<AlertTriangle className="h-8 w-8 text-amber-400" />
|
||||
<div className="text-sm font-semibold text-amber-300">Terminal nicht erreichbar</div>
|
||||
<p className="max-w-sm text-[11px] leading-normal text-muted-foreground">
|
||||
Der <code className="font-mono text-primary">hermes-terminal</code>-Dienst (ttyd) läuft nicht.
|
||||
Auf der Box: <code className="font-mono">systemctl --user restart hermes-terminal</code>.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<iframe src={url} title="Hermes Terminal" className="h-full w-full border-0" style={{ minHeight: "68vh" }} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex min-h-[68vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground">
|
||||
<TerminalSquare className="mr-2 h-4 w-4" /> Lade Terminal…
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user