<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jan Zimprich</title>
    <description>The latest articles on DEV Community by Jan Zimprich (@dryl_dev).</description>
    <link>https://dev.to/dryl_dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4021819%2F88b260ff-fc98-48ac-a0fc-7cbaa073e227.png</url>
      <title>DEV Community: Jan Zimprich</title>
      <link>https://dev.to/dryl_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dryl_dev"/>
    <language>en</language>
    <item>
      <title>Streaming AI straight into Blazor UI: typed structured generation and live AI-built dashboards</title>
      <dc:creator>Jan Zimprich</dc:creator>
      <pubDate>Wed, 22 Jul 2026 20:23:22 +0000</pubDate>
      <link>https://dev.to/dryl_dev/streaming-ai-straight-into-blazor-ui-typed-structured-generation-and-live-ai-built-dashboards-3cmi</link>
      <guid>https://dev.to/dryl_dev/streaming-ai-straight-into-blazor-ui-typed-structured-generation-and-live-ai-built-dashboards-3cmi</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25sfsiagjzben3krp3pl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F25sfsiagjzben3krp3pl.png" alt="DRYL Overview" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most AI UIs today are the same thing: a chat bubble filling up with Markdown. That's fine for text — but your app isn't made of text. It's made of cards, charts, badges, timelines, forms.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;&lt;a href="https://components.dryl.dev" rel="noopener noreferrer"&gt;DRYL&lt;/a&gt;&lt;/strong&gt; — an open-source, AI-native component library for Blazor (Server &amp;amp; WebAssembly) — around one idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The AI shouldn't stream strings at your users. It should stream &lt;em&gt;UI&lt;/em&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this post I want to show you the two features I'm most excited about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DrylAiGenerate&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt; — typed structured generation that streams a &lt;em&gt;partial object&lt;/em&gt; into real Blazor components, token by token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DrylAiCanvas&lt;/code&gt;&lt;/strong&gt; — the model builds a live, &lt;em&gt;interactive&lt;/em&gt; dashboard as a chat artifact, and clicks inside it route back to the model.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;(All GIFs below are recorded from the live docs at &lt;a href="https://components.dryl.dev" rel="noopener noreferrer"&gt;components.dryl.dev&lt;/a&gt; — no mockups.)&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The problem with streaming text
&lt;/h2&gt;

&lt;p&gt;Token streaming is a solved problem for chat. But the moment you want the model to produce something &lt;em&gt;structured&lt;/em&gt; — a recipe, a product draft, a report — you usually end up with one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wait for the full JSON&lt;/strong&gt;, deserialize, then render. The user stares at a spinner for 20 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream Markdown&lt;/strong&gt; and lose all structure — no charts, no badges, no forms, no interactivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you actually want: the model emits JSON for your C# type, and your UI renders the object &lt;em&gt;while it's still incomplete&lt;/em&gt; — title appears first, then the description grows character by character, then list items pop in one by one.&lt;/p&gt;

&lt;p&gt;That's exactly what &lt;code&gt;DrylAiGenerate&amp;lt;T&amp;gt;&lt;/code&gt; does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Typed structured generation: &lt;code&gt;DrylAiGenerate&amp;lt;T&amp;gt;&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;You define a plain C# record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Serves&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Ingredient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Ingredients&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Steps&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask the runner for a stream (it sets a JSON-schema response format for &lt;code&gt;T&lt;/code&gt; and yields raw tokens):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Works with any Microsoft.Agents.AI provider — OpenAI, Azure, Ollama, ...&lt;/span&gt;
&lt;span class="n"&gt;_stream&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateStreamingAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Recipe&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"recipe"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And bind it to your UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DrylAiGenerate T="Recipe" Source="_stream" Key="recipe"&amp;gt;
  &amp;lt;ChildContent Context="snap"&amp;gt;
    &amp;lt;DrylCard Ai="@snap.State"&amp;gt;
      &amp;lt;h3&amp;gt;@snap.Value?.Title&amp;lt;/h3&amp;gt;
      &amp;lt;p&amp;gt;@snap.Value?.Description&amp;lt;/p&amp;gt;

      &amp;lt;DrylTimeline&amp;gt;
        @foreach (var step in snap.Value?.Steps ?? [])
        {
          &amp;lt;DrylTimelineItem Title="@step.Title"&amp;gt;@step.Text&amp;lt;/DrylTimelineItem&amp;gt;
        }
      &amp;lt;/DrylTimeline&amp;gt;
    &amp;lt;/DrylCard&amp;gt;
  &amp;lt;/ChildContent&amp;gt;
