Vienna 1.1.1

dotnet add package Vienna --version 1.1.1
                    
NuGet\Install-Package Vienna -Version 1.1.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Vienna" Version="1.1.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Vienna" Version="1.1.1" />
                    
Directory.Packages.props
<PackageReference Include="Vienna" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Vienna --version 1.1.1
                    
#r "nuget: Vienna, 1.1.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Vienna@1.1.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Vienna&version=1.1.1
                    
Install as a Cake Addin
#tool nuget:?package=Vienna&version=1.1.1
                    
Install as a Cake Tool

Captivate Chat SDK for .NET# Vienna

NuGetA simple, modular .NET SDK for chat applications, inspired by the captivate-chat-api-ts TypeScript project.

License: MIT

Structure

Official .NET SDK for Captivate Chat API. Provides real-time WebSocket communication, HTTP messaging, file management, and multi-tenant support.- Models: Core entities (Conversation, Message, User)

  • Services: Main SDK features (Chat API, File Manager, Manager)

Features- Utilities: Helpers for HTTP and WebSocket

Full TypeScript SDK Parity - Complete feature match with the TypeScript SDK ## Usage

🔌 Real-time WebSocket - Bi-directional communication with automatic reconnection Add your logic to the provided templates and extend as needed for your application.

📤 HTTP Messaging - Send messages via HTTP POST
📁 File Management - Upload files with automatic text extraction
🏢 Multi-tenant Support - Manage multiple API keys
🎯 SOLID Architecture - Built with best practices and dependency injection
Async/Await - Full asynchronous support
📝 Strongly Typed - Complete DTO coverage with XML documentation

Installation

dotnet add package Vienna

Quick Start

using Vienna.Services;
using Vienna.Configuration;

// Create and connect to Captivate Chat API
var api = await CaptivateChatAPI.CreateAsync(
    "your-api-key", 
    EnvironmentMode.Production
);

// Create a conversation
var conversation = await api.CreateConversationAsync(
    userId: "user123",
    metadata: new Dictionary<string, object>
    {
        { "userName", "John Doe" },
        { "source", "web" }
    }
);

// Listen for messages
conversation.MessageReceived += (sender, e) =>
{
    Console.WriteLine($"[{e.MessageType}]: {e.Content}");
};

// Send a message
await conversation.SendMessageAsync("Hello, how can I help you?");

Core Services

1️⃣ CaptivateChatAPI - Main Chat Service

The primary service for managing chat conversations and real-time messaging.

Available Methods:

  • CreateAsync(apiKey, mode, cancellationToken) - Create and connect to API (static factory)
  • ConnectAsync(cancellationToken) - Connect to WebSocket
  • CreateConversationAsync(userId, userBasicInfo, metadata, autoConversationStart, privateMetadata) - Create new conversation
  • GetUserConversationsAsync(userId) - Get all user conversations (simple)
  • GetUserConversationsAsync(options) - Get conversations with filtering/pagination
  • DeleteUserConversationsAsync(userId, options) - Delete conversations (soft/hard)
  • GetConversation(conversationId) - Get or create conversation instance
  • GetConversations() - Get all active conversation instances
  • ReconnectAsync() - Reconnect WebSocket
  • DisconnectAsync() - Disconnect WebSocket
  • IsSocketActive() - Check WebSocket connection status
  • GetSocketId() - Get current socket ID
  • SetDebugMode(enabled) - Enable/disable debug logging (static)
  • GetDebugMode() - Check debug mode status (static)

Properties:

  • ApiKey - The API key being used
  • Mode - Current environment mode (Production/Development)
using Vienna.Services;
using Vienna.Configuration;

// Enable debug mode for troubleshooting (optional)
CaptivateChatAPI.SetDebugMode(true);

// Initialize the API
var api = await CaptivateChatAPI.CreateAsync(
    apiKey: "your-api-key",
    mode: EnvironmentMode.Production
);

Console.WriteLine($"Socket ID: {api.GetSocketId()}");
Console.WriteLine($"Connected: {api.IsSocketActive()}");

// Create a conversation
var conversation = await api.CreateConversationAsync(
    userId: "user123",
    userBasicInfo: new Dictionary<string, object> 
    { 
        { "name", "John Doe" }, 
        { "email", "john@example.com" } 
    },
    metadata: new Dictionary<string, object> 
    { 
        { "department", "support" },
        { "priority", "high" }
    },
    autoConversationStart: "bot-first",  // or "user-first"
    privateMetadata: new Dictionary<string, object>
    {
        { "internal_id", "12345" }
    }
);

// Get user's conversations (simple)
var (conversations, pagination) = await api.GetUserConversationsAsync("user123");
Console.WriteLine($"User has {conversations.Count} conversations");

