Skip to content
55 changes: 29 additions & 26 deletions EssentialCSharp.Web/Controllers/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ namespace EssentialCSharp.Web.Controllers;
[IgnoreAntiforgeryToken]
public partial class ChatController : ControllerBase
{
private readonly AIChatService _AiChatService;
private readonly AIChatService _AIChatService;
private readonly ResponseIdValidationService _ResponseIdValidationService;
private readonly ILogger<ChatController> _Logger;

public ChatController(ILogger<ChatController> logger, AIChatService aiChatService, ResponseIdValidationService responseIdValidationService)
{
_AiChatService = aiChatService;
_AIChatService = aiChatService;
_ResponseIdValidationService = responseIdValidationService;
_Logger = logger;
}
Expand All @@ -46,7 +46,7 @@ public async Task<IActionResult> SendMessage([FromBody] ChatMessageRequest reque

try
{
var (response, responseId) = await _AiChatService.GetChatCompletion(
var (response, responseId) = await _AIChatService.GetChatCompletion(
prompt: request.Message,
previousResponseId: previousResponseId,
enableContextualSearch: request.EnableContextualSearch,
Expand Down Expand Up @@ -75,15 +75,15 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
if (string.IsNullOrEmpty(request.Message))
{
Response.StatusCode = 400;
await Response.WriteAsJsonAsync(new { error = "Message cannot be empty." }, CancellationToken.None);
await Response.WriteAsJsonAsync(new { error = "Message cannot be empty." }, cancellationToken);
return;
}

var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(userId))
{
Response.StatusCode = 401;
await Response.WriteAsJsonAsync(new { error = "Unauthorized." }, CancellationToken.None);
await Response.WriteAsJsonAsync(new { error = "Unauthorized." }, cancellationToken);
return;
}

Expand All @@ -94,7 +94,7 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
if (!_ResponseIdValidationService.ValidateResponseId(userId, previousResponseId))
{
Response.StatusCode = 400;
await Response.WriteAsJsonAsync(new { error = "Invalid conversation context." }, CancellationToken.None);
await Response.WriteAsJsonAsync(new { error = "Invalid conversation context." }, cancellationToken);
return;
}

Expand All @@ -104,7 +104,7 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat

try
{
await foreach (var (text, responseId) in _AiChatService.GetChatCompletionStream(
await foreach (var (text, responseId) in _AIChatService.GetChatCompletionStream(
prompt: request.Message,
previousResponseId: previousResponseId,
enableContextualSearch: request.EnableContextualSearch,
Expand Down Expand Up @@ -133,46 +133,49 @@ public async Task StreamMessage([FromBody] ChatMessageRequest request, Cancellat
await Response.WriteAsync("data: [DONE]\n\n", cancellationToken);
await Response.Body.FlushAsync(cancellationToken);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested || HttpContext.RequestAborted.IsCancellationRequested)
catch (OperationCanceledException)
Comment thread
BenjaminMichaelis marked this conversation as resolved.
{
LogChatStreamCancelled(_Logger, User.Identity?.Name);
}
Comment thread
BenjaminMichaelis marked this conversation as resolved.
Outdated
catch (ConversationContextLimitExceededException) when (!Response.HasStarted)
{
Response.StatusCode = 400;
Response.ContentType = "application/json";
await Response.WriteAsJsonAsync(new { error = "This conversation has grown too long. Please start a new one.", errorCode = "context_limit_exceeded" }, CancellationToken.None);
}
catch (ConversationContextLimitExceededException ex)
{
LogChatStreamErrorMidStream(_Logger, ex, User.Identity?.Name);
try
if (!Response.HasStarted)
{
await Response.WriteAsync("data: {\"type\":\"error\",\"message\":\"This conversation has grown too long. Please start a new one.\",\"errorCode\":\"context_limit_exceeded\"}\n\n", CancellationToken.None);
await Response.Body.FlushAsync(CancellationToken.None);
Response.StatusCode = 400;
Response.ContentType = "application/json";
await Response.WriteAsJsonAsync(new { error = "This conversation has grown too long. Please start a new one.", errorCode = "context_limit_exceeded" }, cancellationToken);
Comment thread
BenjaminMichaelis marked this conversation as resolved.
Outdated
}
catch (Exception)
else
{
// Best-effort write to an already-streaming response. Kestrel can throw
// IOException (connection reset), OperationCanceledException, or
// ObjectDisposedException on abrupt client disconnect — swallow all to
// avoid masking the original exception.
LogChatStreamErrorMidStream(_Logger, ex, User.Identity?.Name);
try
{
await Response.WriteAsync("data: {\"type\":\"error\",\"message\":\"This conversation has grown too long. Please start a new one.\",\"errorCode\":\"context_limit_exceeded\"}\n\n", cancellationToken);
await Response.Body.FlushAsync(cancellationToken);
}
catch (Exception)
{
// Best-effort write to an already-streaming response. Kestrel can throw
// IOException (connection reset), OperationCanceledException, or
// ObjectDisposedException on abrupt client disconnect — swallow all to
// avoid masking the original exception.
}
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
}
}
catch (Exception ex) when (!Response.HasStarted)
{
LogChatStreamErrorBeforeResponseStarted(_Logger, ex, User.Identity?.Name);
Response.StatusCode = 500;
Response.ContentType = "application/json";
await Response.WriteAsJsonAsync(new { error = "Chat service unavailable" }, CancellationToken.None);
await Response.WriteAsJsonAsync(new { error = "Chat service unavailable" }, cancellationToken);
}
catch (Exception ex)
{
LogChatStreamErrorMidStream(_Logger, ex, User.Identity?.Name);
try
{
await Response.WriteAsync("data: {\"type\":\"error\",\"message\":\"Stream interrupted\"}\n\n", CancellationToken.None);
await Response.Body.FlushAsync(CancellationToken.None);
await Response.WriteAsync("data: {\"type\":\"error\",\"message\":\"Stream interrupted\"}\n\n", cancellationToken);
await Response.Body.FlushAsync(cancellationToken);
}
catch (Exception)
{
Expand Down
Loading