&amp;lt;/DrylAiGenerate&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kbxw9krpn3ay62snldu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5kbxw9krpn3ay62snldu.gif" alt="Structured Generation" width="559" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;snap&lt;/code&gt; is a &lt;code&gt;GenerationSnapshot&amp;lt;T&amp;gt;&lt;/code&gt;: on &lt;em&gt;every&lt;/em&gt; chunk you get the best-possible partial &lt;code&gt;Recipe&lt;/code&gt;. Under the hood a tolerant &lt;code&gt;PartialJsonReader&amp;lt;T&amp;gt;&lt;/code&gt; repairs the incomplete JSON on the fly — half-open strings, unclosed arrays, truncated objects. If a chunk lands mid-token and the parse fails, it simply holds the last good snapshot. &lt;strong&gt;No flicker, ever.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few things I care a lot about here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It's typed.&lt;/strong&gt; No &lt;code&gt;dynamic&lt;/code&gt;, no &lt;code&gt;JObject&lt;/code&gt; spelunking. &lt;code&gt;snap.Value&lt;/code&gt; is your &lt;code&gt;Recipe&lt;/code&gt;, IntelliSense and all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's just an &lt;code&gt;IAsyncEnumerable&amp;lt;string&amp;gt;&lt;/code&gt;.&lt;/strong&gt; No SDK lock-in in the component — in the docs the same demo runs against a canned replay &lt;em&gt;and&lt;/em&gt; a real local model via Ollama. Tests don't need a model at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI knows the AI is working.&lt;/strong&gt; Every DRYL component accepts a shared &lt;code&gt;AiState&lt;/code&gt; (&lt;code&gt;Thinking / Streaming / Generated&lt;/code&gt;). Pass &lt;code&gt;Ai="@snap.State"&lt;/code&gt; and the card wears a breathing gradient aura while streaming and plays a one-shot reveal when done. One vocabulary across the whole library — users &lt;em&gt;feel&lt;/em&gt; where the AI is at work without reading a label.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive disclosure is trivial.&lt;/strong&gt; Wrap sections in &lt;code&gt;DrylPresence&lt;/code&gt; gated on the partial value (&lt;code&gt;Visible="@(snap.Value?.Ingredients is { Count: &amp;gt; 0 })"&lt;/code&gt;) and each part of the card animates in the moment the model produces it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The showstopper: &lt;code&gt;DrylAiCanvas&lt;/code&gt; — AI-built interactive artifacts
&lt;/h2&gt;

&lt;p&gt;Structured generation renders &lt;em&gt;your&lt;/em&gt; layout. The canvas goes one step further: &lt;strong&gt;the model designs the layout.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DrylAiCanvas&lt;/code&gt; gives your agent two tools — &lt;code&gt;create_artifact&lt;/code&gt; and &lt;code&gt;update_artifact&lt;/code&gt;. When the user asks for something visual, the model streams a component spec (a curated tree of DRYL components: charts, stat tiles, tables, inputs, buttons) and the canvas builds it &lt;strong&gt;node by node, live&lt;/strong&gt;, next to the chat:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;DrylChat&amp;gt; ... &amp;lt;/DrylChat&amp;gt;