// Get conversations with filtering and pagination
var (filteredConvs, page) = await api.GetUserConversationsAsync(new
{
    userId = "user123",
    filter = new Dictionary<string, object>
    {
        { "status", "active" },
        { "created_after", DateTime.UtcNow.AddDays(-7) }
    },
    search = new Dictionary<string, object>
    {
        { "text", "support" }
    },
    pagination = new
    {
        limit = 20,
        offset = 0
    }
});

// Get existing conversation by ID
var existingConv = api.GetConversation("conversation_id");

// Get all active conversations
var allConversations = api.GetConversations();
Console.WriteLine($"Total active: {allConversations.Count}");

// Delete conversations (soft delete by default)
await api.DeleteUserConversationsAsync(
    userId: "user123",
    options: new { softDelete = true }
);

// Delete specific conversations (hard delete)
await api.DeleteUserConversationsAsync(
    userId: "user123",
    conversationIds: new List<string> { "conv_1", "conv_2" },
    hardDelete: true
);

// Reconnect if needed
if (!api.IsSocketActive())
{
    await api.ReconnectAsync();
}

// Disconnect when done
await api.DisconnectAsync();

// Disable debug mode
CaptivateChatAPI.SetDebugMode(false);

2️⃣ Conversation - Individual Chat Session

Each conversation provides methods for messaging, metadata management, and transcript access.

Available Methods:

  • SendMessageAsync(string text) - Send a text message
  • SendMessageAsync(string text, string[] fileUrls) - Send message with file attachments
  • SetMetadataAsync(Dictionary<string, object> metadata) - Update public metadata
  • SetPrivateMetadataAsync(Dictionary<string, object> privateMetadata) - Update private metadata
  • GetMetadataAsync() - Retrieve conversation metadata
  • GetTranscriptAsync() - Get full conversation transcript
  • EditMessageAsync(string messageId, string newText) - Edit a sent message
  • DeleteAsync(bool hardDelete) - Delete the conversation
  • SendActionAsync(object action) - Send a custom action
  • RequestLiveChatAsync() - Request live chat support
  • EndConversationAsync() - End the conversation

Events:

  • MessageReceived - Bot messages received
  • ActionReceived - Actions received from bot
  • LiveChatEvent - Live chat events
  • ConversationEnded - Conversation ended event
  • ConversationUpdated - Conversation update events (metadata, status, participants)
  • ErrorOccurred - Error events
// Get existing conversation
var conversation = api.GetConversation("conversation_id");

// Or create new conversation
var conversation = await api.CreateConversationAsync("user123");

// Send text message
await conversation.SendMessageAsync("Hello!");

// Send message with file attachments
await conversation.SendMessageAsync(
    text: "Here are the documents",
    fileUrls: new[] 
    { 
        "https://storage.example.com/file1.pdf",
        "https://storage.example.com/file2.docx"
    }
);

// Update public metadata
await conversation.SetMetadataAsync(new Dictionary<string, object>
{
    { "status", "active" },
    { "priority", "high" }
});

// Update private metadata (backend only)
await conversation.SetPrivateMetadataAsync(new Dictionary<string, object>
{
    { "internal_notes", "VIP customer" },
    { "account_tier", "premium" }
});

// Get metadata
var metadata = await conversation.GetMetadataAsync();
Console.WriteLine($"Status: {metadata["status"]}");

// Get full transcript
var transcript = await conversation.GetTranscriptAsync();
foreach (var message in transcript)
{
    Console.WriteLine($"{message.Role}: {message.Content}");
}

// Edit a message
await conversation.EditMessageAsync("msg_123", "Updated message text");

// Send custom action
await conversation.SendActionAsync(new 
{
    action_type = "button_click",
    button_id = "confirm_order"
});

// Request live chat
await conversation.RequestLiveChatAsync();

// End conversation
await conversation.EndConversationAsync();

// Delete conversation (soft delete by default)
await conversation.DeleteAsync(hardDelete: false);

// Event handlers
conversation.MessageReceived += (sender, e) =>
{
    Console.WriteLine($"Message: {e.Content}");
};

conversation.ActionReceived += (sender, e) =>
{
    Console.WriteLine($"Action: {e.ActionType}");
};

conversation.LiveChatEvent += (sender, e) =>
{
    Console.WriteLine($"Live chat: {e.EventType}");
};

conversation.ConversationEnded += (sender, e) =>
{
    Console.WriteLine("Conversation ended");
};

conversation.ConversationUpdated += (sender, e) =>
{
    Console.WriteLine($"Conversation updated: {e.UpdateType}");
    if (e.UpdateType == "metadata_changed")
    {
        Console.WriteLine("Metadata was updated");
    }
};

