From 4549410a45ae29c6cedd96aa1a1d1c6b4104eb8b Mon Sep 17 00:00:00 2001 From: M57 <41645758+Hy4ri@users.noreply.github.com> Date: Fri, 21 Aug 2026 00:07:37 +0300 Subject: [PATCH] chore(ui): replace emoji with Material icons across chat UI (#944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore(ui): replace emoji with Material icons across chat UI Removes emoji glyphs and swaps them for real Material icons / plain text: - ToolSchemaRegistry: iconEmoji String -> ImageVector icon for all ~35 tools; summaryPrefix emoji stripped (terminal keeps '$ '); consumer renders Icon(). - ToolBubble: collapsed summary now shows Icon(config.icon) instead of an emoji Text prefix; web-search url drops the link emoji; warning badge uses Icons.Filled.Warning instead of a 'โš ' Text glyph. - ChatBubble SelfImprovementReviewCard: ๐Ÿ› /๐Ÿง  String -> Build/Psychology Icon. - ChatViewModel / ChatWsEventReducer: drop โš ๏ธ/โœ… prefixes from user-facing status/error messages (severity already conveyed by color/styling). - SubagentInspectionSheet: โœ…/๐Ÿ”„/โŒ/โญ• status markers -> CheckCircle/Autorenew/ Cancel/RadioButtonUnchecked (tinted); ElectricBolt section headers -> FormatListBulleted + Groups. - strings.xml (en/zh/ko): drop 'โœ“' from the Copied label. ktlint clean; compileDebugKotlin SUCCESSFUL. Remaining chat files (ToolViewBuilder, ChatComposer, MessageCards, ChatReactions, ComposerToolbar, FullBleedAgentMessage) tracked in follow-up commits on this branch. * test: align unit tests with emoji-free summary + status messages composeToolSummaryLines dropped the emoji parameter and no longer prepends an emoji glyph to the collapsed summary line, so update ToolSummaryLinesTest call sites + expected strings accordingly. SlashCommandDispatchRpcTest asserted the old 'โš ๏ธ /cmd: ...' error messages; those prefixes were removed in the UI cleanup, so drop them from the expected strings. ChatWsEventReducerTest emoji usages are model-output fixtures, not assertions on changed code paths โ€” left untouched. --- .../com/m57/hermescontrol/data/ws/WsEvent.kt | 2 +- .../m57/hermescontrol/ui/chat/ChatBubble.kt | 13 +- .../hermescontrol/ui/chat/ChatViewModel.kt | 12 +- .../ui/chat/ChatWsEventReducer.kt | 6 +- .../m57/hermescontrol/ui/chat/ToolBubble.kt | 58 +++++---- .../ui/chat/ToolSchemaRegistry.kt | 121 ++++++++++-------- app/src/main/res/values-ko/strings.xml | 2 +- app/src/main/res/values-zh/strings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- .../ui/chat/SlashCommandDispatchRpcTest.kt | 4 +- .../ui/chat/ToolSummaryLinesTest.kt | 34 ++--- 11 files changed, 148 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/com/m57/hermescontrol/data/ws/WsEvent.kt b/app/src/main/java/com/m57/hermescontrol/data/ws/WsEvent.kt index b04890cf..fd10205a 100644 --- a/app/src/main/java/com/m57/hermescontrol/data/ws/WsEvent.kt +++ b/app/src/main/java/com/m57/hermescontrol/data/ws/WsEvent.kt @@ -143,7 +143,7 @@ sealed class WsEvent { /** * Self-improvement background review summary event. * Emitted when background review patches a skill or saves a memory. - * Payload: `{ text: "๐Ÿ’พ Self-improvement review: ..." }` + * Payload: `{ text: "Self-improvement review: ..." }` */ data class ReviewSummary( val text: String, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt index c154933b..241bd522 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatBubble.kt @@ -25,10 +25,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.InsertDriveFile +import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Download +import androidx.compose.material.icons.filled.Psychology import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -51,6 +53,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalConfiguration @@ -65,7 +68,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.m57.hermescontrol.R import com.m57.hermescontrol.data.model.Attachment import com.m57.hermescontrol.theme.DarkOnSurface @@ -257,7 +259,7 @@ private fun SelfImprovementReviewCard( val isSkill = cleanText.contains("skill", ignoreCase = true) || cleanText.contains("SKILL.md", ignoreCase = true) - val icon = if (isSkill) "๐Ÿ› " else "๐Ÿง " + val icon: ImageVector = if (isSkill) Icons.Filled.Build else Icons.Filled.Psychology val title = if (isSkill) { "Self-Improvement Review โ€ข Skill Patched" @@ -279,7 +281,12 @@ private fun SelfImprovementReviewCard( ) { Column(modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { - Text(text = icon, fontSize = 16.sp) + Icon( + imageVector = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(18.dp), + ) Spacer(Modifier.width(8.dp)) Text( text = title, 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 59bf41fa..62dfbfd4 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 @@ -1112,7 +1112,7 @@ class ChatViewModel( val map = result as? Map<*, *> val resolved = (map?.get("resolved") as? Number)?.toInt() ?: 0 if (resolved > 0) { - addSystemMessage("โœ… Approval submitted") + addSystemMessage("Approval submitted") } } } @@ -1308,7 +1308,7 @@ class ChatViewModel( } catch (e: Exception) { Log.e(TAG, "Failed to upload attachment ${attachment.name}", e) _uiState.update { - it.copy(errorMessage = "โš ๏ธ Upload failed: ${attachment.name}") + it.copy(errorMessage = "Upload failed: ${attachment.name}") } } } @@ -1424,7 +1424,7 @@ class ChatViewModel( // (before any RPC fires) with a clear message instead of a doomed call. if (CommandBlocklist.contains(command)) { addAssistantMessage( - "โš ๏ธ ${command.split(" ", limit = 2)[0]} is not supported on mobile", + "${command.split(" ", limit = 2)[0]} is not supported on mobile", ) return } @@ -1608,11 +1608,11 @@ class ChatViewModel( val output = (result as? Map<*, *>)?.get("output") as? String if (!output.isNullOrBlank()) addAssistantMessage(output) } catch (e2: HermesWsClient.HermesRpcException) { - addAssistantMessage("โš ๏ธ /$name: ${e2.message}") + addAssistantMessage("/$name: ${e2.message}") } } else { // Legit error from command.dispatch (busy, no history, etc.) - addAssistantMessage("โš ๏ธ /$name: ${e.message}") + addAssistantMessage("/$name: ${e.message}") } } } @@ -3315,7 +3315,7 @@ class ChatViewModel( private fun handleApprovalRequest(event: WsEvent.ApprovalRequest) { val description = event.description ?: event.command ?: "Unknown command" - val content = "โš ๏ธ **Approval Required**\n$description" + val content = "**Approval Required**\n$description" val msg = ChatMessage( role = MessageRole.SYSTEM, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatWsEventReducer.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatWsEventReducer.kt index 7e1cd2d3..7bf64241 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatWsEventReducer.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ChatWsEventReducer.kt @@ -682,7 +682,7 @@ object ChatWsEventReducer { state = state.copy( isLoading = false, - errorMessage = "โš ๏ธ Backend error: ${event.message ?: "Unknown gateway error"}", + errorMessage = "Backend error: ${event.message ?: "Unknown gateway error"}", ), streamingState = streamingState, ) @@ -700,8 +700,8 @@ object ChatWsEventReducer { when (val label = event.data?.get("label")) { is String -> label else -> event.data?.get("name") as? String - }?.let { "โœ… Background job finished: $it" } - ?: "โœ… Background job finished" + }?.let { "Background job finished: $it" } + ?: "Background job finished" return ReducerResult( state = state.copy(backgroundCompleteMessage = message), streamingState = streamingState, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt index 6096fe39..d2e0a705 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolBubble.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons @@ -26,6 +27,7 @@ import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Error +import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator @@ -79,7 +81,6 @@ import kotlinx.coroutines.launch internal fun composeToolSummaryLines( view: ToolView, toolName: String?, - emoji: String, ): Pair? { val titleIsGeneric = view.title.equals(ToolViewBuilder.genericTitleFor(toolName), ignoreCase = true) val subtitle = view.subtitle.takeIf { it.isNotBlank() && it != view.title } @@ -89,9 +90,8 @@ internal fun composeToolSummaryLines( if (titleIsGeneric) { firstLine = buildString { - append(emoji) if (subtitle != null) { - append(" $subtitle") + append(subtitle) } view.countLabel?.let { append(" ($it)") } } @@ -99,13 +99,13 @@ internal fun composeToolSummaryLines( } else { firstLine = buildString { - append("$emoji ${view.title}") + append(view.title) view.countLabel?.let { append(" ($it)") } } secondLine = subtitle } - return firstLine.takeIf { it.trim() != emoji }?.let { it to secondLine } + return firstLine.takeIf { it.isNotBlank() }?.let { it to secondLine } } @Composable @@ -180,21 +180,32 @@ internal fun ToolBubble( SecurityRiskChip(riskData, contentColor) } - // โ”€โ”€ Collapsed summary: emoji + title, then subtitle โ”€โ”€ + // โ”€โ”€ Collapsed summary: icon + title, then subtitle โ”€โ”€ if (!expanded && view != null) { - composeToolSummaryLines(view, message.toolName, config.iconEmoji)?.let { (firstLine, secondLine) -> - Text( - text = firstLine, - style = - MaterialTheme.typography.bodySmall.copy( - color = contentColor.copy(alpha = 0.7f), - fontFamily = FontFamily.Monospace, - fontSize = 11.sp, - ), - maxLines = 1, - overflow = TextOverflow.Ellipsis, + composeToolSummaryLines(view, message.toolName)?.let { (firstLine, secondLine) -> + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 4.dp, start = 22.dp), - ) + ) { + Icon( + imageVector = config.icon, + contentDescription = null, + tint = contentColor.copy(alpha = 0.7f), + modifier = Modifier.size(13.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = firstLine, + style = + MaterialTheme.typography.bodySmall.copy( + color = contentColor.copy(alpha = 0.7f), + fontFamily = FontFamily.Monospace, + fontSize = 11.sp, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } if (secondLine != null) { Text( text = secondLine, @@ -450,7 +461,7 @@ private fun ExpandedToolContent( } if (hit.url.isNotEmpty()) { Text( - text = "๐Ÿ”— ${hit.url}", + text = hit.url, style = MaterialTheme.typography.bodySmall.copy( color = contentColor.copy(alpha = 0.7f), @@ -567,10 +578,11 @@ internal fun SecurityRiskChip( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), ) { - Text( - text = "โš ", - style = MaterialTheme.typography.labelSmall, - color = chipColor, + Icon( + imageVector = Icons.Filled.Warning, + contentDescription = null, + tint = chipColor, + modifier = Modifier.size(14.dp), ) Text( text = label, diff --git a/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolSchemaRegistry.kt b/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolSchemaRegistry.kt index 51f4e43c..067ff205 100644 --- a/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolSchemaRegistry.kt +++ b/app/src/main/java/com/m57/hermescontrol/ui/chat/ToolSchemaRegistry.kt @@ -1,5 +1,36 @@ package com.m57.hermescontrol.ui.chat +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.MenuBook +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.material.icons.automirrored.filled.VolumeUp +import androidx.compose.material.icons.filled.AccountTree +import androidx.compose.material.icons.filled.AutoStories +import androidx.compose.material.icons.filled.Build +import androidx.compose.material.icons.filled.ChatBubble +import androidx.compose.material.icons.filled.Checklist +import androidx.compose.material.icons.filled.Computer +import androidx.compose.material.icons.filled.Description +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Forum +import androidx.compose.material.icons.filled.Image +import androidx.compose.material.icons.filled.Keyboard +import androidx.compose.material.icons.filled.Language +import androidx.compose.material.icons.filled.Memory +import androidx.compose.material.icons.filled.Movie +import androidx.compose.material.icons.filled.Photo +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Psychology +import androidx.compose.material.icons.filled.Public +import androidx.compose.material.icons.filled.Schedule +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Terminal +import androidx.compose.material.icons.filled.ThumbUp +import androidx.compose.material.icons.filled.TouchApp +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.ui.graphics.vector.ImageVector + /** * Display configuration for a Hermes tool โ€” controls the summary line, * icon, and which arg field is shown when the bubble is collapsed. @@ -11,10 +42,10 @@ data class ToolDisplayConfig( val name: String, /** Key into the `args` dict for the one-line summary (e.g. "command" for terminal). */ val summaryArgKey: String? = null, - /** Prefix for the summary line (e.g. "$ " for terminal, "๐Ÿ“„ " for read_file). */ + /** Prefix for the summary line (e.g. "$ " for terminal). Emoji-free. */ val summaryPrefix: String = "", - /** Emoji icon for this tool type. */ - val iconEmoji: String = "๐Ÿ”ง", + /** Material icon for this tool type. */ + val icon: ImageVector = Icons.Filled.Build, ) /** Maps tool names to their display config. Ordered by expected frequency of use. */ @@ -26,240 +57,230 @@ object ToolSchemaRegistry { name = "terminal", summaryArgKey = "command", summaryPrefix = "$ ", - iconEmoji = "๐Ÿ’ป", + icon = Icons.Filled.Terminal, ), "read_file" to ToolDisplayConfig( name = "read_file", summaryArgKey = "path", - summaryPrefix = "๐Ÿ“„ ", - iconEmoji = "๐Ÿ“„", + icon = Icons.Filled.Description, ), "write_file" to ToolDisplayConfig( name = "write_file", summaryArgKey = "path", - summaryPrefix = "โœ๏ธ ", - iconEmoji = "โœ๏ธ", + icon = Icons.Filled.Edit, ), "patch" to ToolDisplayConfig( name = "patch", summaryArgKey = "path", - summaryPrefix = "๐Ÿ”ง ", - iconEmoji = "๐Ÿ”ง", + icon = Icons.Filled.Build, ), "search_files" to ToolDisplayConfig( name = "search_files", summaryArgKey = "pattern", - summaryPrefix = "๐Ÿ” ", - iconEmoji = "๐Ÿ”", + icon = Icons.Filled.Search, ), "web_search" to ToolDisplayConfig( name = "web_search", summaryArgKey = "query", - summaryPrefix = "๐ŸŒ ", - iconEmoji = "๐ŸŒ", + icon = Icons.Filled.Public, ), "browser_navigate" to ToolDisplayConfig( name = "browser_navigate", summaryArgKey = "url", - summaryPrefix = "๐ŸŒ ", - iconEmoji = "๐ŸŒ", + icon = Icons.Filled.Language, ), "browser_click" to ToolDisplayConfig( name = "browser_click", summaryArgKey = "ref", - summaryPrefix = "๐Ÿ–ฑ ", - iconEmoji = "๐Ÿ–ฑ", + icon = Icons.Filled.TouchApp, ), "browser_snapshot" to ToolDisplayConfig( name = "browser_snapshot", summaryArgKey = null, - iconEmoji = "๐Ÿ“‹", + icon = Icons.Filled.Photo, ), "clarify" to ToolDisplayConfig( name = "clarify", summaryArgKey = "question", - summaryPrefix = "๐Ÿ’ฌ ", - iconEmoji = "๐Ÿ’ฌ", + icon = Icons.Filled.ChatBubble, ), "delegate_task" to ToolDisplayConfig( name = "delegate_task", summaryArgKey = "goal", - summaryPrefix = "๐Ÿ”„ ", - iconEmoji = "๐Ÿ”„", + icon = Icons.Filled.AccountTree, ), "execute_code" to ToolDisplayConfig( name = "execute_code", summaryArgKey = "code", - summaryPrefix = "โ–ถ๏ธ ", - iconEmoji = "โ–ถ๏ธ", + icon = Icons.Filled.PlayArrow, ), "todo" to ToolDisplayConfig( name = "todo", summaryArgKey = null, - iconEmoji = "๐Ÿ“‹", + icon = Icons.Filled.Checklist, ), "fact_store" to ToolDisplayConfig( name = "fact_store", summaryArgKey = null, - iconEmoji = "๐Ÿง ", + icon = Icons.Filled.Psychology, ), "session_search" to ToolDisplayConfig( name = "session_search", summaryArgKey = null, - iconEmoji = "๐Ÿ”", + icon = Icons.Filled.Search, ), // โ”€โ”€ Action-based tools โ”€โ”€ "cronjob" to ToolDisplayConfig( name = "cronjob", summaryArgKey = "action", - iconEmoji = "๐Ÿ”„", + icon = Icons.Filled.Schedule, ), "memory" to ToolDisplayConfig( name = "memory", summaryArgKey = "action", - iconEmoji = "๐Ÿ’พ", + icon = Icons.Filled.Memory, ), "fact_feedback" to ToolDisplayConfig( name = "fact_feedback", summaryArgKey = "action", - iconEmoji = "๐Ÿ‘", + icon = Icons.Filled.ThumbUp, ), "process" to ToolDisplayConfig( name = "process", summaryArgKey = "action", - iconEmoji = "โš™๏ธ", + icon = Icons.Filled.Settings, ), "skill_manage" to ToolDisplayConfig( name = "skill_manage", summaryArgKey = "action", - iconEmoji = "๐Ÿ› ๏ธ", + icon = Icons.Filled.Build, ), // โ”€โ”€ Web / Browser tools โ”€โ”€ "web_extract" to ToolDisplayConfig( name = "web_extract", summaryArgKey = "urls", - iconEmoji = "๐Ÿ•ธ๏ธ", + icon = Icons.Filled.Language, ), "browser_type" to ToolDisplayConfig( name = "browser_type", summaryArgKey = null, - iconEmoji = "โŒจ๏ธ", + icon = Icons.Filled.Keyboard, ), "browser_cdp" to ToolDisplayConfig( name = "browser_cdp", summaryArgKey = "action", - iconEmoji = "๐Ÿ”ง", + icon = Icons.Filled.Build, ), "browser_dialog" to ToolDisplayConfig( name = "browser_dialog", summaryArgKey = "action", - iconEmoji = "๐Ÿ’ฌ", + icon = Icons.Filled.ChatBubble, ), // โ”€โ”€ Media / Vision tools โ”€โ”€ "vision_analyze" to ToolDisplayConfig( name = "vision_analyze", summaryArgKey = "image_url", - iconEmoji = "๐Ÿ‘๏ธ", + icon = Icons.Filled.Visibility, ), "text_to_speech" to ToolDisplayConfig( name = "text_to_speech", summaryArgKey = null, - iconEmoji = "๐Ÿ”Š", + icon = Icons.AutoMirrored.Filled.VolumeUp, ), "video_generate" to ToolDisplayConfig( name = "video_generate", summaryArgKey = null, - iconEmoji = "๐ŸŽฌ", + icon = Icons.Filled.Movie, ), "image_generate" to ToolDisplayConfig( name = "image_generate", summaryArgKey = null, - iconEmoji = "๐ŸŽจ", + icon = Icons.Filled.Image, ), // โ”€โ”€ Social / Messaging tools โ”€โ”€ "x_search" to ToolDisplayConfig( name = "x_search", summaryArgKey = "query", - iconEmoji = "๐•", + icon = Icons.Filled.Forum, ), "send_message" to ToolDisplayConfig( name = "send_message", summaryArgKey = null, - iconEmoji = "๐Ÿ“จ", + icon = Icons.AutoMirrored.Filled.Send, ), // โ”€โ”€ Skills tools โ”€โ”€ "skills_list" to ToolDisplayConfig( name = "skills_list", summaryArgKey = null, - iconEmoji = "๐Ÿ“š", + icon = Icons.AutoMirrored.Filled.MenuBook, ), "skill_view" to ToolDisplayConfig( name = "skill_view", summaryArgKey = "name", - iconEmoji = "๐Ÿ“–", + icon = Icons.Filled.AutoStories, ), // โ”€โ”€ On-demand tools โ”€โ”€ "tool_search" to ToolDisplayConfig( name = "tool_search", summaryArgKey = "query", - iconEmoji = "๐Ÿ”", + icon = Icons.Filled.Search, ), "tool_describe" to ToolDisplayConfig( name = "tool_describe", summaryArgKey = "name", - iconEmoji = "๐Ÿ“–", + icon = Icons.Filled.AutoStories, ), "tool_call" to ToolDisplayConfig( name = "tool_call", summaryArgKey = "name", - iconEmoji = "๐Ÿ”ง", + icon = Icons.Filled.Build, ), // โ”€โ”€ Misc โ”€โ”€ "read_terminal" to ToolDisplayConfig( name = "read_terminal", summaryArgKey = "session_id", - iconEmoji = "๐Ÿ–ฅ๏ธ", + icon = Icons.Filled.Computer, ), "computer_use" to ToolDisplayConfig( name = "computer_use", summaryArgKey = "action", - iconEmoji = "๐Ÿ–ฅ๏ธ", + icon = Icons.Filled.Computer, ), ) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 3a84c8c7..e6f35caf 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -414,7 +414,7 @@ ์ž๋™ ์„ค๋ช… ์„ค์ • ๋ช…๋ น ๋ณต์‚ฌ - ๋ณต์‚ฌ๋จ โœ“ + ๋ณต์‚ฌ๋จ ํ”„๋กœํ•„ ์ด๋ฆ„ ๋ณ€๊ฒฝ: %1$s ํ”„๋กœํ•„ ์‚ญ์ œ: %1$s ์„ค์ • ๋ช…๋ น: %1$s diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 297a9525..95c33248 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -557,7 +557,7 @@ ่‡ชๅŠจๆ่ฟฐ ๅฎ‰่ฃ…ๅ‘ฝไปค ๅคๅˆถ - ๅทฒๅคๅˆถ โœ“ + ๅทฒๅคๅˆถ ้‡ๅ‘ฝๅ้…็ฝฎ๏ผš%1$s ๅˆ ้™ค้…็ฝฎ๏ผš%1$s ๅฎ‰่ฃ…ๅ‘ฝไปค๏ผš%1$s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e33cb24f..3e261d36 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -643,7 +643,7 @@ Auto-Describe Setup Command Copy - Copied โœ“ + Copied Rename Profile: %1$s Delete Profile: %1$s Setup Command: %1$s diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt index 350f666a..79216464 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/SlashCommandDispatchRpcTest.kt @@ -299,7 +299,7 @@ class SlashCommandDispatchRpcTest { val last = vm.uiState.value.messages .lastOrNull() - assertEquals("โš ๏ธ /help: session busy โ€” /interrupt first", last?.content) + assertEquals("/help: session busy โ€” /interrupt first", last?.content) } @Test @@ -422,7 +422,7 @@ class SlashCommandDispatchRpcTest { val last = vm.uiState.value.messages .lastOrNull() - assertEquals("โš ๏ธ /status: slash worker start failed: boom", last?.content) + assertEquals("/status: slash worker start failed: boom", last?.content) } @Test diff --git a/app/src/test/java/com/m57/hermescontrol/ui/chat/ToolSummaryLinesTest.kt b/app/src/test/java/com/m57/hermescontrol/ui/chat/ToolSummaryLinesTest.kt index 2666c96d..8f89f761 100644 --- a/app/src/test/java/com/m57/hermescontrol/ui/chat/ToolSummaryLinesTest.kt +++ b/app/src/test/java/com/m57/hermescontrol/ui/chat/ToolSummaryLinesTest.kt @@ -24,69 +24,69 @@ class ToolSummaryLinesTest { ) @Test - fun `generic title merges into the emoji line`() { - val lines = composeToolSummaryLines(view("Fact Store", "Fact added (ID: 5)"), "fact_store", "๐Ÿง ") + fun `generic title merges into the summary line`() { + val lines = composeToolSummaryLines(view("Fact Store", "Fact added (ID: 5)"), "fact_store") - assertEquals("๐Ÿง  Fact added (ID: 5)", lines!!.first) + assertEquals("Fact added (ID: 5)", lines!!.first) assertNull(lines.second) } @Test fun `todo shows counts on one line`() { - val lines = composeToolSummaryLines(view("Todo", "2 items (1 pending, 1 completed)"), "todo", "โœ…") + val lines = composeToolSummaryLines(view("Todo", "2 items (1 pending, 1 completed)"), "todo") - assertEquals("โœ… 2 items (1 pending, 1 completed)", lines!!.first) + assertEquals("2 items (1 pending, 1 completed)", lines!!.first) assertNull(lines.second) } @Test fun `descriptive title stays with subtitle on line two`() { - val lines = composeToolSummaryLines(view("Searched \"cats\"", "Query: cats", "3 results"), "web_search", "๐ŸŒ") + val lines = composeToolSummaryLines(view("Searched \"cats\"", "Query: cats", "3 results"), "web_search") - assertEquals("๐ŸŒ Searched \"cats\" (3 results)", lines!!.first) + assertEquals("Searched \"cats\" (3 results)", lines!!.first) assertEquals("Query: cats", lines.second) } @Test fun `terminal title stays with output preview on line two`() { - val lines = composeToolSummaryLines(view("Ran ls", "wiring.tsx"), "terminal", "๐Ÿ’ป") + val lines = composeToolSummaryLines(view("Ran ls", "wiring.tsx"), "terminal") - assertEquals("๐Ÿ’ป Ran ls", lines!!.first) + assertEquals("Ran ls", lines!!.first) assertEquals("wiring.tsx", lines.second) } @Test fun `read file title stays with path on line two`() { - val lines = composeToolSummaryLines(view("Read a.kt L10-14", "/repo/src/a.kt"), "read_file", "๐Ÿ“„") + val lines = composeToolSummaryLines(view("Read a.kt L10-14", "/repo/src/a.kt"), "read_file") - assertEquals("๐Ÿ“„ Read a.kt L10-14", lines!!.first) + assertEquals("Read a.kt L10-14", lines!!.first) assertEquals("/repo/src/a.kt", lines.second) } @Test fun `generic title with no subtitle and no count is hidden`() { - assertNull(composeToolSummaryLines(view("Fact Store"), "fact_store", "๐Ÿง ")) + assertNull(composeToolSummaryLines(view("Fact Store"), "fact_store")) } @Test fun `unknown tool generic title merges with engine first line`() { - val lines = composeToolSummaryLines(view("Weird Tool", "- Title: Build report"), "weird_tool", "๐Ÿ”ง") + val lines = composeToolSummaryLines(view("Weird Tool", "- Title: Build report"), "weird_tool") - assertEquals("๐Ÿ”ง - Title: Build report", lines!!.first) + assertEquals("- Title: Build report", lines!!.first) assertNull(lines.second) } @Test fun `null tool name resolves to the generic tool title`() { - val lines = composeToolSummaryLines(view("Tool", "raw payload text"), null, "๐Ÿ”ง") + val lines = composeToolSummaryLines(view("Tool", "raw payload text"), null) - assertEquals("๐Ÿ”ง raw payload text", lines!!.first) + assertEquals("raw payload text", lines!!.first) assertNull(lines.second) } @Test fun `subtitle identical to title is not duplicated`() { - val lines = composeToolSummaryLines(view("Fact Store", "Fact Store"), "fact_store", "๐Ÿง ") + val lines = composeToolSummaryLines(view("Fact Store", "Fact Store"), "fact_store") assertNull(lines) }