Skip to content
# AiArtifactView **Also known as:** Canvas ## Description BitAiArtifactView is the panel an artifact lives in: the substantial piece of content an assistant produces beside its answer - a program, a document, a page, a table, an image - and revises over the turns that follow. The header holds the title, a version selector once there is more than one version, copy, download and a full-screen toggle; the body is chosen by the kind of the artifact - a renderer the app registered, or a built-in one: highlighted code, a streaming Markdown document, an HTML page in a sandboxed frame, a JSON tree, a table, or an image. The panel follows the artifact as it streams, and copy and download act on the version on screen. ## Parameters | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Artifact | `BitAiArtifact` | null | The artifact to show (required). The panel follows it as it changes - a streaming artifact re-renders it at most once per render interval of the AI options - and picks its body by its Kind: the renderer the app registered for that kind, else the built-in one. | | Classes | `BitAiArtifactViewClassStyles?` | null | Custom CSS classes for different parts of the artifact view. | | InitialVersion | `int?` | null | The version to show until the user picks another, for example the one a particular turn produced, so a later revision does not rewrite what an earlier turn appears to have said. The latest version is shown while it is unset, or when the artifact has no such version. Handing the panel another artifact drops the user's pick. | | Styles | `BitAiArtifactViewClassStyles?` | null | Custom CSS styles for different parts of the artifact 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 ### BitAiArtifactKind Enum | Name | Value | Description | | :--- | :--- | :---------- | | Document | "document" | Prose in Markdown, rendered as streaming Markdown and downloaded as .md. | | Code | "code" | Source code, highlighted in its Language and downloaded with that language's extension. | | Html | "html" | A self-contained HTML page, previewed in a sandboxed frame once complete (shown as its code while it streams) and downloaded as .html. | | Json | "json" | A JSON value, rendered as a collapsible tree and downloaded as .json. | | Table | "table" | Tabular data: CSV (or TSV when the Language is tsv) as a table whose first row is the header, or a JSON array of objects as a BitAiDataTable; downloaded as .csv, .tsv or .json. | | Chart | "chart" | A chart specification, for a renderer the app registers; shown as its code otherwise. | | Image | "image" | A raster or vector image, shown from its Url or from a data URL as its content, as the image policy of the options decides. | | Svg | "svg" | SVG markup, shown through an img once complete - so no script inside it runs - as the image policy of the options decides, and downloaded as .svg. | | Diagram | "diagram" | A diagram description, for a renderer the app registers; shown as its code otherwise. | ### 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 ### BitAiArtifactViewClassStyles Properties | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Root | `string?` | null | Custom CSS classes/styles for the root element of the BitAiArtifactView. | | Overlay | `string?` | null | Custom CSS classes/styles for the overlay laid over the page behind the full-screen panel of the BitAiArtifactView. | | Panel | `string?` | null | Custom CSS classes/styles for the panel - the header and the body - of the BitAiArtifactView. | | Header | `string?` | null | Custom CSS classes/styles for the header of the BitAiArtifactView. | | Icon | `string?` | null | Custom CSS classes/styles for the icon of the kind of the artifact in the header of the BitAiArtifactView. | | Title | `string?` | null | Custom CSS classes/styles for the title of the BitAiArtifactView. | | Loading | `BitLoadingClassStyles?` | null | Custom CSS classes/styles for the BitEllipsisLoading the header of the BitAiArtifactView shows while the artifact streams. | | VersionDropdown | `BitDropdownClassStyles?` | null | Custom CSS classes/styles for the BitDropdown that picks the version shown by the BitAiArtifactView. | | CopyButton | `BitAiCopyButtonClassStyles?` | null | Custom CSS classes/styles for the copy button (a BitAiCopyButton) of the BitAiArtifactView. | | DownloadButton | `BitButtonClassStyles?` | null | Custom CSS classes/styles for the download BitButton of the BitAiArtifactView. | | ExpandButton | `BitButtonClassStyles?` | null | Custom CSS classes/styles for the full-screen toggle BitButton of the BitAiArtifactView. | | Body | `string?` | null | Custom CSS classes/styles for the body of the BitAiArtifactView. | | Content | `string?` | null | Custom CSS classes/styles for the container of a document or a JSON tree in the body of the BitAiArtifactView. | | Image | `string?` | null | Custom CSS classes/styles for the image an image or an SVG artifact is shown as by the BitAiArtifactView. | | ImagePlaceholder | `BitActionButtonClassStyles?` | null | Custom CSS classes/styles for the button (a BitActionButton) the BitAiArtifactView shows in place of an image or an SVG artifact the image policy loads only on request. | | BlockedImage | `string?` | null | Custom CSS classes/styles for the title the BitAiArtifactView shows in place of an image or an SVG artifact the image policy blocks. | | Frame | `string?` | null | Custom CSS classes/styles for the sandboxed frame an HTML artifact is previewed in by the BitAiArtifactView. | | Table | `string?` | null | Custom CSS classes/styles for the box of the table a CSV or TSV artifact is shown as by the BitAiArtifactView. | | HeaderCell | `string?` | null | Custom CSS classes/styles for each header cell of the table of a CSV or TSV artifact of the BitAiArtifactView. | | Cell | `string?` | null | Custom CSS classes/styles for each body cell of the table of a CSV or TSV artifact of the BitAiArtifactView. | ### BitAiArtifact Properties A substantial piece of content produced alongside a conversation and revised across turns. It belongs to the conversation rather than to a message (BitAiConversation.Artifacts, GetOrAddArtifact, FindArtifact), so a rewrite three turns later updates one object with a version history instead of scattering near-identical copies through the transcript. A session creates and fills it from the artifact events of its transport; an app creates one to show content of its own. Safe for one writer and concurrent readers. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | new(artifactId, kind, title) | `constructor` | | Creates a streaming artifact at version 1; a null artifactId generates one. Append or SetContent its content, then Complete it. | | ArtifactId | `string` | | The identifier, unique within a conversation. | | Kind | `BitAiArtifactKind` | | The kind of the artifact, which picks its renderer. | | Title | `string` | | The title in the header, which also names the downloaded file. Changed through SetTitle. | | Content | `string?` | "" | The content of the current version, as text. | | Language | `string?` | null | A language or format hint: the language code is highlighted in and downloaded as (csharp, typescript, python, ...), and tsv for a tab-separated table. | | Url | `string?` | null | An address for content that is not inline text, such as the source of an image. | | Version | `int` | 1 | The one-based number of the current version, incremented by every revision. | | IsStreaming | `bool` | true | Whether the current version is still being written: the panel shows its loading dots and previews an HTML page or an SVG only once it is false. | | Versions | `IReadOnlyList<BitAiArtifactVersion>` | [] | Every committed version, oldest first; the current draft is not included. | | Metadata | `IReadOnlyDictionary<string, object?>?` | null | Application-defined values carried through to a registered artifact renderer. | | CreatedAt / UpdatedAt | `DateTimeOffset` | | When the artifact was created, and when its current version last changed. | | Length / ChangeVersion | `int` | 0 | How many characters the content holds, without materializing it; and a counter bumped on every change, for cheap render checks. | | Append(delta) | `void` | | Appends content to the current version, in linear time however long the artifact grows. | | SetContent(content) | `void` | | Replaces the content of the current version. | | SetTitle(title) | `void` | | Renames the artifact. | | Complete(summary?) | `void` | | Commits the current content as a version, with an optional summary of what changed, and stops streaming. Completing it again records nothing new, but can add the summary the first call lacked. | | BeginRevision() | `void` | | Starts the next version, empty and streaming, completing the current one first if it is not; the previous version stays in Versions for the selector. | | Restore(version) | `bool` | | Brings the content of an earlier version back as a new version, summarized "Restored version N"; false when there is no such version. | | Changed | `event Action<BitAiArtifact>` | | Raised after every change, which is what the panel follows the artifact by. | ### BitAiArtifactVersion Properties An immutable snapshot of an artifact at one version, recorded by Complete - one entry of Versions and of the version selector. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Version | `int` | | The one-based version number. | | Content | `string?` | | The content at this version, which the panel shows, copies and downloads when the version is picked. | | CreatedAt | `DateTimeOffset` | | When this version was committed. | | Summary | `string?` | null | An optional description of what changed, from Complete(summary) or the Summary of a BitAiArtifactCompletedEvent. | ### BitAiRendererRegistry Properties Where an app registers its own renderers, which the components resolve before their built-in views. A singleton: register at startup (AddBitBlazorUIAIServices(ai => ai.ConfigureRenderers(renderers => ...))) and treat the entries as fixed afterwards - every entry is app-wide. A region with renderers of its own cascades a registry of its own (new BitAiRendererRegistry(), in a CascadingValue), which every AI component inside it uses in place of the registered one. These are its artifact members; the whole registry is on the AiMessagePartView page (/components/ai/messagepartview#renderer-registry). | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | RegisterArtifact(kind, renderer) | `void` | | Registers a BitAiArtifactRenderer for an artifact kind (case does not matter) - a built-in kind or one of the app's own. Every BitAiArtifactView of an artifact of that kind renders it as its body in place of the built-in one, inside a BitAiRendererBoundary; a later registration replaces an earlier one. | | FindArtifact(kind) | `BitAiArtifactRenderer?` | | The renderer registered for an artifact kind, or null when the built-in body draws it. | ### BitAiArtifactRenderer Properties The delegate a custom artifact renderer is: public delegate RenderFragment BitAiArtifactRenderer(BitAiArtifactContext context). It is called on every render of the panel - each coalesced change of a streaming artifact and each turn of the full-screen toggle - so it reads what it draws off the context as it is called. It draws the context's Content - the content of the version on screen, which follows the panel's version selector - rather than the artifact's own Content, which is always the latest one's. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | context | `BitAiArtifactContext` | | The artifact and how it is being shown. | | (return value) | `RenderFragment` | | The markup drawn as the body of the panel, under its header. | ### BitAiArtifactContext Properties What a renderer registered with BitAiRendererRegistry.RegisterArtifact(kind, context => ...) is handed (a sealed record). The renderer draws the body of every panel of that kind, inside a BitAiRendererBoundary. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Artifact | `BitAiArtifact` | | The artifact being shown. | | IsExpanded | `bool` | false | Whether the panel is full screen, for a renderer that lays itself out to the room it has. | | Version | `int` | Artifact.Version | The version on screen: the latest one unless an earlier one was picked in the version selector of the panel (or set as its InitialVersion). | | Content | `string?` | Artifact.Content | The content of the version on screen - what a renderer draws. The artifact's own Content is always the latest version's, so a renderer reading it would ignore the version selector. | | IsLatest | `bool` | | Whether the version on screen is the latest one - the only one that can still be streaming (Artifact.IsStreaming). | ## 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-AiArtifactView-background` | var(--bit-clr-bg-pri) | Background of the panel. | | `--bit-AiArtifactView-color` | var(--bit-clr-fg-pri) | Color of the text of the panel. | | `--bit-AiArtifactView-border-color` | var(--bit-clr-brd-pri) | Color of the frame of the panel. | | `--bit-AiArtifactView-radius` | var(--bit-shp-radius-surface) | Corner of the panel. | | `--bit-AiArtifactView-header-background` | var(--bit-clr-bg-sec) | Background of the header. | | `--bit-AiArtifactView-header-padding` | spacing(1) spacing(1.5) | Inset of the header. | | `--bit-AiArtifactView-header-gap` | spacing(1) | Room between the parts of the header. | | `--bit-AiArtifactView-divider-color` | var(--bit-clr-brd-sec) | Color of the rule under the header. | | `--bit-AiArtifactView-icon-color` | var(--bit-clr-fg-sec) | Color of the icon of the kind beside the title. | | `--bit-AiArtifactView-title-font-size` | var(--bit-tpg-fs-sm) | Size of the title. | | `--bit-AiArtifactView-title-font-weight` | var(--bit-tpg-fw-semibold) | Weight of the title. | | `--bit-AiArtifactView-max-height` | spacing(64) | Height past which the body of an inline panel scrolls; the full-screen panel fills the screen instead. | | `--bit-AiArtifactView-content-padding` | spacing(1.5) spacing(2) | Inset of a document or a JSON tree in the body, and of what stands in for an image or an SVG artifact the image policy holds back (its placeholder button or its title). | | `--bit-AiArtifactView-muted-color` | var(--bit-clr-fg-sec) | Color of the title shown in place of an image or an SVG artifact the image policy blocks. | | `--bit-AiArtifactView-frame-height` | spacing(52) | Height of the frame of an HTML page in an inline panel; the full-screen panel stretches it to the body. | | `--bit-AiArtifactView-frame-background` | white | Background of the frame of an HTML page - the white page a generated document expects, kept in a dark theme too. | | `--bit-AiArtifactView-expanded-inset` | spacing(2); none on a small screen | Room between the full-screen panel and the edges of the screen. | | `--bit-AiArtifactView-overlay-background` | var(--bit-clr-bg-overlay) | Background of the overlay behind the full-screen panel. | | `--bit-AiArtifactView-table-divider-color` | var(--bit-clr-brd-sec) | Color of the rules between the rows of a CSV or TSV table. | | `--bit-AiArtifactView-table-header-background` | var(--bit-clr-bg-sec) | Background of the header row of a CSV or TSV table. | ## Examples \n**Basic**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact programArtifact = Completed(BitAiArtifactKind.Code, "Retry.cs", """ public static async Task RetryAsync(Func> action, int attempts = 3) { for (var attempt = 1; ; attempt++) { try { return await action(); } catch (HttpRequestException) when (attempt < attempts) { await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); } } } """, "csharp"); ``` \n**Documents**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact documentArtifact = Completed(BitAiArtifactKind.Document, "Release notes", """ # Release notes ## What is new - **Artifacts** keep a version history across turns. - **Structured output** fills in while the JSON is still streaming. | Area | Change | | --- | --- | | Chat | Faster streaming | | Forms | JSON Schema support | ```bash dotnet add package Bit.BlazorUI.AI ``` """); ``` \n**HTML pages**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact pageArtifact = Completed(BitAiArtifactKind.Html, "Counter page", """