conversation.ErrorOccurred += (sender, e) =>
{
    Console.WriteLine($"Error: {e.Message}");
};

3️⃣ CaptivateChatManager - Multi-Tenant Management

Manage multiple API instances for multi-tenant, multi-brand applications.

Available Methods:

  • CreateAsync(List<string> apiKeys, EnvironmentMode mode) - Create manager with multiple API keys (static factory)
  • ConnectAllAsync() - Connect all API instances
  • GetUserConversationsAsync(userId, apiKeys, filter, search, pagination) - Query conversations across tenants
  • GetApiInstance(string apiKey) - Get specific API instance
  • GetAllApiInstances() - Get all managed API instances
  • GetAllApiKeys() - Get all managed API keys
  • HasApiKey(string apiKey) - Check if API key is managed
  • DisconnectAllAsync() - Disconnect all instances
using Vienna.Services;

// Initialize manager with multiple API keys (brands/tenants)
var apiKeys = new List<string>
{
    "brand-a-api-key",
    "brand-b-api-key",
    "brand-c-api-key"
};

var manager = await CaptivateChatManager.CreateAsync(
    apiKeys,
    EnvironmentMode.Production
);

// Get conversations across ALL brands for a user
var (allConversations, pagination) = await manager.GetUserConversationsAsync(
    userId: "user123"
);

Console.WriteLine($"User has {allConversations.Count} conversations across all brands");

// Get conversations from SPECIFIC brands only
var selectedBrands = new List<string> { "brand-a-api-key", "brand-b-api-key" };
var (brandConversations, _) = await manager.GetUserConversationsAsync(
    userId: "user123",
    apiKeys: selectedBrands
);

// Advanced filtering and search
var (filteredConversations, page) = await manager.GetUserConversationsAsync(
    userId: "user123",
    apiKeys: null,  // All brands
    filter: new Dictionary<string, object>
    {
        { "status", "active" },
        { "created_after", DateTime.UtcNow.AddDays(-7) }
    },
    search: new Dictionary<string, object>
    {
        { "text", "support request" }
    },
    pagination: new PaginationRequestDto
    {
        Limit = 20,
        Offset = 0
    }
);

Console.WriteLine($"Found {filteredConversations.Count} matching conversations");
Console.WriteLine($"Total: {page?.Total}, Has more: {page?.HasMore}");

// Get specific API instance for direct operations
var brandA = manager.GetApiInstance("brand-a-api-key");
if (brandA != null)
{
    var conversation = await brandA.CreateConversationAsync(
        userId: "user456",
        metadata: new Dictionary<string, object> { { "source", "mobile" } }
    );
    await conversation.SendMessageAsync("Hello from Brand A!");
}

// Check if managing a specific API key
if (manager.HasApiKey("brand-a-api-key"))
{
    Console.WriteLine("Brand A is managed");
}

// Get all API keys
var allKeys = manager.GetAllApiKeys();
foreach (var key in allKeys)
{
    Console.WriteLine($"Managing: {key}");
}

// Get all API instances
var allInstances = manager.GetAllApiInstances();
foreach (var instance in allInstances)
{
    Console.WriteLine($"Instance connected: {instance.IsSocketActive()}");
}

// Disconnect all tenants
await manager.DisconnectAllAsync();

4️⃣ CaptivateChatFileManager - File Upload & Management

Handle file uploads with automatic text extraction and presigned URL generation.

Available Methods:

  • CreateAsync(Stream file, fileName, fileType, storage, url) - Upload file from stream (static factory)
  • CreateAsync(string filePath, fileType, storage, url) - Upload file from path (static factory)
  • GeneratePresignedUrlAsync(fileKey, expiresIn) - Generate presigned URL for existing file (static)
  • RefreshSecureUrlAsync(fileKey, expiresIn) - Refresh expiring presigned URL
  • GetFilename() - Get uploaded filename
  • GetFileType() - Get file MIME type
  • GetTextContent() - Get extracted text
  • GetFirstFile() - Get first file object
  • Files property - Access all file objects
using Vienna.Services;

// Upload file from path with storage
var fileManager = await CaptivateChatFileManager.CreateAsync(
    filePath: "/path/to/document.pdf",
    fileType: "application/pdf",
    storage: true  // Store in Captivate's storage
);

Console.WriteLine($"Filename: {fileManager.GetFilename()}");
Console.WriteLine($"File type: {fileManager.GetFileType()}");
Console.WriteLine($"Extracted text: {fileManager.GetTextContent()}");

var fileInfo = fileManager.GetFirstFile();
if (fileInfo?.Storage != null)
{
    Console.WriteLine($"File key: {fileInfo.Storage.FileKey}");
    Console.WriteLine($"Presigned URL: {fileInfo.Storage.PresignedUrl}");
    Console.WriteLine($"Expires in: {fileInfo.Storage.ExpiresIn} seconds");
    Console.WriteLine($"File size: {fileInfo.Storage.FileSize} bytes");
}

