AI scenarios
Citations
Bit.BlazorUI.AI Scripted transportNumbered markers placed exactly where the claim is made, and a deduplicated source list under the answer.
Live demo
Ask the starter question, then click a citation marker: the source it points at is shown beside the chat.
“@quote”
} } ``` ```csharp private readonly BitAiChatSession session = new(AiDemoContentTransports.Cited()); private BitAiCitation? selected; private readonly string[] suggestions = ["What happens to state when a circuit drops?"]; private void OnCitationClicked(BitAiCitation citation) => selected = citation; public ValueTask DisposeAsync() => session.DisposeAsync(); ``` `AiDemoContentTransports.cs`: ```csharp // AiScriptedTransport and AiScript are the demo's helpers - see the Basic chat scenario. public static IBitAiTransport Cited() => new AiScriptedTransport((request, token) => AiScript.Stream(token, async yielder => { var source = new BitAiCitationSource("s1", "Blazor render modes") { Kind = BitAiSourceKind.Web, Url = "https://learn.microsoft.com/aspnet/core/blazor/components/render-modes", SiteName = "Microsoft Learn", }; await yielder(new BitAiMessageStartedEvent()); await yielder(new BitAiStatusEvent("Searching the documentation")); await AiScript.Pause(700, token); await yielder(new BitAiStatusEvent(null)); const string first = "Interactive Server keeps component state on the server and streams UI diffs over a SignalR circuit."; await foreach (var evt in AiScript.Write(first, 4, 12, token)) await yielder(evt); // Anchored to the sentence it supports, by character offsets into the text. await yielder(new BitAiCitationEvent(new BitAiCitation("c1", source) { StartIndex = 0, EndIndex = first.Length, Quote = "Interactive Server renders the component on the server and sends UI updates over a circuit.", })); // ... the next sentence, cited to the second source the same way. await yielder(new BitAiMessageCompletedEvent()); })); ```How it works
The transport first shows what it is doing (BitAiStatusEvent("Searching the documentation"),
cleared with BitAiStatusEvent(null)), then writes the answer a sentence at a time, sending a
BitAiCitationEvent after each sentence it can source. Its BitAiCitation names a
BitAiCitationSource - an id, a title, its Kind (web page, document...), its
Url, SiteName and Description - and the Quote it relies
on. A citation sent without offsets is not anchored anywhere: it only adds its source to the list under the
answer.
A BitAiCitation carries character offsets into the text it supports
(StartIndex, EndIndex). The renderer inserts a marker at each offset before
parsing, so the superscript lands mid-sentence instead of being collected into a footnote nobody connects to
anything. The sources the citations name are deduplicated and numbered under the answer, each number matching
its markers.
Offsets that reach past the text received so far are held back, so a marker never appears in the wrong
sentence while the answer streams. OnCitationClick and OnSourceClick hand the
citation or the source to the app, to open it in a panel of its own as this page does.
Components used
BitAiCitationMarker- the numbered chip where a claim is made.BitAiSourceList- the deduplicated sources under the answer.BitAiSourceCard- one source, in that list and beside the chat.BitAiTextPartView- the text the citations are woven into.
Feedback
Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.