All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Added Role to AuthResponse DTO and all auth endpoints - Fixed null config handling in MistralConnector, TtsService, VoskService, MistralService - Fixed BaseAddress setup in MistralConnector to work without API key - Reverted seed data to use hardcoded bcrypt hashes (compatible with PasswordHasher) - Added integration tests for StoryController - Added unit tests for MistralConnector - Updated frontend AuthResponse type to include role Fixes admin redirect to /, story generation null reference, and Docker build failures. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
172 lines
6 KiB
C#
172 lines
6 KiB
C#
using GermanApp.Application.Models;
|
|
using GermanApp.Domain.Interfaces;
|
|
using GermanApp.Infrastructure.Configuration;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace GermanApp.Application.Services;
|
|
|
|
/// <summary>
|
|
/// Application service for Mistral AI text generation.
|
|
/// This is part of the Application layer.
|
|
/// </summary>
|
|
public class MistralService : IMistralService
|
|
{
|
|
private readonly IMistralConnector _connector;
|
|
private readonly MistralConfig _config;
|
|
|
|
public MistralService(
|
|
IMistralConnector connector,
|
|
IOptions<MistralConfig> config)
|
|
{
|
|
_connector = connector;
|
|
_config = config.Value ?? new MistralConfig();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates text from a prompt using Mistral API.
|
|
/// </summary>
|
|
public virtual async Task<string> GenerateTextAsync(
|
|
string prompt,
|
|
string? model = null,
|
|
float temperature = 0.7f,
|
|
int? maxTokens = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
model ??= _config.DefaultModel;
|
|
maxTokens ??= 500;
|
|
|
|
var request = new MistralRequest
|
|
{
|
|
Model = model,
|
|
Prompt = prompt,
|
|
Temperature = temperature,
|
|
MaxTokens = maxTokens.Value
|
|
};
|
|
|
|
var response = await _connector.CompleteAsync(request, cancellationToken);
|
|
|
|
if (response.Choices == null || response.Choices.Count == 0)
|
|
throw new InvalidOperationException("No choices returned from Mistral API");
|
|
|
|
return response.Choices[0].Text ?? string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates text from chat messages using Mistral API.
|
|
/// </summary>
|
|
public virtual async Task<string> GenerateChatAsync(
|
|
IReadOnlyList<(string role, string content)> messages,
|
|
string? model = null,
|
|
float temperature = 0.7f,
|
|
int? maxTokens = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
model ??= _config.DefaultModel;
|
|
maxTokens ??= 500;
|
|
|
|
var chatMessages = messages
|
|
.Select(m => new MistralMessage { Role = m.role, Content = m.content })
|
|
.ToList();
|
|
|
|
var request = new MistralChatRequest
|
|
{
|
|
Model = model,
|
|
Messages = chatMessages,
|
|
Temperature = temperature,
|
|
MaxTokens = maxTokens.Value
|
|
};
|
|
|
|
var response = await _connector.ChatAsync(request, cancellationToken);
|
|
|
|
if (response.Choices == null || response.Choices.Count == 0)
|
|
throw new InvalidOperationException("No choices returned from Mistral API");
|
|
|
|
return response.Choices[0].Message?.Content ?? string.Empty;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a story based on lesson context and vocabulary.
|
|
/// </summary>
|
|
public virtual async Task<string> GenerateStoryAsync(
|
|
string level,
|
|
string topic,
|
|
IReadOnlyList<string> vocabularyWords,
|
|
int length = 200,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var prompt = BuildStoryPrompt(level, topic, vocabularyWords, length);
|
|
|
|
return await GenerateTextAsync(
|
|
prompt,
|
|
model: _config.DefaultModel,
|
|
temperature: 0.8f,
|
|
maxTokens: 1000,
|
|
cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Provides feedback on user writing.
|
|
/// </summary>
|
|
public virtual async Task<string> GenerateWritingFeedbackAsync(
|
|
string userText,
|
|
string level,
|
|
string? prompt = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var messages = new List<(string role, string content)>
|
|
{
|
|
("system", BuildFeedbackSystemPrompt(level)),
|
|
("user", prompt ?? "Please provide feedback on the following German text:"),
|
|
("user", userText)
|
|
};
|
|
|
|
return await GenerateChatAsync(
|
|
messages,
|
|
model: _config.DefaultModel,
|
|
temperature: 0.3f, // Lower temperature for more deterministic feedback
|
|
maxTokens: 800,
|
|
cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests the Mistral API connection.
|
|
/// </summary>
|
|
public virtual async Task<bool> TestConnectionAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
// Send a simple test prompt
|
|
var testPrompt = "Say 'test successful'";
|
|
var result = await GenerateTextAsync(
|
|
testPrompt,
|
|
maxTokens: 10,
|
|
cancellationToken: cancellationToken);
|
|
|
|
return result.Contains("test successful", System.StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a prompt for story generation.
|
|
/// </summary>
|
|
private string BuildStoryPrompt(string level, string topic, IReadOnlyList<string> vocabularyWords, int length)
|
|
{
|
|
var vocabularyList = string.Join(", ", vocabularyWords);
|
|
|
|
return $"You are a helpful German language teacher. Create an engaging story for a {level} level learner.\n\nRequirements:\n- Topic: {topic}\n- Length: approximately {length} words\n- Use these German vocabulary words: {vocabularyList}\n- Write in German language\n- Appropriate for A1-A2 learners (simple sentences, common vocabulary)\n- Include dialogue\n- End with a question for the reader\n\nWrite only the story text, no additional explanation or formatting."
|
|
.Replace("\n\n", "\n");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a system prompt for writing feedback.
|
|
/// </summary>
|
|
private string BuildFeedbackSystemPrompt(string level)
|
|
{
|
|
return $"You are a helpful German language tutor. Provide constructive feedback on the user's German writing.\n\nGuidelines:\n- Respond in English\n- First, identify and correct any grammar mistakes\n- Then, provide suggestions for improvement\n- Finally, give encouragement\n- Be specific and helpful\n- Keep feedback concise (3-5 sentences)\n- Level: {level}"
|
|
.Replace("\n\n", "\n");
|
|
}
|
|
}
|