Skip to content
# AiMarkdownView ## Description BitAiMarkdownView renders a parsed Markdown document: the prose of an assistant message, with its code blocks, tables, task lists, footnotes, mathematics and inline citation markers. It takes an already-parsed document rather than a string, so the caller decides how parsing happens - BitAiStreamingMarkdown reuses an incremental parser across deltas, a static caller parses once with BitAiMarkdownParser. Model output is untrusted, so raw HTML is escaped unless it is opted into, link and image URLs of a scheme that can run script are neutralised, and images follow the image policy of the options. Fenced code in a language the app registered (a chart, a diagram) and mathematics defer to renderers of the app's own. ## Parameters | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Citations | `IReadOnlyList<BitAiCitation>?` | null | The citations that the inline citation markers of the document resolve against. A marker whose number matches the Number of a citation is drawn as a BitAiCitationMarker - or by the BitAiRendererRegistry.CitationRenderer, when the app registered one - unless BitAiUiOptions.InlineCitations is off; one that matches none stays the literal text it was. | | Classes | `BitAiMarkdownViewClassStyles?` | null | Custom CSS classes for different parts of the Markdown view. | | Document | `BitAiMarkdownDocument?` | null | The parsed document to render (BitAiMarkdownParser.Parse, or the incremental parser a BitAiStreamingMarkdown holds). Nothing is rendered while it is null. It is a public tree of blocks and inlines an app can read too (see The document tree). | | OnCitationClick | `EventCallback<BitAiCitation>` | | The callback for when the user activates an inline citation marker, with the citation it stands for. A chip drawn by a registered CitationRenderer is handed it as the OnActivate of its BitAiCitationContext, which the chip raises through ActivateAsync(). | | ShowCaret | `bool` | false | Draws a caret at the end of the text while the document is still being written (parsed with isComplete false), the sign that more text is on its way: after the paragraph or the heading being written, in a list item just started, and after the blocks - where the next one will start - when the text so far ends in a code block, a table or a blank line. BitAiUiOptions.ShowStreamingCaret turns the caret off for every view at once. | | Styles | `BitAiMarkdownViewClassStyles?` | null | Custom CSS styles for different parts of the Markdown view. | | 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. | ## 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. | ## Enums ### BitAiImageMode Enum | Name | Value | Description | | :--- | :--- | :---------- | | Allow | 0 | Show every image whose URL scheme is safe. | | AllowListedHosts | 1 | Show images only from the listed hosts; the others get a placeholder that loads them on request. | | Block | 2 | Show no remote images; each is replaced by its alternative text. | | ClickToLoad | 3 | Show a placeholder that loads the image when the reader asks for it. | ### BitAiImageDecision Enum | Name | Value | Description | | :--- | :--- | :---------- | | Show | 0 | Render the image. | | AskFirst | 1 | Render a placeholder button that loads the image when activated. | | Hide | 2 | Render the alternative text in place of the image. | ### BitAiColumnAlignment Enum | Name | Value | Description | | :--- | :--- | :---------- | | None | 0 | No alignment written (---): the cells sit at the start of the reading direction. | | Left | 1 | Left aligned (:---): drawn at the start of the reading direction, as None is. | | Center | 2 | Centre aligned (:---:). | | Right | 3 | Right aligned (---:): drawn at the end of the reading direction, so it follows a right-to-left text. | ### 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. | ## Sub Classes ### BitAiMarkdownViewClassStyles Properties | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Root | `string?` | null | Custom CSS classes/styles for the root element of the BitAiMarkdownView. | | Paragraph | `string?` | null | Custom CSS classes/styles for every paragraph of the BitAiMarkdownView. | | Heading | `string?` | null | Custom CSS classes/styles for every heading of the BitAiMarkdownView. | | List | `string?` | null | Custom CSS classes/styles for every list (bulleted, numbered or task list) of the BitAiMarkdownView. | | ListItem | `string?` | null | Custom CSS classes/styles for every list item of the BitAiMarkdownView. | | Quote | `string?` | null | Custom CSS classes/styles for every block quote of the BitAiMarkdownView. | | TableContainer | `string?` | null | Custom CSS classes/styles for the scroll container of every table of the BitAiMarkdownView, which also draws its frame. | | Table | `string?` | null | Custom CSS classes/styles for every table of the BitAiMarkdownView. | | Separator | `string?` | null | Custom CSS classes/styles for every thematic break (horizontal rule) of the BitAiMarkdownView. | | InlineCode | `string?` | null | Custom CSS classes/styles for every inline code span of the BitAiMarkdownView. | | Link | `string?` | null | Custom CSS classes/styles for every link of the BitAiMarkdownView. | | Image | `string?` | null | Custom CSS classes/styles for every image of the BitAiMarkdownView. | | ImagePlaceholder | `BitActionButtonClassStyles?` | null | Custom CSS classes/styles for the button (a BitActionButton) the BitAiMarkdownView shows in place of an image the image policy loads only on request. | | BlockedImage | `string?` | null | Custom CSS classes/styles for the alternative text the BitAiMarkdownView shows in place of an image the image policy blocks. | | Math | `string?` | null | Custom CSS classes/styles for the built-in rendering of every mathematical expression of the BitAiMarkdownView (not used while a MathRenderer is registered). | | FootnoteReference | `string?` | null | Custom CSS classes/styles for every footnote reference of the BitAiMarkdownView. | | Footnotes | `string?` | null | Custom CSS classes/styles for the footnotes section of the BitAiMarkdownView. | | Caret | `string?` | null | Custom CSS classes/styles for the caret the BitAiMarkdownView draws after streaming text. | ### BitAiMarkdownParser Properties Parses Markdown into the BitAiMarkdownDocument the view renders: a CommonMark subset with the GitHub-flavoured extensions models write (tables, task lists, strikethrough, autolinks, footnotes, mathematics), built to parse a half-written answer many times a second rather than for conformance. Every block records whether the source ended inside it, which is what keeps streaming settled. A text with link reference or footnote definitions is parsed twice - once to collect them, once to resolve the references - since a definition may follow the text that uses it. For text that grows, the BitAiIncrementalMarkdownParser a BitAiStreamingMarkdown holds (see its page) reuses the work done for the text already seen. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Parse(string? markdown, BitAiMarkdownOptions? options = null, bool isComplete = true) | `BitAiMarkdownDocument` | | Parses the Markdown with the given extensions (BitAiMarkdownOptions.Default when null). isComplete false marks the trailing block open: more text is expected. A null or empty source is an empty document. | ### BitAiInlineParser Properties Parses the inline content of one block - emphasis, strikethrough, code spans, links, images, mathematics, footnote references and citation markers - for an app that shows a line of model text (a title, a label) without the blocks around it. The block parser runs it on every paragraph, heading and table cell. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Parse(string? text, BitAiMarkdownOptions? options = null, bool isComplete = true) | `List<BitAiMarkdownInline>` | | The inlines of the text, with the given extensions (BitAiMarkdownOptions.Default when null). isComplete false - more text expected - leaves an unmatched emphasis or code delimiter at the end out rather than showing it. Reference links and footnotes need the definitions of a whole document, so they are not resolved here. A null or empty text has none. | ### BitAiMarkdownDocument Properties A parsed document: what BitAiMarkdownParser.Parse returns and the view renders, itself a BitAiMarkdownBlock. Its blocks are a tree of BitAiMarkdownBlock nodes (paragraphs, headings, lists, quotes, tables, fenced code, mathematics, ...) whose text is BitAiMarkdownInline nodes - a public tree an app can walk (see The document tree). | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Blocks | `List<BitAiMarkdownBlock>` | [] | The blocks of the document, in order. | | Footnotes | `List<BitAiFootnoteBlock>` | [] | The footnotes the text cites, in the order they are first cited and numbered from one; a definition nobody cites is dropped. The view lists them after the blocks, each with a link back to every reference to it. | | IsOpen | `bool` | false | Whether the document was parsed with more text expected (isComplete false), whatever its last block is - the condition for the caret of ShowCaret. | | Empty | `BitAiMarkdownDocument` | | Static: an empty document. | ### BitAiMarkdownBlock Properties The base of every block of a document (a BitAiMarkdownNode, the base of every node). The parser fills the blocks in; to an app they are read-only. The block types it produces are listed under its one member. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | IsOpen | `bool` | false | True when the block may still grow because the source ended inside it. A renderer defers on it what would flicker: an open fenced block is not highlighted, and the caret follows an open paragraph or heading. | | BitAiParagraphBlock | `sealed class` | | A run of text ended by a blank line or another block: Inlines (List<BitAiMarkdownInline>). | | BitAiHeadingBlock | `sealed class` | | A heading: Level (int, 1 to 6; the view renders it one level lower, so a # is an h2), Inlines, and Id (string? - bit-ai-h-<slug> while HeadingIds is on, which the view renders after its own id). | | BitAiCodeBlock | `sealed class` | | A code block: Code (string, without the fences), Language (string?, from the info string) and Meta (string?, the rest of the info string), and IsFenced (bool: fenced with ``` or ~~~, or indented by four spaces). | | BitAiListBlock | `sealed class` | | A bulleted or numbered list: IsOrdered (bool), Start (int, the first number of a numbered list), IsLoose (bool, items separated by blank lines and spaced apart) and Items (List<BitAiListItemBlock>). | | BitAiListItemBlock | `sealed class` | | An item of a list: IsChecked (bool? - the checkbox of a task-list item, null for an item without one) and Blocks (List<BitAiMarkdownBlock>). | | BitAiQuoteBlock | `sealed class` | | A block quote: Blocks (List<BitAiMarkdownBlock>). | | BitAiTableBlock | `sealed class` | | A pipe table: Header (List<List<BitAiMarkdownInline>>, the inlines of each header cell), Rows (List<List<List<BitAiMarkdownInline>>>, the cells of each body row) and Alignments (List<BitAiColumnAlignment>, one per column). | | BitAiThematicBreakBlock | `sealed class` | | A horizontal rule (---, *** or ___), with no member of its own. | | BitAiFootnoteBlock | `sealed class` | | A footnote the text cites, listed on the Footnotes of the document: Label (string, as written, without the caret), Number (int, its position in the list), ReferenceCount (int, how many references cite it, each with a back-link) and Blocks. | | BitAiMathBlock | `sealed class` | | Displayed mathematics ($$...$$): Expression (string, without the delimiters). | | BitAiHtmlBlock | `sealed class` | | A block of raw HTML, read only while AllowRawHtml is on (otherwise the markup is text of a paragraph): Html (string). | ### BitAiMarkdownInline Properties The base of every inline node - the text of a paragraph, a heading, a list item or a table cell (a BitAiMarkdownNode too). The inline types the parser produces, filled in by it and read-only to an app: | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | BitAiTextInline | `sealed class` | | Literal text: Text (string), which is also its ToString(). | | BitAiEmphasisInline | `sealed class` | | Emphasised text: IsStrong (bool: true for **strong**, false for *ordinary*) and Inlines. | | BitAiStrikethroughInline | `sealed class` | | Struck-through text (~~...~~): Inlines. | | BitAiCodeInline | `sealed class` | | A code span: Code (string). | | BitAiLinkInline | `sealed class` | | A link: Url (string, as written - the view renders it only when its scheme is safe), Title (string?), Inlines (the link text) and IsAutoLink (bool: a bare or angle-bracketed address rather than [text](url)). | | BitAiImageInline | `sealed class` | | An image: Url (string), Alt (string, the alternative text) and Title (string?). The view loads it under the image policy. | | BitAiLineBreakInline | `sealed class` | | A line break inside a paragraph: IsHard (bool - true for a backslash or two spaces at the end of the line, and for every break under SoftBreaksAsHardBreaks; a soft one renders as a space). | | BitAiMathInline | `sealed class` | | Inline mathematics ($...$): Expression (string, without the delimiters). | | BitAiCitationMarkerInline | `sealed class` | | A citation marker written [n] (with CitationMarkers on): Number (int) - what the Citations of the view resolve it against - and CitationId (string?). | | BitAiFootnoteReferenceInline | `sealed class` | | A footnote reference written [^label]: Label (string), Number (int, assigned in the order the footnotes are first cited) and Ordinal (int, which reference to the footnote it is, from one). | | BitAiRawHtmlInline | `sealed class` | | Raw inline HTML, read only while AllowRawHtml is on (otherwise the markup is text): Html (string). An element closed later in the same text is one inline, its content included (<kbd>Ctrl</kbd>). The view renders it as markup only while the options in force allow raw HTML too, and as text otherwise. | ### BitAiMarkdownOptions Properties Which Markdown extensions to enable and how to treat untrusted markup - a record handed to BitAiMarkdownParser.Parse, and the Markdown of BitAiUiOptions, which a BitAiStreamingMarkdown parses with and every view reads AllowRawHtml from. The defaults are what an AI chat wants: the GitHub-flavoured extensions on, raw HTML off. BitAiMarkdownOptions.Default holds them. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Tables | `bool` | true | Parse pipe tables. | | Strikethrough | `bool` | true | Parse strikethrough written with double tildes. | | TaskLists | `bool` | true | Parse task-list checkboxes in list items. The view draws them disabled: they show what the model wrote, nothing of the page can change them. | | AutoLinks | `bool` | true | Turn bare URLs into links. | | Math | `bool` | true | Parse dollar-delimited mathematics ($inline$ and $$display$$). | | CitationMarkers | `bool` | true | Turn bracketed digit runs such as [3] into citation markers, which the Citations of the view resolve. | | AllowRawHtml | `bool` | false | Keep raw HTML as markup instead of escaping it. The view renders it only when the options in force (BitAiUiOptions.Markdown) allow it too. Leave it off unless the Markdown comes from a source you trust as much as your own code: a model can be talked into emitting a script tag. | | HeadingIds | `bool` | true | Give headings anchor ids derived from their text (bit-ai-h-<slug>, numbered -2, -3, ... when a text repeats), which the view renders after its own id. | | HideDanglingDelimiters | `bool` | true | Hide a trailing unmatched emphasis or code delimiter while the text is still streaming, so a bold run does not flash a literal asterisk before its closing one arrives - in a paragraph and in a heading being written alike. With Tables on, the header row of a table whose delimiter row has not arrived yet is held back the same way, rather than shown as a line of pipes. | | MaxNestingDepth | `int` | 32 | How deeply blocks and inline spans may nest before further markers are read as literal text - a bound on the parser's recursion and on the tree the view walks, so a line of ten thousand > cannot take the process down. | | SoftBreaksAsHardBreaks | `bool` | false | Treat a single newline inside a paragraph as a line break, as chat clients often do (CommonMark renders it as a space). | | Footnotes | `bool` | true | Parse footnotes: [^1] in the text with a matching [^1]: note definition. | ### BitAiImagePolicy Properties Which images are fetched - the Images of BitAiUiOptions, which the images of the Markdown follow, and the image parts, the image and SVG artifacts and the favicons of sources too (a favicon only when the policy shows it outright, never as a placeholder). A model can be talked into emitting an image URL, and a browser fetching it leaks the reader's address and the fact that they read the message to whoever controls the host. The default allows images; a host handling sensitive conversations restricts them to its own CDN, asks before loading, or blocks them. Script-capable URL schemes are neutralised whatever the policy. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Allow | `BitAiImagePolicy` | | Static: shows every image whose URL scheme is safe. The default policy. | | Block | `BitAiImagePolicy` | | Static: shows no remote image (nor a data image); each one is replaced by its alternative text. | | ClickToLoad | `BitAiImagePolicy` | | Static: loads each remote image only when the reader activates its placeholder. | | AllowListedHosts(params string[] hosts) | `BitAiImagePolicy` | | Static: shows the remote images of the given hosts (compared without regard to case; a leading *. matches subdomains) and a click-to-load placeholder for the rest. A relative URL is fetched from the host of the document's base address, which is what the views ask about it: list the app's own host to show its images. | | AllowDataImages | `bool` | true | Whether inline data: images are shown. They involve no network request, so they are shown under every policy but Block while this is on. | | Hosts | `IReadOnlyCollection<string>` | [] | The hosts allowed under AllowListedHosts. | | Mode | `BitAiImageMode` | BitAiImageMode.Allow | How images are treated. | | Decide(string url) | `BitAiImageDecision` | | Decides what to do with one image source (already checked for a safe scheme): show it, show a placeholder that loads it on request, or hide it. The views ask about the address the browser fetches - a relative or protocol-relative source resolved against the document's base address; a source handed to it still relative has no host, so no host list shows it. | ### BitAiMathContext Properties What a mathematics renderer is handed. The renderer is a BitAiMathRenderer - delegate RenderFragment BitAiMathRenderer(BitAiMathContext context) - set as BitAiRendererRegistry.MathRenderer, which replaces the built-in monospace fallback of every view. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Expression | `string` | | The expression source, without its dollar delimiters. | | IsBlock | `bool` | | True for display mathematics ($$...$$), false for an inline expression. | ### BitAiCodeBlockContext Properties What a code block renderer is handed. The renderer is a BitAiCodeBlockRenderer - delegate RenderFragment BitAiCodeBlockRenderer(BitAiCodeBlockContext context) - registered for one language with BitAiRendererRegistry.RegisterCodeLanguage, which renders the fenced blocks of that language in place of the BitAiCodeBlockView. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Code | `string` | | The source inside the fence. | | Language | `string?` | | The language of the fence info string, as written. | | Meta | `string?` | | The rest of the info string after the language, when there is one (```bars title="Load"). | | IsStreaming | `bool` | | Whether the block is still receiving content - its closing fence has not arrived. A renderer of a diagram language waits for it to turn false: a half-written diagram is a syntax error, and one flashing on every token is worse than the source. | ### BitAiCitationContext Properties What a citation renderer is handed (a sealed record). The renderer is a BitAiCitationRenderer - delegate RenderFragment BitAiCitationRenderer(BitAiCitationContext context) - set as BitAiRendererRegistry.CitationRenderer, which draws the chip of every resolved citation marker in place of the BitAiCitationMarker, inside a boundary of its own. The source list under a message and the card of a citation part are not drawn by it. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Citation | `BitAiCitation` | | The citation the marker resolved to: its Number, its Source, its Quote and its Locator. | | Message | `BitAiMessage?` | null | The message the citation belongs to - always null from the Markdown view, which is handed the citations rather than a message. | | OnActivate | `EventCallback<BitAiCitation>` | | What activating the citation does where it is drawn: the OnCitationClick of the view. Raise it through ActivateAsync(). | | ActivateAsync() | `Task` | | Activates the citation the way the built-in chip does when it is clicked: raises OnActivate with the Citation. Does nothing when the view was given no OnCitationClick. | ### BitAiRendererRegistry Properties The members of the renderer registry the Markdown view reads (it also holds the renderers of parts, tools, artifacts and structured data). The registry is a singleton service: register at startup, through ConfigureRenderers of AddBitBlazorUIAIServices, and treat the entries as immutable afterwards. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | MathRenderer | `BitAiMathRenderer?` | null | Renders the mathematics of every view when set, in place of the fallback (the source in monospace, under bit-aimdv-mth / bit-aimdv-mtb with a data-math attribute a script can typeset from). | | CitationRenderer | `BitAiCitationRenderer?` | null | Draws the chip of every resolved citation marker of every view when set, in place of the BitAiCitationMarker - handed the OnCitationClick of the view as OnActivate, so its chip acts the way the built-in one does. | | RegisterCodeLanguage(string language, BitAiCodeBlockRenderer renderer) | `void` | | Registers a renderer for the fenced blocks of one language (mermaid, vega, ...), matched without regard to case, in place of the code view. | | FindCodeLanguage(string? language) | `BitAiCodeBlockRenderer?` | | The renderer registered for a language, or null for the built-in code view. | ## CSS Variables Public CSS custom properties read off the component's root. Set one on `:root` to restyle every instance, on any ancestor to restyle the ones inside it, or on the `Style` of one instance to restyle it and every instance nested inside it, which inherits the value too - a parameter changes one alone, as does the real CSS property on its `Style` / `Styles` when that property does not inherit (`opacity`); an inherited one (`color`, `font-family`) reaches the nested instances that do not set it themselves. | Name | Default Value | Description | | :--- | :------------ | :---------- | | `--bit-AiMarkdownView-color` | inherit | Text color of the prose. | | `--bit-AiMarkdownView-font-size` | inherit | Text size of the prose. | | `--bit-AiMarkdownView-line-height` | 1.6 | Line height of the prose. | | `--bit-AiMarkdownView-block-spacing` | spacing(2) | Room between two blocks (paragraphs, lists, tables, code blocks, ...); a heading gets one and a half times as much above it. | | `--bit-AiMarkdownView-heading-color` | inherit | Text color of the headings. | | `--bit-AiMarkdownView-heading-font-weight` | var(--bit-tpg-fw-semibold) | Weight of the headings. | | `--bit-AiMarkdownView-link-color` | var(--bit-clr-pri-fg) | Text color of the links (and of the footnote links). | | `--bit-AiMarkdownView-link-hover-color` | the link color | Text color of a link under the pointer. | | `--bit-AiMarkdownView-code-color` | inherit | Text color of the code spans and of the mathematics fallback. | | `--bit-AiMarkdownView-code-background` | 8% of var(--bit-clr-fg-pri) | Background of the code spans and of the mathematics fallback: a translucent wash of the text color, so it reads on whatever the message sits on. | | `--bit-AiMarkdownView-code-radius` | var(--bit-shp-radius-sm) | Corner radius of the code spans and of the mathematics fallback. | | `--bit-AiMarkdownView-code-font-family` | var(--bit-tpg-font-family-mono) | Font of the code spans and of the mathematics fallback. | | `--bit-AiMarkdownView-marker-color` | var(--bit-clr-fg-ter) | Bullets and numbers of the lists. | | `--bit-AiMarkdownView-quote-color` | var(--bit-clr-fg-sec) | Text color of the block quotes. | | `--bit-AiMarkdownView-quote-border-color` | var(--bit-clr-brd-pri) | Bar down the start edge of the block quotes. | | `--bit-AiMarkdownView-rule-color` | var(--bit-clr-brd-sec) | Thematic breaks, and the line above the footnotes. | | `--bit-AiMarkdownView-table-border-color` | var(--bit-clr-brd-sec) | Border of the tables and of their rows. | | `--bit-AiMarkdownView-table-header-background` | 4% of var(--bit-clr-fg-pri) | Background of the header row of the tables. | | `--bit-AiMarkdownView-table-font-size` | var(--bit-tpg-fs-sm) | Text size of the tables. | | `--bit-AiMarkdownView-table-radius` | var(--bit-shp-radius-surface) | Corner radius of the tables. | | `--bit-AiMarkdownView-image-radius` | var(--bit-shp-radius-surface) | Corner radius of the images. | | `--bit-AiMarkdownView-muted-color` | var(--bit-clr-fg-sec) | The footnotes, and the alternative text of a blocked image. | | `--bit-AiMarkdownView-caret-color` | var(--bit-clr-pri) | The caret after streaming text. | | `--bit-AiMarkdownView-scroll-margin` | spacing(3) | Room kept above a heading, a footnote or a footnote reference an in-page link scrolls to - the height of a sticky app bar, so what the link leads to is not left under it. | ## Examples \n**Basic**: ```razor ``` ```csharp private const string basicMarkdown = """ # Choosing a render mode Blazor renders on the **server**, over a circuit, or in the *browser*, on WebAssembly - and ~~one size fits all~~ each page can pick its own. Start with [the docs](https://learn.microsoft.com/aspnet/core/blazor) or https://blazorui-ai.bitplatform.dev, and set it with `@rendermode`. 1. Measure the first paint. 2. Measure the cost of every update: - on the server, in messages; - in the browser, in milliseconds. """; private readonly BitAiMarkdownDocument basicDocument = BitAiMarkdownParser.Parse(basicMarkdown); ``` \n**Blocks**: ```razor ``` ```csharp private const string blocksMarkdown = """ | Mode | Runs on | First paint | Offline | | :--- | --- | :---: | ---: | | Server | The server, over a circuit | Fast | No | | WebAssembly | The browser | Slower | Yes | > Rendering more often than the eye can follow is not smoother. It is just louder. - [x] Pick a render mode - [ ] Measure the first paint --- ```csharp builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); ``` """; private readonly BitAiMarkdownDocument blocksDocument = BitAiMarkdownParser.Parse(blocksMarkdown); ``` \n**Streaming caret**: ```razor ``` ```csharp private const string openMarkdown = "The answer is still being written, so its last paragraph is open"; private readonly BitAiMarkdownDocument openDocument = BitAiMarkdownParser.Parse(openMarkdown, isComplete: false); private const string openCodeMarkdown = """ Install the package: ```bash dotnet add package Bit.BlazorUI.AI ``` """; private readonly BitAiMarkdownDocument openCodeDocument = BitAiMarkdownParser.Parse(openCodeMarkdown, isComplete: false); ``` \n**Footnotes & headings**: ```razor ``` ```csharp private const string footnotesMarkdown = """ ## Setup Server rendering keeps the state on the server[^1], which is also why a dropped connection loses it[^1]. WebAssembly downloads the runtime once[^2]. [^1]: Over a SignalR circuit. [^2]: And caches it for the next visit. """; private readonly BitAiMarkdownDocument footnotesDocument = BitAiMarkdownParser.Parse(footnotesMarkdown); ``` \n**Mathematics**: ```razor @using System.Text @using System.Text.RegularExpressions ``` ```csharp // An app registers its renderers once, at startup: AddBitBlazorUIAIServices(ai => ai.ConfigureRenderers(...)). That // registry is one instance for the whole app, so a page that wants renderers of its own cascades a registry of its own. private readonly BitAiRendererRegistry pageRenderers = new(); private const string mathMarkdown = """ The energy of a body at rest is $E = mc^2$, and the area of a circle is $\pi r^2$. $$ \sigma = \sqrt{\sum_{i=1}^{n} (x_i - \mu)^2 / n} $$ """; private readonly BitAiMarkdownDocument mathDocument = BitAiMarkdownParser.Parse(mathMarkdown); private bool typesetMath; private void ToggleMathRenderer() => pageRenderers.MathRenderer = typesetMath ? RenderMath : null; private static readonly Dictionary mathSymbols = new() { ["alpha"] = "α", ["beta"] = "β", ["pi"] = "π", ["mu"] = "μ", ["sigma"] = "σ", ["theta"] = "θ", ["sum"] = "∑", ["int"] = "∫", ["infty"] = "∞", ["sqrt"] = "√", ["times"] = "×", ["cdot"] = "·", ["leq"] = "≤", ["geq"] = "≥", ["neq"] = "≠", ["approx"] = "≈", }; // A tiny typesetter - superscripts, subscripts and a few symbols - where an app would use KaTeX or MathJax. The // expression is model output, so it is only ever written as text, never as markup. private static RenderFragment RenderMath(BitAiMathContext context) => builder => { builder.OpenElement(0, context.IsBlock ? "div" : "span"); builder.AddAttribute(1, "style", context.IsBlock ? "text-align: center; font: italic 1.25em serif;" : "font: italic 1.1em serif;"); foreach (var (text, tag) in Typeset(context.Expression)) { if (tag is null) { builder.AddContent(2, text); continue; } builder.OpenElement(3, tag); builder.AddContent(4, text); builder.CloseElement(); } builder.CloseElement(); }; private static IEnumerable<(string Text, string? Tag)> Typeset(string tex) { var text = new StringBuilder(); for (var i = 0; i < tex.Length; i++) { if (tex[i] is '^' or '_' && i + 1 < tex.Length) { if (text.Length > 0) yield return (Symbols(text.ToString()), null); text.Clear(); var tag = tex[i] == '^' ? "sup" : "sub"; var start = i + 1; var end = tex[start] == '{' ? tex.IndexOf('}', start) : start; if (end < 0) end = tex.Length - 1; if (tex[start] == '\\') while (end + 1 < tex.Length && char.IsLetter(tex[end + 1])) end++; yield return (Symbols(tex[start..(end + 1)].Trim('{', '}')), tag); i = end; } else if (tex[i] is not ('{' or '}')) { text.Append(tex[i]); } } if (text.Length > 0) yield return (Symbols(text.ToString()), null); } private static string Symbols(string tex) => Regex.Replace(tex, @"\\([a-zA-Z]+)", m => mathSymbols.GetValueOrDefault(m.Groups[1].Value, m.Groups[1].Value)); ``` \n**Code languages**: ```razor ``` ```csharp private readonly BitAiRendererRegistry pageRenderers = new(); private const string barsMarkdown = """ Where the time of a first visit goes: ```bars Download: 420 Start the runtime: 180 First render: 60 ``` """; private readonly BitAiMarkdownDocument barsDocument = BitAiMarkdownParser.Parse(barsMarkdown); private const string openBarsMarkdown = """ And of a second visit: ```bars Download: 0 Start the run """; private readonly BitAiMarkdownDocument openBarsDocument = BitAiMarkdownParser.Parse(openBarsMarkdown, isComplete: false); protected override void OnInitialized() { // An app registers its renderers once, at startup: AddBitBlazorUIAIServices(ai => ai.ConfigureRenderers(...)). pageRenderers.RegisterCodeLanguage("bars", RenderBars); } // Renders a ```bars block - one "label: value" a line - as a bar chart, and waits for the block to be complete. private static RenderFragment RenderBars(BitAiCodeBlockContext context) => builder => { if (context.IsStreaming) { builder.OpenElement(0, "div"); builder.AddAttribute(1, "style", "padding: 1rem; font-style: italic; opacity: 0.7;"); builder.AddContent(2, "Drawing the chart..."); builder.CloseElement(); return; } var bars = context.Code.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Select(line => line.Split(':', 2, StringSplitOptions.TrimEntries)) .Where(pair => pair.Length == 2 && double.TryParse(pair[1], out _)) .Select(pair => (Label: pair[0], Value: double.Parse(pair[1]))) .ToArray(); var max = bars.Length == 0 ? 1 : Math.Max(1, bars.Max(b => b.Value)); builder.OpenElement(3, "div"); builder.AddAttribute(4, "role", "img"); builder.AddAttribute(5, "aria-label", string.Join(", ", bars.Select(b => $"{b.Label} {b.Value}"))); builder.AddAttribute(6, "style", "display: grid; grid-template-columns: max-content 1fr max-content; gap: 0.5rem 0.75rem; align-items: center;"); foreach (var (label, value) in bars) { builder.OpenElement(7, "span"); builder.AddContent(8, label); builder.CloseElement(); builder.OpenElement(9, "span"); builder.AddAttribute(10, "style", $"height: 0.75rem; border-radius: 0.375rem; background-color: var(--bit-clr-pri); width: {Math.Round(value / max * 100)}%;"); builder.CloseElement(); builder.OpenElement(11, "span"); builder.AddContent(12, $"{value} ms"); builder.CloseElement(); } builder.CloseElement(); }; ``` \n**Citations**: ```razor
Activated: @(clickedSource ?? "none yet")
``` ```csharp private const string citedMarkdown = """ Interactive Server keeps component state on the server and streams UI diffs over a SignalR circuit [1]. If that circuit drops, the state goes with it unless you have persisted it yourself [2]. A number no citation has, such as [3], stays the text it is. """; private readonly BitAiMarkdownDocument citedDocument = BitAiMarkdownParser.Parse(citedMarkdown); private readonly BitAiCitation[] citations = [ new("c1", new BitAiCitationSource("s1", "Blazor render modes") { Url = "https://learn.microsoft.com/aspnet/core/blazor/components/render-modes", SiteName = "Microsoft Learn", }) { Number = 1 }, new("c2", new BitAiCitationSource("s2", "SignalR circuit lifetime") { Url = "https://learn.microsoft.com/aspnet/core/blazor/fundamentals/signalr", SiteName = "Microsoft Learn", }) { Number = 2 }, ]; private string? clickedSource; ``` \n**Custom citation chip**: ```razor
Activated: @(chipSource ?? "none yet")
``` ```csharp // The registered registry is one instance for the whole app: a page with renderers of its own cascades its own. private readonly BitAiRendererRegistry pageRenderers = new(); // citedMarkdown, citedDocument and citations as in the previous example. private bool customChips; private string? chipSource; private void ToggleCitationRenderer() => pageRenderers.CitationRenderer = customChips ? RenderCitationChip : null; // A pill naming the site of the source beside its number. ActivateAsync raises the OnCitationClick of the view the chip // is drawn in, as a click on the built-in chip does. The source is model output, so it is only ever written as text. private static RenderFragment RenderCitationChip(BitAiCitationContext context) => builder => { var citation = context.Citation; builder.OpenElement(0, "button"); builder.AddAttribute(1, "type", "button"); builder.AddAttribute(2, "title", citation.Source.Title); builder.AddAttribute(3, "style", "margin-inline: 0.125rem; padding: 0 0.5rem; border: none; border-radius: 1rem; font: inherit; font-size: 0.75em; line-height: 1.5; cursor: pointer; color: var(--bit-clr-pri-text); background-color: var(--bit-clr-pri);"); builder.AddAttribute(4, "onclick", EventCallback.Factory.Create(context, context.ActivateAsync)); builder.AddContent(5, $"{citation.Number} · {citation.Source.SiteName ?? citation.Source.Title}"); builder.CloseElement(); }; ``` \n**Images**: ```razor
Images.Decide(the clock): @imageOptions.Images.Decide(clockUrl)
``` ```csharp [Inject] private NavigationManager Navigation { get; set; } = default!; private const string clockUrl = "https://img.icons8.com/fluency/2x/clock.png"; private const string imagesMarkdown = """ From this site: ![A portrait of a woman](_content/Bit.BlazorUI.Demo.Client.Core/images/persona/persona-female-72.png) From a listed host: ![A portrait of a woman, from a CDN](https://static2.sharepointonline.com/files/fabric/office-ui-fabric-react-assets/persona-female.png) From another host: ![A clock](https://img.icons8.com/fluency/2x/clock.png) Inline: ![A blue square](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='48'%20height='48'%3E%3Crect%20width='48'%20height='48'%20rx='8'%20fill='%232563eb'/%3E%3C/svg%3E) """; private readonly BitAiMarkdownDocument imagesDocument = BitAiMarkdownParser.Parse(imagesMarkdown); private readonly List> imageModeItems = [ new() { Text = "Allow", Value = nameof(BitAiImageMode.Allow) }, new() { Text = "AllowListedHosts", Value = nameof(BitAiImageMode.AllowListedHosts) }, new() { Text = "ClickToLoad", Value = nameof(BitAiImageMode.ClickToLoad) }, new() { Text = "Block", Value = nameof(BitAiImageMode.Block) }, ]; private string? imageMode = nameof(BitAiImageMode.Allow); private bool allowDataImages = true; private BitAiUiOptions imageOptions = BitAiUiOptions.Default; private void UpdateImageOptions() { var policy = imageMode switch { // A relative image is fetched from the host of the page, so that host is listed beside the CDN. nameof(BitAiImageMode.AllowListedHosts) => BitAiImagePolicy.AllowListedHosts("static2.sharepointonline.com", new Uri(Navigation.BaseUri).Host), nameof(BitAiImageMode.ClickToLoad) => BitAiImagePolicy.ClickToLoad, nameof(BitAiImageMode.Block) => BitAiImagePolicy.Block, _ => BitAiImagePolicy.Allow, }; imageOptions = BitAiUiOptions.Default with { Images = policy with { AllowDataImages = allowDataImages } }; } ``` \n**Untrusted content**: ```razor ``` ```csharp private const string untrustedMarkdown = """ A link a model was talked into: [claim your prize](javascript:alert('pwned')). Press Ctrl + K to search.
An HTML blockRendered as markup only when raw HTML is allowed.
"""; private static readonly BitAiMarkdownOptions rawHtmlMarkdownOptions = BitAiMarkdownOptions.Default with { AllowRawHtml = true }; private bool allowRawHtml; private BitAiMarkdownDocument untrustedDocument = BitAiMarkdownParser.Parse(untrustedMarkdown); private BitAiUiOptions rawHtmlOptions = BitAiUiOptions.Default; private void UpdateRawHtml() { var markdownOptions = allowRawHtml ? rawHtmlMarkdownOptions : BitAiMarkdownOptions.Default; // Both are needed: the parser keeps the markup, and the view renders what the options in force allow. untrustedDocument = BitAiMarkdownParser.Parse(untrustedMarkdown, markdownOptions); rawHtmlOptions = BitAiUiOptions.Default with { Markdown = markdownOptions }; } ``` \n**Markdown options**: ```razor ``` ```csharp // citations as in the Citations example. private const string optionsMarkdown = """ | Option | Default | | --- | --- | | Tables | on | - [x] a task that is done - [ ] one that is not ~~Struck through~~, a bare URL https://bitplatform.dev and $x^2$. One line, and the next. A cited claim [1] and a footnote[^1]. [^1]: Listed under the text. """; private bool parseTables = true; private bool parseTaskLists = true; private bool parseStrikethrough = true; private bool parseAutoLinks = true; private bool parseMath = true; private bool parseFootnotes = true; private bool parseCitationMarkers = true; private bool parseSoftBreaks; private BitAiMarkdownDocument optionsDocument = BitAiMarkdownParser.Parse(optionsMarkdown); private void Reparse() { var options = new BitAiMarkdownOptions { Tables = parseTables, TaskLists = parseTaskLists, Strikethrough = parseStrikethrough, AutoLinks = parseAutoLinks, Math = parseMath, Footnotes = parseFootnotes, CitationMarkers = parseCitationMarkers, SoftBreaksAsHardBreaks = parseSoftBreaks, }; optionsDocument = BitAiMarkdownParser.Parse(optionsMarkdown, options); } ``` \n**More of the syntax**: ```razor ``` ```csharp private const string syntaxMarkdown = """ A heading underlined -------------------- A link with a title: [the docs](https://learn.microsoft.com/aspnet/core/blazor "The Blazor documentation"), and a [reference link][docs] whose address is given further down. www.bitplatform.dev, and are links too. A backslash at the end of a line\ breaks it. Escapes keep \*stars\* and \[brackets\] as they are, and entities read as their characters: © → €. An indented block is code: dotnet run 3. A numbered list may start at any number 4. and counts on from there. [docs]: https://learn.microsoft.com/aspnet/core/blazor """; private readonly BitAiMarkdownDocument syntaxDocument = BitAiMarkdownParser.Parse(syntaxMarkdown); ``` \n**The document tree**: ```razor
Headings:
    @foreach (var heading in Walk(treeDocument.Blocks).OfType()) {
  • level @heading.Level, "@TextOf(heading.Inlines)", id [email protected]
  • }
Code blocks:
    @foreach (var block in Walk(treeDocument.Blocks).OfType()) {
  • @(block.Language ?? "text"), @(block.Code.Split('\n').Length) line(s)
  • }
``` ```csharp private const string treeMarkdown = """ ## Install Add the package to the project: ```bash dotnet add package Bit.BlazorUI.AI ``` ## Register > Register the services once, at startup: > > ```csharp > builder.Services.AddBitBlazorUIAIServices(); > ``` 1. Then put a chat on a page: ```razor ``` """; private readonly BitAiMarkdownDocument treeDocument = BitAiMarkdownParser.Parse(treeMarkdown); // Every block of the document in reading order, the ones inside the quotes and the list items included. private static IEnumerable Walk(IEnumerable blocks) { foreach (var block in blocks) { yield return block; if (block is BitAiQuoteBlock quote) { foreach (var child in Walk(quote.Blocks)) yield return child; } else if (block is BitAiListBlock list) { foreach (var child in Walk(list.Items.SelectMany(item => item.Blocks))) yield return child; } } } // The plain text of a run of inlines: the words of a heading without its emphasis, its code spans or its links. private static string TextOf(IEnumerable inlines) => string.Concat(inlines.Select(inline => inline switch { BitAiTextInline text => text.Text, BitAiCodeInline code => code.Code, BitAiEmphasisInline emphasis => TextOf(emphasis.Inlines), BitAiStrikethroughInline strike => TextOf(strike.Inlines), BitAiLinkInline link => TextOf(link.Inlines), _ => string.Empty, })); ``` \n**Cascading parameters**: ```razor ``` ```csharp private const string paramsMarkdown = """ > A link of a view inside the BitParams, such as [this one](https://bitplatform.dev), is bold - unless its view sets Styles of its own. """; private readonly BitAiMarkdownDocument paramsDocument = BitAiMarkdownParser.Parse(paramsMarkdown); private readonly BitAiMarkdownViewParams[] markdownParams = [ new() { Styles = new() { Link = "font-weight: bold;", Quote = "font-style: italic;" } }, ]; ``` \n**Style & Class**: ```razor
``` ```csharp private const string styleMarkdown = """ #### A styled answer Some prose with a [link](https://bitplatform.dev) and `inline code`. - a first point - a second point > A quote. """; private readonly BitAiMarkdownDocument styleDocument = BitAiMarkdownParser.Parse(styleMarkdown); ``` \n**RTL**: ```razor ``` ```csharp private const string rtlMarkdown = """ #### دو حالت رندر بلیزر روی **سرور** یا در **مرورگر** رندر می‌کند: - روی سرور، از راه یک اتصال؛ - در مرورگر، روی WebAssembly. > رندر بیشتر از آنچه چشم می‌بیند، روان‌تر نیست. | حالت | اجرا | نخستین نمایش | | --- | --- | ---: | | سرور | روی سرور | سریع | | وب‌اسمبلی | در مرورگر | کندتر | """; private readonly BitAiMarkdownDocument rtlDocument = BitAiMarkdownParser.Parse(rtlMarkdown); ```