Skip to content

Commit a971e3a

Browse files
CopilotGodzilla675
andcommitted
feat: render markdown and math formulas in streaming messages and add link support
- Replace plain Text() with StreamingMarkdownText in AssistantStreamingBubble so markdown formatting, code blocks, math formulas, and tables render properly during token streaming - Add StreamingMarkdownText composable (bypasses completed-message cache) - Add markdown link [text](url) support to inline formatter Co-authored-by: Godzilla675 <131464726+Godzilla675@users.noreply.github.com>
1 parent acafcda commit a971e3a

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

app/src/main/java/com/dark/tool_neuron/ui/components/MarkdownText.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,28 @@ fun MarkdownText(text: String, modifier: Modifier = Modifier) {
9595
}
9696
}
9797

98+
/**
99+
* Streaming-optimised markdown renderer.
100+
* Re-parses on every text change (bypasses the completed-message cache).
101+
* Used in [AssistantStreamingBubble] so the user sees formatted text while
102+
* tokens are still being generated.
103+
*/
104+
@Composable
105+
fun StreamingMarkdownText(text: String, modifier: Modifier = Modifier) {
106+
val parsedContent = remember(text) { parseMarkdown(text) }
107+
val colors = InlineColors(
108+
codeBg = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
109+
highlightBg = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
110+
mathColor = MaterialTheme.colorScheme.primary
111+
)
112+
Column(
113+
modifier = modifier.padding(horizontal = Standards.SpacingXs),
114+
verticalArrangement = Arrangement.spacedBy(Standards.SpacingXs)
115+
) {
116+
parsedContent.forEach { element -> MarkdownElementView(element, colors) }
117+
}
118+
}
119+
98120
/**
99121
* Lazy version — each markdown element is a separate LazyList item.
100122
* Only visible items are composed. Use inside a LazyColumn.
@@ -492,6 +514,20 @@ internal fun buildInlineFormatted(text: String, colors: InlineColors): Annotated
492514
i = end + 1
493515
} else { append(chars[i]); i++ }
494516
}
517+
// Markdown link [text](url)
518+
chars[i] == '[' -> {
519+
val closeBracket = text.indexOf(']', i + 1)
520+
if (closeBracket != -1 && closeBracket + 1 < chars.size && chars[closeBracket + 1] == '(') {
521+
val closeParen = text.indexOf(')', closeBracket + 2)
522+
if (closeParen != -1) {
523+
val linkText = text.substring(i + 1, closeBracket)
524+
withStyle(SpanStyle(color = colors.mathColor, textDecoration = TextDecoration.Underline)) {
525+
append(buildInlineFormatted(linkText, colors))
526+
}
527+
i = closeParen + 1
528+
} else { append(chars[i]); i++ }
529+
} else { append(chars[i]); i++ }
530+
}
495531
// Default — handle surrogates
496532
else -> {
497533
val c = chars[i]

app/src/main/java/com/dark/tool_neuron/ui/screen/home/MessageBubbles.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp
2424
import com.dark.tool_neuron.models.messages.Messages
2525
import com.dark.tool_neuron.ui.components.ExpandCollapseIcon
2626
import com.dark.tool_neuron.ui.components.MarkdownText
27+
import com.dark.tool_neuron.ui.components.StreamingMarkdownText
2728
import com.dark.tool_neuron.ui.icons.TnIcons
2829
import com.dark.tool_neuron.ui.theme.Motion
2930
import java.util.Base64
@@ -88,10 +89,8 @@ internal fun AssistantStreamingBubble(text: String) {
8889
.background(MaterialTheme.colorScheme.primary, shape = CircleShape)
8990
)
9091

91-
Text(
92+
StreamingMarkdownText(
9293
text = parsedMessage.actualContent.ifEmpty { "..." },
93-
style = MaterialTheme.typography.bodyMedium,
94-
color = MaterialTheme.colorScheme.onSurface,
9594
modifier = Modifier.weight(1f)
9695
)
9796
}

0 commit comments

Comments
 (0)