// Upload from stream without storage (external URL)
using var stream = File.OpenRead("/path/to/image.jpg");
var externalFileManager = await CaptivateChatFileManager.CreateAsync(
    file: stream,
    fileName: "image.jpg",
    fileType: "image/jpeg",
    storage: false,  // Don't store, use external URL
    url: "https://cdn.example.com/images/image.jpg"
);

Console.WriteLine($"External URL: {externalFileManager.Files[0].Url}");
Console.WriteLine($"Extracted OCR: {externalFileManager.GetTextContent()}");

// Use uploaded file in conversation
await conversation.SendMessageAsync(
    text: "Here's the document you requested",
    fileUrls: new[] { fileInfo.Storage.PresignedUrl }
);

// Upload multiple files
var filePaths = new[] { "doc1.pdf", "doc2.docx", "image.png" };
var fileManagers = new List<CaptivateChatFileManager>();

foreach (var path in filePaths)
{
    var fm = await CaptivateChatFileManager.CreateAsync(
        filePath: path,
        storage: true
    );
    fileManagers.Add(fm);
}

// Get all file URLs
var allFileUrls = fileManagers
    .SelectMany(fm => fm.Files)
    .Select(f => f.Storage?.PresignedUrl)
    .Where(url => url != null)
    .ToArray();

// Send all files in one message
await conversation.SendMessageAsync(
    text: "Here are all the documents",
    fileUrls: allFileUrls
);

// Generate presigned URL for existing file
var presignedUrl = await CaptivateChatFileManager.GeneratePresignedUrlAsync(
    fileKey: "uploads/abc123/document.pdf",
    expiresIn: 3600  // 1 hour
);

Console.WriteLine($"Presigned URL: {presignedUrl.Url}");
Console.WriteLine($"Expires in: {presignedUrl.ExpiresIn} seconds");

// Refresh expiring presigned URL
var refreshedUrl = await fileManager.RefreshSecureUrlAsync(
    fileKey: "uploads/abc123/document.pdf",
    expiresIn: 7200  // 2 hours
);

Console.WriteLine($"Refreshed URL: {refreshedUrl}");

// Access all file information
foreach (var file in fileManager.Files)
{
    Console.WriteLine($"File: {file.Filename}");
    Console.WriteLine($"Type: {file.Type}");
    Console.WriteLine($"Text length: {file.TextContent?.Text?.Length}");
    
    if (file.Storage != null)
    {
        Console.WriteLine($"Storage key: {file.Storage.FileKey}");
        Console.WriteLine($"Processing time: {file.Storage.ProcessingTime}ms");
    }
}

Complete Example - All Services Together

using Vienna.Services;
using Vienna.Configuration;

// 1. Initialize API
var api = await CaptivateChatAPI.CreateAsync(
    "your-api-key",
    EnvironmentMode.Production
);

// 2. Initialize File Manager
var fileManager = new CaptivateChatFileManager("your-api-key");

// 3. Upload a file
var fileResult = await fileManager.UploadFileAsync(
    "/path/to/resume.pdf",
    "resume.pdf"
);

// 4. Create conversation with file
var conversation = await api.CreateConversationAsync(
    userId: "job-applicant-123",
    userBasicInfo: new { name = "Jane Smith", email = "jane@example.com" },
    metadata: new Dictionary<string, object>
    {
        { "position", "Senior Developer" },
        { "resumeUrl", fileResult.Url }
    }
);

// 5. Set up event handlers
conversation.MessageReceived += async (sender, e) =>
{
    Console.WriteLine($"Bot: {e.Content}");
    
    if (e.Content.Contains("interview"))
    {
        await conversation.SetMetadataAsync(new Dictionary<string, object>
        {
            { "status", "interview-scheduled" }
        });
    }
};

// 6. Send initial message
await conversation.SendMessageAsync(
    text: "I'm interested in the Senior Developer position. Here's my resume.",
    fileUrls: new[] { fileResult.Url }
);

// Keep the connection alive
await Task.Delay(TimeSpan.FromMinutes(5));

// 7. Clean up
await api.DisconnectAsync();

Documentation

For complete documentation, examples, and API reference, visit:

Support

License

MIT License - see LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.1 303 1/5/2026
1.1.0 91 12/30/2025
1.0.0 83 12/29/2025

v1.1.1: Added ConversationUpdated event for real-time conversation updates (full TypeScript SDK parity). Enhanced README with comprehensive documentation for all 4 services (14 CaptivateChatAPI methods, 12 Conversation methods, 9 Manager methods, 10 FileManager methods). All 113 tests passing.