A generated page

It runs its own script, in a sandbox that keeps it away from the page around it.

"""); ``` \n**JSON & tables**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact jsonArtifact = Completed(BitAiArtifactKind.Json, "Settings", """ { "theme": "dark", "fontSize": 14, "autosave": true, "recent": ["README.md", "Program.cs"], "proxy": null } """); private readonly BitAiArtifact csvArtifact = Completed(BitAiArtifactKind.Table, "Prices", """" product,price,stock "Keyboard, wireless",89.50,12 Mouse,20,140 "Monitor 27""",329,4 """", "csv"); private readonly BitAiArtifact tsvArtifact = Completed(BitAiArtifactKind.Table, "Regions", "region\tsales\tgrowth\nEurope\t1.2M\t8%\nAmericas\t2.4M\t5%\nAsia\t1.9M\t12%", "tsv"); private readonly BitAiArtifact jsonTableArtifact = Completed(BitAiArtifactKind.Table, "Team", """ [ { "name": "Ada", "role": "Engineer", "remote": true }, { "name": "Grace", "role": "Architect", "remote": false } ] """); ``` \n**Images**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiUiOptions clickToLoadOptions = BitAiUiOptions.Default with { Images = BitAiImagePolicy.ClickToLoad }; private readonly BitAiUiOptions blockOptions = BitAiUiOptions.Default with { Images = BitAiImagePolicy.Block }; private readonly BitAiArtifact imageArtifact = Completed(BitAiArtifactKind.Image, "Mountain lake", null, url: "/_content/Bit.BlazorUI.Demo.Client.Core/images/carousel/img4.jpg"); private readonly BitAiArtifact svgArtifact = Completed(BitAiArtifactKind.Svg, "Badge", """ bit AI """); ``` \n**Other kinds**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact chartArtifact = Completed(BitAiArtifactKind.Chart, "Monthly sales", """ { "type": "bar", "labels": ["Jan", "Feb", "Mar"], "data": [120, 180, 150] } """, "json"); // A kind of the app's own: shown as its code until a renderer is registered for it. private readonly BitAiArtifact mermaidArtifact = Completed(new BitAiArtifactKind("mermaid"), "Sign-in flow", """ flowchart LR User --> Login --> Check{Valid?} Check -- yes --> Home Check -- no --> Login """); ``` \n**Custom renderers**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact chartArtifact = Completed(BitAiArtifactKind.Chart, "Monthly sales", """ { "type": "bar", "labels": ["Jan", "Feb", "Mar"], "data": [120, 180, 150] } """, "json"); private readonly BitAiArtifact brokenChartArtifact = Completed(BitAiArtifactKind.Chart, "Quarterly sales", """ { "type": "bar", "labels": ["Q1", "Q2"] } """, "json"); // An app registers its renderers once, at startup: // AddBitBlazorUIAIServices(ai => ai.ConfigureRenderers(renderers => renderers.RegisterArtifact(BitAiArtifactKind.Chart, RenderChart))); // 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 artifactRenderers = CreateArtifactRenderers(); private static BitAiRendererRegistry CreateArtifactRenderers() { var renderers = new BitAiRendererRegistry(); renderers.RegisterArtifact(BitAiArtifactKind.Chart, RenderChart); return renderers; } // Draws a { "labels": [...], "data": [...] } spec as bars, taller in the full-screen panel. A half-written spec is // waited for, and the labels are model output, so they are only ever written as text. (using System.Text.Json;) private static RenderFragment RenderChart(BitAiArtifactContext context) => builder => { if (context.IsLatest && context.Artifact.IsStreaming) { builder.AddContent(0, "Drawing the chart..."); return; } using var spec = JsonDocument.Parse(context.Content ?? "{}"); var labels = spec.RootElement.GetProperty("labels").EnumerateArray().Select(l => l.GetString()).ToArray(); var values = spec.RootElement.GetProperty("data").EnumerateArray().Select(v => v.GetDouble()).ToArray(); var max = Math.Max(1, values.Max()); var height = context.IsExpanded ? 24 : 8; builder.OpenElement(1, "div"); builder.AddAttribute(2, "role", "img"); builder.AddAttribute(3, "aria-label", string.Join(", ", labels.Zip(values, (label, value) => $"{label} {value}"))); builder.AddAttribute(4, "style", "display: flex; align-items: flex-end; gap: 1rem; padding: 1rem;"); for (var i = 0; i < values.Length; i++) { builder.OpenElement(5, "div"); builder.AddAttribute(6, "style", "flex: 1; display: flex; flex-direction: column; align-items: center; gap: 0.25rem;"); builder.OpenElement(7, "div"); builder.AddAttribute(8, "style", $"width: 100%; height: {Math.Round(values[i] / max * height * 16)}px; border-radius: 0.25rem; background-color: var(--bit-clr-pri);"); builder.CloseElement(); builder.OpenElement(9, "span"); builder.AddContent(10, i < labels.Length ? labels[i] : null); builder.CloseElement(); builder.CloseElement(); } builder.CloseElement(); }; ``` \n**Streaming**: ```razor @implements IAsyncDisposable @(ReadmeArtifact is null ? "Write a README" : "Revise it") @if (ReadmeArtifact is { } readme) { } ``` ```csharp // A scripted transport that writes a README into the artifact art-readme, and revises it on the next turn (see the next tab). private readonly BitAiChatSession readmeSession = new(AiDemoContentTransports.Artifacts()); private BitAiRenderScheduler? renderScheduler; protected override void OnInitialized() { // The session says so on every streamed delta; the scheduler turns any number of those into one render per 50 ms. renderScheduler = new(() => InvokeAsync(StateHasChanged)); readmeSession.StateChanged += renderScheduler.Request; } // The artifact belongs to the conversation, found by its id. private BitAiArtifact? ReadmeArtifact => readmeSession.Conversation.FindArtifact("art-readme"); private Task WriteReadmeAsync() => readmeSession.SubmitAsync(ReadmeArtifact is null ? "Write me a README" : "Tighten it and add an install step"); public async ValueTask DisposeAsync() { if (renderScheduler is not null) await renderScheduler.DisposeAsync(); await readmeSession.DisposeAsync(); } ``` `AiDemoContentTransports.cs`: ```csharp public static IBitAiTransport Artifacts() => new AiScriptedTransport((request, token) => AiScript.Stream(token, async yielder => { const string artifactId = "art-readme"; // Every turn after the first is a revision, numbered in the body, so no version comes out the same as the one // before it. var revision = request.Messages.Count(m => m.Role.IsUser) - 1; var isRevision = revision > 0; await yielder(new BitAiMessageStartedEvent()); var intro = revision switch { 0 => "Here is a first draft. Tell me what to change, and I will revise it.", 1 => "Revised. I tightened the opening and added the install step.", _ => $"Revised again, into revision {revision}.", }; await foreach (var evt in AiScript.Write(intro, 4, 12, token)) await yielder(evt); // Opening the same artifact id again starts its next version. await yielder(new BitAiArtifactCreatedEvent(artifactId, BitAiArtifactKind.Document, "Project README") { Language = "markdown", }); var body = isRevision ? $""" # Bit.BlazorUI.AI The AI UI layer of bit BlazorUI: conversations, streaming, tools, agents, and artifacts. ## Install ```bash dotnet add package Bit.BlazorUI.AI ``` ## Why Chat is the easy part. What takes the time is everything around it: partial JSON, tool approval, citation anchoring, and not re-rendering the world on every token. _Revision {revision}._ """ : """ # Bit.BlazorUI.AI The AI components of bit BlazorUI. ## Why Chat is the easy part. Everything around it is what takes the time. """; foreach (var slice in AiScript.Slice(body, 12)) { await AiScript.Pause(60, token); await yielder(new BitAiArtifactDeltaEvent(artifactId, slice)); } await yielder(new BitAiArtifactCompletedEvent(artifactId) { Summary = revision switch { 0 => "First draft", 1 => "Tightened the opening, added install", _ => $"Revision {revision}", }, }); await yielder(new BitAiMessageCompletedEvent()); })); ``` \n**Versions**: ```razor Restore version 1
    @foreach (var version in pricingArtifact.Versions) {
  • Version @version.Version: @version.Summary
  • }
