Skip to content

AI scenarios

Artifacts

Bit.BlazorUI.AI Scripted transport

A document written into its own panel, then revised on the next turn with the earlier version kept.

Live demo

Send once for a draft, then again to see it revised into version two.

\n**A document, drafted and revised**: ```razor @implements IAsyncDisposable
``` ```csharp private readonly BitAiChatSession session = new(AiDemoContentTransports.Artifacts()); private readonly string[] suggestions = ["Write me a README", "Tighten it and add an install step"]; public ValueTask DisposeAsync() => session.DisposeAsync(); ``` `AiDemoContentTransports.cs`: ```csharp // AiScriptedTransport and AiScript are the demo's helpers - see the Basic chat scenario. public static IBitAiTransport Artifacts() => new AiScriptedTransport((request, token) => AiScript.Stream(token, async yielder => { // The same id on every turn: a later turn revises the artifact rather than creating another. 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()); await foreach (var evt in AiScript.Write(isRevision ? $"Revision {revision}." : "Here is a first draft.", 4, 12, token)) await yielder(evt); await yielder(new BitAiArtifactCreatedEvent(artifactId, BitAiArtifactKind.Document, "Project README") { Language = "markdown", }); var body = isRevision ? $"{RevisedReadme}\n\n_Revision {revision}._" : FirstReadme; 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()); })); ```

How it works

The transport opens an artifact with BitAiArtifactCreatedEvent, streams its body with BitAiArtifactDeltaEvent and closes it with BitAiArtifactCompletedEvent. The artifact belongs to the conversation, not to a message: a revision three turns later updates the same object as a new version, so the UI keeps one artifact with a version selector instead of scattering near-identical code blocks through the transcript.

Here every turn writes a line of text, then opens art-readme with its Kind (BitAiArtifactKind.Document), its title and its Language (markdown), streams the body a dozen characters at a time, and completes it with a Summary of what changed, which labels the version. The same id on a later turn is what makes it a revision: the conversation (BitAiConversation.Artifacts) keeps one BitAiArtifact whose versions grow, and the message gets a BitAiArtifactPart pointing at the version it produced. An artifact that is not streamed is sent whole, with Content on the created event, or as a Url.

Each message shows the artifact opened at the version that turn produced, so a later revision never rewrites what an earlier turn appears to have said. Copy and download work on the version being viewed, and an HTML artifact renders in a sandboxed frame with same-origin access denied. A kind of the app's own gets its own view through renderers.RegisterArtifact(kind, ...).

Components used

Feedback

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