&amp;lt;DrylAiCanvas Run="_run"
              OnInteraction="i =&amp;gt; Send(i.ToPromptMessage())" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DrylCanvasTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_run&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatClientAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;All&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fckhtiim872hkbksud5nl.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fckhtiim872hkbksud5nl.gif" alt="DRYL Canvas" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the part that makes it more than a pretty renderer: &lt;strong&gt;the artifact is interactive, and interactions round-trip to the model.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Buttons inside the artifact carry an &lt;em&gt;intent&lt;/em&gt;. When the user picks a region in the generated filter and clicks "Break down by region", you get a &lt;code&gt;CanvasInteraction&lt;/code&gt; — including the current values of every form control in the artifact. &lt;code&gt;interaction.ToPromptMessage()&lt;/code&gt; turns it into the next chat turn; the model answers with &lt;code&gt;update_artifact&lt;/code&gt;, streaming a &lt;em&gt;patch&lt;/em&gt;. Changed nodes glow, new nodes glide into place (FLIP-animated — nothing snaps), untouched nodes stay put.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxxbc56lq4swb3fv5xil6.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxxbc56lq4swb3fv5xil6.gif" alt="DRYL Canvas Interactive" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the loop is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;user asks → model builds UI → user clicks &lt;em&gt;inside that UI&lt;/em&gt; → model updates it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the "artifacts" experience you know from modern AI chat apps — as a drop-in Blazor component, running on your own agent and your own components.&lt;/p&gt;

&lt;p&gt;Safety-wise: the model never emits HTML or code. It emits a JSON spec validated against a &lt;strong&gt;curated component whitelist&lt;/strong&gt;. Invalid nodes never render — the model gets a corrective error string and retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of the iceberg
&lt;/h2&gt;

&lt;p&gt;These two are my favorites, but the AI layer runs deeper — all speaking the same &lt;code&gt;AiState&lt;/code&gt; vocabulary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DrylAiStream&lt;/code&gt;&lt;/strong&gt; — token streaming into &lt;code&gt;DrylMarkdown&lt;/code&gt; with a paced smooth mode (some providers deliver tool-call turns in one burst; this keeps it reading as a stream).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop tools&lt;/strong&gt; — ready-made &lt;code&gt;AIFunction&lt;/code&gt;s that pop real dialogs mid-run (&lt;code&gt;AskChoice&lt;/code&gt;, &lt;code&gt;AskText&lt;/code&gt;, &lt;code&gt;RequestPermission&lt;/code&gt;) and &lt;em&gt;await&lt;/em&gt; the user's answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Display tools&lt;/strong&gt; — the model answers with live charts, stat rows and timelines inline in the conversation (&lt;code&gt;show_line_chart&lt;/code&gt;, &lt;code&gt;show_stats&lt;/code&gt;, …).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DrylHandoffTrace&lt;/code&gt;&lt;/strong&gt; — multi-agent sequential/concurrent flows rendered as a living timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DrylAiField&lt;/code&gt;&lt;/strong&gt; — wrap any input, get an unobtrusive ✨ affordance: generate into an empty field, or transform just the selected text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And underneath it all sits a full general-purpose component library (60+ components): glass surfaces, token-driven dark/light modes, a fixed motion vocabulary, container-query responsiveness, zero npm dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Docs &amp;amp; live demos:&lt;/strong&gt; &lt;a href="https://components.dryl.dev" rel="noopener noreferrer"&gt;components.dryl.dev&lt;/a&gt; — the AI demos on the site run against real streams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/Zimpi/DRYL.Components" rel="noopener noreferrer"&gt;github.com/Zimpi/DRYL.Components&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NuGet:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package DRYL.Components          &lt;span class="c"&gt;# core library&lt;/span&gt;
dotnet add package DRYL.Components.Agents   &lt;span class="c"&gt;# AI layer (Microsoft Agent Framework bridge)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDrylComponents&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;AddDrylAgents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core is stable and dependency-free (Markdig only); the agents package is experimental and versioned independently while the Microsoft Agent Framework itself matures. You bring your own &lt;code&gt;AIAgent&lt;/code&gt; — OpenAI, Azure, or a local Ollama.&lt;/p&gt;

&lt;p&gt;If you're building anything AI-flavored in Blazor, I'd genuinely love feedback — especially on the canvas spec format and what components you'd want the model to be able to build. Issues and PRs welcome. ⭐&lt;/p&gt;

</description>
      <category>blazor</category>
      <category>dotnet</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
