2 files changed
+56
-21
No files matched your search
+24
@@ -71,4 +71,28 @@ class ReasoningCardTest {
|
|||||||
composeTestRule.onNodeWithTag("reasoning_card").performClick()
|
composeTestRule.onNodeWithTag("reasoning_card").performClick()
|
||||||
composeTestRule.onNodeWithText("Show full").assertDoesNotExist()
|
composeTestRule.onNodeWithText("Show full").assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun completedReasoning_rendersMarkdown() {
|
||||||
|
renderInLazyColumn("**Important** completed thought")
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("reasoning_card").performClick()
|
||||||
|
composeTestRule.onNodeWithText("Important completed thought").assertIsDisplayed()
|
||||||
|
composeTestRule.onNodeWithText("**Important** completed thought").assertDoesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun streamingReasoning_remainsRawText() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
LazyColumn {
|
||||||
|
item(key = "reasoning") {
|
||||||
|
ReasoningCard(reasoningText = "**Thinking**", isStreaming = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("reasoning_card").performClick()
|
||||||
|
composeTestRule.onNodeWithText("**Thinking**").assertIsDisplayed()
|
||||||
|
composeTestRule.onNodeWithText("Thinking").assertDoesNotExist()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -89,6 +89,7 @@ import com.m57.hermescontrol.theme.CodeTerminalBorder
|
|||||||
import com.m57.hermescontrol.theme.CodeTerminalMuted
|
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.SubagentIndicator
|
import com.m57.hermescontrol.ui.chat.SubagentIndicator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@@ -101,7 +102,8 @@ import kotlinx.coroutines.withContext
|
|||||||
* (reasoning-model thinking steps) before the final answer.
|
* (reasoning-model thinking steps) before the final answer.
|
||||||
*
|
*
|
||||||
* Collapsed: "🧠 Reasoning · {N} steps" with chevron.
|
* Collapsed: "🧠 Reasoning · {N} steps" with chevron.
|
||||||
* Expanded: reasoning text in monospace bodySmall, capped at ~40% of the
|
* Expanded: completed reasoning is Markdown; streaming text stays raw,
|
||||||
|
* capped at ~40% of the
|
||||||
* screen height with internal scroll; "Show full" lifts the cap.
|
* screen height with internal scroll; "Show full" lifts the cap.
|
||||||
* Long-press anywhere: copies the reasoning trace to the clipboard.
|
* Long-press anywhere: copies the reasoning trace to the clipboard.
|
||||||
* Streaming: pulsing indicator at the bottom while [isStreaming].
|
* Streaming: pulsing indicator at the bottom while [isStreaming].
|
||||||
@@ -180,26 +182,35 @@ fun ReasoningCard(
|
|||||||
}
|
}
|
||||||
AnimatedVisibility(visible = expanded) {
|
AnimatedVisibility(visible = expanded) {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
val contentModifier =
|
||||||
text = reasoningText,
|
Modifier
|
||||||
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
|
.padding(top = 4.dp)
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
.then(
|
||||||
modifier =
|
if (!fullHeight) {
|
||||||
Modifier
|
// Capped at ~40% of the screen with internal scroll.
|
||||||
.padding(top = 4.dp)
|
Modifier.heightIn(max = capHeight).verticalScroll(scrollState)
|
||||||
.then(
|
} else {
|
||||||
if (!fullHeight) {
|
// "Show full": render the whole trace at natural height.
|
||||||
// Capped at ~40% of the screen with internal scroll.
|
// A scrollable must NEVER be measured with an infinite
|
||||||
Modifier.heightIn(max = capHeight).verticalScroll(scrollState)
|
// max height — inside a LazyColumn item (unbounded main
|
||||||
} else {
|
// axis) that combination throws IllegalStateException.
|
||||||
// "Show full": render the whole trace at natural height.
|
Modifier
|
||||||
// A scrollable must NEVER be measured with an infinite
|
},
|
||||||
// max height — inside a LazyColumn item (unbounded main
|
)
|
||||||
// axis) that combination throws IllegalStateException.
|
if (isStreaming) {
|
||||||
Modifier
|
Text(
|
||||||
},
|
text = reasoningText,
|
||||||
),
|
style = MaterialTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace),
|
||||||
)
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = contentModifier,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MarkdownText(
|
||||||
|
text = reasoningText,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
modifier = contentModifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (isStreaming) {
|
if (isStreaming) {
|
||||||
ReasoningPulsingDot(modifier = Modifier.padding(top = 6.dp))
|
ReasoningPulsingDot(modifier = Modifier.padding(top = 6.dp))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user