Skip to content

AI

AiStreamingMarkdown

Bit.BlazorUI.AI

BitAiStreamingMarkdown renders Markdown that is still being written, as a BitAiMarkdownView that follows the text while it grows. It holds one incremental parser for its lifetime, so the text already seen is parsed once however many deltas arrive - a long streamed answer costs linear work rather than quadratic. While the text streams, a caret trails it, its last block stays open, a code block is highlighted only once its fence closes and a dangling emphasis or code delimiter is hidden until its partner arrives.

Notes

To use this component, you need to install the Bit.BlazorUI.AI(opens in a new tab) nuget package, as described in the Optional steps of the Getting started page.

It needs no chat session: hand it the text received so far. Inside a chat, AiTextPartView renders every text part with it, and the reasoning of a message and a document artifact use it too. Re-render it at a steady pace rather than on every token - a BitAiRenderScheduler (see below; the AI components use BitAiUiOptions.RenderInterval) collapses any number of deltas into one render per 50 ms, which on Blazor Server is what keeps the circuit quiet. Its BitAiIncrementalMarkdownParser (see below) is public too, for a view of your own that renders streamed Markdown with a BitAiMarkdownView. The parser is built from the Markdown options in force (BitAiUiOptions.Markdown, see AiMarkdownView) and the output is untrusted: raw HTML is escaped and script URLs are neutralised. See it at work in the Streaming scenario.

Usage

Every example is live. Open its code to see exactly what produced the component running underneath.

Basic

Text is the Markdown received so far. With IsStreaming left false the text is complete, and it renders like any Markdown view.

Two render modes

Blazor renders on the server, over a circuit, or in the browser, on WebAssembly. Pick one per page with @rendermode, or let InteractiveAuto start on the server and move to the browser once it has downloaded.

Streaming

While IsStreaming is true more text is expected: a caret trails it and its last block is left open. Stream this answer and watch the raw text beside what is drawn: the dangling ** of a bold run never shows, a table fills in row by row, the code block stays plain until its closing fence arrives and is highlighted then. Turn on Slow motion to follow every step. The page re-renders through a BitAiRenderScheduler, at most once per 50 ms.


Received so far
Rendered

Citations

A bracketed number the model writes ([1]) becomes a citation chip when Citations holds a citation of that Number; one that matches none stays the text it was. OnCitationClick is raised when the user activates a chip.

Interactive Server keeps component state on the server and streams UI diffs over a SignalR circuit . If that circuit drops, the state goes with it unless you have persisted it yourself . A number no citation has, such as [3], stays the text it is.


Activated: none yet

Markdown options

The parser follows BitAiUiOptions.Markdown (a BitAiMarkdownOptions), and is built again when a different options instance is cascaded, so the parser and the renderer always agree. Chat-tuned models often write one line per sentence and mean the line breaks: SoftBreaksAsHardBreaks keeps them (the first text). HideDanglingDelimiters, on by default, keeps the opening ** of a bold run that has not closed yet out of sight while the text streams (the second text, frozen mid-word with IsStreaming); turned off, the asterisks flash on the way in. Make a new options instance only when a choice changes and keep it in a field - a new one on every render would rebuild the parser every time.

Server rendering is fast to first paint. WebAssembly works offline. Auto starts on one and moves to the other.


The short answer is that it depends on where

Cascading parameters

BitParams with a BitAiStreamingMarkdownParams sets the look of every streaming Markdown inside it - the text parts of a chat included - here the headings of its Markdown view in the primary color. A parameter of the component itself still wins, and wins whole: the second sets Styles of its own, which take the place of the cascaded ones.

A styled answer
  • a first point

  • a second point


A styled answer
  • a first point

  • a second point

Style & Class

Style and Class reach the root, Styles and Classes its parts: Markdown takes the classes and styles of every part of the Markdown view the text is drawn with. The streaming Markdown publishes no CSS variables of its own: the --bit-AiMarkdownView-* variables restyle it.

A styled answer
  • a first point

  • a second point


A styled answer
  • a first point

  • a second point


A styled answer
  • a first point

  • a second point


A styled answer
  • a first point

  • a second point


A styled answer
  • a first point

  • a second point

RTL

In right-to-left text the Markdown reads from the right and the caret trails the text at its left end, whether the direction comes from Dir or from a dir attribute around it.

دو حالت رندر

بلیزر روی سرور یا در مرورگر رندر می‌کند و پاسخ هنوز در حال نوشته شدن است

API

Every parameter, public member, sub-class and enum this component exposes.

BitAiStreamingMarkdown parameters