``` ```csharp private readonly BitAiArtifact pricingArtifact = Revised(); private static BitAiArtifact Revised() { var artifact = new BitAiArtifact("pricing", BitAiArtifactKind.Code, "pricing.ts") { Language = "typescript" }; artifact.SetContent("export const price = 10;"); artifact.Complete("First draft"); artifact.BeginRevision(); artifact.SetContent("export const price = (quantity: number) => quantity * 10;"); artifact.Complete("Priced per unit"); artifact.BeginRevision(); artifact.SetContent("export const price = (quantity: number) => quantity * (quantity >= 10 ? 9 : 10);"); artifact.Complete("A bulk discount"); return artifact; } private void RestoreFirstVersion() => pricingArtifact.Restore(1); ``` \n**Copy, download & full screen**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact pageArtifact = Completed(BitAiArtifactKind.Html, "Counter page", """

A generated page

It runs its own script, in a sandbox that keeps it away from the page around it.

"""); private readonly BitAiArtifact pricingArtifact = Revised(); private static BitAiArtifact Revised() { var artifact = new BitAiArtifact("pricing", BitAiArtifactKind.Code, "pricing.ts") { Language = "typescript" }; artifact.SetContent("export const price = 10;"); artifact.Complete("First draft"); artifact.BeginRevision(); artifact.SetContent("export const price = (quantity: number) => quantity * 10;"); artifact.Complete("Priced per unit"); artifact.BeginRevision(); artifact.SetContent("export const price = (quantity: number) => quantity * (quantity >= 10 ? 9 : 10);"); artifact.Complete("A bulk discount"); return artifact; } ``` \n**Cascading parameters**: ```razor ``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact programArtifact = Completed(BitAiArtifactKind.Code, "Retry.cs", """ public static async Task RetryAsync(Func> action, int attempts = 3) { for (var attempt = 1; ; attempt++) { try { return await action(); } catch (HttpRequestException) when (attempt < attempts) { await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); } } } """, "csharp"); private readonly BitAiArtifact jsonArtifact = Completed(BitAiArtifactKind.Json, "Settings", """ { "theme": "dark", "fontSize": 14, "autosave": true, "recent": ["README.md", "Program.cs"], "proxy": null } """); private readonly BitAiArtifactViewParams[] artifactViewParams = [ new() { Styles = new() { Panel = "border-radius: 1rem", Header = "background-color: var(--bit-clr-pri-light)", Title = "color: var(--bit-clr-pri)" } }, ]; ``` \n**Style & Class**: ```razor
``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact programArtifact = Completed(BitAiArtifactKind.Code, "Retry.cs", """ public static async Task RetryAsync(Func> action, int attempts = 3) { for (var attempt = 1; ; attempt++) { try { return await action(); } catch (HttpRequestException) when (attempt < attempts) { await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); } } } """, "csharp"); private readonly BitAiArtifact documentArtifact = Completed(BitAiArtifactKind.Document, "Release notes", """ # Release notes ## What is new - **Artifacts** keep a version history across turns. - **Structured output** fills in while the JSON is still streaming. | Area | Change | | --- | --- | | Chat | Faster streaming | | Forms | JSON Schema support | ```bash dotnet add package Bit.BlazorUI.AI ``` """); ``` \n**RTL**: ```razor
``` ```csharp // A finished artifact, as a session leaves it once the transport has completed it. private static BitAiArtifact Completed(BitAiArtifactKind kind, string title, string? content, string? language = null, string? url = null) { var artifact = new BitAiArtifact(null, kind, title) { Language = language, Url = url }; if (content is not null) artifact.SetContent(content); artifact.Complete(); return artifact; } private readonly BitAiArtifact persianArtifact = Completed(BitAiArtifactKind.Document, "یادداشت انتشار", """ # یادداشت انتشار - **آرتیفکت‌ها** تاریخچه نسخه‌ها را نگه می‌دارند. - **خروجی ساختاریافته** همزمان با رسیدن JSON پر می‌شود. """); ```