diff --git a/app/history/[id]/page.tsx b/app/history/[id]/page.tsx index e4a0c52..3b46681 100644 --- a/app/history/[id]/page.tsx +++ b/app/history/[id]/page.tsx @@ -1,12 +1,13 @@ "use client"; import { use, useState, useRef } from "react"; -import { ArrowLeft, Send, Loader2, Eye, RefreshCw } from "lucide-react"; +import { ArrowLeft, Send, Loader2, Eye, RefreshCw, Trash2 } from "lucide-react"; import Link from "next/link"; import { useSession, useMessages } from "@/lib/hooks"; import { timeAgo, formatTokens, platformBadgeClass } from "@/lib/utils"; import { MessageBubble } from "@/components/MessageBubble"; import { useQueryClient } from "@tanstack/react-query"; +import { useRouter } from "next/navigation"; export type Verbosity = "compact" | "normal" | "full"; @@ -19,6 +20,8 @@ const VERBOSITY_OPTIONS: { id: Verbosity; label: string }[] = [ export default function SessionReplayPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params); const qc = useQueryClient(); + const router = useRouter(); + const [confirmDelete, setConfirmDelete] = useState(false); const { data: sessionData } = useSession(id); const { data: messagesData, isLoading } = useMessages(id); const [input, setInput] = useState(""); @@ -33,17 +36,15 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st // Filter based on verbosity let visibleMessages = messages.filter((m) => { - if (verbosity === "compact") return m.role === "user" || (m.role === "assistant" && m.content); - return m.role === "user" || m.role === "assistant"; - }).reverse(); - - // In compact mode, skip assistant messages that are only tool calls (no text) - if (verbosity === "compact") { - visibleMessages = visibleMessages.filter((m) => { - if (m.role === "assistant" && !m.content?.trim()) return false; + if (m.role === "user") return true; + if (m.role === "assistant") { + if (verbosity === "compact") return !!m.content?.trim(); + if (verbosity === "normal") return !!m.content?.trim() || !!m.tool_calls; + // full: show everything including tool-only and reasoning-only messages return true; - }); - } + } + return false; + }).reverse(); const toolResults = new Map(); for (const m of messages) { @@ -74,6 +75,11 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st qc.invalidateQueries({ queryKey: ["session", id] }); }; + const deleteSession = async () => { + await fetch("/api/sessions/" + id, { method: "DELETE" }); + router.push("/history"); + }; + return (
{/* Header */} @@ -96,6 +102,33 @@ export default function SessionReplayPage({ params }: { params: Promise<{ id: st
)} + {/* Delete */} + {confirmDelete ? ( +
+ + +
+ ) : ( + + )} {/* Refresh */} + {open ? ( +
+          {text}
+        
+ ) : ( +

+ {preview}{text.length > 120 ? "..." : ""} +

+ )} + + ); +} + +function ToolCallBlock({ tc, result, defaultOpen, showResult }: { tc: ToolCall; result?: { name: string; content: string }; defaultOpen: boolean; showResult: boolean }) { + const [open, setOpen] = useState(defaultOpen); let args: Record = {}; try { args = JSON.parse(tc.function.arguments); } catch {} @@ -34,8 +62,8 @@ function ToolCallBlock({ tc, result, showResult }: { tc: ToolCall; result?: { na {JSON.stringify(args, null, 2)} {showResult && result && ( -
-              {result.content.slice(0, 1000)}{result.content.length > 1000 ? "..." : ""}
+            
+              {result.content.slice(0, 2000)}{result.content.length > 2000 ? "..." : ""}
             
)} @@ -47,17 +75,17 @@ function ToolCallBlock({ tc, result, showResult }: { tc: ToolCall; result?: { na export function MessageBubble({ message, toolResults, verbosity = "normal" }: Props) { const isUser = message.role === "user"; const content = message.content || ""; + const reasoning = message.reasoning || ""; let toolCalls: ToolCall[] = []; if (message.tool_calls) { try { toolCalls = JSON.parse(message.tool_calls); } catch {} } - if (!content && toolCalls.length === 0) return null; + if (!content && toolCalls.length === 0 && !reasoning) return null; - // Compact: no tool calls at all const showTools = verbosity !== "compact" && toolCalls.length > 0; - // Full: expand tool calls and show results + const showReasoning = verbosity === "full" && reasoning; const expandTools = verbosity === "full"; return ( @@ -69,6 +97,10 @@ export function MessageBubble({ message, toolResults, verbosity = "normal" }: Pr color: isUser ? "white" : "var(--text-1)", }} > + {/* Reasoning / thinking */} + {showReasoning && } + + {/* Content */} {content && (

{verbosity === "compact" && content.length > 300 @@ -76,24 +108,30 @@ export function MessageBubble({ message, toolResults, verbosity = "normal" }: Pr : content}

)} + + {/* Tool calls */} {showTools && (
{expandTools ? ( - // Full: show all tool calls expanded with results toolCalls.map((tc) => ( - + )) ) : toolCalls.length <= 3 ? ( - // Normal: collapsed tool calls, no results toolCalls.map((tc) => ( - + )) ) : ( - // Normal with many tools: collapsed summary )}
)} + + {/* No content indicator for tool-only messages in full mode */} + {!content && showTools && verbosity === "full" && ( +
+ (tool calls only) +
+ )} ); @@ -113,7 +151,7 @@ function CollapsedTools({ toolCalls, toolResults }: { toolCalls: ToolCall[]; too {open ? : } {open && toolCalls.map((tc) => ( - + ))} ); diff --git a/deploy/deploy.sh b/deploy/deploy.sh index a2adc82..90ca711 100644 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -5,34 +5,69 @@ PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SYSTEMD_DIR="$HOME/.config/systemd/user" PORT="${1:-3100}" -echo "Building..." -cd "$PROJECT_DIR" export PATH="$HOME/.hermes/node/bin:$PATH" + +echo "→ Building..." +cd "$PROJECT_DIR" HERMES_BACKEND_URL=http://127.0.0.1:8643 npx next build -echo "Copying static assets to standalone..." +echo "→ Copying static assets..." cp -r .next/static .next/standalone/.next/static cp -r public .next/standalone/public -echo "Installing service..." +echo "→ Installing services..." mkdir -p "$SYSTEMD_DIR" -sed "s|ExecStart=.*|ExecStart=/home/hermes/.hermes/node/bin/node $PROJECT_DIR/.next/standalone/server.js|" \ - "$PROJECT_DIR/deploy/hermes-os.service" > "$SYSTEMD_DIR/hermes-os.service" -# Add port -if ! grep -q "PORT=" "$SYSTEMD_DIR/hermes-os.service"; then - sed -i "/NODE_ENV/a Environment=PORT=$PORT" "$SYSTEMD_DIR/hermes-os.service" - sed -i "/NODE_ENV/a Environment=HOSTNAME=0.0.0.0" "$SYSTEMD_DIR/hermes-os.service" -fi +cat > "$SYSTEMD_DIR/hermes-os.service" << EOF +[Unit] +Description=Hermes OS — Mobile dashboard +After=network.target + +[Service] +Type=simple +WorkingDirectory=$PROJECT_DIR +Environment=PATH=$HOME/.hermes/node/bin:/usr/local/bin:/usr/bin:/bin +Environment=HERMES_BACKEND_URL=http://127.0.0.1:8643 +Environment=HERMES_GATEWAY_URL=http://127.0.0.1:8642 +Environment=HERMES_SOUL_PATH=$HOME/.hermes/SOUL.md +Environment=NODE_ENV=production +Environment=PORT=$PORT +Environment=HOSTNAME=0.0.0.0 +ExecStart=$HOME/.hermes/node/bin/node $PROJECT_DIR/.next/standalone/server.js +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target +EOF + +cat > "$SYSTEMD_DIR/hermes-webapi.service" << EOF +[Unit] +Description=Hermes WebAPI (FastAPI) +After=network.target + +[Service] +Type=simple +WorkingDirectory=$HOME/.hermes/hermes-agent +Environment=HERMES_WEBAPI_HOST=0.0.0.0 +Environment=HERMES_CORS_ORIGINS=* +ExecStart=$HOME/.hermes/hermes-agent/venv/bin/python -m webapi +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=default.target +EOF systemctl --user daemon-reload +systemctl --user enable hermes-os hermes-webapi systemctl --user restart hermes-os -systemctl --user enable hermes-os sleep 3 -if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT" | grep -q 200; then - echo "✅ Hermes OS running on port $PORT" +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT" 2>/dev/null || echo "000") +if [ "$STATUS" = "200" ]; then + echo "✅ Hermes OS running on http://0.0.0.0:$PORT" else - echo "⚠️ Service started but health check failed" - systemctl --user status hermes-os --no-pager + echo "⚠️ Health check returned $STATUS" + systemctl --user status hermes-os --no-pager | head -10 fi