Name Type Default value Description
Citations IReadOnlyList<BitAiCitation>? null The citations that the inline citation markers of the text resolve against: a bracketed number such as [1] is drawn as a BitAiCitationMarker when a citation of that Number is here (and BitAiUiOptions.InlineCitations is on); one that matches none stays the literal text.
Classes BitAiStreamingMarkdownClassStyles? null Custom CSS classes for different parts of the streaming Markdown.
IsStreaming bool false Whether more text is expected. While it is true the streaming caret trails the text (unless BitAiUiOptions.ShowStreamingCaret is off), the last block is left open - a code block is not highlighted, a dangling emphasis or code delimiter is hidden - and turning it false settles the document.
OnCitationClick EventCallback<BitAiCitation> The callback for when the user activates an inline citation marker, with the citation it stands for.
Styles BitAiStreamingMarkdownClassStyles? null Custom CSS styles for different parts of the streaming Markdown.
Text string? null The Markdown received so far. Only the text after the last block boundary already seen is parsed again when it grows; a text that is not a continuation of the previous one (an edit, a new answer) is parsed afresh.

BitComponentBase parameters

Name Type Default value Description
AriaLabel string? null Gets or sets the accessible label for the component, used by assistive technologies.
Class string? null Gets or sets the CSS class name(s) to apply to the rendered element.
Dir BitDir? null Gets or sets the text directionality for the component's content.
Disabled bool false Gets or sets a value indicating whether the component is disabled and cannot respond to user interaction.
ForceAnimation bool false Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested.
HtmlAttributes Dictionary<string, object> new Dictionary<string, object>() Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. An attribute written on the component wins over an entry of the same name.
Id string? null Gets or sets the unique identifier for the component's root element.
Style string? null Gets or sets the CSS style string to apply to the rendered element.
TabIndex string? null Gets or sets the tab order index for the component when navigating with the keyboard.
Visibility BitVisibility BitVisibility.Visible Gets or sets the visibility state (visible, hidden, or collapsed) of the component.

BitComponentBase public members

Name Type Default value Description
UniqueId Guid Guid.NewGuid() Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed.
RootElement ElementReference Gets the reference to the root HTML element associated with this component.

BitAiStreamingMarkdownClassStyles properties

Name Type Default value Description
Root string? null Custom CSS classes/styles for the root element of the BitAiStreamingMarkdown.
Markdown BitAiMarkdownViewClassStyles? null Custom CSS classes/styles for the parts of the BitAiMarkdownView the BitAiStreamingMarkdown renders its text with.

BitAiIncrementalMarkdownParser properties

The parser a BitAiStreamingMarkdown holds for its lifetime, for an app that renders streamed Markdown with a BitAiMarkdownView of its own (ShowCaret while it streams). It keeps the blocks before the last point no later text can change - the start of a line after a blank line that begins a new top-level block - and parses only the tail again on every call, so a long answer costs linear work rather than quadratic. A text with a link reference or footnote definition, which makes any text depend on any other, is parsed whole on every call. An instance belongs to one streaming text and is not thread-safe.

Name Type Default value Description
BitAiIncrementalMarkdownParser(BitAiMarkdownOptions? options = null) constructor Creates a parser for the given extensions (BitAiMarkdownOptions.Default when null). Make a new one when the options change.
Parse(string? source, bool isComplete = false) BitAiMarkdownDocument Parses the whole text received so far - not just the new part - and returns a new document whose blocks may be shared with the previous one. isComplete false leaves the last block open; a source that does not continue the previous one is parsed afresh.
Reset() void Discards all cached state, so the next parse starts from scratch.
StableLength int 0 How many characters of the source have been promoted to the stable prefix that is never parsed again - for diagnostics and tests: the measure of how much work is being reused.
StableBlockCount int 0 How many blocks are in the stable prefix.

BitAiRenderScheduler properties

Coalesces the change notifications of a stream - hundreds of deltas a second - into a bounded number of renders, the way the AI components do with BitAiUiOptions.RenderInterval. Rendering every delta is wasted work in WebAssembly and a flood of diffs down the circuit of Blazor Server; the scheduler makes the frame rate matter rather than the throughput. While a render is in flight, further requests only mark it dirty and one more render follows, so a slow renderer falls behind in render count, never in memory. Dispose it with the page (IAsyncDisposable).

Name Type Default value Description
BitAiRenderScheduler(Func<Task> flush, TimeSpan? interval = null) constructor Creates a scheduler that calls flush when a render is due - in a component, () => InvokeAsync(StateHasChanged) - at most once per interval (DefaultInterval when null).
DefaultInterval TimeSpan 50 ms Static: one render every 50 milliseconds, twenty a second - continuous to the eye, and comfortable over a Blazor Server circuit on a poor connection.
Request() void Asks for a render. Cheap and safe to call from any thread, on every token.
FlushAsync() Task Renders at once, bypassing the interval: call it when the stream ends, so the final state is never left waiting behind a timer.
RenderCount int 0 How many renders the scheduler has performed, for tests and diagnostics.
CoalescedCount int 0 How many requests have been coalesced away.
DisposeAsync() ValueTask Stops the scheduler: a pending render is dropped, and later requests are ignored.

BitVisibility enum

Name Value Description
Visible 0 The content of the component is visible.
Hidden 1 The content of the component is hidden, but the space it takes on the page remains (visibility:hidden).
Collapsed 2 The component is hidden (display:none).

BitDir enum

Name Value Description
Ltr 0 Ltr (left to right) is to be used for languages that are written from the left to the right (like English).
Rtl 1 Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic).
Auto 2 Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element.

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.