# AI Chat Primitives

Source: https://lumeo.nativ.sh/components/ai

# AI Chat Primitives

Streaming-ready building blocks for LLM-powered chat interfaces. Render tokens as they arrive, show tool calls, surface reasoning, and let users send prompts — all over Blazor Server's SignalR pipe.

## Installation

.NET CLI PackageReference Lumeo CLI

dotnet add package Lumeo

One-time app setup (`AddLumeo()`, CSS & JS) is covered in the [installation guide](docs/introduction).

## When to Use

-   Building any chat, copilot, or agentic UI on top of a streaming LLM.
-   You want token-by-token rendering without the SSE plumbing React apps require — Blazor Server pipes chunks over SignalR with one `StateHasChanged()`.
-   Displaying tool calls (function calls, RAG lookups, SQL runs) as collapsible cards inline with the conversation.
-   Showing chain-of-thought / reasoning segments that users can expand on demand.
-   Giving users a prompt input with auto-grow, Enter-to-send, attach slot, and a token counter.

Preview Code

Live Demo — full chat transcript

Session started 21:36

21:34You

Summarise the quarterly revenue numbers for EMEA and tell me which region grew fastest.

Lumeo Copilot21:34

Reasoned for 2.4s

User wants EMEA revenue breakdown. I should call the sales tool to pull Q1–Q4 numbers, then compute growth per sub-region. DACH looked strong last quarter.

query\_sales\_db success842ms

Input

```
{
  "region": "EMEA",
  "period": "2026-Q1"
}
```

Output

```
{
  "DACH": 12_400_000,
  "UK": 9_800_000,
  "Nordics": 4_200_000,
  "Iberia": 3_100_000
}
```

Lumeo Copilot

The fastest grower was DACH at +18% YoY, driven mainly by…

0 / 2000

Preview Code

PromptInput

0 tokens

Preview Code

StreamingText

Lumeo streams tokens straight through SignalR — no SSE plumbing, no fetch() wrappers, just one StateHasChanged() per chunk. 

The caret and suffix fade-in re-render as chunks append.

Preview Code

ToolCallCard

search\_web

running

Input

```
{
  "query": "blazor server streaming"
}
```

fetch\_url success312ms

Input

```
{
  "url": "https://example.com"
}
```

Output

```
200 OK — 14.2KB payload
```

run\_python error28ms

Input

```
print(x)
```

Error

```
NameError: name 'x' is not defined
```

Preview Code

ReasoningDisplay

Thinking…

Let me break this down — first I need to identify the entities, then decide which tool to invoke. Given the user asked about EMEA revenue…

Reasoned for 3.8s

Fast path: query\_sales\_db with region=EMEA, period=2026-Q1. That returns per-sub-region totals. Then rank by YoY growth.

## Wiring StreamingText to a SignalR hub

Blazor Server already runs each circuit over SignalR. Capture a `@ref` to your parent component's streaming state and call `InvokeAsync(StateHasChanged)` when a new chunk arrives. That's the whole streaming story.

```
// In your Blazor component:
@inject IHubContext<ChatHub> Hub

private string _assistantText = "";
private bool _isStreaming;

protected override void OnInitialized()
{
    ChatHub.OnChunk += async chunk =>
    {
        _assistantText += chunk;
        await InvokeAsync(StateHasChanged);  // Blazor Server pushes over SignalR automatically
    };
    ChatHub.OnComplete += async () =>
    {
        _isStreaming = false;
        await InvokeAsync(StateHasChanged);
    };
}

// In your markup:
<StreamingText Text="@_assistantText" IsStreaming="@_isStreaming" />
```

## API Reference

### PromptInput

Property

Type

Default

Description

Value

string?

null

Current text. Two-way bindable via `@bind-Value`.

Placeholder

string

"Ask anything…"

Placeholder when empty.

MaxHeight

int

240

Pixel cap before the textarea scrolls internally.

MinHeight

int

56

Minimum pixel height of the textarea.

IsLoading

bool

false

Disables input and shows a spinner on the send button.

OnSend

EventCallback<string>

—

Fires with the current Value on Enter or Send click.

DisableSendOnEmpty

bool

true

Disables the send button when Value is blank.

LeadingContent

RenderFragment?

—

Slot on the left of the toolbar (attach button, etc.).

TrailingContent

RenderFragment?

—

Slot on the right of the toolbar (token count, model selector).

Class

string?

—

Extra CSS classes on the root container.

ValueChanged

EventCallback<string?>

—

Two-way binding callback; fires as the user types.

### StreamingText

Property

Type

Default

Description

Text

string?

null

Full accumulated text. Newly-appended suffix fades in.

IsStreaming

bool

false

When true, shows a pulsing caret at the end of the text.

Prose

bool

false

Applies prose typography (Tailwind) to the content.

### AgentMessageList

Property

Type

Default

Description

ChildContent

RenderFragment?

—

Usually one or more `<AgentMessage>` children.

AutoScroll

bool

true

Sticks to the bottom when new content arrives, using a MutationObserver.

### AgentMessage

Property

Type

Default

Description

Role

AgentMessageRole

Assistant

One of User, Assistant, System, Tool.

Avatar

RenderFragment?

—

Optional avatar rendered alongside the bubble.

Name

string?

—

Display name shown above the bubble.

Timestamp

DateTimeOffset?

—

Optional timestamp shown next to the name.

IsStreaming

bool

false

Appends a pulsing caret to the body.

ChildContent

RenderFragment?

—

Message body. Usually plain text or `<StreamingText>`.

### ToolCallCard

Property

Type

Default

Description

ToolName

string

""

Name of the tool being invoked (e.g. `search_web`).

Status

ToolCallStatus

Pending

Pending, Running, Success, or Error. Drives chip colors and icons.

Input

string?

—

Arguments to the tool call, shown in a `<pre>` block.

Output

string?

—

Tool result shown when expanded.

ErrorMessage

string?

—

Error details shown when Status=Error.

DurationMs

long?

—

Wall-clock duration of the call.

DefaultOpen

bool

false

Whether the card starts expanded.

### ReasoningDisplay

Property

Type

Default

Description

Text

string?

—

Reasoning / chain-of-thought text.

IsStreaming

bool

false

Applies a subtle opacity pulse while the model is still thinking.

Summary

string?

—

Override the default summary label.

DurationMs

long?

—

When set, summary becomes "Reasoned for Xs".

DefaultOpen

bool

false

Whether the reasoning section starts expanded.
