version 2
This commit is contained in:
358
src/GreenHome.Api/Controllers/AIController.cs
Normal file
358
src/GreenHome.Api/Controllers/AIController.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using GreenHome.AI.DeepSeek;
|
||||
using GreenHome.Application;
|
||||
using GreenHome.Domain;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace GreenHome.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AIController : ControllerBase
|
||||
{
|
||||
private readonly IDeepSeekService deepSeekService;
|
||||
private readonly IAIQueryService aiQueryService;
|
||||
private readonly ILogger<AIController> logger;
|
||||
|
||||
public AIController(
|
||||
IDeepSeekService deepSeekService,
|
||||
IAIQueryService aiQueryService,
|
||||
ILogger<AIController> logger)
|
||||
{
|
||||
this.deepSeekService = deepSeekService;
|
||||
this.aiQueryService = aiQueryService;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a simple question to the AI and get a response
|
||||
/// </summary>
|
||||
/// <param name="request">Question request</param>
|
||||
/// <returns>AI response</returns>
|
||||
[HttpPost("ask")]
|
||||
public async Task<IActionResult> AskQuestion([FromBody] SimpleQuestionRequest request)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request.Question))
|
||||
{
|
||||
return BadRequest(new { error = "Question is required" });
|
||||
}
|
||||
|
||||
logger.LogInformation("Processing AI question: {Question}", request.Question);
|
||||
|
||||
// Build chat request to get token information
|
||||
var messages = new List<ChatMessage>();
|
||||
if (!string.IsNullOrWhiteSpace(request.SystemPrompt))
|
||||
{
|
||||
messages.Add(new ChatMessage { Role = "system", Content = request.SystemPrompt });
|
||||
}
|
||||
messages.Add(new ChatMessage { Role = "user", Content = request.Question });
|
||||
|
||||
var chatRequest = new ChatRequest { Messages = messages };
|
||||
var chatResponse = await deepSeekService.AskAsync(chatRequest);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
if (chatResponse == null || chatResponse.Choices == null || !chatResponse.Choices.Any())
|
||||
{
|
||||
return StatusCode(500, new { error = "No response received from AI" });
|
||||
}
|
||||
|
||||
var answer = chatResponse.Choices.FirstOrDefault()?.Message?.Content ?? "";
|
||||
|
||||
// Save to database
|
||||
var aiQuery = new AIQuery
|
||||
{
|
||||
Question = request.Question,
|
||||
Answer = answer,
|
||||
DeviceId = request.DeviceId,
|
||||
UserId = request.UserId,
|
||||
Model = chatResponse.Model,
|
||||
PromptTokens = chatResponse.Usage?.PromptTokens ?? 0,
|
||||
CompletionTokens = chatResponse.Usage?.CompletionTokens ?? 0,
|
||||
TotalTokens = chatResponse.Usage?.TotalTokens ?? 0,
|
||||
ResponseTimeMs = stopwatch.ElapsedMilliseconds
|
||||
};
|
||||
|
||||
await aiQueryService.SaveQueryAsync(aiQuery);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
question = request.Question,
|
||||
answer = answer,
|
||||
deviceId = request.DeviceId,
|
||||
tokens = new
|
||||
{
|
||||
prompt = aiQuery.PromptTokens,
|
||||
completion = aiQuery.CompletionTokens,
|
||||
total = aiQuery.TotalTokens
|
||||
},
|
||||
responseTimeMs = aiQuery.ResponseTimeMs,
|
||||
timestamp = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error processing AI question");
|
||||
return StatusCode(500, new { error = "An error occurred while processing your question" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a complex chat request with multiple messages to the AI
|
||||
/// </summary>
|
||||
/// <param name="request">Extended chat request with deviceId</param>
|
||||
/// <returns>AI chat response</returns>
|
||||
[HttpPost("chat")]
|
||||
public async Task<IActionResult> Chat([FromBody] ExtendedChatRequest request)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
if (request.Messages == null || !request.Messages.Any())
|
||||
{
|
||||
return BadRequest(new { error = "At least one message is required" });
|
||||
}
|
||||
|
||||
logger.LogInformation("Processing AI chat with {MessageCount} messages", request.Messages.Count);
|
||||
|
||||
var chatRequest = new ChatRequest
|
||||
{
|
||||
Messages = request.Messages,
|
||||
Model = request.Model,
|
||||
Temperature = request.Temperature,
|
||||
MaxTokens = request.MaxTokens
|
||||
};
|
||||
|
||||
var response = await deepSeekService.AskAsync(chatRequest);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
if (response == null || response.Choices == null || !response.Choices.Any())
|
||||
{
|
||||
return StatusCode(500, new { error = "No response received from AI" });
|
||||
}
|
||||
|
||||
// Extract question and answer
|
||||
var userMessage = request.Messages.LastOrDefault(m => m.Role == "user");
|
||||
var question = userMessage?.Content ?? "Complex chat";
|
||||
var answer = response.Choices.FirstOrDefault()?.Message?.Content ?? "";
|
||||
|
||||
// Save to database
|
||||
var aiQuery = new AIQuery
|
||||
{
|
||||
Question = question,
|
||||
Answer = answer,
|
||||
DeviceId = request.DeviceId,
|
||||
UserId = request.UserId,
|
||||
Model = response.Model,
|
||||
Temperature = request.Temperature,
|
||||
PromptTokens = response.Usage?.PromptTokens ?? 0,
|
||||
CompletionTokens = response.Usage?.CompletionTokens ?? 0,
|
||||
TotalTokens = response.Usage?.TotalTokens ?? 0,
|
||||
ResponseTimeMs = stopwatch.ElapsedMilliseconds
|
||||
};
|
||||
|
||||
await aiQueryService.SaveQueryAsync(aiQuery);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error processing AI chat");
|
||||
return StatusCode(500, new { error = "An error occurred while processing your chat request" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get suggestions for smart home automation based on device data
|
||||
/// </summary>
|
||||
/// <param name="request">Device context request</param>
|
||||
/// <returns>AI suggestions</returns>
|
||||
[HttpPost("suggest")]
|
||||
public async Task<IActionResult> GetSuggestions([FromBody] SuggestionRequest request)
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
var systemPrompt = @"شما یک مشاور هوشمند خانه هوشمند هستید. بر اساس دادههای دستگاههای IoT، پیشنهادهای عملی و مفید برای بهینهسازی مصرف انرژی، راحتی و امنیت ارائه دهید. پاسخ را به زبان فارسی و به صورت خلاصه و کاربردی بنویسید.";
|
||||
|
||||
var question = $@"وضعیت فعلی دستگاههای خانه هوشمند:
|
||||
{request.DeviceContext}
|
||||
|
||||
لطفاً پیشنهادات خود را برای بهبود وضعیت ارائه دهید.";
|
||||
|
||||
var messages = new List<ChatMessage>
|
||||
{
|
||||
new ChatMessage { Role = "system", Content = systemPrompt },
|
||||
new ChatMessage { Role = "user", Content = question }
|
||||
};
|
||||
|
||||
var chatRequest = new ChatRequest { Messages = messages };
|
||||
var chatResponse = await deepSeekService.AskAsync(chatRequest);
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
if (chatResponse == null || chatResponse.Choices == null || !chatResponse.Choices.Any())
|
||||
{
|
||||
return StatusCode(500, new { error = "No suggestions received from AI" });
|
||||
}
|
||||
|
||||
var answer = chatResponse.Choices.FirstOrDefault()?.Message?.Content ?? "";
|
||||
|
||||
// Save to database
|
||||
var aiQuery = new AIQuery
|
||||
{
|
||||
Question = question,
|
||||
Answer = answer,
|
||||
DeviceId = request.DeviceId,
|
||||
UserId = request.UserId,
|
||||
Model = chatResponse.Model,
|
||||
PromptTokens = chatResponse.Usage?.PromptTokens ?? 0,
|
||||
CompletionTokens = chatResponse.Usage?.CompletionTokens ?? 0,
|
||||
TotalTokens = chatResponse.Usage?.TotalTokens ?? 0,
|
||||
ResponseTimeMs = stopwatch.ElapsedMilliseconds
|
||||
};
|
||||
|
||||
await aiQueryService.SaveQueryAsync(aiQuery);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
suggestions = answer,
|
||||
deviceId = request.DeviceId,
|
||||
tokens = new
|
||||
{
|
||||
prompt = aiQuery.PromptTokens,
|
||||
completion = aiQuery.CompletionTokens,
|
||||
total = aiQuery.TotalTokens
|
||||
},
|
||||
responseTimeMs = aiQuery.ResponseTimeMs,
|
||||
timestamp = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error getting AI suggestions");
|
||||
return StatusCode(500, new { error = "An error occurred while getting suggestions" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get AI query history for a device
|
||||
/// </summary>
|
||||
[HttpGet("history/device/{deviceId}")]
|
||||
public async Task<IActionResult> GetDeviceHistory(int deviceId, [FromQuery] int take = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
var queries = await aiQueryService.GetDeviceQueriesAsync(deviceId, take);
|
||||
var totalTokens = await aiQueryService.GetDeviceTotalTokensAsync(deviceId);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
queries = queries.Select(q => new
|
||||
{
|
||||
q.Id,
|
||||
q.Question,
|
||||
q.Answer,
|
||||
q.TotalTokens,
|
||||
q.PromptTokens,
|
||||
q.CompletionTokens,
|
||||
q.Model,
|
||||
q.ResponseTimeMs,
|
||||
q.CreatedAt
|
||||
}),
|
||||
totalTokens
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error getting device history");
|
||||
return StatusCode(500, new { error = "An error occurred" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get AI query statistics
|
||||
/// </summary>
|
||||
[HttpGet("stats")]
|
||||
public async Task<IActionResult> GetStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await aiQueryService.GetStatsAsync();
|
||||
return Ok(stats);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error getting stats");
|
||||
return StatusCode(500, new { error = "An error occurred" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple question request model
|
||||
/// </summary>
|
||||
public class SimpleQuestionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The question to ask the AI
|
||||
/// </summary>
|
||||
public required string Question { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional system prompt to set context for the AI
|
||||
/// </summary>
|
||||
public string? SystemPrompt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional device ID to associate with this query
|
||||
/// </summary>
|
||||
public int? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional user ID to associate with this query
|
||||
/// </summary>
|
||||
public int? UserId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extended chat request with device tracking
|
||||
/// </summary>
|
||||
public class ExtendedChatRequest
|
||||
{
|
||||
public required List<ChatMessage> Messages { get; set; }
|
||||
public string Model { get; set; } = "deepseek-chat";
|
||||
public double? Temperature { get; set; }
|
||||
public int? MaxTokens { get; set; }
|
||||
public int? DeviceId { get; set; }
|
||||
public int? UserId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suggestion request for smart home automation
|
||||
/// </summary>
|
||||
public class SuggestionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Context about devices and their current state
|
||||
/// </summary>
|
||||
public required string DeviceContext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Device ID for this suggestion request
|
||||
/// </summary>
|
||||
public int? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User ID for this suggestion request
|
||||
/// </summary>
|
||||
public int? UserId { get; set; }
|
||||
}
|
||||
|
||||
85
src/GreenHome.Api/Controllers/AlertConditionsController.cs
Normal file
85
src/GreenHome.Api/Controllers/AlertConditionsController.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using GreenHome.Application;
|
||||
|
||||
namespace GreenHome.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AlertConditionsController : ControllerBase
|
||||
{
|
||||
private readonly IAlertConditionService alertConditionService;
|
||||
|
||||
public AlertConditionsController(IAlertConditionService alertConditionService)
|
||||
{
|
||||
this.alertConditionService = alertConditionService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت تمام شرایط هشدار یک دستگاه
|
||||
/// </summary>
|
||||
[HttpGet("device/{deviceId}")]
|
||||
public async Task<ActionResult<IReadOnlyList<AlertConditionDto>>> GetByDeviceId(int deviceId, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await alertConditionService.GetByDeviceIdAsync(deviceId, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت یک شرط هشدار با ID
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<AlertConditionDto>> GetById(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await alertConditionService.GetByIdAsync(id, cancellationToken);
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ایجاد شرط هشدار جدید
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<int>> Create(CreateAlertConditionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var id = await alertConditionService.CreateAsync(request, cancellationToken);
|
||||
return Ok(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// بهروزرسانی شرط هشدار
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
public async Task<ActionResult> Update(UpdateAlertConditionRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
await alertConditionService.UpdateAsync(request, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// حذف شرط هشدار
|
||||
/// </summary>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<ActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
await alertConditionService.DeleteAsync(id, cancellationToken);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// فعال/غیرفعال کردن شرط هشدار
|
||||
/// </summary>
|
||||
[HttpPatch("{id}/toggle")]
|
||||
public async Task<ActionResult> ToggleEnabled(int id, [FromBody] bool isEnabled, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await alertConditionService.ToggleEnabledAsync(id, isEnabled, cancellationToken);
|
||||
if (!result)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
77
src/GreenHome.Api/Controllers/DailyReportController.cs
Normal file
77
src/GreenHome.Api/Controllers/DailyReportController.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using GreenHome.Application;
|
||||
|
||||
namespace GreenHome.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DailyReportController : ControllerBase
|
||||
{
|
||||
private readonly IDailyReportService _dailyReportService;
|
||||
private readonly ILogger<DailyReportController> _logger;
|
||||
|
||||
public DailyReportController(
|
||||
IDailyReportService dailyReportService,
|
||||
ILogger<DailyReportController> logger)
|
||||
{
|
||||
_dailyReportService = dailyReportService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// دریافت یا ایجاد گزارش تحلیل روزانه گلخانه
|
||||
/// </summary>
|
||||
/// <param name="deviceId">شناسه دستگاه</param>
|
||||
/// <param name="persianDate">تاریخ شمسی به فرمت yyyy/MM/dd</param>
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <returns>گزارش تحلیل روزانه</returns>
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<DailyReportResponse>> GetDailyReport(
|
||||
[FromQuery] int deviceId,
|
||||
[FromQuery] string persianDate,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (deviceId <= 0)
|
||||
{
|
||||
return BadRequest(new { error = "شناسه دستگاه نامعتبر است" });
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(persianDate))
|
||||
{
|
||||
return BadRequest(new { error = "تاریخ نباید خالی باشد" });
|
||||
}
|
||||
|
||||
var request = new DailyReportRequest
|
||||
{
|
||||
DeviceId = deviceId,
|
||||
PersianDate = persianDate.Trim()
|
||||
};
|
||||
|
||||
var result = await _dailyReportService.GetOrCreateDailyReportAsync(request, cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"گزارش روزانه برای دستگاه {DeviceId} و تاریخ {Date} با موفقیت برگشت داده شد (FromCache: {FromCache})",
|
||||
deviceId, persianDate, result.FromCache);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "درخواست نامعتبر برای دستگاه {DeviceId} و تاریخ {Date}", deviceId, persianDate);
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "خطا در پردازش گزارش روزانه برای دستگاه {DeviceId} و تاریخ {Date}", deviceId, persianDate);
|
||||
return NotFound(new { error = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "خطای سرور در دریافت گزارش روزانه برای دستگاه {DeviceId} و تاریخ {Date}", deviceId, persianDate);
|
||||
return StatusCode(500, new { error = "خطای سرور در پردازش درخواست" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user