Vienna 1.1.1
dotnet add package Vienna --version 1.1.1
NuGet\Install-Package Vienna -Version 1.1.1
<PackageReference Include="Vienna" Version="1.1.1" />
<PackageVersion Include="Vienna" Version="1.1.1" />
<PackageReference Include="Vienna" />
paket add Vienna --version 1.1.1
#r "nuget: Vienna, 1.1.1"
#:package Vienna@1.1.1
#addin nuget:?package=Vienna&version=1.1.1
#tool nuget:?package=Vienna&version=1.1.1
Captivate Chat SDK for .NET# Vienna
A simple, modular .NET SDK for chat applications, inspired by the captivate-chat-api-ts TypeScript project.
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 WebSocketCreateConversationAsync(userId, userBasicInfo, metadata, autoConversationStart, privateMetadata)- Create new conversationGetUserConversationsAsync(userId)- Get all user conversations (simple)GetUserConversationsAsync(options)- Get conversations with filtering/paginationDeleteUserConversationsAsync(userId, options)- Delete conversations (soft/hard)GetConversation(conversationId)- Get or create conversation instanceGetConversations()- Get all active conversation instancesReconnectAsync()- Reconnect WebSocketDisconnectAsync()- Disconnect WebSocketIsSocketActive()- Check WebSocket connection statusGetSocketId()- Get current socket IDSetDebugMode(enabled)- Enable/disable debug logging (static)GetDebugMode()- Check debug mode status (static)
Properties:
ApiKey- The API key being usedMode- 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 messageSendMessageAsync(string text, string[] fileUrls)- Send message with file attachmentsSetMetadataAsync(Dictionary<string, object> metadata)- Update public metadataSetPrivateMetadataAsync(Dictionary<string, object> privateMetadata)- Update private metadataGetMetadataAsync()- Retrieve conversation metadataGetTranscriptAsync()- Get full conversation transcriptEditMessageAsync(string messageId, string newText)- Edit a sent messageDeleteAsync(bool hardDelete)- Delete the conversationSendActionAsync(object action)- Send a custom actionRequestLiveChatAsync()- Request live chat supportEndConversationAsync()- End the conversation
Events:
MessageReceived- Bot messages receivedActionReceived- Actions received from botLiveChatEvent- Live chat eventsConversationEnded- Conversation ended eventConversationUpdated- 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 instancesGetUserConversationsAsync(userId, apiKeys, filter, search, pagination)- Query conversations across tenantsGetApiInstance(string apiKey)- Get specific API instanceGetAllApiInstances()- Get all managed API instancesGetAllApiKeys()- Get all managed API keysHasApiKey(string apiKey)- Check if API key is managedDisconnectAllAsync()- 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 URLGetFilename()- Get uploaded filenameGetFileType()- Get file MIME typeGetTextContent()- Get extracted textGetFirstFile()- Get first file objectFilesproperty - 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
- 💬 Discord Community
- 🐛 Issue Tracker
- 📧 Email: support@captivatechat.ai
License
MIT License - see LICENSE file for details.
| Product | Versions 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. |
-
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.
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.