NexusChat
FlagshipCross-platform multi-model AI streaming client with local encrypted persistence in .NET 9 MAUI
Architecture overview
NexusChat is a single-project cross-platform application built with C# and .NET 9 MAUI. The application targets Windows (WinUI 3), Android (API 21+), iOS (15+), and macOS (MacCatalyst).
It implements the MVVM architecture pattern using CommunityToolkit.Mvvm 8.4.0 source generators ([ObservableProperty], [RelayCommand]), providing compiled reactive data-binding with minimal boilerplate.
Core engineering implementations
1. Decoupled AI provider factory and SSE streaming
The application abstracts multiple AI model APIs behind a unified IAIService interface. Instead of buffering complete HTTP responses, NexusChat consumes Server-Sent Events (SSE) using IAsyncEnumerable<string>:
View C# implementation: OpenAiService with SSE token streaming
public interface IAIService
{
IAsyncEnumerable<string> StreamCompletionAsync(
ConversationContext context,
CancellationToken cancellationToken = default
);
Task<ModelCapabilities> GetCapabilitiesAsync();
}
public sealed class OpenAiService(HttpClient httpClient, string apiKey) : IAIService
{
public async IAsyncEnumerable<string> StreamCompletionAsync(
ConversationContext context,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.openai.com/v1/chat/completions")
{
Content = JsonContent.Create(new
{
model = context.ModelName,
messages = context.Messages,
stream = true
})
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
using var response = await httpClient.SendAsync(
request,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken
);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var reader = new StreamReader(stream);
while (!reader.EndOfStream && !cancellationToken.IsCancellationRequested)
{
var line = await reader.ReadLineAsync(cancellationToken);
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data: ")) continue;
if (line.Contains("[DONE]")) break;
var json = line["data: ".Length..];
using var doc = JsonDocument.Parse(json);
var delta = doc.RootElement
.GetProperty("choices")[0]
.GetProperty("delta");
if (delta.TryGetProperty("content", out var contentElement))
{
var content = contentElement.GetString();
if (!string.IsNullOrEmpty(content)) yield return content;
}
}
}
public Task<ModelCapabilities> GetCapabilitiesAsync() =>
Task.FromResult(new ModelCapabilities(SupportsStreaming: true, SupportsVision: true, MaxContextTokens: 128_000));
}
2. Reactive chat ViewModel with CommunityToolkit.Mvvm
The ViewModel manages live token accumulation, thread state, and auto-scroll triggers:
View C# implementation: ChatViewModel
public partial class ChatViewModel : ObservableObject
{
private readonly IAIServiceFactory serviceFactory;
private readonly IConversationRepository conversationRepo;
[ObservableProperty]
private string inputMessage = string.Empty;
[ObservableProperty]
private bool isStreaming;
public ObservableCollection<ChatMessage> Messages { get; } = [];
public ChatViewModel(IAIServiceFactory serviceFactory, IConversationRepository conversationRepo)
{
this.serviceFactory = serviceFactory;
this.conversationRepo = conversationRepo;
}
[RelayCommand(CanExecute = nameof(CanSendMessage))]
private async Task SendMessageAsync(CancellationToken ct)
{
var userText = InputMessage.Trim();
InputMessage = string.Empty;
var userMessage = new ChatMessage(Role.User, userText, DateTime.UtcNow);
Messages.Add(userMessage);
var assistantMessage = new ChatMessage(Role.Assistant, string.Empty, DateTime.UtcNow);
Messages.Add(assistantMessage);
IsStreaming = true;
try
{
var service = serviceFactory.GetProvider("OpenAI");
var context = new ConversationContext("gpt-4o", Messages);
await foreach (var token in service.StreamCompletionAsync(context, ct))
{
assistantMessage.Content += token;
}
}
finally
{
IsStreaming = false;
await conversationRepo.SaveMessageAsync(assistantMessage);
}
}
private bool CanSendMessage() => !string.IsNullOrWhiteSpace(InputMessage) && !IsStreaming;
}
3. Offline simulation engine (DummyAIService)
To enable offline development, UI benchmarking, and automated testing without incurring token costs, the architecture includes a DummyAIService. It simulates realistic response streaming latency, configurable typing speeds (characters per millisecond), and mock network errors.
4. Local encrypted persistence and credentials
All conversations, message branches, and user preferences persist locally using sqlite-net-pcl and SQLiteNetExtensions.Async. User authentication secrets are hashed using BCrypt.Net-Next 4.0.3. Provider API keys are stored securely using platform-native APIs (SecureStorage via Windows Credential Locker, Android Keystore, and iOS Keychain).
5. In-app developer diagnostic tools
- Live Database Viewer: In-app SQLite table explorer allowing developers to inspect and filter table rows directly on device.
- Model Playground: Configuration sandbox to adjust model parameters (temperature, top-p, presence penalty) and measure response latency.
- Dynamic Theme Manager: Theme token system handling dark and light modes with vector icon tinting across all target platforms.