From 89b242f97e87b3da29f8ac1c225897c3dd5e9d79 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=88=B4=E5=BB=B7=E6=9D=B0?= <2200150071@qq.com>
Date: Tue, 5 Aug 2025 09:16:01 +0800
Subject: [PATCH 01/31] =?UTF-8?q?=E5=90=8E=E7=AB=AF=E6=A1=86=E6=9E=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/RAG.Api/Program.cs | 44 ++++++++++++++++++
.../RAG.Api/Properties/launchSettings.json | 41 +++++++++++++++++
backend/RAG.Api/RAG.Api.csproj | 27 +++++++++++
backend/RAG.Api/RAG.Api.http | 6 +++
backend/RAG.Api/appsettings.json | 9 ++++
backend/RAG.Application/Class1.cs | 6 +++
.../RAG.Application/RAG.Application.csproj | 19 ++++++++
backend/RAG.Domain/Class1.cs | 6 +++
backend/RAG.Domain/RAG.Domain.csproj | 9 ++++
backend/RAG.Infrastructure/Class1.cs | 6 +++
.../RAG.Infrastructure.csproj | 22 +++++++++
backend/RAG.Tests/RAG.Tests.csproj | 29 ++++++++++++
backend/RAG.Tests/UnitTest1.cs | 10 ++++
backend/RAG.sln | 46 +++++++++++++++++++
backend/txt.txt | 0
frontend/txt.txt | 0
16 files changed, 280 insertions(+)
create mode 100644 backend/RAG.Api/Program.cs
create mode 100644 backend/RAG.Api/Properties/launchSettings.json
create mode 100644 backend/RAG.Api/RAG.Api.csproj
create mode 100644 backend/RAG.Api/RAG.Api.http
create mode 100644 backend/RAG.Api/appsettings.json
create mode 100644 backend/RAG.Application/Class1.cs
create mode 100644 backend/RAG.Application/RAG.Application.csproj
create mode 100644 backend/RAG.Domain/Class1.cs
create mode 100644 backend/RAG.Domain/RAG.Domain.csproj
create mode 100644 backend/RAG.Infrastructure/Class1.cs
create mode 100644 backend/RAG.Infrastructure/RAG.Infrastructure.csproj
create mode 100644 backend/RAG.Tests/RAG.Tests.csproj
create mode 100644 backend/RAG.Tests/UnitTest1.cs
create mode 100644 backend/RAG.sln
delete mode 100644 backend/txt.txt
delete mode 100644 frontend/txt.txt
diff --git a/backend/RAG.Api/Program.cs b/backend/RAG.Api/Program.cs
new file mode 100644
index 0000000..00ff539
--- /dev/null
+++ b/backend/RAG.Api/Program.cs
@@ -0,0 +1,44 @@
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+var summaries = new[]
+{
+ "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+};
+
+app.MapGet("/weatherforecast", () =>
+{
+ var forecast = Enumerable.Range(1, 5).Select(index =>
+ new WeatherForecast
+ (
+ DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+ Random.Shared.Next(-20, 55),
+ summaries[Random.Shared.Next(summaries.Length)]
+ ))
+ .ToArray();
+ return forecast;
+})
+.WithName("GetWeatherForecast")
+.WithOpenApi();
+
+app.Run();
+
+record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
+{
+ public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+}
diff --git a/backend/RAG.Api/Properties/launchSettings.json b/backend/RAG.Api/Properties/launchSettings.json
new file mode 100644
index 0000000..e02ba24
--- /dev/null
+++ b/backend/RAG.Api/Properties/launchSettings.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:27975",
+ "sslPort": 44389
+ }
+ },
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "http://localhost:5128",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:7158;http://localhost:5128",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/backend/RAG.Api/RAG.Api.csproj b/backend/RAG.Api/RAG.Api.csproj
new file mode 100644
index 0000000..2db79c4
--- /dev/null
+++ b/backend/RAG.Api/RAG.Api.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+
+
diff --git a/backend/RAG.Api/RAG.Api.http b/backend/RAG.Api/RAG.Api.http
new file mode 100644
index 0000000..28f4958
--- /dev/null
+++ b/backend/RAG.Api/RAG.Api.http
@@ -0,0 +1,6 @@
+@RAG.Api_HostAddress = http://localhost:5128
+
+GET {{RAG.Api_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/backend/RAG.Api/appsettings.json b/backend/RAG.Api/appsettings.json
new file mode 100644
index 0000000..10f68b8
--- /dev/null
+++ b/backend/RAG.Api/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/backend/RAG.Application/Class1.cs b/backend/RAG.Application/Class1.cs
new file mode 100644
index 0000000..973f93a
--- /dev/null
+++ b/backend/RAG.Application/Class1.cs
@@ -0,0 +1,6 @@
+namespace RAG.Application;
+
+public class Class1
+{
+
+}
diff --git a/backend/RAG.Application/RAG.Application.csproj b/backend/RAG.Application/RAG.Application.csproj
new file mode 100644
index 0000000..7d7b78b
--- /dev/null
+++ b/backend/RAG.Application/RAG.Application.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/backend/RAG.Domain/Class1.cs b/backend/RAG.Domain/Class1.cs
new file mode 100644
index 0000000..43b53cf
--- /dev/null
+++ b/backend/RAG.Domain/Class1.cs
@@ -0,0 +1,6 @@
+namespace RAG.Domain;
+
+public class Class1
+{
+
+}
diff --git a/backend/RAG.Domain/RAG.Domain.csproj b/backend/RAG.Domain/RAG.Domain.csproj
new file mode 100644
index 0000000..fa71b7a
--- /dev/null
+++ b/backend/RAG.Domain/RAG.Domain.csproj
@@ -0,0 +1,9 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/backend/RAG.Infrastructure/Class1.cs b/backend/RAG.Infrastructure/Class1.cs
new file mode 100644
index 0000000..cfa7d06
--- /dev/null
+++ b/backend/RAG.Infrastructure/Class1.cs
@@ -0,0 +1,6 @@
+namespace RAG.Infrastructure;
+
+public class Class1
+{
+
+}
diff --git a/backend/RAG.Infrastructure/RAG.Infrastructure.csproj b/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
new file mode 100644
index 0000000..bcf0485
--- /dev/null
+++ b/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
diff --git a/backend/RAG.Tests/RAG.Tests.csproj b/backend/RAG.Tests/RAG.Tests.csproj
new file mode 100644
index 0000000..5d9e2d3
--- /dev/null
+++ b/backend/RAG.Tests/RAG.Tests.csproj
@@ -0,0 +1,29 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/backend/RAG.Tests/UnitTest1.cs b/backend/RAG.Tests/UnitTest1.cs
new file mode 100644
index 0000000..0c054e8
--- /dev/null
+++ b/backend/RAG.Tests/UnitTest1.cs
@@ -0,0 +1,10 @@
+namespace RAG.Tests;
+
+public class UnitTest1
+{
+ [Fact]
+ public void Test1()
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.sln b/backend/RAG.sln
new file mode 100644
index 0000000..f53c424
--- /dev/null
+++ b/backend/RAG.sln
@@ -0,0 +1,46 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Api", "RAG.Api\RAG.Api.csproj", "{3DAC1A57-A784-43CD-B665-68896913F082}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Application", "RAG.Application\RAG.Application.csproj", "{FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Domain", "RAG.Domain\RAG.Domain.csproj", "{2937A929-EB24-43B6-8BD9-EEA32B895C9C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Infrastructure", "RAG.Infrastructure\RAG.Infrastructure.csproj", "{46C0164B-CF85-4CA1-95A6-16DDC05476CB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Tests", "RAG.Tests\RAG.Tests.csproj", "{994D11A9-A40A-4FC2-A7FB-BB366380EEF2}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3DAC1A57-A784-43CD-B665-68896913F082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3DAC1A57-A784-43CD-B665-68896913F082}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3DAC1A57-A784-43CD-B665-68896913F082}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3DAC1A57-A784-43CD-B665-68896913F082}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/backend/txt.txt b/backend/txt.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/frontend/txt.txt b/frontend/txt.txt
deleted file mode 100644
index e69de29..0000000
--
Gitee
From c3468a958a004568ddecc3f9e201fae7ad0d43de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B0=A2=E9=9B=A8=E6=99=B4?= <1207713896@qq.com>
Date: Tue, 5 Aug 2025 09:45:14 +0800
Subject: [PATCH 02/31] =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=90=8E=E7=AB=AF?=
=?UTF-8?q?=E6=9E=B6=E6=9E=84=EF=BC=8C=E5=B9=B6=E5=AE=8C=E6=88=90=E9=A1=B9?=
=?UTF-8?q?=E7=9B=AE=E4=B9=8B=E9=97=B4=E7=9A=84=E4=BE=9D=E8=B5=96=EF=BC=8C?=
=?UTF-8?q?=E5=B7=B2=E5=AE=8C=E6=88=90=E5=AE=9E=E4=BD=93=E7=B1=BB=E5=88=9B?=
=?UTF-8?q?=E5=BB=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/RAG.Api/Controllers/ChatController.cs | 89 +++++++++++
.../RAG.Api/Controllers/DocumentController.cs | 63 ++++++++
backend/RAG.Api/Program.cs | 11 +-
.../RAG.Api/Properties/launchSettings.json | 28 +---
backend/RAG.Api/RAG.Api.csproj | 13 +-
backend/RAG.Api/RAG.Api.http | 2 +-
.../RAG.Application/RAG.Application.csproj | 8 +-
backend/RAG.Domain/Entities/ChatHistory.cs | 37 +++++
backend/RAG.Domain/Entities/Document.cs | 45 ++++++
backend/RAG.Domain/Entities/User.cs | 19 +++
backend/RAG.Domain/RAG.Domain.csproj | 2 +-
.../RAG.Domain/Repositories/IVectorStore.cs | 20 +++
.../Services/IDocumentProcessingService.cs | 13 ++
.../Data/ApplicationDbContext.cs | 26 ++++
.../RAG.Infrastructure.csproj | 10 +-
.../Services/DocumentProcessingService.cs | 143 ++++++++++++++++++
.../VectorStore/PgVectorStore.cs | 109 +++++++++++++
backend/RAG.Tests/RAG.Tests.csproj | 14 +-
backend/RAG.Tests/UnitTest1.cs | 4 +-
backend/RAG.sln | 98 ++++++++----
backend/txt.txt | 0
21 files changed, 658 insertions(+), 96 deletions(-)
create mode 100644 backend/RAG.Api/Controllers/ChatController.cs
create mode 100644 backend/RAG.Api/Controllers/DocumentController.cs
create mode 100644 backend/RAG.Domain/Entities/ChatHistory.cs
create mode 100644 backend/RAG.Domain/Entities/Document.cs
create mode 100644 backend/RAG.Domain/Entities/User.cs
create mode 100644 backend/RAG.Domain/Repositories/IVectorStore.cs
create mode 100644 backend/RAG.Domain/Services/IDocumentProcessingService.cs
create mode 100644 backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
create mode 100644 backend/RAG.Infrastructure/Services/DocumentProcessingService.cs
create mode 100644 backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs
create mode 100644 backend/txt.txt
diff --git a/backend/RAG.Api/Controllers/ChatController.cs b/backend/RAG.Api/Controllers/ChatController.cs
new file mode 100644
index 0000000..1723df4
--- /dev/null
+++ b/backend/RAG.Api/Controllers/ChatController.cs
@@ -0,0 +1,89 @@
+// using Microsoft.AspNetCore.Mvc;
+// using System.Threading.Tasks;
+// using RAG.Domain.Repositories;
+// using RAG.Domain.Entities;
+// using Microsoft.Extensions.Logging;
+// using System.Collections.Generic;
+// using System.Linq;
+
+// namespace RAG.Api.Controllers
+// {
+// [ApiController]
+// [Route("api/[controller]")]
+// public class ChatController : ControllerBase
+// {
+// private readonly IVectorStore _vectorStore;
+// private readonly ILogger _logger;
+
+// public ChatController(
+// IVectorStore vectorStore,
+// ILogger logger)
+// {
+// _vectorStore = vectorStore;
+// _logger = logger;
+// }
+
+// [HttpPost("query")]
+// public async Task Query([FromBody] ChatQuery query)
+// {
+// try
+// {
+// if (string.IsNullOrEmpty(query.Message))
+// {
+// return BadRequest("Message cannot be empty.");
+// }
+
+// _logger.LogInformation("Processing chat query: {Message}", query.Message);
+
+// // In a real implementation, you would generate an embedding for the query
+// // var queryEmbedding = await GenerateEmbeddingAsync(query.Message);
+
+// // For demonstration purposes, create a dummy embedding
+// var queryEmbedding = new float[1536]; // Match the vector dimension used in PgVectorStore
+
+// // Search for similar vectors
+// var results = await _vectorStore.SearchVectorsAsync(queryEmbedding, query.TopK ?? 5);
+
+// // In a real implementation, you would use these results to generate a response
+// // using a language model
+// var response = new ChatResponse
+// {
+// Query = query.Message,
+// Response = "This is a placeholder response generated based on retrieved context.",
+// References = results.Select(r => new Reference
+// {
+// Content = r.Content.Substring(0, Math.Min(100, r.Content.Length)) + "...",
+// Similarity = r.Similarity
+// }).ToList()
+// };
+
+// return Ok(response);
+// }
+// catch (Exception ex)
+// {
+// _logger.LogError(ex, "Error processing chat query");
+// return StatusCode(500, "Internal server error");
+// }
+// }
+// }
+
+// public class ChatQuery
+// {
+// public string Message { get; set; }
+// public int? TopK { get; set; }
+// public string SessionId { get; set; }
+// }
+
+// public class ChatResponse
+// {
+// public string Query { get; set; }
+// public string Response { get; set; }
+// public List References { get; set; }
+// }
+
+// public class Reference
+// {
+// public string Content { get; set; }
+// public double Similarity { get; set; }
+// }
+// }
\ No newline at end of file
diff --git a/backend/RAG.Api/Controllers/DocumentController.cs b/backend/RAG.Api/Controllers/DocumentController.cs
new file mode 100644
index 0000000..1e4540c
--- /dev/null
+++ b/backend/RAG.Api/Controllers/DocumentController.cs
@@ -0,0 +1,63 @@
+// using Microsoft.AspNetCore.Mvc;
+// using Microsoft.AspNetCore.Http;
+// using System.Threading.Tasks;
+// using RAG.Domain.Services;
+// using RAG.Domain.Entities;
+// using Microsoft.Extensions.Logging;
+
+// namespace RAG.Api.Controllers
+// {
+// [ApiController]
+// [Route("api/[controller]")]
+// public class DocumentController : ControllerBase
+// {
+// private readonly IDocumentProcessingService _documentProcessingService;
+// private readonly ILogger _logger;
+
+// public DocumentController(
+// IDocumentProcessingService documentProcessingService,
+// ILogger logger)
+// {
+// _documentProcessingService = documentProcessingService;
+// _logger = logger;
+// }
+
+// [HttpPost("upload")]
+// public async Task UploadDocument(IFormFile file)
+// {
+// try
+// {
+// if (file == null || file.Length == 0)
+// {
+// return BadRequest("No file uploaded.");
+// }
+
+// // In a real application, you would get the user ID from authentication
+// var userId = 1; // Placeholder
+
+// using (var stream = file.OpenReadStream())
+// {
+// var document = await _documentProcessingService.ProcessDocumentAsync(
+// stream,
+// file.FileName,
+// file.ContentType,
+// userId);
+
+// return Ok(new { document.Id, document.Title, document.FileType, document.CreatedAt });
+// }
+// }
+// catch (Exception ex)
+// {
+// _logger.LogError(ex, "Error uploading document");
+// return StatusCode(500, "Internal server error");
+// }
+// }
+
+// [HttpGet("supported-types")]
+// public IActionResult GetSupportedFileTypes()
+// {
+// var supportedTypes = new[] { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
+// return Ok(supportedTypes);
+// }
+// }
+// }
\ No newline at end of file
diff --git a/backend/RAG.Api/Program.cs b/backend/RAG.Api/Program.cs
index 00ff539..ee9d65d 100644
--- a/backend/RAG.Api/Program.cs
+++ b/backend/RAG.Api/Program.cs
@@ -1,17 +1,15 @@
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
-// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
+builder.Services.AddOpenApi();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
- app.UseSwagger();
- app.UseSwaggerUI();
+ app.MapOpenApi();
}
app.UseHttpsRedirection();
@@ -33,8 +31,7 @@ app.MapGet("/weatherforecast", () =>
.ToArray();
return forecast;
})
-.WithName("GetWeatherForecast")
-.WithOpenApi();
+.WithName("GetWeatherForecast");
app.Run();
diff --git a/backend/RAG.Api/Properties/launchSettings.json b/backend/RAG.Api/Properties/launchSettings.json
index e02ba24..8749fc5 100644
--- a/backend/RAG.Api/Properties/launchSettings.json
+++ b/backend/RAG.Api/Properties/launchSettings.json
@@ -1,20 +1,11 @@
{
- "$schema": "http://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "http://localhost:27975",
- "sslPort": 44389
- }
- },
+ "$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "http://localhost:5128",
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5097",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -22,17 +13,8 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "https://localhost:7158;http://localhost:5128",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "launchUrl": "swagger",
+ "launchBrowser": false,
+ "applicationUrl": "https://localhost:7117;http://localhost:5097",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
diff --git a/backend/RAG.Api/RAG.Api.csproj b/backend/RAG.Api/RAG.Api.csproj
index 2db79c4..b4c5283 100644
--- a/backend/RAG.Api/RAG.Api.csproj
+++ b/backend/RAG.Api/RAG.Api.csproj
@@ -1,22 +1,13 @@
- net8.0
+ net9.0
enable
enable
-
-
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
+
diff --git a/backend/RAG.Api/RAG.Api.http b/backend/RAG.Api/RAG.Api.http
index 28f4958..df87c00 100644
--- a/backend/RAG.Api/RAG.Api.http
+++ b/backend/RAG.Api/RAG.Api.http
@@ -1,4 +1,4 @@
-@RAG.Api_HostAddress = http://localhost:5128
+@RAG.Api_HostAddress = http://localhost:5097
GET {{RAG.Api_HostAddress}}/weatherforecast/
Accept: application/json
diff --git a/backend/RAG.Application/RAG.Application.csproj b/backend/RAG.Application/RAG.Application.csproj
index 7d7b78b..8c0b9f3 100644
--- a/backend/RAG.Application/RAG.Application.csproj
+++ b/backend/RAG.Application/RAG.Application.csproj
@@ -4,14 +4,8 @@
-
-
-
-
-
-
- net8.0
+ net9.0
enable
enable
diff --git a/backend/RAG.Domain/Entities/ChatHistory.cs b/backend/RAG.Domain/Entities/ChatHistory.cs
new file mode 100644
index 0000000..5c7c5bd
--- /dev/null
+++ b/backend/RAG.Domain/Entities/ChatHistory.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Entities
+{
+ public class ChatHistory
+ {
+ public int Id { get; set; }
+ public string SessionId { get; set; }
+ public int UserId { get; set; }
+ public User User { get; set; }
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+ public ICollection Messages { get; set; } = new List();
+ }
+
+ public class ChatMessage
+ {
+ public int Id { get; set; }
+ public int ChatHistoryId { get; set; }
+ public ChatHistory ChatHistory { get; set; }
+ public string Content { get; set; }
+ public bool IsUserMessage { get; set; }
+ public DateTime Timestamp { get; set; } = DateTime.UtcNow;
+ public ICollection References { get; set; } = new List();
+ }
+
+ public class MessageReference
+ {
+ public int Id { get; set; }
+ public int ChatMessageId { get; set; }
+ public ChatMessage ChatMessage { get; set; }
+ public int DocumentChunkId { get; set; }
+ public DocumentChunk DocumentChunk { get; set; }
+ public double RelevanceScore { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/Document.cs b/backend/RAG.Domain/Entities/Document.cs
new file mode 100644
index 0000000..771f36f
--- /dev/null
+++ b/backend/RAG.Domain/Entities/Document.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Entities
+{
+ public class Document
+ {
+ public int Id { get; set; }
+ public string Title { get; set; }
+ public string Content { get; set; }
+ public string FilePath { get; set; }
+ public string FileName { get; set; }
+ public string FileType { get; set; }
+ public long FileSize { get; set; }
+ public string Description { get; set; }
+ public string Tags { get; set; }
+ public string AccessLevel { get; set; } = "internal"; // 'internal' or 'public'
+ public string Status { get; set; } = "pending"; // 'pending', 'processing', 'completed', 'failed'
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+ public int UserId { get; set; }
+ public User User { get; set; }
+ public ICollection Chunks { get; set; } = new List();
+ }
+
+ public class DocumentChunk
+ {
+ public int Id { get; set; }
+ public int DocumentId { get; set; }
+ public Document Document { get; set; }
+ public string Content { get; set; }
+ public int Position { get; set; }
+ public int TokenCount { get; set; }
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ }
+
+ public class Vector
+ {
+ public int Id { get; set; }
+ public int ChunkId { get; set; }
+ public DocumentChunk Chunk { get; set; }
+ public float[] Embedding { get; set; }
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/User.cs b/backend/RAG.Domain/Entities/User.cs
new file mode 100644
index 0000000..b7787a3
--- /dev/null
+++ b/backend/RAG.Domain/Entities/User.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Entities
+{
+ public class User
+ {
+ public int Id { get; set; }
+ public string Username { get; set; }
+ public string Email { get; set; }
+ public string PasswordHash { get; set; }
+ public string FullName { get; set; }
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+ public bool IsActive { get; set; } = true;
+ public ICollection Documents { get; set; } = new List();
+ public ICollection ChatHistories { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/RAG.Domain.csproj b/backend/RAG.Domain/RAG.Domain.csproj
index fa71b7a..125f4c9 100644
--- a/backend/RAG.Domain/RAG.Domain.csproj
+++ b/backend/RAG.Domain/RAG.Domain.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
enable
enable
diff --git a/backend/RAG.Domain/Repositories/IVectorStore.cs b/backend/RAG.Domain/Repositories/IVectorStore.cs
new file mode 100644
index 0000000..3dfb6b4
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IVectorStore.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IVectorStore
+ {
+ Task InitializeAsync();
+ Task StoreVectorAsync(string content, float[] embedding, object metadata = null);
+ Task> SearchVectorsAsync(float[] queryVector, int topK = 5);
+ }
+
+ public class VectorResult
+ {
+ public int Id { get; set; }
+ public string Content { get; set; }
+ public Dictionary Metadata { get; set; }
+ public double Similarity { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Services/IDocumentProcessingService.cs b/backend/RAG.Domain/Services/IDocumentProcessingService.cs
new file mode 100644
index 0000000..5ffcc6d
--- /dev/null
+++ b/backend/RAG.Domain/Services/IDocumentProcessingService.cs
@@ -0,0 +1,13 @@
+using System.IO;
+using System.Threading.Tasks;
+using RAG.Domain.Entities;
+
+namespace RAG.Domain.Services
+{
+ public interface IDocumentProcessingService
+ {
+ Task ProcessDocumentAsync(Stream fileStream, string fileName, string contentType, int userId);
+ bool SupportsFileType(string fileExtension);
+ Task ExtractTextFromDocumentAsync(Stream fileStream, string fileExtension);
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs b/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
new file mode 100644
index 0000000..5f21a8b
--- /dev/null
+++ b/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
@@ -0,0 +1,26 @@
+// using Microsoft.EntityFrameworkCore;
+// using RAG.Domain.Entities;
+
+// namespace RAG.Infrastructure.Data
+// {
+// public class ApplicationDbContext : DbContext
+// {
+// public ApplicationDbContext(DbContextOptions options)
+// : base(options)
+// {}
+
+// // DbSets for your entities
+// public DbSet Documents { get; set; }
+// public DbSet ChatHistories { get; set; }
+// public DbSet Users { get; set; }
+
+// protected override void OnModelCreating(ModelBuilder modelBuilder)
+// {
+// base.OnModelCreating(modelBuilder);
+
+// // Configure entity mappings if needed
+// modelBuilder.Entity()
+// .HasIndex(d => d.Title);
+// }
+// }
+// }
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/RAG.Infrastructure.csproj b/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
index bcf0485..2b89d02 100644
--- a/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
+++ b/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
@@ -5,16 +5,8 @@
-
-
-
-
-
-
-
-
- net8.0
+ net9.0
enable
enable
diff --git a/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs b/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs
new file mode 100644
index 0000000..5ab2d03
--- /dev/null
+++ b/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs
@@ -0,0 +1,143 @@
+// using System;
+// using System.Collections.Generic;
+// using System.IO;
+// using System.Linq;
+// using System.Threading.Tasks;
+// using iTextSharp.text.pdf;
+// using iTextSharp.text.pdf.parser;
+// using Microsoft.Extensions.Logging;
+// using Microsoft.Extensions.Configuration;
+// using RAG.Domain.Entities;
+// using RAG.Domain.Repositories;
+// using RAG.Domain.Services;
+
+// namespace RAG.Infrastructure.Services
+// {
+// public class DocumentProcessingService : IDocumentProcessingService
+// {
+// private readonly ILogger _logger;
+// private readonly IVectorStore _vectorStore;
+// private readonly IConfiguration _configuration;
+
+// public DocumentProcessingService(
+// ILogger logger,
+// IVectorStore vectorStore,
+// IConfiguration configuration)
+// {
+// _logger = logger;
+// _vectorStore = vectorStore;
+// _configuration = configuration;
+// }
+
+// public async Task ProcessDocumentAsync(Stream fileStream, string fileName, string contentType, int userId)
+// {
+// try
+// {
+// _logger.LogInformation("Processing document: {FileName}", fileName);
+
+// // Extract file extension
+// var fileExtension = Path.GetExtension(fileName)?.ToLowerInvariant() ?? string.Empty;
+// if (string.IsNullOrEmpty(fileExtension))
+// {
+// throw new ArgumentException("File extension is required.");
+// }
+
+// // Check if file type is supported
+// if (!SupportsFileType(fileExtension))
+// {
+// throw new NotSupportedException($"File type {fileExtension} is not supported.");
+// }
+
+// // Extract text from document
+// var textChunks = await ExtractTextFromDocumentAsync(fileStream, fileExtension);
+
+// // Create document entity
+// var document = new Document
+// {
+// Title = Path.GetFileNameWithoutExtension(fileName),
+// FilePath = fileName,
+// FileType = fileExtension,
+// UserId = userId,
+// Content = string.Join("\n", textChunks)
+// };
+
+// // Process each chunk for vector storage
+// for (int i = 0; i < textChunks.Length; i++)
+// {
+// var chunk = textChunks[i];
+// document.Chunks.Add(new DocumentChunk
+// {
+// Content = chunk,
+// Position = i
+// });
+
+// // In a real implementation, you would generate embeddings here
+// // For example using OpenAI API or other embedding service
+// // var embedding = await GenerateEmbeddingAsync(chunk);
+// // await _vectorStore.StoreVectorAsync(chunk, embedding, new { documentId = document.Id, chunkId = i });
+// }
+
+// _logger.LogInformation("Document processed successfully: {FileName}", fileName);
+// return document;
+// }
+// catch (Exception ex)
+// {
+// _logger.LogError(ex, "Error processing document: {FileName}", fileName);
+// throw;
+// }
+// }
+
+// public bool SupportsFileType(string fileExtension)
+// {
+// var supportedTypes = new List { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
+// return supportedTypes.Contains(fileExtension);
+// }
+
+// public async Task ExtractTextFromDocumentAsync(Stream fileStream, string fileExtension)
+// {
+// switch (fileExtension)
+// {
+// case ".pdf":
+// return await ExtractTextFromPdfAsync(fileStream);
+// case ".txt":
+// return await ExtractTextFromTextAsync(fileStream);
+// // Add cases for other file types as needed
+// default:
+// throw new NotSupportedException($"File type {fileExtension} is not supported for text extraction.");
+// }
+// }
+
+// private async Task ExtractTextFromPdfAsync(Stream fileStream)
+// {
+// var textChunks = new List();
+
+// using (var reader = new PdfReader(fileStream))
+// {
+// for (int i = 1; i <= reader.NumberOfPages; i++)
+// {
+// var text = PdfTextExtractor.GetTextFromPage(reader, i);
+// textChunks.Add(text);
+// }
+// }
+
+// return textChunks.ToArray();
+// }
+
+// private async Task ExtractTextFromTextAsync(Stream fileStream)
+// {
+// using (var reader = new StreamReader(fileStream))
+// {
+// var text = await reader.ReadToEndAsync();
+// // Split text into chunks (simple implementation)
+// return new[] { text };
+// }
+// }
+
+// // In a real implementation, you would implement this method
+// // private async Task GenerateEmbeddingAsync(string text)
+// // {
+// // // Call OpenAI API or other embedding service
+// // // return embedding;
+// // }
+// }
+// }
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs b/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs
new file mode 100644
index 0000000..926bfa4
--- /dev/null
+++ b/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs
@@ -0,0 +1,109 @@
+// using System.Data;
+// using Microsoft.Extensions.Configuration;
+// using Npgsql;
+// using RAG.Domain.Repositories;
+// using RAG.Domain.Entities;
+
+// namespace RAG.Infrastructure.VectorStore
+// {
+// public class PgVectorStore : IVectorStore
+// {
+// private readonly string _connectionString;
+// private readonly string _tableName;
+
+// public PgVectorStore(IConfiguration configuration)
+// {
+// _connectionString = configuration.GetSection("VectorStore:ConnectionString").Value;
+// _tableName = configuration.GetSection("VectorStore:TableName").Value ?? "vectors";
+// }
+
+// public async Task InitializeAsync()
+// {
+// // Create extension if not exists
+// await ExecuteNonQueryAsync("CREATE EXTENSION IF NOT EXISTS vector");
+
+// // Create table if not exists
+// await ExecuteNonQueryAsync($@"CREATE TABLE IF NOT EXISTS {_tableName} (
+// id SERIAL PRIMARY KEY,
+// content TEXT NOT NULL,
+// metadata JSONB,
+// embedding VECTOR(1536) NOT NULL
+// )");
+
+// // Create index if not exists
+// await ExecuteNonQueryAsync($@"CREATE INDEX IF NOT EXISTS {_tableName}_embedding_idx ON {_tableName} USING ivfflat (embedding vector_cosine_ops)");
+// }
+
+// public async Task StoreVectorAsync(string content, float[] embedding, object metadata = null)
+// {
+// var metadataJson = metadata != null ? System.Text.Json.JsonSerializer.Serialize(metadata) : null;
+
+// await ExecuteNonQueryAsync($@"INSERT INTO {_tableName} (content, metadata, embedding)
+// VALUES (@content, @metadata, @embedding)",
+// new NpgsqlParameter("@content", content),
+// new NpgsqlParameter("@metadata", metadataJson ?? (object)DBNull.Value),
+// new NpgsqlParameter("@embedding", NpgsqlTypes.NpgsqlDbType.Vector) { Value = embedding });
+// }
+
+// public async Task> SearchVectorsAsync(float[] queryVector, int topK = 5)
+// {
+// var results = new List();
+
+// using (var connection = new NpgsqlConnection(_connectionString))
+// {
+// await connection.OpenAsync();
+
+// using (var command = new NpgsqlCommand($@"SELECT id, content, metadata, embedding <=> @queryVector AS similarity
+// FROM {_tableName}
+// ORDER BY similarity
+// LIMIT @topK", connection))
+// {
+// command.Parameters.AddWithValue("@queryVector", NpgsqlTypes.NpgsqlDbType.Vector, queryVector);
+// command.Parameters.AddWithValue("@topK", topK);
+
+// using (var reader = await command.ExecuteReaderAsync())
+// {
+// while (await reader.ReadAsync())
+// {
+// results.Add(new VectorResult
+// {
+// Id = reader.GetInt32(0),
+// Content = reader.GetString(1),
+// Metadata = reader.IsDBNull(2) ? null : System.Text.Json.JsonSerializer.Deserialize>(reader.GetString(2)),
+// Similarity = reader.GetDouble(3)
+// });
+// }
+// }
+// }
+// }
+
+// return results;
+// }
+
+// private async Task ExecuteNonQueryAsync(string sql, params NpgsqlParameter[] parameters)
+// {
+// using (var connection = new NpgsqlConnection(_connectionString))
+// {
+// await connection.OpenAsync();
+
+// using (var command = new NpgsqlCommand(sql, connection))
+// {
+// if (parameters != null)
+// {
+// command.Parameters.AddRange(parameters);
+// }
+
+// await command.ExecuteNonQueryAsync();
+// }
+// }
+// }
+// }
+
+// public class VectorResult
+// {
+// public int Id { get; set; }
+// public string Content { get; set; }
+// public Dictionary Metadata { get; set; }
+// public double Similarity { get; set; }
+// }
+// }
\ No newline at end of file
diff --git a/backend/RAG.Tests/RAG.Tests.csproj b/backend/RAG.Tests/RAG.Tests.csproj
index 5d9e2d3..66603ab 100644
--- a/backend/RAG.Tests/RAG.Tests.csproj
+++ b/backend/RAG.Tests/RAG.Tests.csproj
@@ -1,19 +1,17 @@
-
+
- net8.0
+ net9.0
enable
enable
-
false
- true
-
-
-
-
+
+
+
+
diff --git a/backend/RAG.Tests/UnitTest1.cs b/backend/RAG.Tests/UnitTest1.cs
index 0c054e8..3326ee9 100644
--- a/backend/RAG.Tests/UnitTest1.cs
+++ b/backend/RAG.Tests/UnitTest1.cs
@@ -1,4 +1,4 @@
-namespace RAG.Tests;
+namespace RAG.Tests;
public class UnitTest1
{
@@ -7,4 +7,4 @@ public class UnitTest1
{
}
-}
\ No newline at end of file
+}
diff --git a/backend/RAG.sln b/backend/RAG.sln
index f53c424..70304a8 100644
--- a/backend/RAG.sln
+++ b/backend/RAG.sln
@@ -3,44 +3,88 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Api", "RAG.Api\RAG.Api.csproj", "{3DAC1A57-A784-43CD-B665-68896913F082}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Api", "RAG.Api\RAG.Api.csproj", "{7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Application", "RAG.Application\RAG.Application.csproj", "{FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Application", "RAG.Application\RAG.Application.csproj", "{3BFEF6F7-44BA-470F-8265-A35E061D4465}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Domain", "RAG.Domain\RAG.Domain.csproj", "{2937A929-EB24-43B6-8BD9-EEA32B895C9C}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Domain", "RAG.Domain\RAG.Domain.csproj", "{09BBBB92-0E93-4195-8830-ED2546A17771}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Infrastructure", "RAG.Infrastructure\RAG.Infrastructure.csproj", "{46C0164B-CF85-4CA1-95A6-16DDC05476CB}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Infrastructure", "RAG.Infrastructure\RAG.Infrastructure.csproj", "{4461B67F-4DA9-419E-9BAC-452DEFC585ED}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Tests", "RAG.Tests\RAG.Tests.csproj", "{994D11A9-A40A-4FC2-A7FB-BB366380EEF2}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RAG.Tests", "RAG.Tests\RAG.Tests.csproj", "{0EC59D41-9602-49F9-B60C-97A249767630}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Debug|x64.Build.0 = Debug|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Debug|x86.Build.0 = Debug|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Release|x64.ActiveCfg = Release|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Release|x64.Build.0 = Release|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Release|x86.ActiveCfg = Release|Any CPU
+ {7AA1BD7D-89C9-4DD3-902D-5036A29DD33D}.Release|x86.Build.0 = Release|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Debug|x64.Build.0 = Debug|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Debug|x86.Build.0 = Debug|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Release|x64.ActiveCfg = Release|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Release|x64.Build.0 = Release|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Release|x86.ActiveCfg = Release|Any CPU
+ {3BFEF6F7-44BA-470F-8265-A35E061D4465}.Release|x86.Build.0 = Release|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Debug|x64.Build.0 = Debug|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Debug|x86.Build.0 = Debug|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Release|Any CPU.Build.0 = Release|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Release|x64.ActiveCfg = Release|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Release|x64.Build.0 = Release|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Release|x86.ActiveCfg = Release|Any CPU
+ {09BBBB92-0E93-4195-8830-ED2546A17771}.Release|x86.Build.0 = Release|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Debug|x64.Build.0 = Debug|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Debug|x86.Build.0 = Debug|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Release|x64.ActiveCfg = Release|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Release|x64.Build.0 = Release|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Release|x86.ActiveCfg = Release|Any CPU
+ {4461B67F-4DA9-419E-9BAC-452DEFC585ED}.Release|x86.Build.0 = Release|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Debug|x64.Build.0 = Debug|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Debug|x86.Build.0 = Debug|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Release|x64.ActiveCfg = Release|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Release|x64.Build.0 = Release|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Release|x86.ActiveCfg = Release|Any CPU
+ {0EC59D41-9602-49F9-B60C-97A249767630}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {3DAC1A57-A784-43CD-B665-68896913F082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3DAC1A57-A784-43CD-B665-68896913F082}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3DAC1A57-A784-43CD-B665-68896913F082}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3DAC1A57-A784-43CD-B665-68896913F082}.Release|Any CPU.Build.0 = Release|Any CPU
- {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FB90A32A-968E-4FDB-BA02-25C8A3F0D05A}.Release|Any CPU.Build.0 = Release|Any CPU
- {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2937A929-EB24-43B6-8BD9-EEA32B895C9C}.Release|Any CPU.Build.0 = Release|Any CPU
- {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {46C0164B-CF85-4CA1-95A6-16DDC05476CB}.Release|Any CPU.Build.0 = Release|Any CPU
- {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {994D11A9-A40A-4FC2-A7FB-BB366380EEF2}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
EndGlobal
diff --git a/backend/txt.txt b/backend/txt.txt
new file mode 100644
index 0000000..e69de29
--
Gitee
From b497aa40456eb528fd5c4acb1d688f3d6dd71934 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B0=A2=E9=9B=A8=E6=99=B4?= <1207713896@qq.com>
Date: Tue, 5 Aug 2025 10:33:16 +0800
Subject: [PATCH 03/31] =?UTF-8?q?=E5=B7=B2=E4=B8=8B=E8=BD=BD=E5=A5=BD?=
=?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=8C=85=EF=BC=8C=E4=B8=94=E5=88=9B=E5=BB=BA?=
=?UTF-8?q?=E4=BA=86=E5=AE=9E=E4=BD=93=E7=B1=BB=EF=BC=8C=E4=BB=93=E5=82=A8?=
=?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=BD=86=E6=9C=89=E8=AE=B8=E5=A4=9A?=
=?UTF-8?q?=E6=8A=A5=E9=94=99=EF=BC=8C=E8=BF=98=E5=9C=A8=E5=A4=84=E7=90=86?=
=?UTF-8?q?ing?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/RAG.Api/Controllers/ChatController.cs | 154 +++----
.../RAG.Api/Controllers/DocumentController.cs | 107 +++--
backend/RAG.Api/Program.cs | 70 +++-
backend/RAG.Api/RAG.Api.csproj | 9 +
backend/RAG.Api/appsettings.json | 9 +-
.../Commands/UploadDocumentCommand.cs | 8 +
.../Handlers/UploadDocumentHandler.cs | 66 +++
.../RAG.Application/RAG.Application.csproj | 6 +
backend/RAG.Domain/Entities/BaseEntity.cs | 27 ++
backend/RAG.Domain/Entities/Document.cs | 92 ++++-
.../RAG.Domain/Repositories/IRepository.cs | 20 +
.../RAG.Domain/Repositories/IUnitOfWork.cs | 10 +
.../RAG.Domain/ValueObjects/AccessLevel.cs | 27 ++
.../RAG.Domain/ValueObjects/DocumentTitle.cs | 25 ++
backend/RAG.Domain/ValueObjects/FileName.cs | 25 ++
backend/RAG.Domain/ValueObjects/FilePath.cs | 22 +
backend/RAG.Domain/ValueObjects/FileSize.cs | 22 +
backend/RAG.Domain/ValueObjects/FileType.cs | 22 +
.../Data/ApplicationDbContext.cs | 44 +-
.../RAG.Infrastructure.csproj | 9 +
.../Repositories/ApplicationUnitOfWork.cs | 34 ++
.../Repositories/Repository.cs | 68 +++
.../Services/DocumentProcessingService.cs | 387 +++++++++++-------
.../VectorStore/PgVectorStore.cs | 220 +++++-----
24 files changed, 1041 insertions(+), 442 deletions(-)
create mode 100644 backend/RAG.Application/Commands/UploadDocumentCommand.cs
create mode 100644 backend/RAG.Application/Handlers/UploadDocumentHandler.cs
create mode 100644 backend/RAG.Domain/Entities/BaseEntity.cs
create mode 100644 backend/RAG.Domain/Repositories/IRepository.cs
create mode 100644 backend/RAG.Domain/Repositories/IUnitOfWork.cs
create mode 100644 backend/RAG.Domain/ValueObjects/AccessLevel.cs
create mode 100644 backend/RAG.Domain/ValueObjects/DocumentTitle.cs
create mode 100644 backend/RAG.Domain/ValueObjects/FileName.cs
create mode 100644 backend/RAG.Domain/ValueObjects/FilePath.cs
create mode 100644 backend/RAG.Domain/ValueObjects/FileSize.cs
create mode 100644 backend/RAG.Domain/ValueObjects/FileType.cs
create mode 100644 backend/RAG.Infrastructure/Repositories/ApplicationUnitOfWork.cs
create mode 100644 backend/RAG.Infrastructure/Repositories/Repository.cs
diff --git a/backend/RAG.Api/Controllers/ChatController.cs b/backend/RAG.Api/Controllers/ChatController.cs
index 1723df4..febaf3c 100644
--- a/backend/RAG.Api/Controllers/ChatController.cs
+++ b/backend/RAG.Api/Controllers/ChatController.cs
@@ -1,89 +1,89 @@
-// using Microsoft.AspNetCore.Mvc;
-// using System.Threading.Tasks;
-// using RAG.Domain.Repositories;
-// using RAG.Domain.Entities;
-// using Microsoft.Extensions.Logging;
-// using System.Collections.Generic;
-// using System.Linq;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using RAG.Domain.Repositories;
+using RAG.Domain.Entities;
+using Microsoft.Extensions.Logging;
+using System.Collections.Generic;
+using System.Linq;
-// namespace RAG.Api.Controllers
-// {
-// [ApiController]
-// [Route("api/[controller]")]
-// public class ChatController : ControllerBase
-// {
-// private readonly IVectorStore _vectorStore;
-// private readonly ILogger _logger;
+namespace RAG.Api.Controllers
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class ChatController : ControllerBase
+ {
+ private readonly IVectorStore _vectorStore;
+ private readonly ILogger _logger;
-// public ChatController(
-// IVectorStore vectorStore,
-// ILogger logger)
-// {
-// _vectorStore = vectorStore;
-// _logger = logger;
-// }
+ public ChatController(
+ IVectorStore vectorStore,
+ ILogger logger)
+ {
+ _vectorStore = vectorStore;
+ _logger = logger;
+ }
-// [HttpPost("query")]
-// public async Task Query([FromBody] ChatQuery query)
-// {
-// try
-// {
-// if (string.IsNullOrEmpty(query.Message))
-// {
-// return BadRequest("Message cannot be empty.");
-// }
+ [HttpPost("query")]
+ public async Task Query([FromBody] ChatQuery query)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(query.Message))
+ {
+ return BadRequest("Message cannot be empty.");
+ }
-// _logger.LogInformation("Processing chat query: {Message}", query.Message);
+ _logger.LogInformation("Processing chat query: {Message}", query.Message);
-// // In a real implementation, you would generate an embedding for the query
-// // var queryEmbedding = await GenerateEmbeddingAsync(query.Message);
+ // In a real implementation, you would generate an embedding for the query
+ // var queryEmbedding = await GenerateEmbeddingAsync(query.Message);
-// // For demonstration purposes, create a dummy embedding
-// var queryEmbedding = new float[1536]; // Match the vector dimension used in PgVectorStore
+ // For demonstration purposes, create a dummy embedding
+ var queryEmbedding = new float[1536]; // Match the vector dimension used in PgVectorStore
-// // Search for similar vectors
-// var results = await _vectorStore.SearchVectorsAsync(queryEmbedding, query.TopK ?? 5);
+ // Search for similar vectors
+ var results = await _vectorStore.SearchVectorsAsync(queryEmbedding, query.TopK ?? 5);
-// // In a real implementation, you would use these results to generate a response
-// // using a language model
-// var response = new ChatResponse
-// {
-// Query = query.Message,
-// Response = "This is a placeholder response generated based on retrieved context.",
-// References = results.Select(r => new Reference
-// {
-// Content = r.Content.Substring(0, Math.Min(100, r.Content.Length)) + "...",
-// Similarity = r.Similarity
-// }).ToList()
-// };
+ // In a real implementation, you would use these results to generate a response
+ // using a language model
+ var response = new ChatResponse
+ {
+ Query = query.Message,
+ Response = "This is a placeholder response generated based on retrieved context.",
+ References = results.Select(r => new Reference
+ {
+ Content = r.Content.Substring(0, Math.Min(100, r.Content.Length)) + "...",
+ Similarity = r.Similarity
+ }).ToList()
+ };
-// return Ok(response);
-// }
-// catch (Exception ex)
-// {
-// _logger.LogError(ex, "Error processing chat query");
-// return StatusCode(500, "Internal server error");
-// }
-// }
-// }
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing chat query");
+ return StatusCode(500, "Internal server error");
+ }
+ }
+ }
-// public class ChatQuery
-// {
-// public string Message { get; set; }
-// public int? TopK { get; set; }
-// public string SessionId { get; set; }
-// }
+ public class ChatQuery
+ {
+ public string Message { get; set; }
+ public int? TopK { get; set; }
+ public string SessionId { get; set; }
+ }
-// public class ChatResponse
-// {
-// public string Query { get; set; }
-// public string Response { get; set; }
-// public List References { get; set; }
-// }
+ public class ChatResponse
+ {
+ public string Query { get; set; }
+ public string Response { get; set; }
+ public List References { get; set; }
+ }
-// public class Reference
-// {
-// public string Content { get; set; }
-// public double Similarity { get; set; }
-// }
-// }
\ No newline at end of file
+ public class Reference
+ {
+ public string Content { get; set; }
+ public double Similarity { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Api/Controllers/DocumentController.cs b/backend/RAG.Api/Controllers/DocumentController.cs
index 1e4540c..766cee6 100644
--- a/backend/RAG.Api/Controllers/DocumentController.cs
+++ b/backend/RAG.Api/Controllers/DocumentController.cs
@@ -1,63 +1,58 @@
-// using Microsoft.AspNetCore.Mvc;
-// using Microsoft.AspNetCore.Http;
-// using System.Threading.Tasks;
-// using RAG.Domain.Services;
-// using RAG.Domain.Entities;
-// using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using System.Threading.Tasks;
+using MediatR;
+using RAG.Application.Commands;
+using RAG.Domain.ValueObjects;
+using Microsoft.Extensions.Logging;
-// namespace RAG.Api.Controllers
-// {
-// [ApiController]
-// [Route("api/[controller]")]
-// public class DocumentController : ControllerBase
-// {
-// private readonly IDocumentProcessingService _documentProcessingService;
-// private readonly ILogger _logger;
+namespace RAG.Api.Controllers
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class DocumentController : ControllerBase
+ {
+ private readonly IMediator _mediator;
+ private readonly ILogger _logger;
-// public DocumentController(
-// IDocumentProcessingService documentProcessingService,
-// ILogger logger)
-// {
-// _documentProcessingService = documentProcessingService;
-// _logger = logger;
-// }
+ public DocumentController(
+ IMediator mediator,
+ ILogger logger)
+ {
+ _mediator = mediator;
+ _logger = logger;
+ }
-// [HttpPost("upload")]
-// public async Task UploadDocument(IFormFile file)
-// {
-// try
-// {
-// if (file == null || file.Length == 0)
-// {
-// return BadRequest("No file uploaded.");
-// }
+ [HttpPost("upload")]
+ public async Task UploadDocument(IFormFile file, [FromForm] string accessLevel = "internal")
+ {
+ try
+ {
+ if (file == null || file.Length == 0)
+ {
+ return BadRequest("No file uploaded.");
+ }
-// // In a real application, you would get the user ID from authentication
-// var userId = 1; // Placeholder
+ // 创建访问级别值对象
+ var accessLevelObj = AccessLevel.Create(accessLevel);
-// using (var stream = file.OpenReadStream())
-// {
-// var document = await _documentProcessingService.ProcessDocumentAsync(
-// stream,
-// file.FileName,
-// file.ContentType,
-// userId);
+ // 发送上传文档命令
+ var documentId = await _mediator.Send(new UploadDocumentCommand(file, accessLevelObj));
-// return Ok(new { document.Id, document.Title, document.FileType, document.CreatedAt });
-// }
-// }
-// catch (Exception ex)
-// {
-// _logger.LogError(ex, "Error uploading document");
-// return StatusCode(500, "Internal server error");
-// }
-// }
+ return Ok(new { DocumentId = documentId });
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error uploading document");
+ return StatusCode(500, "Internal server error");
+ }
+ }
-// [HttpGet("supported-types")]
-// public IActionResult GetSupportedFileTypes()
-// {
-// var supportedTypes = new[] { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
-// return Ok(supportedTypes);
-// }
-// }
-// }
\ No newline at end of file
+ [HttpGet("supported-types")]
+ public IActionResult GetSupportedFileTypes()
+ {
+ var supportedTypes = new[] { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
+ return Ok(supportedTypes);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Api/Program.cs b/backend/RAG.Api/Program.cs
index ee9d65d..b21db8c 100644
--- a/backend/RAG.Api/Program.cs
+++ b/backend/RAG.Api/Program.cs
@@ -1,9 +1,55 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using RAG.Application.Commands;
+using RAG.Application.Handlers;
+using RAG.Domain.Repositories;
+using RAG.Domain.Services;
+using RAG.Infrastructure.Data;
+using RAG.Infrastructure.Repositories;
+using RAG.Infrastructure.Services;
+using RAG.Infrastructure.VectorStore;
+using MediatR;
+using System.Reflection;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
+
+builder.Services.AddControllers();
+
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
+// Add MediatR
+builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(UploadDocumentCommand).Assembly));
+
+builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(UploadDocumentHandler).Assembly));
+
+// Add DbContext
+builder.Services.AddDbContext(options =>
+ options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
+
+// Add Repositories and Unit of Work
+builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
+builder.Services.AddScoped();
+
+// Add Services
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+// Add CORS
+builder.Services.AddCors(options =>
+{
+ options.AddPolicy("AllowAll", policy =>
+ {
+ policy.AllowAnyOrigin()
+ .AllowAnyMethod()
+ .AllowAnyHeader();
+ });
+});
+
var app = builder.Build();
// Configure the HTTP request pipeline.
@@ -14,28 +60,10 @@ if (app.Environment.IsDevelopment())
app.UseHttpsRedirection();
-var summaries = new[]
-{
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
-};
+app.UseCors("AllowAll");
-app.MapGet("/weatherforecast", () =>
-{
- var forecast = Enumerable.Range(1, 5).Select(index =>
- new WeatherForecast
- (
- DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
- Random.Shared.Next(-20, 55),
- summaries[Random.Shared.Next(summaries.Length)]
- ))
- .ToArray();
- return forecast;
-})
-.WithName("GetWeatherForecast");
+app.MapControllers();
app.Run();
-record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
-{
- public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
-}
+public partial class Program {}
diff --git a/backend/RAG.Api/RAG.Api.csproj b/backend/RAG.Api/RAG.Api.csproj
index b4c5283..e7b12a3 100644
--- a/backend/RAG.Api/RAG.Api.csproj
+++ b/backend/RAG.Api/RAG.Api.csproj
@@ -7,7 +7,16 @@
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
diff --git a/backend/RAG.Api/appsettings.json b/backend/RAG.Api/appsettings.json
index 10f68b8..147f30e 100644
--- a/backend/RAG.Api/appsettings.json
+++ b/backend/RAG.Api/appsettings.json
@@ -5,5 +5,12 @@
"Microsoft.AspNetCore": "Warning"
}
},
- "AllowedHosts": "*"
+ "AllowedHosts": "*",
+ "ConnectionStrings": {
+ "DefaultConnection": "Host=localhost;Port=5432;Database=rag_db;Username=postgres;Password=your_password_here"
+ },
+ "VectorStore": {
+ "Type": "Postgres",
+ "ConnectionString": "Host=localhost;Port=5432;Database=rag_db;Username=postgres;Password=your_password_here"
+ }
}
diff --git a/backend/RAG.Application/Commands/UploadDocumentCommand.cs b/backend/RAG.Application/Commands/UploadDocumentCommand.cs
new file mode 100644
index 0000000..f9cb20f
--- /dev/null
+++ b/backend/RAG.Application/Commands/UploadDocumentCommand.cs
@@ -0,0 +1,8 @@
+using MediatR;
+using Microsoft.AspNetCore.Http;
+using RAG.Domain.ValueObjects;
+
+namespace RAG.Application.Commands
+{
+ public record UploadDocumentCommand(IFormFile File, AccessLevel AccessLevel) : IRequest;
+}
\ No newline at end of file
diff --git a/backend/RAG.Application/Handlers/UploadDocumentHandler.cs b/backend/RAG.Application/Handlers/UploadDocumentHandler.cs
new file mode 100644
index 0000000..e0c7ef4
--- /dev/null
+++ b/backend/RAG.Application/Handlers/UploadDocumentHandler.cs
@@ -0,0 +1,66 @@
+using MediatR;
+using Microsoft.Extensions.Logging;
+using RAG.Application.Commands;
+using RAG.Domain.Entities;
+using RAG.Domain.Repositories;
+using RAG.Domain.Services;
+using RAG.Domain.ValueObjects;
+
+namespace RAG.Application.Handlers
+{
+ public class UploadDocumentHandler : IRequestHandler
+ {
+ private readonly IDocumentProcessingService _documentProcessingService;
+ private readonly IRepository _documentRepository;
+ private readonly IUnitOfWork _unitOfWork;
+ private readonly ILogger _logger;
+
+ public UploadDocumentHandler(
+ IDocumentProcessingService documentProcessingService,
+ IRepository documentRepository,
+ IUnitOfWork unitOfWork,
+ ILogger logger)
+ {
+ _documentProcessingService = documentProcessingService;
+ _documentRepository = documentRepository;
+ _unitOfWork = unitOfWork;
+ _logger = logger;
+ }
+
+ public async Task Handle(UploadDocumentCommand request, CancellationToken cancellationToken)
+ {
+ try
+ {
+ _logger.LogInformation("Handling document upload request");
+
+ // 获取当前用户ID(实际应用中应从身份验证上下文中获取)
+ var userId = 1; // 占位符
+
+ // 使用文档处理服务处理上传的文件
+ using (var stream = request.File.OpenReadStream())
+ {
+ var document = await _documentProcessingService.ProcessDocumentAsync(
+ stream,
+ request.File.FileName,
+ request.File.ContentType,
+ userId);
+
+ // 设置文档访问级别
+ document.AccessLevel = request.AccessLevel.Value;
+
+ // 保存文档到数据库
+ await _documentRepository.AddAsync(document);
+ await _unitOfWork.SaveChangesAsync();
+
+ _logger.LogInformation("Document uploaded successfully with ID: {DocumentId}", document.Id);
+ return document.Id;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error handling document upload request");
+ throw;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Application/RAG.Application.csproj b/backend/RAG.Application/RAG.Application.csproj
index 8c0b9f3..748d7dd 100644
--- a/backend/RAG.Application/RAG.Application.csproj
+++ b/backend/RAG.Application/RAG.Application.csproj
@@ -4,6 +4,12 @@
+
+
+
+
+
+
net9.0
enable
diff --git a/backend/RAG.Domain/Entities/BaseEntity.cs b/backend/RAG.Domain/Entities/BaseEntity.cs
new file mode 100644
index 0000000..092db55
--- /dev/null
+++ b/backend/RAG.Domain/Entities/BaseEntity.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Entities
+{
+ public abstract class BaseEntity
+ {
+ private readonly List _domainEvents = new();
+
+ public IReadOnlyList DomainEvents => _domainEvents.AsReadOnly();
+
+ protected void AddDomainEvent(DomainEvent domainEvent)
+ {
+ _domainEvents.Add(domainEvent);
+ }
+
+ public void ClearDomainEvents()
+ {
+ _domainEvents.Clear();
+ }
+ }
+
+ public abstract class DomainEvent
+ {
+ public DateTime OccurredOn { get; } = DateTime.UtcNow;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/Document.cs b/backend/RAG.Domain/Entities/Document.cs
index 771f36f..1cb36ed 100644
--- a/backend/RAG.Domain/Entities/Document.cs
+++ b/backend/RAG.Domain/Entities/Document.cs
@@ -1,26 +1,92 @@
using System;
using System.Collections.Generic;
+using RAG.Domain.ValueObjects;
+using RAG.Domain.Events;
namespace RAG.Domain.Entities
{
- public class Document
+ public class Document : BaseEntity
{
- public int Id { get; set; }
- public string Title { get; set; }
- public string Content { get; set; }
- public string FilePath { get; set; }
- public string FileName { get; set; }
- public string FileType { get; set; }
- public long FileSize { get; set; }
+ private readonly List _chunks = new();
+
+ // 私有构造函数,确保通过工厂方法创建
+ private Document() { }
+
+ // 工厂方法
+ public static Document Create(
+ DocumentTitle title,
+ FileName fileName,
+ FilePath filePath,
+ FileType fileType,
+ FileSize fileSize,
+ AccessLevel accessLevel)
+ {
+ var document = new Document
+ {
+ Title = title,
+ FileName = fileName,
+ FilePath = filePath,
+ FileType = fileType,
+ FileSize = fileSize,
+ AccessLevel = accessLevel,
+ Status = "pending",
+ CreatedAt = DateTime.UtcNow,
+ UpdatedAt = DateTime.UtcNow
+ };
+
+ // 发布领域事件
+ document.AddDomainEvent(new DocumentCreatedEvent(document.Id));
+
+ return document;
+ }
+
+ public int Id { get; private set; }
+ public DocumentTitle Title { get; private set; }
+ public FileName FileName { get; private set; }
+ public FilePath FilePath { get; private set; }
+ public FileType FileType { get; private set; }
+ public FileSize FileSize { get; private set; }
public string Description { get; set; }
public string Tags { get; set; }
- public string AccessLevel { get; set; } = "internal"; // 'internal' or 'public'
- public string Status { get; set; } = "pending"; // 'pending', 'processing', 'completed', 'failed'
- public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
- public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
+ public AccessLevel AccessLevel { get; private set; }
+ public string Status { get; private set; }
+ public DateTime CreatedAt { get; private set; }
+ public DateTime UpdatedAt { get; private set; }
public int UserId { get; set; }
public User User { get; set; }
- public ICollection Chunks { get; set; } = new List();
+
+ public IReadOnlyList Chunks => _chunks.AsReadOnly();
+
+ // 业务方法
+ public void UpdateProcessingStatus()
+ {
+ if (Status != "pending")
+ throw new InvalidOperationException("Document must be in pending status");
+
+ Status = "processing";
+ UpdatedAt = DateTime.UtcNow;
+
+ AddDomainEvent(new DocumentProcessingStartedEvent(Id));
+ }
+
+ public void CompleteProcessing()
+ {
+ if (Status != "processing")
+ throw new InvalidOperationException("Document must be in processing status");
+
+ Status = "completed";
+ UpdatedAt = DateTime.UtcNow;
+
+ AddDomainEvent(new DocumentProcessingCompletedEvent(Id));
+ }
+
+ public void AddChunk(DocumentChunk chunk)
+ {
+ if (chunk.DocumentId != Id)
+ throw new ArgumentException("Chunk must belong to this document");
+
+ _chunks.Add(chunk);
+ }
}
public class DocumentChunk
diff --git a/backend/RAG.Domain/Repositories/IRepository.cs b/backend/RAG.Domain/Repositories/IRepository.cs
new file mode 100644
index 0000000..11e1774
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IRepository.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading.Tasks;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IRepository where TEntity : class
+ {
+ Task GetByIdAsync(int id);
+ Task> GetAllAsync();
+ Task> FindAsync(Expression> predicate);
+ Task SingleOrDefaultAsync(Expression> predicate);
+ Task AddAsync(TEntity entity);
+ Task AddRangeAsync(IEnumerable entities);
+ void Remove(TEntity entity);
+ void RemoveRange(IEnumerable entities);
+ Task CountAsync(Expression> predicate = null);
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Repositories/IUnitOfWork.cs b/backend/RAG.Domain/Repositories/IUnitOfWork.cs
new file mode 100644
index 0000000..20429ac
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IUnitOfWork.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IUnitOfWork
+ {
+ IRepository GetRepository() where TEntity : class;
+ Task SaveChangesAsync();
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/AccessLevel.cs b/backend/RAG.Domain/ValueObjects/AccessLevel.cs
new file mode 100644
index 0000000..633f3a0
--- /dev/null
+++ b/backend/RAG.Domain/ValueObjects/AccessLevel.cs
@@ -0,0 +1,27 @@
+namespace RAG.Domain.ValueObjects
+{
+ public record AccessLevel
+ {
+ public string Value { get; init; }
+
+ private AccessLevel(string value)
+ {
+ Value = value;
+ }
+
+ public static AccessLevel Create(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("Access level cannot be empty");
+
+ value = value.ToLowerInvariant();
+
+ if (value != "internal" && value != "public")
+ throw new ArgumentException("Access level must be 'internal' or 'public'");
+
+ return new AccessLevel(value);
+ }
+
+ public static implicit operator string(AccessLevel accessLevel) => accessLevel.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/DocumentTitle.cs b/backend/RAG.Domain/ValueObjects/DocumentTitle.cs
new file mode 100644
index 0000000..50ddd1f
--- /dev/null
+++ b/backend/RAG.Domain/ValueObjects/DocumentTitle.cs
@@ -0,0 +1,25 @@
+namespace RAG.Domain.ValueObjects
+{
+ public record DocumentTitle
+ {
+ public string Value { get; init; }
+
+ private DocumentTitle(string value)
+ {
+ Value = value;
+ }
+
+ public static DocumentTitle Create(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("Document title cannot be empty");
+
+ if (value.Length > 255)
+ throw new ArgumentException("Document title cannot exceed 255 characters");
+
+ return new DocumentTitle(value);
+ }
+
+ public static implicit operator string(DocumentTitle title) => title.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FileName.cs b/backend/RAG.Domain/ValueObjects/FileName.cs
new file mode 100644
index 0000000..22aa95a
--- /dev/null
+++ b/backend/RAG.Domain/ValueObjects/FileName.cs
@@ -0,0 +1,25 @@
+namespace RAG.Domain.ValueObjects
+{
+ public record FileName
+ {
+ public string Value { get; init; }
+
+ private FileName(string value)
+ {
+ Value = value;
+ }
+
+ public static FileName Create(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("File name cannot be empty");
+
+ if (value.Length > 255)
+ throw new ArgumentException("File name cannot exceed 255 characters");
+
+ return new FileName(value);
+ }
+
+ public static implicit operator string(FileName fileName) => fileName.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FilePath.cs b/backend/RAG.Domain/ValueObjects/FilePath.cs
new file mode 100644
index 0000000..7263ee4
--- /dev/null
+++ b/backend/RAG.Domain/ValueObjects/FilePath.cs
@@ -0,0 +1,22 @@
+namespace RAG.Domain.ValueObjects
+{
+ public record FilePath
+ {
+ public string Value { get; init; }
+
+ private FilePath(string value)
+ {
+ Value = value;
+ }
+
+ public static FilePath Create(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("File path cannot be empty");
+
+ return new FilePath(value);
+ }
+
+ public static implicit operator string(FilePath filePath) => filePath.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FileSize.cs b/backend/RAG.Domain/ValueObjects/FileSize.cs
new file mode 100644
index 0000000..63f5288
--- /dev/null
+++ b/backend/RAG.Domain/ValueObjects/FileSize.cs
@@ -0,0 +1,22 @@
+namespace RAG.Domain.ValueObjects
+{
+ public record FileSize
+ {
+ public long Value { get; init; }
+
+ private FileSize(long value)
+ {
+ Value = value;
+ }
+
+ public static FileSize Create(long value)
+ {
+ if (value < 0)
+ throw new ArgumentException("File size cannot be negative");
+
+ return new FileSize(value);
+ }
+
+ public static implicit operator long(FileSize fileSize) => fileSize.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FileType.cs b/backend/RAG.Domain/ValueObjects/FileType.cs
new file mode 100644
index 0000000..3117187
--- /dev/null
+++ b/backend/RAG.Domain/ValueObjects/FileType.cs
@@ -0,0 +1,22 @@
+namespace RAG.Domain.ValueObjects
+{
+ public record FileType
+ {
+ public string Value { get; init; }
+
+ private FileType(string value)
+ {
+ Value = value;
+ }
+
+ public static FileType Create(string value)
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentException("File type cannot be empty");
+
+ return new FileType(value.ToLowerInvariant());
+ }
+
+ public static implicit operator string(FileType fileType) => fileType.Value;
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs b/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
index 5f21a8b..e6f1a71 100644
--- a/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
+++ b/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
@@ -1,26 +1,26 @@
-// using Microsoft.EntityFrameworkCore;
-// using RAG.Domain.Entities;
+using Microsoft.EntityFrameworkCore;
+using RAG.Domain.Entities;
-// namespace RAG.Infrastructure.Data
-// {
-// public class ApplicationDbContext : DbContext
-// {
-// public ApplicationDbContext(DbContextOptions options)
-// : base(options)
-// {}
+namespace RAG.Infrastructure.Data
+{
+ public class ApplicationDbContext : DbContext
+ {
+ public ApplicationDbContext(DbContextOptions options)
+ : base(options)
+ {}
-// // DbSets for your entities
-// public DbSet Documents { get; set; }
-// public DbSet ChatHistories { get; set; }
-// public DbSet Users { get; set; }
+ // DbSets for your entities
+ public DbSet Documents { get; set; }
+ public DbSet ChatHistories { get; set; }
+ public DbSet Users { get; set; }
-// protected override void OnModelCreating(ModelBuilder modelBuilder)
-// {
-// base.OnModelCreating(modelBuilder);
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
-// // Configure entity mappings if needed
-// modelBuilder.Entity()
-// .HasIndex(d => d.Title);
-// }
-// }
-// }
\ No newline at end of file
+ // Configure entity mappings if needed
+ modelBuilder.Entity()
+ .HasIndex(d => d.Title);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/RAG.Infrastructure.csproj b/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
index 2b89d02..a673a92 100644
--- a/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
+++ b/backend/RAG.Infrastructure/RAG.Infrastructure.csproj
@@ -5,6 +5,15 @@
+
+
+
+
+
+
+
+
+
net9.0
enable
diff --git a/backend/RAG.Infrastructure/Repositories/ApplicationUnitOfWork.cs b/backend/RAG.Infrastructure/Repositories/ApplicationUnitOfWork.cs
new file mode 100644
index 0000000..4bbe243
--- /dev/null
+++ b/backend/RAG.Infrastructure/Repositories/ApplicationUnitOfWork.cs
@@ -0,0 +1,34 @@
+using Microsoft.EntityFrameworkCore;
+using RAG.Domain.Repositories;
+using RAG.Infrastructure.Data;
+
+namespace RAG.Infrastructure.Repositories
+{
+ public class ApplicationUnitOfWork : IUnitOfWork
+ {
+ private readonly ApplicationDbContext _context;
+ private readonly Dictionary _repositories = new();
+
+ public ApplicationUnitOfWork(ApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ public IRepository GetRepository() where TEntity : class
+ {
+ var type = typeof(TEntity);
+ if (!_repositories.ContainsKey(type))
+ {
+ var repositoryType = typeof(Repository<>).MakeGenericType(type);
+ var repository = Activator.CreateInstance(repositoryType, _context);
+ _repositories[type] = repository;
+ }
+ return (IRepository)_repositories[type];
+ }
+
+ public async Task SaveChangesAsync()
+ {
+ return await _context.SaveChangesAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/Repositories/Repository.cs b/backend/RAG.Infrastructure/Repositories/Repository.cs
new file mode 100644
index 0000000..c306450
--- /dev/null
+++ b/backend/RAG.Infrastructure/Repositories/Repository.cs
@@ -0,0 +1,68 @@
+using Microsoft.EntityFrameworkCore;
+using RAG.Domain.Repositories;
+using RAG.Infrastructure.Data;
+using System.Linq.Expressions;
+
+namespace RAG.Infrastructure.Repositories
+{
+ public class Repository : IRepository where TEntity : class
+ {
+ protected readonly ApplicationDbContext _context;
+ protected readonly DbSet _dbSet;
+
+ public Repository(ApplicationDbContext context)
+ {
+ _context = context;
+ _dbSet = context.Set();
+ }
+
+ public async Task GetByIdAsync(int id)
+ {
+ return await _dbSet.FindAsync(id);
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _dbSet.ToListAsync();
+ }
+
+ public async Task> FindAsync(Expression> predicate)
+ {
+ return await _dbSet.Where(predicate).ToListAsync();
+ }
+
+ public async Task SingleOrDefaultAsync(Expression> predicate)
+ {
+ return await _dbSet.SingleOrDefaultAsync(predicate);
+ }
+
+ public async Task AddAsync(TEntity entity)
+ {
+ await _dbSet.AddAsync(entity);
+ }
+
+ public async Task AddRangeAsync(IEnumerable entities)
+ {
+ await _dbSet.AddRangeAsync(entities);
+ }
+
+ public void Remove(TEntity entity)
+ {
+ _dbSet.Remove(entity);
+ }
+
+ public void RemoveRange(IEnumerable entities)
+ {
+ _dbSet.RemoveRange(entities);
+ }
+
+ public async Task CountAsync(Expression> predicate = null)
+ {
+ if (predicate == null)
+ {
+ return await _dbSet.CountAsync();
+ }
+ return await _dbSet.CountAsync(predicate);
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs b/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs
index 5ab2d03..bbd0f53 100644
--- a/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs
+++ b/backend/RAG.Infrastructure/Services/DocumentProcessingService.cs
@@ -1,143 +1,244 @@
-// using System;
-// using System.Collections.Generic;
-// using System.IO;
-// using System.Linq;
-// using System.Threading.Tasks;
-// using iTextSharp.text.pdf;
-// using iTextSharp.text.pdf.parser;
-// using Microsoft.Extensions.Logging;
-// using Microsoft.Extensions.Configuration;
-// using RAG.Domain.Entities;
-// using RAG.Domain.Repositories;
-// using RAG.Domain.Services;
-
-// namespace RAG.Infrastructure.Services
-// {
-// public class DocumentProcessingService : IDocumentProcessingService
-// {
-// private readonly ILogger _logger;
-// private readonly IVectorStore _vectorStore;
-// private readonly IConfiguration _configuration;
-
-// public DocumentProcessingService(
-// ILogger logger,
-// IVectorStore vectorStore,
-// IConfiguration configuration)
-// {
-// _logger = logger;
-// _vectorStore = vectorStore;
-// _configuration = configuration;
-// }
-
-// public async Task ProcessDocumentAsync(Stream fileStream, string fileName, string contentType, int userId)
-// {
-// try
-// {
-// _logger.LogInformation("Processing document: {FileName}", fileName);
-
-// // Extract file extension
-// var fileExtension = Path.GetExtension(fileName)?.ToLowerInvariant() ?? string.Empty;
-// if (string.IsNullOrEmpty(fileExtension))
-// {
-// throw new ArgumentException("File extension is required.");
-// }
-
-// // Check if file type is supported
-// if (!SupportsFileType(fileExtension))
-// {
-// throw new NotSupportedException($"File type {fileExtension} is not supported.");
-// }
-
-// // Extract text from document
-// var textChunks = await ExtractTextFromDocumentAsync(fileStream, fileExtension);
-
-// // Create document entity
-// var document = new Document
-// {
-// Title = Path.GetFileNameWithoutExtension(fileName),
-// FilePath = fileName,
-// FileType = fileExtension,
-// UserId = userId,
-// Content = string.Join("\n", textChunks)
-// };
-
-// // Process each chunk for vector storage
-// for (int i = 0; i < textChunks.Length; i++)
-// {
-// var chunk = textChunks[i];
-// document.Chunks.Add(new DocumentChunk
-// {
-// Content = chunk,
-// Position = i
-// });
-
-// // In a real implementation, you would generate embeddings here
-// // For example using OpenAI API or other embedding service
-// // var embedding = await GenerateEmbeddingAsync(chunk);
-// // await _vectorStore.StoreVectorAsync(chunk, embedding, new { documentId = document.Id, chunkId = i });
-// }
-
-// _logger.LogInformation("Document processed successfully: {FileName}", fileName);
-// return document;
-// }
-// catch (Exception ex)
-// {
-// _logger.LogError(ex, "Error processing document: {FileName}", fileName);
-// throw;
-// }
-// }
-
-// public bool SupportsFileType(string fileExtension)
-// {
-// var supportedTypes = new List { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
-// return supportedTypes.Contains(fileExtension);
-// }
-
-// public async Task ExtractTextFromDocumentAsync(Stream fileStream, string fileExtension)
-// {
-// switch (fileExtension)
-// {
-// case ".pdf":
-// return await ExtractTextFromPdfAsync(fileStream);
-// case ".txt":
-// return await ExtractTextFromTextAsync(fileStream);
-// // Add cases for other file types as needed
-// default:
-// throw new NotSupportedException($"File type {fileExtension} is not supported for text extraction.");
-// }
-// }
-
-// private async Task ExtractTextFromPdfAsync(Stream fileStream)
-// {
-// var textChunks = new List();
-
-// using (var reader = new PdfReader(fileStream))
-// {
-// for (int i = 1; i <= reader.NumberOfPages; i++)
-// {
-// var text = PdfTextExtractor.GetTextFromPage(reader, i);
-// textChunks.Add(text);
-// }
-// }
-
-// return textChunks.ToArray();
-// }
-
-// private async Task ExtractTextFromTextAsync(Stream fileStream)
-// {
-// using (var reader = new StreamReader(fileStream))
-// {
-// var text = await reader.ReadToEndAsync();
-// // Split text into chunks (simple implementation)
-// return new[] { text };
-// }
-// }
-
-// // In a real implementation, you would implement this method
-// // private async Task GenerateEmbeddingAsync(string text)
-// // {
-// // // Call OpenAI API or other embedding service
-// // // return embedding;
-// // }
-// }
-// }
\ No newline at end of file
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using iTextSharp.text.pdf;
+using iTextSharp.text.pdf.parser;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Configuration;
+using RAG.Domain.Entities;
+using RAG.Domain.Repositories;
+using RAG.Domain.Services;
+using System.Text;
+using NPOI.XSSF.UserModel;
+using NPOI.HSSF.UserModel;
+using NPOI.SS.UserModel;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+
+namespace RAG.Infrastructure.Services
+{
+ public class DocumentProcessingService : IDocumentProcessingService
+ {
+ private readonly ILogger _logger;
+ private readonly IVectorStore _vectorStore;
+ private readonly IConfiguration _configuration;
+
+ public DocumentProcessingService(
+ ILogger logger,
+ IVectorStore vectorStore,
+ IConfiguration configuration)
+ {
+ _logger = logger;
+ _vectorStore = vectorStore;
+ _configuration = configuration;
+ }
+
+ public async Task ProcessDocumentAsync(Stream fileStream, string fileName, string contentType, int userId)
+ {
+ try
+ {
+ _logger.LogInformation("Processing document: {FileName}", fileName);
+
+ // Extract file extension
+ var fileExtension = System.IO.Path.GetExtension(fileName)?.ToLowerInvariant() ?? string.Empty;
+ if (string.IsNullOrEmpty(fileExtension))
+ {
+ throw new ArgumentException("File extension is required.");
+ }
+
+ // Check if file type is supported
+ if (!SupportsFileType(fileExtension))
+ {
+ throw new NotSupportedException($"File type {fileExtension} is not supported.");
+ }
+
+ // Extract text from document
+ var textChunks = await ExtractTextFromDocumentAsync(fileStream, fileExtension);
+
+ // Create document entity
+ var document = new Document
+ {
+ Title = System.IO.Path.GetFileNameWithoutExtension(fileName),
+ FilePath = fileName,
+ FileType = fileExtension,
+ UserId = userId,
+ Content = string.Join("\n", textChunks)
+ };
+
+ // Process each chunk for vector storage
+ for (int i = 0; i < textChunks.Length; i++)
+ {
+ var chunk = textChunks[i];
+ document.Chunks.Add(new DocumentChunk
+ {
+ Content = chunk,
+ Position = i
+ });
+
+ // 生成向量
+ var embedding = await GenerateEmbeddingAsync(chunk);
+ // 存储向量
+ await _vectorStore.StoreVectorAsync(chunk, embedding, new { documentId = document.Id, chunkId = i });
+ }
+
+ _logger.LogInformation("Document processed successfully: {FileName}", fileName);
+ return document;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing document: {FileName}", fileName);
+ throw;
+ }
+ }
+
+ public bool SupportsFileType(string fileExtension)
+ {
+ var supportedTypes = new List { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
+ return supportedTypes.Contains(fileExtension);
+ }
+
+ public async Task ExtractTextFromDocumentAsync(Stream fileStream, string fileExtension)
+ {
+ switch (fileExtension)
+ {
+ case ".pdf":
+ return await ExtractTextFromPdfAsync(fileStream);
+ case ".txt":
+ return await ExtractTextFromTxtAsync(fileStream);
+ case ".docx":
+ return await ExtractTextFromDocxAsync(fileStream);
+ case ".doc":
+ throw new NotSupportedException("Legacy .doc files are not supported. Please convert to .docx.");
+ case ".xlsx":
+ case ".xls":
+ return await ExtractTextFromExcelAsync(fileStream, fileExtension);
+ default:
+ throw new NotSupportedException($"File type {fileExtension} is not supported for text extraction.");
+ }
+ }
+
+ private async Task ExtractTextFromPdfAsync(Stream fileStream)
+ {
+ var textBuilder = new StringBuilder();
+ using (var reader = new PdfReader(fileStream))
+ {
+ for (int i = 1; i <= reader.NumberOfPages; i++)
+ {
+ textBuilder.AppendLine(PdfTextExtractor.GetTextFromPage(reader, i));
+ }
+ }
+ return SplitTextIntoChunks(textBuilder.ToString());
+ }
+
+ private async Task ExtractTextFromTxtAsync(Stream fileStream)
+ {
+ using (var reader = new StreamReader(fileStream, Encoding.UTF8))
+ {
+ var text = await reader.ReadToEndAsync();
+ return SplitTextIntoChunks(text);
+ }
+ }
+
+ private async Task ExtractTextFromDocxAsync(Stream fileStream)
+ {
+ var textBuilder = new StringBuilder();
+ using (var document = WordprocessingDocument.Open(fileStream, false))
+ {
+ var mainPart = document.MainDocumentPart;
+ if (mainPart != null)
+ {
+ var body = mainPart.Document.Body;
+ if (body != null)
+ {
+ foreach (var paragraph in body.Descendants())
+ {
+ textBuilder.AppendLine(paragraph.InnerText);
+ }
+ }
+ }
+ }
+ return SplitTextIntoChunks(textBuilder.ToString());
+ }
+
+ private async Task ExtractTextFromExcelAsync(Stream fileStream, string fileExtension)
+ {
+ var textBuilder = new StringBuilder();
+ IWorkbook workbook = null;
+
+ if (fileExtension == ".xlsx")
+ {
+ workbook = new XSSFWorkbook(fileStream);
+ }
+ else if (fileExtension == ".xls")
+ {
+ workbook = new HSSFWorkbook(fileStream);
+ }
+
+ if (workbook != null)
+ {
+ for (int sheetIndex = 0; sheetIndex < workbook.NumberOfSheets; sheetIndex++)
+ {
+ var sheet = workbook.GetSheetAt(sheetIndex);
+ textBuilder.AppendLine($"Sheet: {sheet.SheetName}");
+
+ for (int rowIndex = 0; rowIndex <= sheet.LastRowNum; rowIndex++)
+ {
+ var row = sheet.GetRow(rowIndex);
+ if (row != null)
+ {
+ for (int cellIndex = 0; cellIndex < row.LastCellNum; cellIndex++)
+ {
+ var cell = row.GetCell(cellIndex);
+ if (cell != null)
+ {
+ textBuilder.Append(cell.ToString() + "\t");
+ }
+ }
+ textBuilder.AppendLine();
+ }
+ }
+ }
+ }
+ return SplitTextIntoChunks(textBuilder.ToString());
+ }
+
+ private string[] SplitTextIntoChunks(string text, int chunkSize = 1000)
+ {
+ var chunks = new List();
+ int start = 0;
+
+ while (start < text.Length)
+ {
+ int end = Math.Min(start + chunkSize, text.Length);
+ // Try to split at sentence boundary if possible
+ if (end < text.Length)
+ {
+ int lastPeriod = text.LastIndexOf('.', end, end - start);
+ if (lastPeriod > start)
+ {
+ end = lastPeriod + 1;
+ }
+ }
+ chunks.Add(text.Substring(start, end - start).Trim());
+ start = end;
+ }
+ return chunks.ToArray();
+ }
+
+ // 添加向量生成功能(模拟实现,实际项目中应使用真实的嵌入服务)
+ private async Task GenerateEmbeddingAsync(string text)
+ {
+ // 这里是模拟实现,实际项目中应调用OpenAI或其他嵌入服务
+ // 示例使用随机向量作为占位符
+ var random = new Random();
+ var embedding = new float[1536]; // 假设使用1536维度的向量
+ for (int i = 0; i < embedding.Length; i++)
+ {
+ embedding[i] = (float)(random.NextDouble() * 2 - 1); // 范围 [-1, 1)
+ }
+ return embedding;
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs b/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs
index 926bfa4..6229963 100644
--- a/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs
+++ b/backend/RAG.Infrastructure/VectorStore/PgVectorStore.cs
@@ -1,109 +1,111 @@
-// using System.Data;
-// using Microsoft.Extensions.Configuration;
-// using Npgsql;
-// using RAG.Domain.Repositories;
-// using RAG.Domain.Entities;
-
-// namespace RAG.Infrastructure.VectorStore
-// {
-// public class PgVectorStore : IVectorStore
-// {
-// private readonly string _connectionString;
-// private readonly string _tableName;
-
-// public PgVectorStore(IConfiguration configuration)
-// {
-// _connectionString = configuration.GetSection("VectorStore:ConnectionString").Value;
-// _tableName = configuration.GetSection("VectorStore:TableName").Value ?? "vectors";
-// }
-
-// public async Task InitializeAsync()
-// {
-// // Create extension if not exists
-// await ExecuteNonQueryAsync("CREATE EXTENSION IF NOT EXISTS vector");
-
-// // Create table if not exists
-// await ExecuteNonQueryAsync($@"CREATE TABLE IF NOT EXISTS {_tableName} (
-// id SERIAL PRIMARY KEY,
-// content TEXT NOT NULL,
-// metadata JSONB,
-// embedding VECTOR(1536) NOT NULL
-// )");
-
-// // Create index if not exists
-// await ExecuteNonQueryAsync($@"CREATE INDEX IF NOT EXISTS {_tableName}_embedding_idx ON {_tableName} USING ivfflat (embedding vector_cosine_ops)");
-// }
-
-// public async Task StoreVectorAsync(string content, float[] embedding, object metadata = null)
-// {
-// var metadataJson = metadata != null ? System.Text.Json.JsonSerializer.Serialize(metadata) : null;
-
-// await ExecuteNonQueryAsync($@"INSERT INTO {_tableName} (content, metadata, embedding)
-// VALUES (@content, @metadata, @embedding)",
-// new NpgsqlParameter("@content", content),
-// new NpgsqlParameter("@metadata", metadataJson ?? (object)DBNull.Value),
-// new NpgsqlParameter("@embedding", NpgsqlTypes.NpgsqlDbType.Vector) { Value = embedding });
-// }
-
-// public async Task> SearchVectorsAsync(float[] queryVector, int topK = 5)
-// {
-// var results = new List();
-
-// using (var connection = new NpgsqlConnection(_connectionString))
-// {
-// await connection.OpenAsync();
-
-// using (var command = new NpgsqlCommand($@"SELECT id, content, metadata, embedding <=> @queryVector AS similarity
-// FROM {_tableName}
-// ORDER BY similarity
-// LIMIT @topK", connection))
-// {
-// command.Parameters.AddWithValue("@queryVector", NpgsqlTypes.NpgsqlDbType.Vector, queryVector);
-// command.Parameters.AddWithValue("@topK", topK);
-
-// using (var reader = await command.ExecuteReaderAsync())
-// {
-// while (await reader.ReadAsync())
-// {
-// results.Add(new VectorResult
-// {
-// Id = reader.GetInt32(0),
-// Content = reader.GetString(1),
-// Metadata = reader.IsDBNull(2) ? null : System.Text.Json.JsonSerializer.Deserialize>(reader.GetString(2)),
-// Similarity = reader.GetDouble(3)
-// });
-// }
-// }
-// }
-// }
-
-// return results;
-// }
-
-// private async Task ExecuteNonQueryAsync(string sql, params NpgsqlParameter[] parameters)
-// {
-// using (var connection = new NpgsqlConnection(_connectionString))
-// {
-// await connection.OpenAsync();
-
-// using (var command = new NpgsqlCommand(sql, connection))
-// {
-// if (parameters != null)
-// {
-// command.Parameters.AddRange(parameters);
-// }
-
-// await command.ExecuteNonQueryAsync();
-// }
-// }
-// }
-// }
-
-// public class VectorResult
-// {
-// public int Id { get; set; }
-// public string Content { get; set; }
-// public Dictionary Metadata { get; set; }
-// public double Similarity { get; set; }
-// }
-// }
\ No newline at end of file
+using System.Data;
+using Microsoft.Extensions.Configuration;
+using Npgsql;
+using RAG.Domain.Repositories;
+using RAG.Domain.Entities;
+
+namespace RAG.Infrastructure.VectorStore
+{
+ public class PgVectorStore : IVectorStore
+
+
+ {
+ private readonly string _connectionString;
+ private readonly string _tableName;
+
+ public PgVectorStore(IConfiguration configuration)
+ {
+ _connectionString = configuration.GetSection("VectorStore:ConnectionString").Value;
+ _tableName = configuration.GetSection("VectorStore:TableName").Value ?? "vectors";
+ }
+
+ public async Task InitializeAsync()
+ {
+ // Create extension if not exists
+ await ExecuteNonQueryAsync("CREATE EXTENSION IF NOT EXISTS vector");
+
+ // Create table if not exists
+ await ExecuteNonQueryAsync($@"CREATE TABLE IF NOT EXISTS {_tableName} (
+ id SERIAL PRIMARY KEY,
+ content TEXT NOT NULL,
+ metadata JSONB,
+ embedding VECTOR(1536) NOT NULL
+ )");
+
+ // Create index if not exists
+ await ExecuteNonQueryAsync($@"CREATE INDEX IF NOT EXISTS {_tableName}_embedding_idx ON {_tableName} USING ivfflat (embedding vector_cosine_ops)");
+ }
+
+ public async Task StoreVectorAsync(string content, float[] embedding, object metadata = null)
+ {
+ var metadataJson = metadata != null ? System.Text.Json.JsonSerializer.Serialize(metadata) : null;
+
+ await ExecuteNonQueryAsync($@"INSERT INTO {_tableName} (content, metadata, embedding)
+ VALUES (@content, @metadata, @embedding)",
+ new NpgsqlParameter("@content", content),
+ new NpgsqlParameter("@metadata", metadataJson ?? (object)DBNull.Value),
+ new NpgsqlParameter("@embedding", NpgsqlTypes.NpgsqlDbType.Vector) { Value = embedding });
+ }
+
+ public async Task> SearchVectorsAsync(float[] queryVector, int topK = 5)
+ {
+ var results = new List();
+
+ using (var connection = new NpgsqlConnection(_connectionString))
+ {
+ await connection.OpenAsync();
+
+ using (var command = new NpgsqlCommand($@"SELECT id, content, metadata, embedding <=> @queryVector AS similarity
+ FROM {_tableName}
+ ORDER BY similarity
+ LIMIT @topK", connection))
+ {
+ command.Parameters.AddWithValue("@queryVector", NpgsqlTypes.NpgsqlDbType.Vector, queryVector);
+ command.Parameters.AddWithValue("@topK", topK);
+
+ using (var reader = await command.ExecuteReaderAsync())
+ {
+ while (await reader.ReadAsync())
+ {
+ results.Add(new VectorResult
+ {
+ Id = reader.GetInt32(0),
+ Content = reader.GetString(1),
+ Metadata = reader.IsDBNull(2) ? null : System.Text.Json.JsonSerializer.Deserialize>(reader.GetString(2)),
+ Similarity = reader.GetDouble(3)
+ });
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+
+ private async Task ExecuteNonQueryAsync(string sql, params NpgsqlParameter[] parameters)
+ {
+ using (var connection = new NpgsqlConnection(_connectionString))
+ {
+ await connection.OpenAsync();
+
+ using (var command = new NpgsqlCommand(sql, connection))
+ {
+ if (parameters != null)
+ {
+ command.Parameters.AddRange(parameters);
+ }
+
+ await command.ExecuteNonQueryAsync();
+ }
+ }
+ }
+ }
+
+ public class VectorResult
+ {
+ public int Id { get; set; }
+ public string Content { get; set; }
+ public Dictionary Metadata { get; set; }
+ public double Similarity { get; set; }
+ }
+}
\ No newline at end of file
--
Gitee
From 29dd0872036aba53d6ba3115b6220c16a99807d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B0=A2=E9=9B=A8=E6=99=B4?= <1207713896@qq.com>
Date: Tue, 5 Aug 2025 15:44:10 +0800
Subject: [PATCH 04/31] =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=AE=BE?=
=?UTF-8?q?=E8=AE=A1=E6=88=90=E5=8A=9F=EF=BC=8C=E5=8F=AF=E4=BB=A5=E7=94=9F?=
=?UTF-8?q?=E6=88=90=E6=95=B0=E6=8D=AE=E5=BA=93=EF=BC=8C=E4=BD=86=E8=BF=98?=
=?UTF-8?q?=E6=9C=AA=E8=BF=9E=E6=8E=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../RAG.Api/Controllers/DocumentController.cs | 58 ---
backend/RAG.Api/Program.cs | 62 ++-
backend/RAG.Api/RAG.Api.csproj | 8 +-
backend/RAG.Api/appsettings.json | 2 +-
.../Commands/UploadDocumentCommand.cs | 8 -
.../Handlers/UploadDocumentHandler.cs | 66 ---
.../RAG.Application/RAG.Application.csproj | 5 +-
backend/RAG.Domain/Entities/ChatHistory.cs | 80 +++-
backend/RAG.Domain/Entities/Document.cs | 87 ++--
backend/RAG.Domain/Entities/DocumentChunk.cs | 41 ++
backend/RAG.Domain/Entities/DocumentStatus.cs | 10 +
.../Entities/{BaseEntity.cs => EntityBase.cs} | 8 +-
backend/RAG.Domain/Entities/User.cs | 39 +-
.../RAG.Domain/Events/DocumentCreatedEvent.cs | 15 +
.../DocumentProcessingCompletedEvent.cs | 15 +
.../Events/DocumentProcessingStartedEvent.cs | 15 +
backend/RAG.Domain/RAG.Domain.csproj | 4 +-
.../Repositories/IChatHistoryRepository.cs | 12 +
.../Repositories/IDocumentChunkRepository.cs | 12 +
.../Repositories/IDocumentRepository.cs | 13 +
.../RAG.Domain/Repositories/IUnitOfWork.cs | 7 +
.../Repositories/IUserRepository.cs | 10 +
.../RAG.Domain/ValueObjects/AccessLevel.cs | 27 --
.../RAG.Domain/ValueObjects/DocumentTitle.cs | 25 -
backend/RAG.Domain/ValueObjects/FileName.cs | 25 -
backend/RAG.Domain/ValueObjects/FilePath.cs | 22 -
backend/RAG.Domain/ValueObjects/FileSize.cs | 22 -
backend/RAG.Domain/ValueObjects/FileType.cs | 22 -
.../Data/ApplicationDbContext.cs | 160 ++++++-
.../20250805073332_InitialCreate.Designer.cs | 427 ++++++++++++++++++
.../20250805073332_InitialCreate.cs | 324 +++++++++++++
.../ApplicationDbContextModelSnapshot.cs | 424 +++++++++++++++++
.../RAG.Infrastructure.csproj | 18 +-
.../Repositories/ApplicationUnitOfWork.cs | 34 --
.../Repositories/Repository.cs | 68 ---
.../Services/DocumentProcessingService.cs | 244 ----------
.../VectorStore/PgVectorStore.cs | 111 -----
.../RAG.Tests/DomainTests/DocumentTests.cs | 0
backend/RAG.Tests/RAG.Tests.csproj | 5 +-
39 files changed, 1657 insertions(+), 878 deletions(-)
create mode 100644 backend/RAG.Domain/Entities/DocumentChunk.cs
create mode 100644 backend/RAG.Domain/Entities/DocumentStatus.cs
rename backend/RAG.Domain/Entities/{BaseEntity.cs => EntityBase.cs} (67%)
create mode 100644 backend/RAG.Domain/Events/DocumentCreatedEvent.cs
create mode 100644 backend/RAG.Domain/Events/DocumentProcessingCompletedEvent.cs
create mode 100644 backend/RAG.Domain/Events/DocumentProcessingStartedEvent.cs
create mode 100644 backend/RAG.Domain/Repositories/IChatHistoryRepository.cs
create mode 100644 backend/RAG.Domain/Repositories/IDocumentChunkRepository.cs
create mode 100644 backend/RAG.Domain/Repositories/IDocumentRepository.cs
create mode 100644 backend/RAG.Domain/Repositories/IUserRepository.cs
delete mode 100644 backend/RAG.Domain/ValueObjects/AccessLevel.cs
delete mode 100644 backend/RAG.Domain/ValueObjects/DocumentTitle.cs
delete mode 100644 backend/RAG.Domain/ValueObjects/FileName.cs
delete mode 100644 backend/RAG.Domain/ValueObjects/FilePath.cs
delete mode 100644 backend/RAG.Domain/ValueObjects/FileSize.cs
delete mode 100644 backend/RAG.Domain/ValueObjects/FileType.cs
create mode 100644 backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.Designer.cs
create mode 100644 backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.cs
create mode 100644 backend/RAG.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs
create mode 100644 backend/RAG.Tests/DomainTests/DocumentTests.cs
diff --git a/backend/RAG.Api/Controllers/DocumentController.cs b/backend/RAG.Api/Controllers/DocumentController.cs
index 766cee6..e69de29 100644
--- a/backend/RAG.Api/Controllers/DocumentController.cs
+++ b/backend/RAG.Api/Controllers/DocumentController.cs
@@ -1,58 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
-using MediatR;
-using RAG.Application.Commands;
-using RAG.Domain.ValueObjects;
-using Microsoft.Extensions.Logging;
-
-namespace RAG.Api.Controllers
-{
- [ApiController]
- [Route("api/[controller]")]
- public class DocumentController : ControllerBase
- {
- private readonly IMediator _mediator;
- private readonly ILogger _logger;
-
- public DocumentController(
- IMediator mediator,
- ILogger logger)
- {
- _mediator = mediator;
- _logger = logger;
- }
-
- [HttpPost("upload")]
- public async Task UploadDocument(IFormFile file, [FromForm] string accessLevel = "internal")
- {
- try
- {
- if (file == null || file.Length == 0)
- {
- return BadRequest("No file uploaded.");
- }
-
- // 创建访问级别值对象
- var accessLevelObj = AccessLevel.Create(accessLevel);
-
- // 发送上传文档命令
- var documentId = await _mediator.Send(new UploadDocumentCommand(file, accessLevelObj));
-
- return Ok(new { DocumentId = documentId });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error uploading document");
- return StatusCode(500, "Internal server error");
- }
- }
-
- [HttpGet("supported-types")]
- public IActionResult GetSupportedFileTypes()
- {
- var supportedTypes = new[] { ".pdf", ".txt", ".docx", ".doc", ".xlsx", ".xls" };
- return Ok(supportedTypes);
- }
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Api/Program.cs b/backend/RAG.Api/Program.cs
index b21db8c..ac60a97 100644
--- a/backend/RAG.Api/Program.cs
+++ b/backend/RAG.Api/Program.cs
@@ -1,53 +1,44 @@
using Microsoft.AspNetCore.Builder;
-using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using RAG.Application.Commands;
-using RAG.Application.Handlers;
-using RAG.Domain.Repositories;
-using RAG.Domain.Services;
-using RAG.Infrastructure.Data;
-using RAG.Infrastructure.Repositories;
-using RAG.Infrastructure.Services;
-using RAG.Infrastructure.VectorStore;
+using Microsoft.EntityFrameworkCore;
using MediatR;
+using RAG.Infrastructure.Data;
using System.Reflection;
+
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
-builder.Services.AddControllers();
-
-// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
-builder.Services.AddOpenApi();
+// 配置数据库上下文
+builder.Services.AddDbContext(options =>
+ options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
-// Add MediatR
-builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(UploadDocumentCommand).Assembly));
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
-builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(UploadDocumentHandler).Assembly));
+// 配置MediatR
+builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
+builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(RAG.Application.Class1).Assembly));
-// Add DbContext
-builder.Services.AddDbContext(options =>
- options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
+// 配置AutoMapper
+builder.Services.AddAutoMapper(cfg => cfg.AddMaps(Assembly.GetExecutingAssembly()));
+builder.Services.AddAutoMapper(cfg => cfg.AddMaps(typeof(RAG.Application.Class1).Assembly));
-// Add Repositories and Unit of Work
-builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
-builder.Services.AddScoped();
-// Add Services
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-// Add CORS
builder.Services.AddCors(options =>
{
- options.AddPolicy("AllowAll", policy =>
- {
- policy.AllowAnyOrigin()
- .AllowAnyMethod()
- .AllowAnyHeader();
- });
+ options.AddPolicy("AllowAll",
+ builder =>
+ {
+ builder.AllowAnyOrigin()
+ .AllowAnyMethod()
+ .AllowAnyHeader();
+ });
});
var app = builder.Build();
@@ -55,15 +46,18 @@ var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
- app.MapOpenApi();
+ app.UseSwagger();
+ app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
+app.UseAuthorization();
+
app.MapControllers();
app.Run();
-public partial class Program {}
+public partial class Program { }
\ No newline at end of file
diff --git a/backend/RAG.Api/RAG.Api.csproj b/backend/RAG.Api/RAG.Api.csproj
index e7b12a3..2a17132 100644
--- a/backend/RAG.Api/RAG.Api.csproj
+++ b/backend/RAG.Api/RAG.Api.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net8.0
enable
enable
@@ -9,9 +9,9 @@
-
-
-
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/backend/RAG.Api/appsettings.json b/backend/RAG.Api/appsettings.json
index 147f30e..f7852dc 100644
--- a/backend/RAG.Api/appsettings.json
+++ b/backend/RAG.Api/appsettings.json
@@ -7,7 +7,7 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
- "DefaultConnection": "Host=localhost;Port=5432;Database=rag_db;Username=postgres;Password=your_password_here"
+ "DefaultConnection": "Host=localhost;port=5432;Database=rag_db;Username=postgres;Password=xyq0217"
},
"VectorStore": {
"Type": "Postgres",
diff --git a/backend/RAG.Application/Commands/UploadDocumentCommand.cs b/backend/RAG.Application/Commands/UploadDocumentCommand.cs
index f9cb20f..e69de29 100644
--- a/backend/RAG.Application/Commands/UploadDocumentCommand.cs
+++ b/backend/RAG.Application/Commands/UploadDocumentCommand.cs
@@ -1,8 +0,0 @@
-using MediatR;
-using Microsoft.AspNetCore.Http;
-using RAG.Domain.ValueObjects;
-
-namespace RAG.Application.Commands
-{
- public record UploadDocumentCommand(IFormFile File, AccessLevel AccessLevel) : IRequest;
-}
\ No newline at end of file
diff --git a/backend/RAG.Application/Handlers/UploadDocumentHandler.cs b/backend/RAG.Application/Handlers/UploadDocumentHandler.cs
index e0c7ef4..e69de29 100644
--- a/backend/RAG.Application/Handlers/UploadDocumentHandler.cs
+++ b/backend/RAG.Application/Handlers/UploadDocumentHandler.cs
@@ -1,66 +0,0 @@
-using MediatR;
-using Microsoft.Extensions.Logging;
-using RAG.Application.Commands;
-using RAG.Domain.Entities;
-using RAG.Domain.Repositories;
-using RAG.Domain.Services;
-using RAG.Domain.ValueObjects;
-
-namespace RAG.Application.Handlers
-{
- public class UploadDocumentHandler : IRequestHandler
- {
- private readonly IDocumentProcessingService _documentProcessingService;
- private readonly IRepository _documentRepository;
- private readonly IUnitOfWork _unitOfWork;
- private readonly ILogger _logger;
-
- public UploadDocumentHandler(
- IDocumentProcessingService documentProcessingService,
- IRepository documentRepository,
- IUnitOfWork unitOfWork,
- ILogger logger)
- {
- _documentProcessingService = documentProcessingService;
- _documentRepository = documentRepository;
- _unitOfWork = unitOfWork;
- _logger = logger;
- }
-
- public async Task Handle(UploadDocumentCommand request, CancellationToken cancellationToken)
- {
- try
- {
- _logger.LogInformation("Handling document upload request");
-
- // 获取当前用户ID(实际应用中应从身份验证上下文中获取)
- var userId = 1; // 占位符
-
- // 使用文档处理服务处理上传的文件
- using (var stream = request.File.OpenReadStream())
- {
- var document = await _documentProcessingService.ProcessDocumentAsync(
- stream,
- request.File.FileName,
- request.File.ContentType,
- userId);
-
- // 设置文档访问级别
- document.AccessLevel = request.AccessLevel.Value;
-
- // 保存文档到数据库
- await _documentRepository.AddAsync(document);
- await _unitOfWork.SaveChangesAsync();
-
- _logger.LogInformation("Document uploaded successfully with ID: {DocumentId}", document.Id);
- return document.Id;
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error handling document upload request");
- throw;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Application/RAG.Application.csproj b/backend/RAG.Application/RAG.Application.csproj
index 748d7dd..c826abf 100644
--- a/backend/RAG.Application/RAG.Application.csproj
+++ b/backend/RAG.Application/RAG.Application.csproj
@@ -1,4 +1,4 @@
-
+
@@ -8,10 +8,11 @@
+
- net9.0
+ net8.0
enable
enable
diff --git a/backend/RAG.Domain/Entities/ChatHistory.cs b/backend/RAG.Domain/Entities/ChatHistory.cs
index 5c7c5bd..d6e2ac8 100644
--- a/backend/RAG.Domain/Entities/ChatHistory.cs
+++ b/backend/RAG.Domain/Entities/ChatHistory.cs
@@ -1,37 +1,71 @@
using System;
using System.Collections.Generic;
+using System;
namespace RAG.Domain.Entities
{
- public class ChatHistory
+ public class ChatHistory : EntityBase
{
- public int Id { get; set; }
- public string SessionId { get; set; }
- public int UserId { get; set; }
- public User User { get; set; }
- public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
- public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
- public ICollection Messages { get; set; } = new List();
+ private ChatHistory() { }
+
+ public static ChatHistory Create(long userId, string sessionId)
+ {
+ return new ChatHistory
+ {
+ Id = new Random().NextInt64(1, long.MaxValue),
+ SessionId = sessionId,
+ UserId = userId
+ };
+ }
+
+ public string SessionId { get; private set; }
+ public long UserId { get; private set; }
+ public User User { get; private set; }
+ public ICollection Messages { get; private set; } = new List();
}
- public class ChatMessage
+ public class ChatMessage : EntityBase
{
- public int Id { get; set; }
- public int ChatHistoryId { get; set; }
- public ChatHistory ChatHistory { get; set; }
- public string Content { get; set; }
- public bool IsUserMessage { get; set; }
- public DateTime Timestamp { get; set; } = DateTime.UtcNow;
- public ICollection References { get; set; } = new List();
+ private ChatMessage() { }
+
+ public static ChatMessage Create(long chatHistoryId, string content, bool isUserMessage)
+ {
+ return new ChatMessage
+ {
+ Id = new Random().NextInt64(1, long.MaxValue),
+ ChatHistoryId = chatHistoryId,
+ Content = content,
+ IsUserMessage = isUserMessage
+ };
+ }
+
+ public long ChatHistoryId { get; private set; }
+ public ChatHistory ChatHistory { get; private set; }
+ public string Content { get; private set; }
+ public bool IsUserMessage { get; private set; }
+ public DateTime Timestamp { get; private set; } = DateTime.UtcNow;
+ public ICollection References { get; private set; } = new List();
}
- public class MessageReference
+ public class MessageReference : EntityBase
{
- public int Id { get; set; }
- public int ChatMessageId { get; set; }
- public ChatMessage ChatMessage { get; set; }
- public int DocumentChunkId { get; set; }
- public DocumentChunk DocumentChunk { get; set; }
- public double RelevanceScore { get; set; }
+ private MessageReference() { }
+
+ public static MessageReference Create(long chatMessageId, long documentChunkId, double relevanceScore)
+ {
+ return new MessageReference
+ {
+ Id = new Random().NextInt64(1, long.MaxValue),
+ ChatMessageId = chatMessageId,
+ DocumentChunkId = documentChunkId,
+ RelevanceScore = relevanceScore
+ };
+ }
+
+ public long ChatMessageId { get; private set; }
+ public ChatMessage ChatMessage { get; private set; }
+ public long DocumentChunkId { get; private set; }
+ public DocumentChunk DocumentChunk { get; private set; }
+ public double RelevanceScore { get; private set; }
}
}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/Document.cs b/backend/RAG.Domain/Entities/Document.cs
index 1cb36ed..52d5864 100644
--- a/backend/RAG.Domain/Entities/Document.cs
+++ b/backend/RAG.Domain/Entities/Document.cs
@@ -1,11 +1,10 @@
+// RAG.Domain/Entities/Document.cs
using System;
-using System.Collections.Generic;
-using RAG.Domain.ValueObjects;
using RAG.Domain.Events;
namespace RAG.Domain.Entities
{
- public class Document : BaseEntity
+ public class Document : EntityBase
{
private readonly List _chunks = new();
@@ -14,24 +13,36 @@ namespace RAG.Domain.Entities
// 工厂方法
public static Document Create(
- DocumentTitle title,
- FileName fileName,
- FilePath filePath,
- FileType fileType,
- FileSize fileSize,
- AccessLevel accessLevel)
+ string title,
+ string fileName,
+ string filePath,
+ string fileType,
+ long fileSize,
+ string accessLevel)
{
+ if (string.IsNullOrWhiteSpace(title))
+ throw new ArgumentException("Title cannot be empty");
+ if (string.IsNullOrWhiteSpace(fileName))
+ throw new ArgumentException("File name cannot be empty");
+ if (string.IsNullOrWhiteSpace(filePath))
+ throw new ArgumentException("File path cannot be empty");
+ if (string.IsNullOrWhiteSpace(fileType))
+ throw new ArgumentException("File type cannot be empty");
+ if (fileSize < 0)
+ throw new ArgumentException("File size cannot be negative");
+ if (string.IsNullOrWhiteSpace(accessLevel) || (accessLevel.ToLower() != "internal" && accessLevel.ToLower() != "public"))
+ throw new ArgumentException("Access level must be 'internal' or 'public'");
+
var document = new Document
{
+ Id = new Random().NextInt64(1, long.MaxValue),
Title = title,
FileName = fileName,
FilePath = filePath,
FileType = fileType,
FileSize = fileSize,
- AccessLevel = accessLevel,
- Status = "pending",
- CreatedAt = DateTime.UtcNow,
- UpdatedAt = DateTime.UtcNow
+ AccessLevel = accessLevel.ToLower(),
+ Status = DocumentStatus.Pending
};
// 发布领域事件
@@ -40,30 +51,24 @@ namespace RAG.Domain.Entities
return document;
}
- public int Id { get; private set; }
- public DocumentTitle Title { get; private set; }
- public FileName FileName { get; private set; }
- public FilePath FilePath { get; private set; }
- public FileType FileType { get; private set; }
- public FileSize FileSize { get; private set; }
- public string Description { get; set; }
- public string Tags { get; set; }
- public AccessLevel AccessLevel { get; private set; }
- public string Status { get; private set; }
- public DateTime CreatedAt { get; private set; }
- public DateTime UpdatedAt { get; private set; }
- public int UserId { get; set; }
- public User User { get; set; }
+ public long Id { get; private set; }
+ public string Title { get; private set; }
+ public string FileName { get; private set; }
+ public string FilePath { get; private set; }
+ public string FileType { get; private set; }
+ public long FileSize { get; private set; }
+ public string AccessLevel { get; private set; }
+ public DocumentStatus Status { get; private set; }
public IReadOnlyList Chunks => _chunks.AsReadOnly();
// 业务方法
public void UpdateProcessingStatus()
{
- if (Status != "pending")
+ if (Status != DocumentStatus.Pending)
throw new InvalidOperationException("Document must be in pending status");
- Status = "processing";
+ Status = DocumentStatus.Processing;
UpdatedAt = DateTime.UtcNow;
AddDomainEvent(new DocumentProcessingStartedEvent(Id));
@@ -71,10 +76,10 @@ namespace RAG.Domain.Entities
public void CompleteProcessing()
{
- if (Status != "processing")
+ if (Status != DocumentStatus.Processing)
throw new InvalidOperationException("Document must be in processing status");
- Status = "completed";
+ Status = DocumentStatus.Completed;
UpdatedAt = DateTime.UtcNow;
AddDomainEvent(new DocumentProcessingCompletedEvent(Id));
@@ -88,24 +93,4 @@ namespace RAG.Domain.Entities
_chunks.Add(chunk);
}
}
-
- public class DocumentChunk
- {
- public int Id { get; set; }
- public int DocumentId { get; set; }
- public Document Document { get; set; }
- public string Content { get; set; }
- public int Position { get; set; }
- public int TokenCount { get; set; }
- public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
- }
-
- public class Vector
- {
- public int Id { get; set; }
- public int ChunkId { get; set; }
- public DocumentChunk Chunk { get; set; }
- public float[] Embedding { get; set; }
- public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
- }
}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/DocumentChunk.cs b/backend/RAG.Domain/Entities/DocumentChunk.cs
new file mode 100644
index 0000000..c69c10e
--- /dev/null
+++ b/backend/RAG.Domain/Entities/DocumentChunk.cs
@@ -0,0 +1,41 @@
+
+
+using System;
+
+namespace RAG.Domain.Entities
+{
+ public class DocumentChunk : EntityBase
+ {
+ private DocumentChunk() { }
+
+ public static DocumentChunk Create(
+ long documentId,
+ string content,
+ int position,
+ int tokenCount)
+ {
+ if (documentId <= 0)
+ throw new ArgumentException("Document ID must be greater than 0");
+ if (string.IsNullOrWhiteSpace(content))
+ throw new ArgumentException("Content cannot be empty");
+ if (position < 0)
+ throw new ArgumentException("Position cannot be negative");
+ if (tokenCount < 0)
+ throw new ArgumentException("Token count cannot be negative");
+
+ return new DocumentChunk
+ {
+ Id = new Random().NextInt64(1, long.MaxValue),
+ DocumentId = documentId,
+ Content = content,
+ Position = position,
+ TokenCount = tokenCount
+ };
+ }
+
+ public long DocumentId { get; private set; }
+ public string Content { get; private set; }
+ public int Position { get; private set; }
+ public int TokenCount { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/DocumentStatus.cs b/backend/RAG.Domain/Entities/DocumentStatus.cs
new file mode 100644
index 0000000..5aec26f
--- /dev/null
+++ b/backend/RAG.Domain/Entities/DocumentStatus.cs
@@ -0,0 +1,10 @@
+namespace RAG.Domain.Entities
+{
+ public enum DocumentStatus
+ {
+ Pending,
+ Processing,
+ Completed,
+ Failed
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Entities/BaseEntity.cs b/backend/RAG.Domain/Entities/EntityBase.cs
similarity index 67%
rename from backend/RAG.Domain/Entities/BaseEntity.cs
rename to backend/RAG.Domain/Entities/EntityBase.cs
index 092db55..2a2ab07 100644
--- a/backend/RAG.Domain/Entities/BaseEntity.cs
+++ b/backend/RAG.Domain/Entities/EntityBase.cs
@@ -1,17 +1,21 @@
-using System;
using System.Collections.Generic;
namespace RAG.Domain.Entities
{
- public abstract class BaseEntity
+ public abstract class EntityBase
{
private readonly List _domainEvents = new();
+ public long Id { get; protected set; }
+ public DateTime CreatedAt { get; protected set; } = DateTime.UtcNow;
+ public DateTime UpdatedAt { get; protected set; } = DateTime.UtcNow;
+
public IReadOnlyList DomainEvents => _domainEvents.AsReadOnly();
protected void AddDomainEvent(DomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
+ UpdatedAt = DateTime.UtcNow;
}
public void ClearDomainEvents()
diff --git a/backend/RAG.Domain/Entities/User.cs b/backend/RAG.Domain/Entities/User.cs
index b7787a3..b78bf42 100644
--- a/backend/RAG.Domain/Entities/User.cs
+++ b/backend/RAG.Domain/Entities/User.cs
@@ -1,19 +1,36 @@
using System;
using System.Collections.Generic;
+using System;
namespace RAG.Domain.Entities
{
- public class User
+ public class User : EntityBase
{
- public int Id { get; set; }
- public string Username { get; set; }
- public string Email { get; set; }
- public string PasswordHash { get; set; }
- public string FullName { get; set; }
- public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
- public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
- public bool IsActive { get; set; } = true;
- public ICollection Documents { get; set; } = new List();
- public ICollection ChatHistories { get; set; } = new List();
+ private User() { }
+
+ public static User Create(
+ string username,
+ string email,
+ string passwordHash,
+ string fullName)
+ {
+ return new User
+ {
+ Id = new Random().NextInt64(1, long.MaxValue),
+ Username = username,
+ Email = email,
+ PasswordHash = passwordHash,
+ FullName = fullName,
+ IsActive = true
+ };
+ }
+
+ public string Username { get; private set; }
+ public string Email { get; private set; }
+ public string PasswordHash { get; private set; }
+ public string FullName { get; private set; }
+ public bool IsActive { get; private set; }
+ public ICollection Documents { get; private set; } = new List();
+ public ICollection ChatHistories { get; private set; } = new List();
}
}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Events/DocumentCreatedEvent.cs b/backend/RAG.Domain/Events/DocumentCreatedEvent.cs
new file mode 100644
index 0000000..3ef12d0
--- /dev/null
+++ b/backend/RAG.Domain/Events/DocumentCreatedEvent.cs
@@ -0,0 +1,15 @@
+using RAG.Domain.Entities;
+namespace RAG.Domain.Events
+{
+ public class DocumentCreatedEvent : DomainEvent
+ {
+ public long DocumentId { get; }
+
+ public DocumentCreatedEvent(long documentId)
+ {
+ if (documentId <= 0)
+ throw new ArgumentException("Document ID must be greater than 0");
+ DocumentId = documentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Events/DocumentProcessingCompletedEvent.cs b/backend/RAG.Domain/Events/DocumentProcessingCompletedEvent.cs
new file mode 100644
index 0000000..cd7258a
--- /dev/null
+++ b/backend/RAG.Domain/Events/DocumentProcessingCompletedEvent.cs
@@ -0,0 +1,15 @@
+using RAG.Domain.Entities;
+namespace RAG.Domain.Events
+{
+ public class DocumentProcessingCompletedEvent : DomainEvent
+ {
+ public long DocumentId { get; }
+
+ public DocumentProcessingCompletedEvent(long documentId)
+ {
+ if (documentId <= 0)
+ throw new ArgumentException("Document ID must be greater than 0");
+ DocumentId = documentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Events/DocumentProcessingStartedEvent.cs b/backend/RAG.Domain/Events/DocumentProcessingStartedEvent.cs
new file mode 100644
index 0000000..8fa972c
--- /dev/null
+++ b/backend/RAG.Domain/Events/DocumentProcessingStartedEvent.cs
@@ -0,0 +1,15 @@
+using RAG.Domain.Entities;
+namespace RAG.Domain.Events
+{
+ public class DocumentProcessingStartedEvent : DomainEvent
+ {
+ public long DocumentId { get; }
+
+ public DocumentProcessingStartedEvent(long documentId)
+ {
+ if (documentId <= 0)
+ throw new ArgumentException("Document ID must be greater than 0");
+ DocumentId = documentId;
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/RAG.Domain.csproj b/backend/RAG.Domain/RAG.Domain.csproj
index 125f4c9..30402ac 100644
--- a/backend/RAG.Domain/RAG.Domain.csproj
+++ b/backend/RAG.Domain/RAG.Domain.csproj
@@ -1,7 +1,7 @@
-
+
- net9.0
+ net8.0
enable
enable
diff --git a/backend/RAG.Domain/Repositories/IChatHistoryRepository.cs b/backend/RAG.Domain/Repositories/IChatHistoryRepository.cs
new file mode 100644
index 0000000..ef230eb
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IChatHistoryRepository.cs
@@ -0,0 +1,12 @@
+using RAG.Domain.Entities;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IChatHistoryRepository : IRepository
+ {
+ Task> GetByUserIdAsync(long userId);
+ Task CountByUserIdAsync(long userId);
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Repositories/IDocumentChunkRepository.cs b/backend/RAG.Domain/Repositories/IDocumentChunkRepository.cs
new file mode 100644
index 0000000..d74f7a7
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IDocumentChunkRepository.cs
@@ -0,0 +1,12 @@
+using RAG.Domain.Entities;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IDocumentChunkRepository : IRepository
+ {
+ Task> GetByDocumentIdAsync(long documentId);
+ Task CountByDocumentIdAsync(long documentId);
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Repositories/IDocumentRepository.cs b/backend/RAG.Domain/Repositories/IDocumentRepository.cs
new file mode 100644
index 0000000..86f8eba
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IDocumentRepository.cs
@@ -0,0 +1,13 @@
+using RAG.Domain.Entities;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IDocumentRepository : IRepository
+ {
+ Task> GetByStatusAsync(DocumentStatus status);
+ Task> GetByAccessLevelAsync(string accessLevel);
+ Task CountByStatusAsync(DocumentStatus status);
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Repositories/IUnitOfWork.cs b/backend/RAG.Domain/Repositories/IUnitOfWork.cs
index 20429ac..b655f93 100644
--- a/backend/RAG.Domain/Repositories/IUnitOfWork.cs
+++ b/backend/RAG.Domain/Repositories/IUnitOfWork.cs
@@ -1,10 +1,17 @@
using System.Threading.Tasks;
+using RAG.Domain.Entities;
+
namespace RAG.Domain.Repositories
{
public interface IUnitOfWork
{
IRepository GetRepository() where TEntity : class;
Task SaveChangesAsync();
+
+ IUserRepository Users { get; }
+ IDocumentRepository Documents { get; }
+ IChatHistoryRepository ChatHistories { get; }
+ IDocumentChunkRepository DocumentChunks { get; }
}
}
\ No newline at end of file
diff --git a/backend/RAG.Domain/Repositories/IUserRepository.cs b/backend/RAG.Domain/Repositories/IUserRepository.cs
new file mode 100644
index 0000000..add4a52
--- /dev/null
+++ b/backend/RAG.Domain/Repositories/IUserRepository.cs
@@ -0,0 +1,10 @@
+using RAG.Domain.Entities;
+
+namespace RAG.Domain.Repositories
+{
+ public interface IUserRepository : IRepository
+ {
+ Task GetByUsernameAsync(string username);
+ Task GetByEmailAsync(string email);
+ }
+}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/AccessLevel.cs b/backend/RAG.Domain/ValueObjects/AccessLevel.cs
deleted file mode 100644
index 633f3a0..0000000
--- a/backend/RAG.Domain/ValueObjects/AccessLevel.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-namespace RAG.Domain.ValueObjects
-{
- public record AccessLevel
- {
- public string Value { get; init; }
-
- private AccessLevel(string value)
- {
- Value = value;
- }
-
- public static AccessLevel Create(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentException("Access level cannot be empty");
-
- value = value.ToLowerInvariant();
-
- if (value != "internal" && value != "public")
- throw new ArgumentException("Access level must be 'internal' or 'public'");
-
- return new AccessLevel(value);
- }
-
- public static implicit operator string(AccessLevel accessLevel) => accessLevel.Value;
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/DocumentTitle.cs b/backend/RAG.Domain/ValueObjects/DocumentTitle.cs
deleted file mode 100644
index 50ddd1f..0000000
--- a/backend/RAG.Domain/ValueObjects/DocumentTitle.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace RAG.Domain.ValueObjects
-{
- public record DocumentTitle
- {
- public string Value { get; init; }
-
- private DocumentTitle(string value)
- {
- Value = value;
- }
-
- public static DocumentTitle Create(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentException("Document title cannot be empty");
-
- if (value.Length > 255)
- throw new ArgumentException("Document title cannot exceed 255 characters");
-
- return new DocumentTitle(value);
- }
-
- public static implicit operator string(DocumentTitle title) => title.Value;
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FileName.cs b/backend/RAG.Domain/ValueObjects/FileName.cs
deleted file mode 100644
index 22aa95a..0000000
--- a/backend/RAG.Domain/ValueObjects/FileName.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace RAG.Domain.ValueObjects
-{
- public record FileName
- {
- public string Value { get; init; }
-
- private FileName(string value)
- {
- Value = value;
- }
-
- public static FileName Create(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentException("File name cannot be empty");
-
- if (value.Length > 255)
- throw new ArgumentException("File name cannot exceed 255 characters");
-
- return new FileName(value);
- }
-
- public static implicit operator string(FileName fileName) => fileName.Value;
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FilePath.cs b/backend/RAG.Domain/ValueObjects/FilePath.cs
deleted file mode 100644
index 7263ee4..0000000
--- a/backend/RAG.Domain/ValueObjects/FilePath.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace RAG.Domain.ValueObjects
-{
- public record FilePath
- {
- public string Value { get; init; }
-
- private FilePath(string value)
- {
- Value = value;
- }
-
- public static FilePath Create(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentException("File path cannot be empty");
-
- return new FilePath(value);
- }
-
- public static implicit operator string(FilePath filePath) => filePath.Value;
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FileSize.cs b/backend/RAG.Domain/ValueObjects/FileSize.cs
deleted file mode 100644
index 63f5288..0000000
--- a/backend/RAG.Domain/ValueObjects/FileSize.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace RAG.Domain.ValueObjects
-{
- public record FileSize
- {
- public long Value { get; init; }
-
- private FileSize(long value)
- {
- Value = value;
- }
-
- public static FileSize Create(long value)
- {
- if (value < 0)
- throw new ArgumentException("File size cannot be negative");
-
- return new FileSize(value);
- }
-
- public static implicit operator long(FileSize fileSize) => fileSize.Value;
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Domain/ValueObjects/FileType.cs b/backend/RAG.Domain/ValueObjects/FileType.cs
deleted file mode 100644
index 3117187..0000000
--- a/backend/RAG.Domain/ValueObjects/FileType.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace RAG.Domain.ValueObjects
-{
- public record FileType
- {
- public string Value { get; init; }
-
- private FileType(string value)
- {
- Value = value;
- }
-
- public static FileType Create(string value)
- {
- if (string.IsNullOrWhiteSpace(value))
- throw new ArgumentException("File type cannot be empty");
-
- return new FileType(value.ToLowerInvariant());
- }
-
- public static implicit operator string(FileType fileType) => fileType.Value;
- }
-}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs b/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
index e6f1a71..d4b2a5b 100644
--- a/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
+++ b/backend/RAG.Infrastructure/Data/ApplicationDbContext.cs
@@ -5,22 +5,168 @@ namespace RAG.Infrastructure.Data
{
public class ApplicationDbContext : DbContext
{
- public ApplicationDbContext(DbContextOptions options)
- : base(options)
+ public ApplicationDbContext(DbContextOptions options) : base(options)
{}
- // DbSets for your entities
+ // 核心数据表
+ public DbSet Users { get; set; }
public DbSet Documents { get; set; }
+ public DbSet DocumentChunks { get; set; }
public DbSet ChatHistories { get; set; }
- public DbSet Users { get; set; }
+ public DbSet ChatMessages { get; set; }
+ public DbSet MessageReferences { get; set; }
+
+ // 向量存储表
+ public DbSet VectorStores { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
- // Configure entity mappings if needed
- modelBuilder.Entity()
- .HasIndex(d => d.Title);
+ // 忽略所有实体的DomainEvents属性
+ modelBuilder.Entity().Ignore(u => u.DomainEvents);
+ modelBuilder.Entity().Ignore(d => d.DomainEvents);
+ modelBuilder.Entity().Ignore(dc => dc.DomainEvents);
+ modelBuilder.Entity().Ignore(ch => ch.DomainEvents);
+ modelBuilder.Entity().Ignore(cm => cm.DomainEvents);
+ modelBuilder.Entity().Ignore(mr => mr.DomainEvents);
+
+ // 配置User实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(u => u.Id);
+ entity.Property(u => u.Username).IsRequired().HasMaxLength(50);
+ entity.Property(u => u.Email).IsRequired().HasMaxLength(100);
+ entity.Property(u => u.PasswordHash).IsRequired().HasMaxLength(255);
+ entity.Property(u => u.FullName).HasMaxLength(100);
+ entity.Property(u => u.IsActive).IsRequired();
+
+ // 索引
+ entity.HasIndex(u => u.Username).IsUnique();
+ entity.HasIndex(u => u.Email).IsUnique();
+ });
+
+ // 配置Document实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(d => d.Id);
+ entity.Property(d => d.Title).IsRequired().HasMaxLength(200);
+ entity.Property(d => d.FileName).IsRequired().HasMaxLength(255);
+ entity.Property(d => d.FilePath).IsRequired().HasMaxLength(500);
+ entity.Property(d => d.FileType).IsRequired().HasMaxLength(50);
+ entity.Property(d => d.FileSize).IsRequired();
+ entity.Property(d => d.AccessLevel).IsRequired().HasMaxLength(20);
+ entity.Property(d => d.Status).IsRequired();
+
+ // 索引
+ entity.HasIndex(d => d.Status);
+ entity.HasIndex(d => d.AccessLevel);
+ });
+
+ // 配置DocumentChunk实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(dc => dc.Id);
+ entity.Property(dc => dc.DocumentId).IsRequired();
+ entity.Property(dc => dc.Content).IsRequired();
+ entity.Property(dc => dc.Position).IsRequired();
+ entity.Property(dc => dc.TokenCount).IsRequired();
+
+ // 关系
+ entity.HasOne()
+ .WithMany(d => d.Chunks)
+ .HasForeignKey(dc => dc.DocumentId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ // 索引
+ entity.HasIndex(dc => dc.DocumentId);
+ });
+
+ // 配置ChatHistory实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(ch => ch.Id);
+ entity.Property(ch => ch.SessionId).IsRequired().HasMaxLength(100);
+ entity.Property(ch => ch.UserId).IsRequired();
+
+ // 关系
+ entity.HasOne()
+ .WithMany(u => u.ChatHistories)
+ .HasForeignKey(ch => ch.UserId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ // 索引
+ entity.HasIndex(ch => ch.SessionId).IsUnique();
+ entity.HasIndex(ch => ch.UserId);
+ });
+
+ // 配置ChatMessage实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(cm => cm.Id);
+ entity.Property(cm => cm.ChatHistoryId).IsRequired();
+ entity.Property(cm => cm.Content).IsRequired();
+ entity.Property(cm => cm.IsUserMessage).IsRequired();
+ entity.Property(cm => cm.Timestamp).IsRequired();
+
+ // 关系
+ entity.HasOne()
+ .WithMany(ch => ch.Messages)
+ .HasForeignKey(cm => cm.ChatHistoryId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ // 索引
+ entity.HasIndex(cm => cm.ChatHistoryId);
+ entity.HasIndex(cm => cm.Timestamp);
+ });
+
+ // 配置MessageReference实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(mr => mr.Id);
+ entity.Property(mr => mr.ChatMessageId).IsRequired();
+ entity.Property(mr => mr.DocumentChunkId).IsRequired();
+ entity.Property(mr => mr.RelevanceScore).IsRequired();
+
+ // 关系
+ entity.HasOne()
+ .WithMany(cm => cm.References)
+ .HasForeignKey(mr => mr.ChatMessageId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ entity.HasOne()
+ .WithMany()
+ .HasForeignKey(mr => mr.DocumentChunkId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ // 索引
+ entity.HasIndex(mr => new { mr.ChatMessageId, mr.DocumentChunkId }).IsUnique();
+ });
+
+ // 配置VectorStore实体
+ modelBuilder.Entity(entity =>
+ {
+ entity.HasKey(vs => vs.Id);
+ entity.Property(vs => vs.DocumentChunkId).IsRequired();
+ entity.Property(vs => vs.Vector).IsRequired();
+
+ // 关系
+ entity.HasOne()
+ .WithMany()
+ .HasForeignKey(vs => vs.DocumentChunkId)
+ .OnDelete(DeleteBehavior.Cascade);
+
+ // 索引
+ entity.HasIndex(vs => vs.DocumentChunkId).IsUnique();
+ });
}
}
+
+ // 向量存储实体
+ public class VectorStore
+ {
+ public long Id { get; set; }
+ public long DocumentChunkId { get; set; }
+ public byte[] Vector { get; set; }
+ }
}
\ No newline at end of file
diff --git a/backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.Designer.cs b/backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.Designer.cs
new file mode 100644
index 0000000..e6132e5
--- /dev/null
+++ b/backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.Designer.cs
@@ -0,0 +1,427 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using RAG.Infrastructure.Data;
+
+#nullable disable
+
+namespace RAG.Infrastructure.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20250805073332_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.8")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("RAG.Domain.Entities.ChatHistory", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SessionId")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.Property("UserId1")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SessionId")
+ .IsUnique();
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("UserId1");
+
+ b.ToTable("ChatHistories");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.ChatMessage", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChatHistoryId")
+ .HasColumnType("bigint");
+
+ b.Property("ChatHistoryId1")
+ .HasColumnType("bigint");
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IsUserMessage")
+ .HasColumnType("boolean");
+
+ b.Property("Timestamp")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatHistoryId");
+
+ b.HasIndex("ChatHistoryId1");
+
+ b.HasIndex("Timestamp");
+
+ b.ToTable("ChatMessages");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.Document", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("AccessLevel")
+ .IsRequired()
+ .HasMaxLength(20)
+ .HasColumnType("character varying(20)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("FileName")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b.Property("FilePath")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("character varying(500)");
+
+ b.Property("FileSize")
+ .HasColumnType("bigint");
+
+ b.Property("FileType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.Property("Status")
+ .HasColumnType("integer");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasMaxLength(200)
+ .HasColumnType("character varying(200)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("bigint");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AccessLevel");
+
+ b.HasIndex("Status");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Documents");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.DocumentChunk", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Content")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DocumentId")
+ .HasColumnType("bigint");
+
+ b.Property("Position")
+ .HasColumnType("integer");
+
+ b.Property("TokenCount")
+ .HasColumnType("integer");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DocumentId");
+
+ b.ToTable("DocumentChunks");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.MessageReference", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ChatMessageId")
+ .HasColumnType("bigint");
+
+ b.Property("ChatMessageId1")
+ .HasColumnType("bigint");
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DocumentChunkId")
+ .HasColumnType("bigint");
+
+ b.Property("DocumentChunkId1")
+ .HasColumnType("bigint");
+
+ b.Property("RelevanceScore")
+ .HasColumnType("double precision");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChatMessageId1");
+
+ b.HasIndex("DocumentChunkId");
+
+ b.HasIndex("DocumentChunkId1");
+
+ b.HasIndex("ChatMessageId", "DocumentChunkId")
+ .IsUnique();
+
+ b.ToTable("MessageReferences");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("FullName")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("character varying(100)");
+
+ b.Property("IsActive")
+ .HasColumnType("boolean");
+
+ b.Property("PasswordHash")
+ .IsRequired()
+ .HasMaxLength(255)
+ .HasColumnType("character varying(255)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Username")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("character varying(50)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Email")
+ .IsUnique();
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("RAG.Infrastructure.Data.VectorStore", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("bigint");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("DocumentChunkId")
+ .HasColumnType("bigint");
+
+ b.Property("Vector")
+ .IsRequired()
+ .HasColumnType("bytea");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DocumentChunkId")
+ .IsUnique();
+
+ b.ToTable("VectorStores");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.ChatHistory", b =>
+ {
+ b.HasOne("RAG.Domain.Entities.User", null)
+ .WithMany("ChatHistories")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("RAG.Domain.Entities.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId1")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.ChatMessage", b =>
+ {
+ b.HasOne("RAG.Domain.Entities.ChatHistory", null)
+ .WithMany("Messages")
+ .HasForeignKey("ChatHistoryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("RAG.Domain.Entities.ChatHistory", "ChatHistory")
+ .WithMany()
+ .HasForeignKey("ChatHistoryId1")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ChatHistory");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.Document", b =>
+ {
+ b.HasOne("RAG.Domain.Entities.User", null)
+ .WithMany("Documents")
+ .HasForeignKey("UserId");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.DocumentChunk", b =>
+ {
+ b.HasOne("RAG.Domain.Entities.Document", null)
+ .WithMany("Chunks")
+ .HasForeignKey("DocumentId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.MessageReference", b =>
+ {
+ b.HasOne("RAG.Domain.Entities.ChatMessage", null)
+ .WithMany("References")
+ .HasForeignKey("ChatMessageId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("RAG.Domain.Entities.ChatMessage", "ChatMessage")
+ .WithMany()
+ .HasForeignKey("ChatMessageId1")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("RAG.Domain.Entities.DocumentChunk", null)
+ .WithMany()
+ .HasForeignKey("DocumentChunkId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("RAG.Domain.Entities.DocumentChunk", "DocumentChunk")
+ .WithMany()
+ .HasForeignKey("DocumentChunkId1")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("ChatMessage");
+
+ b.Navigation("DocumentChunk");
+ });
+
+ modelBuilder.Entity("RAG.Infrastructure.Data.VectorStore", b =>
+ {
+ b.HasOne("RAG.Domain.Entities.DocumentChunk", null)
+ .WithMany()
+ .HasForeignKey("DocumentChunkId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.ChatHistory", b =>
+ {
+ b.Navigation("Messages");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.ChatMessage", b =>
+ {
+ b.Navigation("References");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.Document", b =>
+ {
+ b.Navigation("Chunks");
+ });
+
+ modelBuilder.Entity("RAG.Domain.Entities.User", b =>
+ {
+ b.Navigation("ChatHistories");
+
+ b.Navigation("Documents");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.cs b/backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.cs
new file mode 100644
index 0000000..277e645
--- /dev/null
+++ b/backend/RAG.Infrastructure/Migrations/20250805073332_InitialCreate.cs
@@ -0,0 +1,324 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace RAG.Infrastructure.Migrations
+{
+ ///
+ public partial class InitialCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(type: "bigint", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Username = table.Column(type: "character varying(50)", maxLength: 50, nullable: false),
+ Email = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ PasswordHash = table.Column(type: "character varying(255)", maxLength: 255, nullable: false),
+ FullName = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ IsActive = table.Column(type: "boolean", nullable: false),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false),
+ UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ChatHistories",
+ columns: table => new
+ {
+ Id = table.Column(type: "bigint", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ SessionId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false),
+ UserId = table.Column(type: "bigint", nullable: false),
+ UserId1 = table.Column(type: "bigint", nullable: false),
+ CreatedAt = table.Column(type: "timestamp with time zone", nullable: false),
+ UpdatedAt = table.Column(type: "timestamp with time zone", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ChatHistories", x => x.Id);
+ table.ForeignKey(
+ name: "FK_ChatHistories_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_ChatHistories_Users_UserId1",
+ column: x => x.UserId1,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Documents",
+ columns: table => new
+ {
+ Id = table.Column(type: "bigint", nullable: false)
+ .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
+ Title = table.Column(type: "character varying(200)", maxLength: 200, nullable: false),
+ FileName = table.Column