AI-Powered Navigation in Shiny MAUI Shell — Chat to Navigate, Intent to Form
What if a user could type “My furnace is broken — it’s urgent!” into a chat and your app would automatically open the work order form with the description filled in and the priority set to Urgent?
That’s the new AI integration in Shiny MAUI Shell.
The Problem
Mobile apps are full of forms behind menus behind tabs. Users have to know the app’s structure to get anything done. They tap through navigation hierarchies, find the right page, and manually key in every field.
Meanwhile, AI chat is everywhere. Users already talk to assistants in natural language. But wiring up an AI to navigate your app has always meant building one-off tool functions for each page — code that doesn’t scale and breaks every time you add a route.
The Insight: Your Source Generator Already Knows Everything
Shiny Shell’s [ShellMap] and [ShellProperty] attributes already describe every route and parameter in your app. The source generator uses them to produce type-safe navigation methods and route constants.
The new AI integration simply extends that same metadata with two additional pieces of information:
- A
descriptionon[ShellMap]that tells the AI when this page is relevant (user intent signals) - A
descriptionon[ShellProperty]that tells the AI how to infer the value from natural language
public enum WorkOrderPriority { Low, Medium, High, Urgent }
[ShellMap<WorkOrderPage>(description:
"Use when the user reports something broken, malfunctioning, needing repair, maintenance, or service")]
public partial class WorkOrderViewModel : ObservableObject
{
[ShellProperty("Summarize what is broken based on what the user said", required: true)]
public string Description { get; set; } = string.Empty;
[ShellProperty("Infer urgency from the user's tone. Must be: Low, Medium, High, or Urgent", required: true)]
public WorkOrderPriority Priority { get; set; } = WorkOrderPriority.Medium;
[ShellProperty("The physical location if the user mentioned one, otherwise leave empty", required: false)]
public string Location { get; set; } = string.Empty;
}
Two Tools, Any Number of Pages
Instead of generating a separate AI tool function per page (which pollutes the tool list and doesn’t scale), the source generator produces exactly two tools:
1. Discover Routes
The AI calls GetAiToolApplicableGeneratedRoutes() to see what pages exist, what they do, and what parameters they accept. This returns only routes that have a description on [ShellMap] and at least one [ShellProperty].
2. Navigate
The AI calls NavigateToRoute(route, args) with a Dictionary<string, string> of parameters — because AI models handle dictionaries natively in tool calling schemas.
Both of these live on the generated AiMauiShellTools class, which is injected via DI. The class also provides a Prompt property (pre-formatted route descriptions for system messages) and a Tools property (ready-to-use AITool[]).
Note that AI-compatible ViewModels do not need to implement IQueryAttributable. The generated NavigateToRoute sets [ShellProperty] properties directly on the ViewModel instance — no query attribute plumbing required.
Properties can use real types — enums, ints, bools, doubles, DateTimes, Guids — and the generator handles conversion automatically. The AI sends "Urgent" as a string, and the generated NavigateToRoute converts it to WorkOrderPriority.Urgent via case-insensitive Enum.Parse.
The Flow
- AI calls
GetAiToolApplicableGeneratedRoutes()→ sees all available pages with intent descriptions and parameter schemas - AI matches user’s message to the right route based on descriptions
- AI extracts parameter values from natural language using inference hints
- AI calls
NavigateToRoute("WorkOrder", {"Description": "Furnace is not working", "Priority": "Urgent"}) - App navigates to the work order page with fields pre-filled
Add a new page? Just add [ShellMap] and [ShellProperty] descriptions. The AI discovers it automatically on the next chat message. Zero tool registration changes.
Rich Parameter Metadata
The generated GeneratedRouteParameter now includes type information and requirement flags:
public record GeneratedRouteParameter(
string ParameterName, // Property name — used as key in NavigateToRoute args
string Description, // Inference hint for the AI
string TypeName, // "string", "int", "bool", etc.
bool IsRequired // Whether the AI must provide this value
);
This gives the AI everything it needs: what to call the parameter, how to infer it, what type it expects, and whether it’s optional.
Wiring It Up with Microsoft.Extensions.AI
AI extensions are now enabled by default — just install Microsoft.Extensions.AI and register the generated class:
// MauiProgram.cs
builder.UseShinyShell(x => x
.AddGeneratedMaps()
.AddAiTools() // registers AiMauiShellTools as singleton
);
Then inject AiMauiShellTools and use the Prompt and Tools properties:
public class ChatViewModel(AiMauiShellTools aiTools)
{
// Seed the system prompt with route info
history.Add(new ChatMessage(ChatRole.System, aiTools.Prompt));
// Use ready-to-use AITool instances
var options = new ChatOptions { Tools = [.. aiTools.Tools] };
}
The class name is customizable via the ShinyMauiShell_AiToolsClassName MSBuild property if AiMauiShellTools doesn’t fit your naming conventions.
The Sample App
The sample includes a full working demo with:
- GitHub Copilot authentication via the OAuth device flow — users authenticate with their own GitHub account
- ChatView from Shiny.Maui.Controls for the chat UI
- Two sample forms: Work Order and Contact — each with intent-driven descriptions
Try these prompts:
- “My furnace is not working! URGENT” → opens the work order form with description and priority filled in
- “I’d like to discuss a partnership. My name is Allan, email allan@test.com, phone 555-1234” → opens the contact form with fields populated
- “The sink in the kitchen is leaking” → work order with “Kitchen” as location and inferred description
Describe Intent, Not Pages
The quality of AI navigation depends entirely on how you write descriptions:
| Do | Don’t |
|---|---|
| Describe user intent signals | Name the page |
| ”Use when the user reports something broken" | "Work order form” |
| Tell AI to infer from context | Expect exact field values |
| ”Infer urgency from tone" | "The priority level” |
| Make optional fields truly optional | Require everything |
| ”Leave empty if not mentioned” | Require the user to state everything |
What’s Next
This is the first step toward making Shiny MAUI Shell apps genuinely AI-native. The two-tool pattern scales to any number of routes and works with any IChatClient — OpenAI, Anthropic, local models, or GitHub Copilot.
Check out the full documentation and the source on GitHub.
comments powered by Disqus