chore(chat): remove dead transcript code (#1309) (#1341)

This commit is contained in:
m57 authored and GitHub committed 2026-09-28 00:49:41 +03:00
1 parent 6a00696e68
commit 4bfc015cff
11 files changed
+19 -190

No files matched your search

@@ -133,8 +133,6 @@ class FullBleedChatAnchorTest {
isLoadingOlder = loading.value, isLoadingOlder = loading.value,
listState = listState, listState = listState,
scrollController = controller, scrollController = controller,
lastAnimatedMessageId = null,
onLastAnimatedMessageIdChange = {},
viewModel = viewModel, viewModel = viewModel,
pagingSessionId = session.value, pagingSessionId = session.value,
clarifyRequest = if (tailKey != null) ClarifyUi("Choose an option", listOf("Yes")) else null, clarifyRequest = if (tailKey != null) ClarifyUi("Choose an option", listOf("Yes")) else null,
@@ -94,8 +94,6 @@ class FullBleedChatListTest {
listState = listState, listState = listState,
scope = CoroutineScope(Dispatchers.Main.immediate), scope = CoroutineScope(Dispatchers.Main.immediate),
), ),
lastAnimatedMessageId = null,
onLastAnimatedMessageIdChange = {},
viewModel = mockk<ChatViewModel>(relaxed = true), viewModel = mockk<ChatViewModel>(relaxed = true),
clarifyRequest = clarifyRequest =
if (clarify) { if (clarify) {
@@ -272,15 +272,3 @@ class ChatMediaDelegate(
uiState.update { it.copy(openError = null) } uiState.update { it.copy(openError = null) }
} }
} }
internal fun sameMessages(
left: List<ChatMessage>,
right: List<ChatMessage>,
): Boolean =
left.size == right.size &&
left.zip(right).all { (a, b) ->
a.id == b.id &&
a.role == b.role &&
a.content == b.content &&
a.reasoningText == b.reasoningText
}
@@ -375,7 +375,6 @@ fun ChatScreen(
viewModel.consumeComposerTextRestore() viewModel.consumeComposerTextRestore()
} }
} }
var lastAnimatedMessageId by rememberSaveable { mutableStateOf<String?>(null) }
var showReloginDialog by rememberSaveable { mutableStateOf(false) } var showReloginDialog by rememberSaveable { mutableStateOf(false) }
var showSubagentInspectionSheet by rememberSaveable { mutableStateOf(false) } var showSubagentInspectionSheet by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(showSubagentInspectionSheet) { LaunchedEffect(showSubagentInspectionSheet) {
@@ -783,8 +782,6 @@ fun ChatScreen(
}, },
listState = listState, listState = listState,
scrollController = scrollController, scrollController = scrollController,
lastAnimatedMessageId = lastAnimatedMessageId,
onLastAnimatedMessageIdChange = { lastAnimatedMessageId = it },
viewModel = viewModel, viewModel = viewModel,
clarifyRequest = state.clarifyRequest.takeUnless { timelineState.isHistorical }, clarifyRequest = state.clarifyRequest.takeUnless { timelineState.isHistorical },
onRespondClarify = viewModel::respondToClarify, onRespondClarify = viewModel::respondToClarify,
@@ -74,7 +74,6 @@ import com.m57.hermescontrol.ui.chat.markdown.splitInlineMath
import com.m57.hermescontrol.util.BidiUtils import com.m57.hermescontrol.util.BidiUtils
private val URL_PATTERN = Regex("""https?://[^\s)>\[\]"'‘’]+""") private val URL_PATTERN = Regex("""https?://[^\s)>\[\]"'‘’]+""")
private val TABLE_COL_WIDTH = 160.dp
private fun bidiTextDirection(isRtl: Boolean): TextDirection = if (isRtl) TextDirection.Rtl else TextDirection.Ltr private fun bidiTextDirection(isRtl: Boolean): TextDirection = if (isRtl) TextDirection.Rtl else TextDirection.Ltr
@@ -359,13 +358,13 @@ fun MarkdownText(
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 4.dp), .padding(vertical = 4.dp),
) { ) {
com.m57.hermescontrol.ui.chat.components.InlineVideoPlayer( com.m57.hermescontrol.ui.chat.components.InlineMediaPlayer(
videoUri = resolvedUri, uri = resolvedUri,
onFullScreenClick = { showVideoDialog = true }, onFullScreenClick = { showVideoDialog = true },
) )
if (showVideoDialog) { if (showVideoDialog) {
com.m57.hermescontrol.ui.chat.components.VideoViewerDialog( com.m57.hermescontrol.ui.chat.components.MediaViewerDialog(
videoUri = resolvedUri, mediaUri = resolvedUri,
onDismissRequest = { showVideoDialog = false }, onDismissRequest = { showVideoDialog = false },
) )
} }
@@ -40,10 +40,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
*/ */
data class ToolDisplayConfig( data class ToolDisplayConfig(
val name: String, 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). Emoji-free. */
val summaryPrefix: String = "",
/** Material icon for this tool type. */ /** Material icon for this tool type. */
val icon: ImageVector = Icons.Filled.Build, val icon: ImageVector = Icons.Filled.Build,
) )
@@ -55,285 +51,238 @@ object ToolSchemaRegistry {
"terminal" to "terminal" to
ToolDisplayConfig( ToolDisplayConfig(
name = "terminal", name = "terminal",
summaryArgKey = "command",
summaryPrefix = "$ ",
icon = Icons.Filled.Terminal, icon = Icons.Filled.Terminal,
), ),
"read_file" to "read_file" to
ToolDisplayConfig( ToolDisplayConfig(
name = "read_file", name = "read_file",
summaryArgKey = "path",
icon = Icons.Filled.Description, icon = Icons.Filled.Description,
), ),
"write_file" to "write_file" to
ToolDisplayConfig( ToolDisplayConfig(
name = "write_file", name = "write_file",
summaryArgKey = "path",
icon = Icons.Filled.Edit, icon = Icons.Filled.Edit,
), ),
"patch" to "patch" to
ToolDisplayConfig( ToolDisplayConfig(
name = "patch", name = "patch",
summaryArgKey = "path",
icon = Icons.Filled.Build, icon = Icons.Filled.Build,
), ),
"search_files" to "search_files" to
ToolDisplayConfig( ToolDisplayConfig(
name = "search_files", name = "search_files",
summaryArgKey = "pattern",
icon = Icons.Filled.Search, icon = Icons.Filled.Search,
), ),
"web_search" to "web_search" to
ToolDisplayConfig( ToolDisplayConfig(
name = "web_search", name = "web_search",
summaryArgKey = "query",
icon = Icons.Filled.Public, icon = Icons.Filled.Public,
), ),
"browser_navigate" to "browser_navigate" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_navigate", name = "browser_navigate",
summaryArgKey = "url",
icon = Icons.Filled.Language, icon = Icons.Filled.Language,
), ),
"browser_click" to "browser_click" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_click", name = "browser_click",
summaryArgKey = "ref",
icon = Icons.Filled.TouchApp, icon = Icons.Filled.TouchApp,
), ),
"browser_snapshot" to "browser_snapshot" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_snapshot", name = "browser_snapshot",
summaryArgKey = null,
icon = Icons.Filled.Photo, icon = Icons.Filled.Photo,
), ),
"clarify" to "clarify" to
ToolDisplayConfig( ToolDisplayConfig(
name = "clarify", name = "clarify",
summaryArgKey = "question",
icon = Icons.Filled.ChatBubble, icon = Icons.Filled.ChatBubble,
), ),
"delegate_task" to "delegate_task" to
ToolDisplayConfig( ToolDisplayConfig(
name = "delegate_task", name = "delegate_task",
summaryArgKey = "goal",
icon = Icons.Filled.AccountTree, icon = Icons.Filled.AccountTree,
), ),
"execute_code" to "execute_code" to
ToolDisplayConfig( ToolDisplayConfig(
name = "execute_code", name = "execute_code",
summaryArgKey = "code",
icon = Icons.Filled.PlayArrow, icon = Icons.Filled.PlayArrow,
), ),
"todo" to "todo" to
ToolDisplayConfig( ToolDisplayConfig(
name = "todo", name = "todo",
summaryArgKey = null,
icon = Icons.Filled.Checklist, icon = Icons.Filled.Checklist,
), ),
"todo_list" to "todo_list" to
ToolDisplayConfig( ToolDisplayConfig(
name = "todo_list", name = "todo_list",
summaryArgKey = null,
icon = Icons.Filled.Checklist, icon = Icons.Filled.Checklist,
), ),
"fact_store" to "fact_store" to
ToolDisplayConfig( ToolDisplayConfig(
name = "fact_store", name = "fact_store",
summaryArgKey = null,
icon = Icons.Filled.Psychology, icon = Icons.Filled.Psychology,
), ),
"session_search" to "session_search" to
ToolDisplayConfig( ToolDisplayConfig(
name = "session_search", name = "session_search",
summaryArgKey = null,
icon = Icons.Filled.Search, icon = Icons.Filled.Search,
), ),
// ── Action-based tools ── // ── Action-based tools ──
"cronjob" to "cronjob" to
ToolDisplayConfig( ToolDisplayConfig(
name = "cronjob", name = "cronjob",
summaryArgKey = "action",
icon = Icons.Filled.Schedule, icon = Icons.Filled.Schedule,
), ),
"cronjob_manage" to "cronjob_manage" to
ToolDisplayConfig( ToolDisplayConfig(
name = "cronjob_manage", name = "cronjob_manage",
summaryArgKey = "action",
icon = Icons.Filled.Schedule, icon = Icons.Filled.Schedule,
), ),
"memory" to "memory" to
ToolDisplayConfig( ToolDisplayConfig(
name = "memory", name = "memory",
summaryArgKey = "action",
icon = Icons.Filled.Memory, icon = Icons.Filled.Memory,
), ),
"fact_feedback" to "fact_feedback" to
ToolDisplayConfig( ToolDisplayConfig(
name = "fact_feedback", name = "fact_feedback",
summaryArgKey = "action",
icon = Icons.Filled.ThumbUp, icon = Icons.Filled.ThumbUp,
), ),
"process" to "process" to
ToolDisplayConfig( ToolDisplayConfig(
name = "process", name = "process",
summaryArgKey = "action",
icon = Icons.Filled.Settings, icon = Icons.Filled.Settings,
), ),
"process_manage" to "process_manage" to
ToolDisplayConfig( ToolDisplayConfig(
name = "process_manage", name = "process_manage",
summaryArgKey = "action",
icon = Icons.Filled.Settings, icon = Icons.Filled.Settings,
), ),
"skill_manage" to "skill_manage" to
ToolDisplayConfig( ToolDisplayConfig(
name = "skill_manage", name = "skill_manage",
summaryArgKey = "action",
icon = Icons.Filled.Build, icon = Icons.Filled.Build,
), ),
// ── Web / Browser tools ── // ── Web / Browser tools ──
"web_extract" to "web_extract" to
ToolDisplayConfig( ToolDisplayConfig(
name = "web_extract", name = "web_extract",
summaryArgKey = "urls",
icon = Icons.Filled.Language, icon = Icons.Filled.Language,
), ),
"browser_type" to "browser_type" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_type", name = "browser_type",
summaryArgKey = null,
icon = Icons.Filled.Keyboard, icon = Icons.Filled.Keyboard,
), ),
"browser_scroll" to "browser_scroll" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_scroll", name = "browser_scroll",
summaryArgKey = "direction",
icon = Icons.Filled.TouchApp, icon = Icons.Filled.TouchApp,
), ),
"browser_back" to "browser_back" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_back", name = "browser_back",
summaryArgKey = null,
icon = Icons.Filled.Language, icon = Icons.Filled.Language,
), ),
"browser_press" to "browser_press" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_press", name = "browser_press",
summaryArgKey = "key",
icon = Icons.Filled.Keyboard, icon = Icons.Filled.Keyboard,
), ),
"browser_get_images" to "browser_get_images" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_get_images", name = "browser_get_images",
summaryArgKey = null,
icon = Icons.Filled.Image, icon = Icons.Filled.Image,
), ),
"browser_vision" to "browser_vision" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_vision", name = "browser_vision",
summaryArgKey = "question",
icon = Icons.Filled.Visibility, icon = Icons.Filled.Visibility,
), ),
"browser_console" to "browser_console" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_console", name = "browser_console",
summaryArgKey = null,
icon = Icons.Filled.Terminal, icon = Icons.Filled.Terminal,
), ),
"browser_cdp" to "browser_cdp" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_cdp", name = "browser_cdp",
summaryArgKey = "action",
icon = Icons.Filled.Build, icon = Icons.Filled.Build,
), ),
"browser_dialog" to "browser_dialog" to
ToolDisplayConfig( ToolDisplayConfig(
name = "browser_dialog", name = "browser_dialog",
summaryArgKey = "action",
icon = Icons.Filled.ChatBubble, icon = Icons.Filled.ChatBubble,
), ),
// ── Media / Vision tools ── // ── Media / Vision tools ──
"vision_analyze" to "vision_analyze" to
ToolDisplayConfig( ToolDisplayConfig(
name = "vision_analyze", name = "vision_analyze",
summaryArgKey = "image_url",
icon = Icons.Filled.Visibility, icon = Icons.Filled.Visibility,
), ),
"text_to_speech" to "text_to_speech" to
ToolDisplayConfig( ToolDisplayConfig(
name = "text_to_speech", name = "text_to_speech",
summaryArgKey = null,
icon = Icons.AutoMirrored.Filled.VolumeUp, icon = Icons.AutoMirrored.Filled.VolumeUp,
), ),
"video_generate" to "video_generate" to
ToolDisplayConfig( ToolDisplayConfig(
name = "video_generate", name = "video_generate",
summaryArgKey = null,
icon = Icons.Filled.Movie, icon = Icons.Filled.Movie,
), ),
"image_generate" to "image_generate" to
ToolDisplayConfig( ToolDisplayConfig(
name = "image_generate", name = "image_generate",
summaryArgKey = null,
icon = Icons.Filled.Image, icon = Icons.Filled.Image,
), ),
// ── Social / Messaging tools ── // ── Social / Messaging tools ──
"x_search" to "x_search" to
ToolDisplayConfig( ToolDisplayConfig(
name = "x_search", name = "x_search",
summaryArgKey = "query",
icon = Icons.Filled.Forum, icon = Icons.Filled.Forum,
), ),
"send_message" to "send_message" to
ToolDisplayConfig( ToolDisplayConfig(
name = "send_message", name = "send_message",
summaryArgKey = null,
icon = Icons.AutoMirrored.Filled.Send, icon = Icons.AutoMirrored.Filled.Send,
), ),
// ── Skills tools ── // ── Skills tools ──
"skills_list" to "skills_list" to
ToolDisplayConfig( ToolDisplayConfig(
name = "skills_list", name = "skills_list",
summaryArgKey = null,
icon = Icons.AutoMirrored.Filled.MenuBook, icon = Icons.AutoMirrored.Filled.MenuBook,
), ),
"skill_view" to "skill_view" to
ToolDisplayConfig( ToolDisplayConfig(
name = "skill_view", name = "skill_view",
summaryArgKey = "name",
icon = Icons.Filled.AutoStories, icon = Icons.Filled.AutoStories,
), ),
// ── On-demand tools ── // ── On-demand tools ──
"tool_search" to "tool_search" to
ToolDisplayConfig( ToolDisplayConfig(
name = "tool_search", name = "tool_search",
summaryArgKey = "query",
icon = Icons.Filled.Search, icon = Icons.Filled.Search,
), ),
"tool_describe" to "tool_describe" to
ToolDisplayConfig( ToolDisplayConfig(
name = "tool_describe", name = "tool_describe",
summaryArgKey = "name",
icon = Icons.Filled.AutoStories, icon = Icons.Filled.AutoStories,
), ),
"tool_call" to "tool_call" to
ToolDisplayConfig( ToolDisplayConfig(
name = "tool_call", name = "tool_call",
summaryArgKey = "name",
icon = Icons.Filled.Build, icon = Icons.Filled.Build,
), ),
// ── Misc ── // ── Misc ──
"read_terminal" to "read_terminal" to
ToolDisplayConfig( ToolDisplayConfig(
name = "read_terminal", name = "read_terminal",
summaryArgKey = "session_id",
icon = Icons.Filled.Computer, icon = Icons.Filled.Computer,
), ),
"computer_use" to "computer_use" to
ToolDisplayConfig( ToolDisplayConfig(
name = "computer_use", name = "computer_use",
summaryArgKey = "action",
icon = Icons.Filled.Computer, icon = Icons.Filled.Computer,
), ),
) )
@@ -209,3 +209,18 @@ private fun InlineVideoPreview(
} }
} }
} }
/** Formats milliseconds into `M:SS` or `H:MM:SS` */
fun formatMediaDuration(durationMs: Long): String {
if (durationMs <= 0) return "0:00"
val totalSeconds = durationMs / 1000
val seconds = totalSeconds % 60
val minutes = (totalSeconds / 60) % 60
val hours = totalSeconds / 3600
return if (hours > 0) {
"%d:%02d:%02d".format(hours, minutes, seconds)
} else {
"%d:%02d".format(minutes, seconds)
}
}
@@ -1,35 +0,0 @@
package com.m57.hermescontrol.ui.chat.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
/**
* Backwards-compatible wrapper delegating to [InlineMediaPlayer].
*/
@Composable
fun InlineVideoPlayer(
videoUri: String,
modifier: Modifier = Modifier,
onFullScreenClick: () -> Unit = {},
) {
InlineMediaPlayer(
uri = videoUri,
modifier = modifier,
onFullScreenClick = onFullScreenClick,
)
}
/** Formats milliseconds into `M:SS` or `H:MM:SS` */
fun formatMediaDuration(durationMs: Long): String {
if (durationMs <= 0) return "0:00"
val totalSeconds = durationMs / 1000
val seconds = totalSeconds % 60
val minutes = (totalSeconds / 60) % 60
val hours = totalSeconds / 3600
return if (hours > 0) {
"%d:%02d:%02d".format(hours, minutes, seconds)
} else {
"%d:%02d".format(minutes, seconds)
}
}
@@ -90,7 +90,6 @@ import com.m57.hermescontrol.theme.CodeTerminalMuted
import com.m57.hermescontrol.theme.CodeTerminalText import com.m57.hermescontrol.theme.CodeTerminalText
import com.m57.hermescontrol.ui.chat.ClarifyUi import com.m57.hermescontrol.ui.chat.ClarifyUi
import com.m57.hermescontrol.ui.chat.MarkdownText import com.m57.hermescontrol.ui.chat.MarkdownText
import com.m57.hermescontrol.ui.chat.SubagentIndicator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -400,14 +399,6 @@ private data class TokenPattern(
val color: Color, val color: Color,
) )
private fun copyToClipboard(
context: Context,
text: String,
) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager ?: return
clipboard.setPrimaryClip(ClipData.newPlainText(null, text))
}
// ── ClarifyBubble ───────────────────────────────────────────────────────── // ── ClarifyBubble ─────────────────────────────────────────────────────────
/** /**
@@ -670,58 +661,6 @@ fun ClarifyBubble(
) )
} }
// ── SubagentCard ──────────────────────────────────────────────────────────
/**
* Inline card showing subagent task progress.
*
* - While running: [CircularProgressIndicator] + goal text on [tertiaryContainer].
* - On completion: ✅ checkmark + summary text.
*/
@Composable
fun SubagentCard(
indicator: SubagentIndicator,
modifier: Modifier = Modifier,
) {
val isComplete = indicator.type == "subagent.complete"
Surface(
modifier = modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 2.dp).testTag("subagent_card"),
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.tertiaryContainer,
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
if (isComplete) {
Text(text = "✅", fontSize = 14.sp)
} else {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
color = MaterialTheme.colorScheme.onTertiaryContainer,
)
}
Spacer(Modifier.width(8.dp))
val displayText =
if (isComplete && !indicator.summary.isNullOrBlank()) {
indicator.summary
} else if (!indicator.goal.isNullOrBlank()) {
indicator.goal
} else {
"Subagent task"
}
Text(
text = displayText,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onTertiaryContainer,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
}
}
// ── TypingIndicator ─────────────────────────────────────────────────────── // ── TypingIndicator ───────────────────────────────────────────────────────
/** /**
@@ -1,17 +0,0 @@
package com.m57.hermescontrol.ui.chat.components
import androidx.compose.runtime.Composable
/**
* Backwards-compatible wrapper delegating to [MediaViewerDialog].
*/
@Composable
fun VideoViewerDialog(
videoUri: String,
onDismissRequest: () -> Unit,
) {
MediaViewerDialog(
mediaUri = videoUri,
onDismissRequest = onDismissRequest,
)
}
@@ -93,8 +93,6 @@ fun FullBleedChatList(
isLoadingOlder: Boolean, isLoadingOlder: Boolean,
listState: androidx.compose.foundation.lazy.LazyListState, listState: androidx.compose.foundation.lazy.LazyListState,
scrollController: ChatScrollController, scrollController: ChatScrollController,
lastAnimatedMessageId: String?,
onLastAnimatedMessageIdChange: (String?) -> Unit,
viewModel: ChatViewModel, viewModel: ChatViewModel,
clarifyRequest: ClarifyUi? = null, clarifyRequest: ClarifyUi? = null,
onRespondClarify: ((String) -> Unit)? = null, onRespondClarify: ((String) -> Unit)? = null,