feat: добавил изменение промта для админа
Backend CI / build-and-test (push) Failing after 11m26s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 14m2s
Frontend CI / build-and-check (push) Failing after 19m55s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m7s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m59s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 15m0s

This commit is contained in:
2026-05-21 21:58:33 +03:00
parent 27a2811806
commit 935e4ed37a
22 changed files with 1880 additions and 15 deletions
@@ -20,6 +20,7 @@ public class AppDbContext : DbContext
public DbSet<CourseTag> CourseTags { get; set; } = null!;
public DbSet<LectureEnrollment> LectureEnrollments { get; set; } = null!;
public DbSet<Review> Reviews { get; set; } = null!;
public DbSet<ReviewPromptSetting> ReviewPromptSettings { get; set; } = null!;
public DbSet<Achievement> Achievements { get; set; } = null!;
public DbSet<UserAchievement> UserAchievements { get; set; } = null!;
public DbSet<CoinTransaction> CoinTransactions { get; set; } = null!;
@@ -0,0 +1,27 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using UniVerse.Domain.Entities;
namespace UniVerse.Infrastructure.Data.Configurations;
public class ReviewPromptSettingConfiguration : IEntityTypeConfiguration<ReviewPromptSetting>
{
public void Configure(EntityTypeBuilder<ReviewPromptSetting> builder)
{
builder.ToTable("review_prompt_settings");
builder.HasKey(setting => setting.Id);
builder.Property(setting => setting.Id)
.HasColumnName("id")
.ValueGeneratedNever();
builder.Property(setting => setting.Prompt)
.HasColumnName("prompt")
.IsRequired();
builder.Property(setting => setting.CreatedAt)
.HasColumnName("created_at")
.HasDefaultValueSql("NOW()");
builder.Property(setting => setting.UpdatedAt)
.HasColumnName("updated_at")
.HasDefaultValueSql("NOW()");
}
}
@@ -1,9 +1,9 @@
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using UniVerse.Application.Interfaces;
using UniVerse.Application.Prompts;
namespace UniVerse.Infrastructure.ExternalServices;
@@ -11,25 +11,25 @@ public class LlmClient : ILlmClient
{
private readonly HttpClient _http;
private readonly IConfiguration _config;
private readonly IReviewPromptService _reviewPrompts;
private readonly ILogger<LlmClient> _logger;
public LlmClient(HttpClient http, IConfiguration config, ILogger<LlmClient> logger)
public LlmClient(
HttpClient http,
IConfiguration config,
IReviewPromptService reviewPrompts,
ILogger<LlmClient> logger)
{
_http = http; _config = config; _logger = logger;
_http = http;
_config = config;
_reviewPrompts = reviewPrompts;
_logger = logger;
}
public async Task<LlmReviewAnalysis> AnalyzeReviewAsync(string reviewText, string lectureContext)
{
var prompt = $"""
Analyze the following student review of a lecture. Return a JSON object with:
- quality_score: float 0-1 indicating review quality
- sentiment: "Positive", "Neutral", or "Negative"
- tags: array of relevant topic tags
- is_informative: boolean indicating if the review is informative
Lecture context: {lectureContext}
Review text: {reviewText}
""";
var promptSetting = await _reviewPrompts.GetAsync();
var prompt = ReviewPromptTemplate.Render(promptSetting.Prompt, reviewText, lectureContext);
var request = new
{
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace UniVerse.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class ReviewPromptSettings : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "review_prompt_settings",
columns: table => new
{
id = table.Column<int>(type: "integer", nullable: false),
prompt = table.Column<string>(type: "text", nullable: false),
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "NOW()"),
updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "NOW()")
},
constraints: table =>
{
table.PrimaryKey("PK_review_prompt_settings", x => x.id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "review_prompt_settings");
}
}
}
@@ -563,6 +563,34 @@ namespace UniVerse.Infrastructure.Migrations
b.ToTable("reviews", (string)null);
});
modelBuilder.Entity("UniVerse.Domain.Entities.ReviewPromptSetting", b =>
{
b.Property<int>("Id")
.HasColumnType("integer")
.HasColumnName("id");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("NOW()");
b.Property<string>("Prompt")
.IsRequired()
.HasColumnType("text")
.HasColumnName("prompt");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at")
.HasDefaultValueSql("NOW()");
b.HasKey("Id");
b.ToTable("review_prompt_settings", (string)null);
});
modelBuilder.Entity("UniVerse.Domain.Entities.StudentProfile", b =>
{
b.Property<int>("Id")
@@ -0,0 +1,70 @@
using Microsoft.EntityFrameworkCore;
using UniVerse.Application.DTOs.Reviews;
using UniVerse.Application.Interfaces;
using UniVerse.Application.Prompts;
using UniVerse.Domain.Entities;
using UniVerse.Domain.Exceptions;
using UniVerse.Infrastructure.Data;
namespace UniVerse.Infrastructure.Services;
public class ReviewPromptService : IReviewPromptService
{
private readonly AppDbContext _db;
public ReviewPromptService(AppDbContext db)
{
_db = db;
}
public async Task<ReviewPromptDto> GetAsync()
{
var setting = await _db.ReviewPromptSettings
.AsNoTracking()
.FirstOrDefaultAsync(s => s.Id == ReviewPromptSetting.SingletonId);
return setting is null
? new ReviewPromptDto(ReviewPromptTemplate.Default, null)
: new ReviewPromptDto(setting.Prompt, setting.UpdatedAt);
}
public async Task<ReviewPromptDto> UpdateAsync(UpdateReviewPromptRequest request)
{
ValidatePrompt(request.Prompt);
var now = DateTime.UtcNow;
var setting = await _db.ReviewPromptSettings
.FirstOrDefaultAsync(s => s.Id == ReviewPromptSetting.SingletonId);
if (setting is null)
{
setting = new ReviewPromptSetting
{
Id = ReviewPromptSetting.SingletonId,
Prompt = request.Prompt,
CreatedAt = now,
UpdatedAt = now
};
_db.ReviewPromptSettings.Add(setting);
}
else
{
setting.Prompt = request.Prompt;
setting.UpdatedAt = now;
}
await _db.SaveChangesAsync();
return new ReviewPromptDto(setting.Prompt, setting.UpdatedAt);
}
private static void ValidatePrompt(string prompt)
{
if (string.IsNullOrWhiteSpace(prompt))
throw new BadRequestException("Prompt must not be empty.");
if (!ReviewPromptTemplate.HasRequiredPlaceholders(prompt))
throw new BadRequestException(
$"Prompt must contain {ReviewPromptTemplate.LectureContextPlaceholder} and {ReviewPromptTemplate.ReviewTextPlaceholder} placeholders.");
}
}