From 48bf0a1ffc8dd7e7e28d0a3760a88423048798ab Mon Sep 17 00:00:00 2001 From: Hy4ri Date: Tue, 16 Jun 2026 00:32:10 +0300 Subject: [PATCH] feat: add full chat slash commands support (/help, /status, /sessions, /stats) --- PROGRESS_STATUS.md | 2 + .../hermescontrol/ui/chat/ChatViewModel.kt | 125 ++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/PROGRESS_STATUS.md b/PROGRESS_STATUS.md index 1bd07ab4..62332560 100644 --- a/PROGRESS_STATUS.md +++ b/PROGRESS_STATUS.md @@ -34,6 +34,8 @@ This document tracks the completed features and the pending backlog for the Herm * **System View (`SystemScreen`)**: Diagnostics doctor reports (`GET /api/ops/doctor`) and backups trigger (`POST /api/ops/backup`). * **Kanban View (`KanbanScreen`)**: Full task boards, task columns (To Do, In Progress, Done), new tasks dialog, and task movement with optimistic updates. * **Navigation Drawer**: Material3 navigation drawer using Navigation3 keys to switch between all screens. +* **Chat Slash Commands**: Local interception and execution of custom commands (`/help`, `/status`, `/sessions`, `/stats` / `/system`, `/new`, `/stop` / `/interrupt`) displaying results inline. + --- diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt index 49b5c0fd..fc5f10eb 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatViewModel.kt @@ -310,6 +310,12 @@ class ChatViewModel : ViewModel() { if (text.isBlank()) return val sessionId = _uiState.value.currentSessionId ?: return + val trimmed = text.trim() + if (trimmed.startsWith("/", ignoreCase = true)) { + handleSlashCommand(trimmed) + return + } + // Add user message to UI val userMessage = ChatMessage( @@ -329,6 +335,125 @@ class ChatViewModel : ViewModel() { } } + private fun handleSlashCommand(command: String) { + val userMsg = ChatMessage(role = MessageRole.USER, content = command) + _uiState.update { it.copy(messages = it.messages + userMsg) } + + val parts = command.split(" ") + val cmd = parts[0].lowercase() + + when (cmd) { + "/stop", "/interrupt" -> { + interruptSession() + } + + "/new" -> { + createNewSession() + } + + "/help" -> { + val helpText = + """ + **Available Commands:** + • `/help` - Show this help menu + • `/status` - Check gateway and platform status + • `/sessions` - List all chat sessions + • `/stats` or `/system` - Check system resource usage + • `/new` - Create a new chat session + • `/stop` or `/interrupt` - Interrupt the active run + """.trimIndent() + addAssistantMessage(helpText) + } + + "/status" -> { + viewModelScope.launch { + try { + val response = withContext(Dispatchers.IO) { ApiClient.hermesApi.getStatus() } + if (response.isSuccessful && response.body() != null) { + val body = response.body()!! + val platformsStr = + body.gateway_platforms?.entries?.joinToString("\n") { (k, v) -> + " • **$k**: ${v.state ?: "Unknown"}${if (v.error_code != null) " (Error: ${v.error_code})" else ""}" + } ?: "No active platforms" + + val statusText = + """ + **Hermes Gateway Status:** + • **Version:** ${body.version ?: "Unknown"} + • **Gateway Running:** ${body.gateway_running ?: false} + • **Active Sessions:** ${body.active_sessions ?: 0} + • **Auth Required:** ${body.auth_required ?: false} + + **Platforms:** + $platformsStr + """.trimIndent() + addAssistantMessage(statusText) + } else { + addAssistantMessage("Failed to retrieve status: HTTP ${response.code()}") + } + } catch (e: Exception) { + addAssistantMessage("Failed to retrieve status: ${e.message}") + } + } + } + + "/sessions" -> { + viewModelScope.launch { + try { + val response = withContext(Dispatchers.IO) { ApiClient.hermesApi.getSessions() } + if (response.isSuccessful && response.body() != null) { + val body = response.body()!! + val sessionsStr = + body.sessions.joinToString("\n") { s -> + "• **${s.title ?: "Untitled"}** (ID: `${s.id}`, Messages: ${s.message_count ?: 0})" + } + addAssistantMessage("**Sessions List:**\n$sessionsStr") + } else { + addAssistantMessage("Failed to list sessions: HTTP ${response.code()}") + } + } catch (e: Exception) { + addAssistantMessage("Failed to list sessions: ${e.message}") + } + } + } + + "/stats", "/system" -> { + viewModelScope.launch { + try { + val response = withContext(Dispatchers.IO) { ApiClient.hermesApi.getSystemStats() } + if (response.isSuccessful && response.body() != null) { + val body = response.body()!! + val cpuPct = body.cpuPercent?.let { String.format("%.1f%%", it) } ?: "N/A" + val memPct = body.memoryPercent?.let { String.format("%.1f%%", it) } ?: "N/A" + val uptimeVal = body.uptime ?: "N/A" + val statsText = + """ + **System Resource Stats:** + • **CPU Usage:** $cpuPct + • **Memory Usage:** $memPct + • **Uptime:** $uptimeVal + """.trimIndent() + addAssistantMessage(statsText) + } else { + addAssistantMessage("Failed to retrieve system stats: HTTP ${response.code()}") + } + } catch (e: Exception) { + addAssistantMessage("Failed to retrieve system stats: ${e.message}") + } + } + } + + else -> { + addAssistantMessage("Unknown command: `$cmd`. Type `/help` to view a list of available commands.") + } + } + } + + private fun addAssistantMessage(text: String) { + val msg = ChatMessage(role = MessageRole.ASSISTANT, content = text) + _uiState.update { it.copy(messages = it.messages + msg) } + } + fun interruptSession() { val sessionId = _uiState.value.currentSessionId ?: return viewModelScope.launch(Dispatchers.IO) {