188 lines
6.9 KiB
C#
188 lines
6.9 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using DramaLing.Api.Models.DTOs;
|
|
using DramaLing.Api.Services;
|
|
using System.Diagnostics;
|
|
using System.Security.Claims;
|
|
|
|
namespace DramaLing.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/ai")]
|
|
public class AIController : ControllerBase
|
|
{
|
|
private readonly IGeminiService _geminiService;
|
|
private readonly IAnalysisCacheService _cacheService;
|
|
private readonly IUsageTrackingService _usageTrackingService;
|
|
private readonly ILogger<AIController> _logger;
|
|
|
|
public AIController(
|
|
IGeminiService geminiService,
|
|
IAnalysisCacheService cacheService,
|
|
IUsageTrackingService usageTrackingService,
|
|
ILogger<AIController> logger)
|
|
{
|
|
_geminiService = geminiService;
|
|
_cacheService = cacheService;
|
|
_usageTrackingService = usageTrackingService;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 智能分析英文句子
|
|
/// </summary>
|
|
/// <param name="request">分析請求</param>
|
|
/// <returns>分析結果</returns>
|
|
[HttpPost("analyze-sentence")]
|
|
public async Task<ActionResult<SentenceAnalysisResponse>> AnalyzeSentence(
|
|
[FromBody] SentenceAnalysisRequest request)
|
|
{
|
|
var requestId = Guid.NewGuid().ToString();
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
try
|
|
{
|
|
// For testing without auth - use dummy user ID
|
|
var userId = "test-user-id";
|
|
|
|
_logger.LogInformation("Processing sentence analysis request {RequestId} for user {UserId}",
|
|
requestId, userId);
|
|
|
|
// Input validation
|
|
if (!ModelState.IsValid)
|
|
{
|
|
return BadRequest(CreateErrorResponse("INVALID_INPUT", "輸入格式錯誤",
|
|
ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(),
|
|
requestId));
|
|
}
|
|
|
|
// For testing - skip usage limits
|
|
// var userGuid = Guid.Parse(userId);
|
|
// var canUseService = await _usageTrackingService.CheckUsageLimitAsync(userGuid);
|
|
// if (!canUseService)
|
|
// {
|
|
// return StatusCode(429, CreateErrorResponse("RATE_LIMIT_EXCEEDED", "已超過每日使用限制",
|
|
// new { limit = 5, resetTime = DateTime.UtcNow.Date.AddDays(1) },
|
|
// requestId));
|
|
// }
|
|
|
|
// Check cache first
|
|
var cachedResult = await _cacheService.GetCachedAnalysisAsync(request.InputText);
|
|
if (cachedResult != null)
|
|
{
|
|
_logger.LogInformation("Returning cached result for request {RequestId}", requestId);
|
|
|
|
// Parse cached result
|
|
var cachedData = System.Text.Json.JsonSerializer.Deserialize<SentenceAnalysisData>(cachedResult.AnalysisResult);
|
|
if (cachedData != null)
|
|
{
|
|
return Ok(new SentenceAnalysisResponse
|
|
{
|
|
Success = true,
|
|
ProcessingTime = stopwatch.Elapsed.TotalSeconds,
|
|
Data = cachedData
|
|
});
|
|
}
|
|
}
|
|
|
|
// Perform AI analysis
|
|
var options = request.Options ?? new AnalysisOptions();
|
|
var analysisData = await _geminiService.AnalyzeSentenceAsync(
|
|
request.InputText, request.UserLevel, options);
|
|
|
|
// Cache the result
|
|
await _cacheService.SetCachedAnalysisAsync(request.InputText, analysisData, TimeSpan.FromHours(24));
|
|
|
|
// Skip usage tracking for testing
|
|
// await _usageTrackingService.RecordSentenceAnalysisAsync(userGuid);
|
|
|
|
stopwatch.Stop();
|
|
analysisData.Metadata.ProcessingDate = DateTime.UtcNow;
|
|
|
|
_logger.LogInformation("Sentence analysis completed for request {RequestId} in {ElapsedMs}ms",
|
|
requestId, stopwatch.ElapsedMilliseconds);
|
|
|
|
return Ok(new SentenceAnalysisResponse
|
|
{
|
|
Success = true,
|
|
ProcessingTime = stopwatch.Elapsed.TotalSeconds,
|
|
Data = analysisData
|
|
});
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
_logger.LogWarning(ex, "Invalid input for request {RequestId}", requestId);
|
|
return BadRequest(CreateErrorResponse("INVALID_INPUT", ex.Message, null, requestId));
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
_logger.LogError(ex, "AI service error for request {RequestId}", requestId);
|
|
return StatusCode(500, CreateErrorResponse("AI_SERVICE_ERROR", "AI服務暫時不可用", null, requestId));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Unexpected error processing request {RequestId}", requestId);
|
|
return StatusCode(500, CreateErrorResponse("INTERNAL_ERROR", "伺服器內部錯誤", null, requestId));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 健康檢查端點
|
|
/// </summary>
|
|
[HttpGet("health")]
|
|
[AllowAnonymous]
|
|
public ActionResult GetHealth()
|
|
{
|
|
return Ok(new
|
|
{
|
|
Status = "Healthy",
|
|
Service = "AI Analysis Service",
|
|
Timestamp = DateTime.UtcNow,
|
|
Version = "1.0"
|
|
});
|
|
}
|
|
|
|
private string GetCurrentUserId()
|
|
{
|
|
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)
|
|
?? User.FindFirst("sub")
|
|
?? User.FindFirst("user_id");
|
|
|
|
if (userIdClaim?.Value == null)
|
|
{
|
|
throw new UnauthorizedAccessException("用戶ID未找到");
|
|
}
|
|
|
|
return userIdClaim.Value;
|
|
}
|
|
|
|
private ApiErrorResponse CreateErrorResponse(string code, string message, object? details, string requestId)
|
|
{
|
|
var suggestions = GetSuggestionsForError(code);
|
|
|
|
return new ApiErrorResponse
|
|
{
|
|
Success = false,
|
|
Error = new ApiError
|
|
{
|
|
Code = code,
|
|
Message = message,
|
|
Details = details,
|
|
Suggestions = suggestions
|
|
},
|
|
RequestId = requestId,
|
|
Timestamp = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
private List<string> GetSuggestionsForError(string errorCode)
|
|
{
|
|
return errorCode switch
|
|
{
|
|
"INVALID_INPUT" => new List<string> { "請檢查輸入格式", "確保文本長度在限制內" },
|
|
"RATE_LIMIT_EXCEEDED" => new List<string> { "升級到Premium帳戶以獲得無限使用", "明天重新嘗試" },
|
|
"AI_SERVICE_ERROR" => new List<string> { "請稍後重試", "如果問題持續,請聯繫客服" },
|
|
_ => new List<string> { "請稍後重試" }
|
|
};
|
|
}
|
|
} |