All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Add Domain interfaces: IMistralService, IVoskService, ITtsService - Add Configuration classes: VoskConfig, CoquiConfig - Add Application service: MistralService (text generation with Mistral) - Add Infrastructure services: VoskService (speech recognition), TtsService (TTS) - Add Presentation controllers: MistralController, SpeechController, TtsController - Fix MistralService to use correct IMistralConnector methods (CompleteAsync, ChatAsync) - Fix TtsController to use record constructor syntax - Fix TtsService Task.FromResult type specification - Fix Program.cs service registration and remove merge conflict markers - Update docs/features/ai-services.md with progress (Phases 0-4 complete) - Update docs/ROADMAP.md with AI Services status and test metrics (296 tests) Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
193 lines
5.6 KiB
C#
193 lines
5.6 KiB
C#
using GermanApp.Domain.Interfaces;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace GermanApp.Presentation.Controllers;
|
|
|
|
/// <summary>
|
|
/// API controller for text-to-speech using Coqui TTS.
|
|
/// This is part of the Presentation layer.
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class TtsController : ControllerBase
|
|
{
|
|
private readonly ITtsService _ttsService;
|
|
|
|
public TtsController(ITtsService ttsService)
|
|
{
|
|
_ttsService = ttsService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates audio from text.
|
|
/// </summary>
|
|
/// <param name="request">TTS generation request</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>Audio file as bytes</returns>
|
|
[HttpPost("generate")]
|
|
[RequestSizeLimit(5_000_000)] // 5MB limit for response
|
|
public async Task<IActionResult> GenerateAudio(
|
|
[FromBody] TtsGenerationRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.Text))
|
|
return BadRequest("Text is required");
|
|
|
|
try
|
|
{
|
|
var audioBytes = await _ttsService.GenerateAudioAsync(
|
|
request.Text,
|
|
request.Speaker,
|
|
request.Language,
|
|
cancellationToken);
|
|
|
|
// Return audio as WAV or specified format
|
|
var contentType = GetContentType(request.Format);
|
|
return File(audioBytes, contentType);
|
|
}
|
|
catch (TimeoutException ex)
|
|
{
|
|
return StatusCode(504, new { Error = ex.Message });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new { Error = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates audio and returns a file URL.
|
|
/// </summary>
|
|
/// <param name="request">TTS generation request with filename</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>Audio file URL</returns>
|
|
[HttpPost("generate-file")]
|
|
public async Task<IActionResult> GenerateAudioToFile(
|
|
[FromBody] TtsFileGenerationRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(request.Text))
|
|
return BadRequest("Text is required");
|
|
if (string.IsNullOrWhiteSpace(request.Filename))
|
|
return BadRequest("Filename is required");
|
|
|
|
try
|
|
{
|
|
var outputPath = Path.Combine("audio", "tts", request.Filename);
|
|
await _ttsService.GenerateAudioToFileAsync(
|
|
request.Text,
|
|
outputPath,
|
|
request.Speaker,
|
|
request.Language,
|
|
cancellationToken);
|
|
|
|
return Ok(new TtsFileResponse(
|
|
Url: $"/audio/tts/{request.Filename}",
|
|
Filename: request.Filename));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new { Error = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests the TTS service.
|
|
/// </summary>
|
|
/// <returns>Health check result</returns>
|
|
[HttpGet("health")]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> HealthCheck(CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var isHealthy = await _ttsService.TestModelAsync(cancellationToken);
|
|
return Ok(new { Healthy = isHealthy });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new { Error = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets information about the current TTS model.
|
|
/// </summary>
|
|
/// <returns>Model information</returns>
|
|
[HttpGet("model-info")]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> GetModelInfo(CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var (modelName, modelPath) = await _ttsService.GetModelInfoAsync();
|
|
return Ok(new { ModelName = modelName, ModelPath = modelPath });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new { Error = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets list of available voices/speakers.
|
|
/// </summary>
|
|
/// <returns>List of speaker IDs</returns>
|
|
[HttpGet("speakers")]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> GetSpeakers(CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var speakers = await _ttsService.GetAvailableSpeakersAsync(cancellationToken);
|
|
return Ok(new { Speakers = speakers });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new { Error = ex.Message });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the content type for the specified audio format.
|
|
/// </summary>
|
|
private string GetContentType(string? format)
|
|
{
|
|
format = format?.ToLower() ?? "wav";
|
|
return format switch
|
|
{
|
|
"wav" => "audio/wav",
|
|
"mp3" => "audio/mpeg",
|
|
"ogg" => "audio/ogg",
|
|
"flac" => "audio/flac",
|
|
_ => "audio/wav"
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request DTO for TTS generation.
|
|
/// </summary>
|
|
public record TtsGenerationRequest(
|
|
string Text,
|
|
string? Speaker = null,
|
|
string Language = "de",
|
|
string? Format = null);
|
|
|
|
/// <summary>
|
|
/// Request DTO for TTS file generation.
|
|
/// </summary>
|
|
public record TtsFileGenerationRequest(
|
|
string Text,
|
|
string Filename,
|
|
string? Speaker = null,
|
|
string Language = "de");
|
|
|
|
/// <summary>
|
|
/// Response DTO for TTS file generation.
|
|
/// </summary>
|
|
public record TtsFileResponse(
|
|
string Url,
|
|
string Filename);
|