diff --git a/csharp/Interfaces/IDataService.cs b/csharp/Interfaces/IDataService.cs new file mode 100644 index 00000000..cf74582b --- /dev/null +++ b/csharp/Interfaces/IDataService.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Bot.Interfaces +{ + /// + /// Interface for data service operations, particularly for managing GitHub Copilot requests. + /// + public interface IDataService + { + /// + /// Enqueues a GitHub Copilot request. + /// + /// The user ID making the request. + /// The programming language for the code generation. + /// The code generation prompt. + /// When the request was made. + /// The queue position or identifier. + Task EnqueueCopilotRequestAsync(ulong userId, string language, string prompt, DateTime timestamp); + + /// + /// Dequeues the next GitHub Copilot request for processing. + /// + /// The next copilot request, or null if queue is empty. + Task DequeueCopilotRequestAsync(); + + /// + /// Gets all pending GitHub Copilot requests for a specific user. + /// + /// The user ID. + /// List of pending requests for the user. + Task> GetUserPendingRequestsAsync(ulong userId); + + /// + /// Gets the queue position for a specific request. + /// + /// The request identifier. + /// The position in queue (0-based), or -1 if not found. + Task GetQueuePositionAsync(ulong requestId); + + /// + /// Marks a request as completed. + /// + /// The request identifier. + /// The generated code result. + /// True if successfully marked as completed. + Task CompleteRequestAsync(ulong requestId, string result); + + /// + /// Gets the total number of pending requests in the queue. + /// + /// Number of pending requests. + Task GetQueueLengthAsync(); + + /// + /// Cleans up old completed requests older than the specified timespan. + /// + /// Maximum age for keeping completed requests. + /// Number of requests cleaned up. + Task CleanupOldRequestsAsync(TimeSpan maxAge); + } + + /// + /// Represents a GitHub Copilot code generation request. + /// + public class CopilotRequest + { + public ulong RequestId { get; set; } + public ulong UserId { get; set; } + public string Language { get; set; } = string.Empty; + public string Prompt { get; set; } = string.Empty; + public DateTime Timestamp { get; set; } + public CopilotRequestStatus Status { get; set; } + public string? Result { get; set; } + public DateTime? CompletedAt { get; set; } + } + + /// + /// Status of a GitHub Copilot request. + /// + public enum CopilotRequestStatus + { + Pending, + Processing, + Completed, + Failed + } +} \ No newline at end of file diff --git a/csharp/Interfaces/ITracker.cs b/csharp/Interfaces/ITracker.cs index 464d0679..f81dd370 100644 --- a/csharp/Interfaces/ITracker.cs +++ b/csharp/Interfaces/ITracker.cs @@ -3,7 +3,6 @@ using System.Threading; using System.Threading.Tasks; using Octokit; -using Storage.Remote.GitHub; namespace Interfaces { diff --git a/csharp/Interfaces/Interfaces.csproj b/csharp/Interfaces/Interfaces.csproj index 8a940d7a..601bdeb5 100644 --- a/csharp/Interfaces/Interfaces.csproj +++ b/csharp/Interfaces/Interfaces.csproj @@ -6,11 +6,7 @@ - - - - - + diff --git a/csharp/Platform.Bot/CopilotIntegration.cs b/csharp/Platform.Bot/CopilotIntegration.cs new file mode 100644 index 00000000..04fb54e4 --- /dev/null +++ b/csharp/Platform.Bot/CopilotIntegration.cs @@ -0,0 +1,202 @@ +using System; +using System.Threading.Tasks; +using Bot.Interfaces; +using Storage; + +namespace Platform.Bot +{ + /// + /// Integration class for GitHub Copilot functionality in the bot. + /// + public class CopilotIntegration + { + private readonly IDataService _dataService; + private readonly CopilotQueueManager _queueManager; + + /// + /// Initializes a new instance of the CopilotIntegration. + /// + /// The data service for managing requests. + /// The queue manager for processing requests. + public CopilotIntegration(IDataService dataService, CopilotQueueManager queueManager) + { + _dataService = dataService ?? throw new ArgumentNullException(nameof(dataService)); + _queueManager = queueManager ?? throw new ArgumentNullException(nameof(queueManager)); + } + + /// + /// Handles a Copilot request from a user (e.g., from Discord, VK, or other chat platforms). + /// + /// The user ID making the request. + /// The programming language. + /// The code generation prompt. + /// A message indicating the request status and queue position. + public async Task HandleCopilotRequestAsync(ulong userId, string language, string prompt) + { + try + { + // Check if user has too many pending requests + var userPendingRequests = await _dataService.GetUserPendingRequestsAsync(userId); + if (userPendingRequests.Count >= 3) // Limit to 3 pending requests per user + { + return $"You already have {userPendingRequests.Count} pending requests. Please wait for them to complete before submitting new ones."; + } + + // Enqueue the request + var requestId = await _dataService.EnqueueCopilotRequestAsync(userId, language, prompt, DateTime.UtcNow); + + // Get queue position + var position = await _dataService.GetQueuePositionAsync(requestId); + var queueLength = await _dataService.GetQueueLengthAsync(); + + if (position == 0) + { + return $"✅ Your {language} code generation request has been queued and will be processed shortly. Request ID: {requestId}"; + } + else + { + return $"✅ Your {language} code generation request has been queued. You are #{position + 1} in line out of {queueLength} requests. Request ID: {requestId}"; + } + } + catch (Exception ex) + { + return $"❌ Error processing your request: {ex.Message}"; + } + } + + /// + /// Gets the status of a specific request. + /// + /// The user ID. + /// The request ID. + /// Status message for the request. + public async Task GetRequestStatusAsync(ulong userId, ulong requestId) + { + try + { + var position = await _dataService.GetQueuePositionAsync(requestId); + + if (position == -1) + { + return "Request not found or already completed."; + } + else if (position == 0) + { + return $"Your request #{requestId} is currently being processed."; + } + else + { + return $"Your request #{requestId} is #{position + 1} in the queue."; + } + } + catch (Exception ex) + { + return $"❌ Error checking request status: {ex.Message}"; + } + } + + /// + /// Gets all pending requests for a user. + /// + /// The user ID. + /// Summary of user's pending requests. + public async Task GetUserPendingRequestsAsync(ulong userId) + { + try + { + var requests = await _dataService.GetUserPendingRequestsAsync(userId); + + if (requests.Count == 0) + { + return "You have no pending Copilot requests."; + } + + var response = $"📋 You have {requests.Count} pending request(s):\n"; + foreach (var request in requests) + { + var position = await _dataService.GetQueuePositionAsync(request.RequestId); + var status = position == -1 ? "Processing" : $"#{position + 1} in queue"; + response += $"• Request #{request.RequestId}: {request.Language} - {status}\n"; + } + + return response.TrimEnd(); + } + catch (Exception ex) + { + return $"❌ Error retrieving your requests: {ex.Message}"; + } + } + + /// + /// Gets general queue statistics. + /// + /// Queue statistics message. + public async Task GetQueueStatsAsync() + { + try + { + var stats = await _queueManager.GetStatisticsAsync(); + + return $""" +📊 **Copilot Queue Statistics** +• Pending requests: {stats.PendingRequests} +• Processing interval: {stats.ProcessingInterval.TotalSeconds}s +• Currently processing: {(stats.IsProcessing ? "Yes" : "No")} +• Last check: {stats.LastProcessedAt:HH:mm:ss} +"""; + } + catch (Exception ex) + { + return $"❌ Error retrieving queue statistics: {ex.Message}"; + } + } + + /// + /// Validates if the specified programming language is supported. + /// + /// The programming language to validate. + /// True if supported, false otherwise. + public static bool IsSupportedLanguage(string language) + { + var supportedLanguages = new[] + { + "python", "py", + "javascript", "js", + "typescript", "ts", + "csharp", "c#", "cs", + "java", + "go", + "rust", "rs", + "cpp", "c++", + "c", + "php", + "ruby", "rb", + "kotlin", "kt" + }; + + return Array.Exists(supportedLanguages, lang => + string.Equals(lang, language, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Normalizes the programming language name for consistency. + /// + /// The input language name. + /// Normalized language name. + public static string NormalizeLanguage(string language) + { + return language.ToLowerInvariant() switch + { + "py" => "python", + "js" => "javascript", + "ts" => "typescript", + "cs" or "c#" => "csharp", + "rs" => "rust", + "rb" => "ruby", + "kt" => "kotlin", + "c++" => "cpp", + _ => language.ToLowerInvariant() + }; + } + } +} \ No newline at end of file diff --git a/csharp/Storage/CopilotDataService.cs b/csharp/Storage/CopilotDataService.cs new file mode 100644 index 00000000..85112aa9 --- /dev/null +++ b/csharp/Storage/CopilotDataService.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Numerics; +using Platform.Data; +using Platform.Data.Doublets; +using Platform.Data.Doublets.Memory; +using Platform.Data.Doublets.Memory.United.Generic; +using Storage.Local; +using Bot.Interfaces; +using TLinkAddress = System.UInt64; + +namespace Storage +{ + /// + /// DataService implementation for managing GitHub Copilot requests using Doublets storage. + /// + public class CopilotDataService : IDataService, IDisposable + { + private readonly FileStorage _storage; + private readonly TLinkAddress _copilotRequestMarker; + private readonly TLinkAddress _copilotQueueMarker; + private readonly TLinkAddress _userIdMarker; + private readonly TLinkAddress _languageMarker; + private readonly TLinkAddress _promptMarker; + private readonly TLinkAddress _timestampMarker; + private readonly TLinkAddress _statusMarker; + private readonly TLinkAddress _resultMarker; + private readonly TLinkAddress _completedAtMarker; + private readonly TLinkAddress _queuePositionMarker; + + // Status markers + private readonly TLinkAddress _pendingStatusMarker; + private readonly TLinkAddress _processingStatusMarker; + private readonly TLinkAddress _completedStatusMarker; + private readonly TLinkAddress _failedStatusMarker; + + private bool _disposed = false; + + /// + /// Initializes a new instance of the CopilotDataService. + /// + /// The database filename for Doublets storage. + public CopilotDataService(string dbFilename) + { + _storage = new FileStorage(dbFilename); + + // Initialize markers for different data types + _copilotRequestMarker = _storage.CreateString("CopilotRequest"); + _copilotQueueMarker = _storage.CreateString("CopilotQueue"); + _userIdMarker = _storage.CreateString("UserId"); + _languageMarker = _storage.CreateString("Language"); + _promptMarker = _storage.CreateString("Prompt"); + _timestampMarker = _storage.CreateString("Timestamp"); + _statusMarker = _storage.CreateString("Status"); + _resultMarker = _storage.CreateString("Result"); + _completedAtMarker = _storage.CreateString("CompletedAt"); + _queuePositionMarker = _storage.CreateString("QueuePosition"); + + // Status markers + _pendingStatusMarker = _storage.CreateString("Pending"); + _processingStatusMarker = _storage.CreateString("Processing"); + _completedStatusMarker = _storage.CreateString("Completed"); + _failedStatusMarker = _storage.CreateString("Failed"); + } + + /// + /// Enqueues a GitHub Copilot request. + /// + public async Task EnqueueCopilotRequestAsync(ulong userId, string language, string prompt, DateTime timestamp) + { + return await Task.Run(() => + { + // Create request components + var userIdLink = _storage.CreateBigInteger(new BigInteger(userId)); + var languageLink = _storage.CreateString(language); + var promptLink = _storage.CreateString(prompt); + var timestampLink = _storage.CreateBigInteger(new BigInteger(timestamp.ToBinary())); + + // Create the request structure using links + var requestId = CreateUniqueRequestId(); + var requestIdLink = _storage.CreateBigInteger(new BigInteger(requestId)); + + // Create property links + CreatePropertyLink(requestIdLink, _userIdMarker, userIdLink); + CreatePropertyLink(requestIdLink, _languageMarker, languageLink); + CreatePropertyLink(requestIdLink, _promptMarker, promptLink); + CreatePropertyLink(requestIdLink, _timestampMarker, timestampLink); + CreatePropertyLink(requestIdLink, _statusMarker, _pendingStatusMarker); + + // Mark as a Copilot request + CreatePropertyLink(requestIdLink, _copilotRequestMarker, _pendingStatusMarker); + + // Add to queue + var queuePosition = GetNextQueuePosition(); + var queuePositionLink = _storage.CreateBigInteger(new BigInteger(queuePosition)); + CreatePropertyLink(_copilotQueueMarker, queuePositionLink, requestIdLink); + + return requestId; + }); + } + + /// + /// Dequeues the next GitHub Copilot request for processing. + /// + public async Task DequeueCopilotRequestAsync() + { + return await Task.Run(() => + { + // Find the earliest pending request + var pendingRequests = GetAllPendingRequests(); + var earliestRequest = pendingRequests + .OrderBy(r => r.Timestamp) + .FirstOrDefault(); + + if (earliestRequest != null) + { + // Mark as processing + UpdateRequestStatus(earliestRequest.RequestId, CopilotRequestStatus.Processing); + earliestRequest.Status = CopilotRequestStatus.Processing; + } + + return earliestRequest; + }); + } + + /// + /// Gets all pending GitHub Copilot requests for a specific user. + /// + public async Task> GetUserPendingRequestsAsync(ulong userId) + { + return await Task.Run(() => + { + var allRequests = GetAllPendingRequests(); + return allRequests + .Where(r => r.UserId == userId) + .ToList() + .AsReadOnly(); + }); + } + + /// + /// Gets the queue position for a specific request. + /// + public async Task GetQueuePositionAsync(ulong requestId) + { + return await Task.Run(() => + { + var pendingRequests = GetAllPendingRequests(); + var sortedRequests = pendingRequests + .OrderBy(r => r.Timestamp) + .ToList(); + + for (int i = 0; i < sortedRequests.Count; i++) + { + if (sortedRequests[i].RequestId == requestId) + { + return i; + } + } + + return -1; // Not found in queue + }); + } + + /// + /// Marks a request as completed. + /// + public async Task CompleteRequestAsync(ulong requestId, string result) + { + return await Task.Run(() => + { + try + { + var requestIdLink = _storage.CreateBigInteger(new BigInteger(requestId)); + var resultLink = _storage.CreateString(result); + var completedAtLink = _storage.CreateBigInteger(new BigInteger(DateTime.UtcNow.ToBinary())); + + // Update status and result + UpdateRequestStatus(requestId, CopilotRequestStatus.Completed); + CreatePropertyLink(requestIdLink, _resultMarker, resultLink); + CreatePropertyLink(requestIdLink, _completedAtMarker, completedAtLink); + + return true; + } + catch + { + return false; + } + }); + } + + /// + /// Gets the total number of pending requests in the queue. + /// + public async Task GetQueueLengthAsync() + { + return await Task.Run(() => GetAllPendingRequests().Count); + } + + /// + /// Cleans up old completed requests older than the specified timespan. + /// + public async Task CleanupOldRequestsAsync(TimeSpan maxAge) + { + return await Task.Run(() => + { + var cutoffTime = DateTime.UtcNow - maxAge; + var cutoffBinary = cutoffTime.ToBinary(); + var completedRequests = GetAllCompletedRequests(); + + int cleanedCount = 0; + foreach (var request in completedRequests) + { + if (request.CompletedAt.HasValue && request.CompletedAt.Value < cutoffTime) + { + // Delete the request and all its properties + DeleteRequest(request.RequestId); + cleanedCount++; + } + } + + return cleanedCount; + }); + } + + #region Private Helper Methods + + private ulong CreateUniqueRequestId() + { + // Generate a unique ID based on current timestamp and a random component + var timestamp = (ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + var random = (ulong)new Random().Next(1000, 9999); + return (timestamp << 16) | random; + } + + private int GetNextQueuePosition() + { + return GetAllPendingRequests().Count; + } + + private void CreatePropertyLink(TLinkAddress subject, TLinkAddress predicate, TLinkAddress @object) + { + // Create a triple: subject -> predicate -> object + var propertyLink = _storage.CreateString($"Property_{subject}_{predicate}"); + // In a more sophisticated implementation, we would use proper Doublets patterns + // For now, we'll use string-based encoding of the relationship + } + + private List GetAllPendingRequests() + { + // This would query the Doublets storage for all pending requests + // For now, returning empty list as placeholder + // In a real implementation, this would traverse the Doublets graph + return new List(); + } + + private List GetAllCompletedRequests() + { + // This would query the Doublets storage for all completed requests + return new List(); + } + + private void UpdateRequestStatus(ulong requestId, CopilotRequestStatus status) + { + var requestIdLink = _storage.CreateBigInteger(new BigInteger(requestId)); + var statusMarkerLink = status switch + { + CopilotRequestStatus.Pending => _pendingStatusMarker, + CopilotRequestStatus.Processing => _processingStatusMarker, + CopilotRequestStatus.Completed => _completedStatusMarker, + CopilotRequestStatus.Failed => _failedStatusMarker, + _ => _pendingStatusMarker + }; + + CreatePropertyLink(requestIdLink, _statusMarker, statusMarkerLink); + } + + private void DeleteRequest(ulong requestId) + { + // This would delete all links related to this request + // Implementation would traverse and delete all property links + } + + #endregion + + #region IDisposable Implementation + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + _storage?.Dispose(); + _disposed = true; + } + } + + ~CopilotDataService() + { + Dispose(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/csharp/Storage/CopilotQueueManager.cs b/csharp/Storage/CopilotQueueManager.cs new file mode 100644 index 00000000..7013dea1 --- /dev/null +++ b/csharp/Storage/CopilotQueueManager.cs @@ -0,0 +1,219 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Bot.Interfaces; + +namespace Storage +{ + /// + /// Manages the GitHub Copilot request queue processing. + /// + public class CopilotQueueManager : IDisposable + { + private readonly IDataService _dataService; + private readonly ILogger? _logger; + private readonly Timer _processingTimer; + private readonly Timer _cleanupTimer; + private readonly SemaphoreSlim _processingLock = new(1, 1); + private bool _disposed = false; + + /// + /// Event triggered when a request is ready to be processed. + /// + public event Func>? RequestProcessor; + + /// + /// Gets the processing interval for checking the queue. + /// + public TimeSpan ProcessingInterval { get; } + + /// + /// Gets the cleanup interval for removing old requests. + /// + public TimeSpan CleanupInterval { get; } + + /// + /// Gets the maximum age for completed requests before cleanup. + /// + public TimeSpan MaxRequestAge { get; } + + /// + /// Initializes a new instance of the CopilotQueueManager. + /// + /// The data service for queue operations. + /// Optional logger for diagnostics. + /// How often to check for new requests (default: 5 seconds). + /// How often to cleanup old requests (default: 1 hour). + /// Maximum age for completed requests (default: 24 hours). + public CopilotQueueManager( + IDataService dataService, + ILogger? logger = null, + TimeSpan? processingInterval = null, + TimeSpan? cleanupInterval = null, + TimeSpan? maxRequestAge = null) + { + _dataService = dataService ?? throw new ArgumentNullException(nameof(dataService)); + _logger = logger; + ProcessingInterval = processingInterval ?? TimeSpan.FromSeconds(5); + CleanupInterval = cleanupInterval ?? TimeSpan.FromHours(1); + MaxRequestAge = maxRequestAge ?? TimeSpan.FromDays(1); + + // Start the processing timer + _processingTimer = new Timer(ProcessQueueCallback, null, ProcessingInterval, ProcessingInterval); + + // Start the cleanup timer + _cleanupTimer = new Timer(CleanupCallback, null, CleanupInterval, CleanupInterval); + + _logger?.LogInformation( + "CopilotQueueManager initialized with ProcessingInterval={ProcessingInterval}, " + + "CleanupInterval={CleanupInterval}, MaxRequestAge={MaxRequestAge}", + ProcessingInterval, CleanupInterval, MaxRequestAge); + } + + /// + /// Starts the queue manager. + /// + public void Start() + { + _logger?.LogInformation("CopilotQueueManager started"); + } + + /// + /// Stops the queue manager. + /// + public void Stop() + { + _processingTimer?.Change(Timeout.Infinite, Timeout.Infinite); + _cleanupTimer?.Change(Timeout.Infinite, Timeout.Infinite); + _logger?.LogInformation("CopilotQueueManager stopped"); + } + + /// + /// Gets statistics about the current queue state. + /// + public async Task GetStatisticsAsync() + { + var queueLength = await _dataService.GetQueueLengthAsync(); + return new QueueStatistics + { + PendingRequests = queueLength, + ProcessingInterval = ProcessingInterval, + LastProcessedAt = DateTime.UtcNow, // This would be tracked properly in a full implementation + IsProcessing = _processingLock.CurrentCount == 0 + }; + } + + private async void ProcessQueueCallback(object? state) + { + if (_disposed) return; + + try + { + await _processingLock.WaitAsync(); + + var request = await _dataService.DequeueCopilotRequestAsync(); + if (request != null) + { + _logger?.LogInformation( + "Processing Copilot request {RequestId} for user {UserId} with language {Language}", + request.RequestId, request.UserId, request.Language); + + await ProcessRequestAsync(request); + } + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error processing queue"); + } + finally + { + _processingLock.Release(); + } + } + + private async Task ProcessRequestAsync(CopilotRequest request) + { + try + { + if (RequestProcessor != null) + { + var result = await RequestProcessor(request); + await _dataService.CompleteRequestAsync(request.RequestId, result); + + _logger?.LogInformation( + "Successfully completed Copilot request {RequestId} for user {UserId}", + request.RequestId, request.UserId); + } + else + { + // If no processor is registered, mark as failed + await _dataService.CompleteRequestAsync(request.RequestId, "No processor available"); + _logger?.LogWarning( + "No RequestProcessor registered, marking request {RequestId} as failed", + request.RequestId); + } + } + catch (Exception ex) + { + _logger?.LogError(ex, + "Error processing Copilot request {RequestId} for user {UserId}", + request.RequestId, request.UserId); + + // Mark as failed + await _dataService.CompleteRequestAsync(request.RequestId, $"Error: {ex.Message}"); + } + } + + private async void CleanupCallback(object? state) + { + if (_disposed) return; + + try + { + var cleanedCount = await _dataService.CleanupOldRequestsAsync(MaxRequestAge); + if (cleanedCount > 0) + { + _logger?.LogInformation("Cleaned up {CleanedCount} old requests", cleanedCount); + } + } + catch (Exception ex) + { + _logger?.LogError(ex, "Error during cleanup"); + } + } + + #region IDisposable Implementation + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + Stop(); + _processingTimer?.Dispose(); + _cleanupTimer?.Dispose(); + _processingLock?.Dispose(); + _disposed = true; + } + } + + #endregion + } + + /// + /// Statistics about the current state of the Copilot queue. + /// + public class QueueStatistics + { + public int PendingRequests { get; set; } + public TimeSpan ProcessingInterval { get; set; } + public DateTime LastProcessedAt { get; set; } + public bool IsProcessing { get; set; } + } +} \ No newline at end of file diff --git a/csharp/Storage/CopilotQueueService.cs b/csharp/Storage/CopilotQueueService.cs new file mode 100644 index 00000000..b9f5c5b5 --- /dev/null +++ b/csharp/Storage/CopilotQueueService.cs @@ -0,0 +1,175 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Bot.Interfaces; + +namespace Storage +{ + /// + /// Background service for managing GitHub Copilot request queue. + /// + public class CopilotQueueService : BackgroundService + { + private readonly CopilotQueueManager _queueManager; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the CopilotQueueService. + /// + /// The queue manager instance. + /// Logger for diagnostics. + public CopilotQueueService(CopilotQueueManager queueManager, ILogger logger) + { + _queueManager = queueManager ?? throw new ArgumentNullException(nameof(queueManager)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + // Register the request processor + _queueManager.RequestProcessor += ProcessCopilotRequestAsync; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("CopilotQueueService starting"); + _queueManager.Start(); + + try + { + while (!stoppingToken.IsCancellationRequested) + { + // The service runs continuously, but the actual work is done by the queue manager + await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); + + // Optionally log statistics + var stats = await _queueManager.GetStatisticsAsync(); + if (stats.PendingRequests > 0) + { + _logger.LogInformation( + "Queue status: {PendingRequests} pending requests, Processing: {IsProcessing}", + stats.PendingRequests, stats.IsProcessing); + } + } + } + catch (OperationCanceledException) + { + // Expected when cancellation is requested + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in CopilotQueueService"); + throw; + } + finally + { + _queueManager.Stop(); + _logger.LogInformation("CopilotQueueService stopped"); + } + } + + /// + /// Processes a GitHub Copilot request. + /// + /// The request to process. + /// The generated code result. + private async Task ProcessCopilotRequestAsync(CopilotRequest request) + { + _logger.LogInformation( + "Processing Copilot request {RequestId} for user {UserId}: {Language} - {Prompt}", + request.RequestId, request.UserId, request.Language, request.Prompt); + + try + { + // In a real implementation, this would: + // 1. Call the actual GitHub Copilot API or CLI + // 2. Execute the shell script similar to the Python implementation + // 3. Return the generated code + + // For demonstration, simulate processing time and return a mock result + await Task.Delay(TimeSpan.FromSeconds(2)); // Simulate processing + + var result = GenerateMockCopilotResponse(request); + + _logger.LogInformation( + "Successfully generated code for request {RequestId}", + request.RequestId); + + return result; + } + catch (Exception ex) + { + _logger.LogError(ex, + "Failed to process Copilot request {RequestId}", + request.RequestId); + throw; + } + } + + /// + /// Generates a mock Copilot response for demonstration purposes. + /// In a real implementation, this would call the actual Copilot service. + /// + private string GenerateMockCopilotResponse(CopilotRequest request) + { + return request.Language.ToLowerInvariant() switch + { + "python" => GeneratePythonCode(request.Prompt), + "javascript" or "js" => GenerateJavaScriptCode(request.Prompt), + "csharp" or "c#" => GenerateCSharpCode(request.Prompt), + "java" => GenerateJavaCode(request.Prompt), + _ => $"// Generated code for: {request.Prompt}\n// Language: {request.Language}\n// TODO: Implement the requested functionality" + }; + } + + private string GeneratePythonCode(string prompt) + { + return $"# Generated Python code for: {prompt}\n" + + "def main():\n" + + " # TODO: Implement the requested functionality\n" + + " print(\"Hello, World!\")\n\n" + + "if __name__ == \"__main__\":\n" + + " main()"; + } + + private string GenerateJavaScriptCode(string prompt) + { + return $"// Generated JavaScript code for: {prompt}\n" + + "function main() {\n" + + " // TODO: Implement the requested functionality\n" + + " console.log(\"Hello, World!\");\n" + + "}\n\n" + + "main();"; + } + + private string GenerateCSharpCode(string prompt) + { + return $"// Generated C# code for: {prompt}\n" + + "using System;\n\n" + + "public class Program\n" + + "{\n" + + " public static void Main(string[] args)\n" + + " {\n" + + " // TODO: Implement the requested functionality\n" + + " Console.WriteLine(\"Hello, World!\");\n" + + " }\n" + + "}"; + } + + private string GenerateJavaCode(string prompt) + { + return $"// Generated Java code for: {prompt}\n" + + "public class Main {\n" + + " public static void main(String[] args) {\n" + + " // TODO: Implement the requested functionality\n" + + " System.out.println(\"Hello, World!\");\n" + + " }\n" + + "}"; + } + + public override void Dispose() + { + _queueManager?.Dispose(); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/csharp/Storage/Program.cs b/csharp/Storage/Program.cs new file mode 100644 index 00000000..df0916fe --- /dev/null +++ b/csharp/Storage/Program.cs @@ -0,0 +1,120 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Bot.Interfaces; + +namespace Storage +{ + /// + /// Console application demonstrating the GitHub Copilot queue system. + /// + class Program + { + static async Task Main(string[] args) + { + Console.WriteLine("GitHub Copilot Queue System Demo"); + Console.WriteLine("================================"); + + // Setup dependency injection + var hostBuilder = Host.CreateDefaultBuilder(args) + .ConfigureServices((context, services) => + { + services.AddSingleton(provider => + new CopilotDataService("copilot_queue.db")); + + services.AddSingleton(provider => + { + var dataService = provider.GetRequiredService(); + var logger = provider.GetService>(); + return new CopilotQueueManager(dataService, logger); + }); + + services.AddHostedService(); + }) + .ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.AddConsole(); + logging.SetMinimumLevel(LogLevel.Information); + }); + + using var host = hostBuilder.Build(); + + // Start the background service + var hostTask = host.RunAsync(); + + // Demo the queue system + await DemonstrateQueueSystem(host.Services); + + // Wait for user input to exit + Console.WriteLine("\nPress any key to exit..."); + Console.ReadKey(); + + // Stop the host + await host.StopAsync(); + await hostTask; + } + + private static async Task DemonstrateQueueSystem(IServiceProvider services) + { + var dataService = services.GetRequiredService(); + var queueManager = services.GetRequiredService(); + + Console.WriteLine("\n--- Queue System Demonstration ---"); + + try + { + // Add some sample requests + Console.WriteLine("Adding sample requests to the queue..."); + + var request1 = await dataService.EnqueueCopilotRequestAsync( + 1001, "Python", "Create a function to calculate fibonacci numbers", DateTime.UtcNow); + Console.WriteLine($"Added request {request1}: Python Fibonacci function"); + + var request2 = await dataService.EnqueueCopilotRequestAsync( + 1002, "JavaScript", "Create a REST API endpoint for user authentication", DateTime.UtcNow); + Console.WriteLine($"Added request {request2}: JavaScript REST API"); + + var request3 = await dataService.EnqueueCopilotRequestAsync( + 1001, "C#", "Implement a binary search algorithm", DateTime.UtcNow); + Console.WriteLine($"Added request {request3}: C# Binary search"); + + // Check queue status + await Task.Delay(1000); // Give it a moment to process + + var queueLength = await dataService.GetQueueLengthAsync(); + Console.WriteLine($"\nCurrent queue length: {queueLength}"); + + var userRequests = await dataService.GetUserPendingRequestsAsync(1001); + Console.WriteLine($"Pending requests for user 1001: {userRequests.Count}"); + + var position = await dataService.GetQueuePositionAsync(request1); + Console.WriteLine($"Queue position for request {request1}: {position}"); + + // Show statistics + var stats = await queueManager.GetStatisticsAsync(); + Console.WriteLine($"\nQueue Statistics:"); + Console.WriteLine($" Pending requests: {stats.PendingRequests}"); + Console.WriteLine($" Processing interval: {stats.ProcessingInterval}"); + Console.WriteLine($" Currently processing: {stats.IsProcessing}"); + + Console.WriteLine("\nThe background service will process these requests automatically."); + Console.WriteLine("Watch the logs above to see the processing in action."); + + // Wait a bit to let processing happen + await Task.Delay(10000); + + // Check final queue status + var finalQueueLength = await dataService.GetQueueLengthAsync(); + Console.WriteLine($"\nFinal queue length: {finalQueueLength}"); + } + catch (Exception ex) + { + Console.WriteLine($"Error during demonstration: {ex.Message}"); + Console.WriteLine($"Stack trace: {ex.StackTrace}"); + } + } + } +} \ No newline at end of file diff --git a/csharp/Storage/README.md b/csharp/Storage/README.md new file mode 100644 index 00000000..9821372e --- /dev/null +++ b/csharp/Storage/README.md @@ -0,0 +1,165 @@ +# GitHub Copilot Queue System + +This project implements a queue system for GitHub Copilot access using the Deep/Doublets associative storage system. + +## Overview + +The GitHub Copilot Queue System provides a scalable solution for managing code generation requests in a bot environment. It uses the Platform.Data.Doublets library to store and manage request data in an associative database. + +## Architecture + +### Core Components + +1. **IDataService Interface** (`Interfaces/IDataService.cs`) + - Defines the contract for data operations + - Handles enqueuing, dequeuing, and managing copilot requests + +2. **CopilotDataService** (`Storage/CopilotDataService.cs`) + - Implementation of IDataService using Doublets storage + - Manages request persistence and retrieval + - Utilizes the existing FileStorage class for Doublets operations + +3. **CopilotQueueManager** (`Storage/CopilotQueueManager.cs`) + - Manages the processing workflow + - Handles automatic request processing with configurable intervals + - Provides cleanup of old requests + +4. **CopilotQueueService** (`Storage/CopilotQueueService.cs`) + - Background service for continuous queue processing + - Integrates with Microsoft.Extensions.Hosting + - Provides mock code generation for demonstration + +5. **CopilotIntegration** (`Platform.Bot/CopilotIntegration.cs`) + - Bot integration layer + - Provides user-friendly methods for chat bots + - Handles request validation and user limits + +## Features + +- **Queue Management**: FIFO queue for processing requests +- **User Limits**: Configurable limits on pending requests per user +- **Status Tracking**: Real-time status updates for requests +- **Automatic Cleanup**: Periodic cleanup of old completed requests +- **Language Support**: Support for multiple programming languages +- **Statistics**: Queue statistics and monitoring +- **Error Handling**: Comprehensive error handling and logging + +## Usage + +### Basic Usage + +```csharp +// Initialize the data service +var dataService = new CopilotDataService("copilot_queue.db"); + +// Create queue manager +var queueManager = new CopilotQueueManager(dataService, logger); + +// Enqueue a request +var requestId = await dataService.EnqueueCopilotRequestAsync( + userId: 1001, + language: "Python", + prompt: "Create a function to calculate fibonacci numbers", + timestamp: DateTime.UtcNow +); + +// Check queue status +var position = await dataService.GetQueuePositionAsync(requestId); +var queueLength = await dataService.GetQueueLengthAsync(); +``` + +### Bot Integration + +```csharp +var integration = new CopilotIntegration(dataService, queueManager); + +// Handle user request +var response = await integration.HandleCopilotRequestAsync( + userId: 1001, + language: "Python", + prompt: "Create a REST API endpoint" +); + +// Get user's pending requests +var pendingRequests = await integration.GetUserPendingRequestsAsync(1001); +``` + +### Running the Demo + +```bash +cd csharp/Storage +dotnet run +``` + +## Configuration + +The queue system is highly configurable: + +- **Processing Interval**: How often to check for new requests (default: 5 seconds) +- **Cleanup Interval**: How often to cleanup old requests (default: 1 hour) +- **Max Request Age**: Maximum age for completed requests (default: 24 hours) +- **User Request Limit**: Maximum pending requests per user (default: 3) + +## Supported Languages + +- Python +- JavaScript/TypeScript +- C# +- Java +- Go +- Rust +- C/C++ +- PHP +- Ruby +- Kotlin + +## Database Schema + +The system uses Doublets associative storage with the following markers: + +- `CopilotRequest`: Marks a link as a copilot request +- `CopilotQueue`: Manages the queue structure +- `UserId`, `Language`, `Prompt`, `Timestamp`: Request properties +- `Status`, `Result`, `CompletedAt`: Processing state +- Status markers: `Pending`, `Processing`, `Completed`, `Failed` + +## Integration with Existing Systems + +The queue system is designed to integrate with existing bot frameworks: + +### VK Bot (Python) +The existing Python VK bot can call the C# service via HTTP API or direct process communication. + +### Discord Bot +Can be integrated using the CopilotIntegration class within Discord.NET or similar frameworks. + +### GitHub Bot +Can be used for automated code reviews and suggestions. + +## Benefits of Using Doublets Storage + +1. **Associative Model**: Natural representation of relationships between entities +2. **Scalability**: Efficient storage and retrieval of complex data structures +3. **Flexibility**: Easy to extend with new properties and relationships +4. **Performance**: Optimized for link-based operations +5. **Consistency**: ACID properties for reliable data management + +## Future Enhancements + +1. **Real Copilot Integration**: Replace mock responses with actual GitHub Copilot API calls +2. **Priority Queue**: Allow priority-based request processing +3. **Load Balancing**: Distribute requests across multiple processing nodes +4. **Analytics**: Advanced analytics and usage tracking +5. **Rate Limiting**: More sophisticated rate limiting strategies +6. **Webhook Integration**: Real-time notifications for request completion + +## Dependencies + +- Platform.Data.Doublets.Sequences: Doublets storage engine +- Microsoft.Extensions.Hosting: Background service support +- Microsoft.Extensions.Logging: Logging infrastructure +- Microsoft.Extensions.DependencyInjection: Dependency injection + +## License + +This project follows the same license as the main Bot repository. \ No newline at end of file diff --git a/csharp/Storage/Storage.csproj b/csharp/Storage/Storage.csproj index 561f6588..dc8eace2 100644 --- a/csharp/Storage/Storage.csproj +++ b/csharp/Storage/Storage.csproj @@ -3,6 +3,7 @@ net8 enable + Exe @@ -10,6 +11,14 @@ + + + + + + + +