diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml index e61cd983..b02f3180 100644 --- a/.github/workflows/build-container.yml +++ b/.github/workflows/build-container.yml @@ -97,6 +97,12 @@ jobs: name: chats-fe path: ./src/BE/wwwroot + - name: sed version + run: | + # file: ./src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs + # const int buildVersion = 0; -> const int buildVersion = github.run_number; + sed -i "s/const int buildVersion = 0;/const int buildVersion = ${{ github.run_number }};/" ./src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs + - name: Build container run: | dotnet publish ./src/BE/Chats.BE.csproj -c Release --os linux --arch x64 /t:PublishContainer /p:ContainerRepository=chats @@ -143,6 +149,12 @@ jobs: name: chats-fe path: ./src/BE/wwwroot + - name: sed version + run: | + # file: ./src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs + # const int buildVersion = 0; -> const int buildVersion = github.run_number; + sed -i "s/const int buildVersion = 0;/const int buildVersion = ${{ github.run_number }};/" ./src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs + - name: Build container run: | dotnet publish ./src/BE/Chats.BE.csproj -c Release --os ${{ matrix.os }} --arch ${{ matrix.arch }} /t:PublishContainer /p:ContainerRepository=chats ${{ matrix.args }} @@ -228,6 +240,12 @@ jobs: name: chats-fe path: ./src/BE/wwwroot + - name: sed version + run: | + # file: ./src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs + # const int buildVersion = 0; -> const int buildVersion = github.run_number; + sed -i "s/const int buildVersion = 0;/const int buildVersion = ${{ github.run_number }};/" ./src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs + - name: build binary run: | dotnet publish ./src/BE/Chats.BE.csproj -c Release -o ./Publish ${{ matrix.args }} /p:DeleteExistingFiles=True @@ -291,6 +309,8 @@ jobs: target_commitish: '${{ github.sha }}', body: `### Full Changelogs https://github.com/sdcb/chats/compare/${latestTag}...r-${{ github.run_number }} + +
### Docker | Description | Docker Image | @@ -317,6 +337,8 @@ jobs: **NOTE**: Replace \`r${{ github.run_number }}\` with \`latest\` in the download link to get the latest version, for example: \`${{ vars.MINIO_URL }}/chats/latest/chats-win-x64.7z\` + +
`, draft: false, prerelease: false, diff --git a/README.md b/README.md index 04ab3725..b9f7b24b 100644 --- a/README.md +++ b/README.md @@ -132,8 +132,9 @@ C:\Users\ZhouJie\Downloads\chats-win-x64>dir - **启动应用**:运行 `Chats.BE.exe` 即可启动 Chats 应用,该文件名虽指“后端”,但实际同时包含前端和后端组件。 - **数据库配置**:默认情况下,应用将在当前目录创建名为 `AppData` 的目录,并以 SQLite 作为数据库。命令行参数可用于指定不同的数据库类型: ```pwsh - .\Chats.BE.exe --DBType=mssql --ConnectionStrings:ChatsDB="Data Source=(localdb)\mssqllocaldb; Initial Catalog=ChatsDB; Integrated Security=True" + .\Chats.BE.exe --urls http://+:5000 --DBType=mssql --ConnectionStrings:ChatsDB="Data Source=(localdb)\mssqllocaldb; Initial Catalog=ChatsDB; Integrated Security=True" ``` + - 参数 `--urls`:用于指定应用监听的地址和端口。 - 参数 `DBType`:可选 `sqlite`、`mssql` 或 `pgsql`。 - 参数 `--ConnectionStrings:ChatsDB`:用于指定数据库的ADO.NET连接字符串。 diff --git a/README_EN.md b/README_EN.md index 363278e5..5eb923fd 100644 --- a/README_EN.md +++ b/README_EN.md @@ -130,8 +130,9 @@ C:\Users\ZhouJie\Downloads\chats-win-x64>dir - **Start Application**: Run `Chats.BE.exe` to start the Chats application. Although this filename indicates "backend," it actually contains both frontend and backend components. - **Database Configuration**: By default, the application will create a directory named `AppData` in the current directory and use SQLite as the database. Command-line parameters can be used to specify a different database type: ```pwsh - .\Chats.BE.exe --DBType=mssql --ConnectionStrings:ChatsDB="Data Source=(localdb)\mssqllocaldb; Initial Catalog=ChatsDB; Integrated Security=True" + .\Chats.BE.exe --urls http://+:5000 --DBType=mssql --ConnectionStrings:ChatsDB="Data Source=(localdb)\mssqllocaldb; Initial Catalog=ChatsDB; Integrated Security=True" ``` + - Parameter `--urls`: Used to specify the address and port the application listens on. - Parameter `DBType`: Options are `sqlite`, `mssql`, or `pgsql`. - Parameter `--ConnectionStrings:ChatsDB`: For specifying the ADO.NET connection string for the database. diff --git a/src/BE.Tests/Services/Conversations/ChatCompletionOptionsTests.cs b/src/BE.Tests/Services/Conversations/ChatCompletionOptionsTests.cs index 2a2ad9fc..a44f5ea7 100644 --- a/src/BE.Tests/Services/Conversations/ChatCompletionOptionsTests.cs +++ b/src/BE.Tests/Services/Conversations/ChatCompletionOptionsTests.cs @@ -1,4 +1,4 @@ -using Chats.BE.Services.ChatServices.Extensions; +using Chats.BE.Services.Models.Extensions; using OpenAI.Chat; using System.Runtime.CompilerServices; diff --git a/src/BE/Chats.BE.csproj b/src/BE/Chats.BE.csproj index 6958fba4..b8f06b4d 100644 --- a/src/BE/Chats.BE.csproj +++ b/src/BE/Chats.BE.csproj @@ -9,11 +9,11 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -22,16 +22,16 @@ - + - + - - + + diff --git a/src/BE/Controllers/Admin/AdminMessage/AdminMessageController.cs b/src/BE/Controllers/Admin/AdminMessage/AdminMessageController.cs index 5f829825..d66436b5 100644 --- a/src/BE/Controllers/Admin/AdminMessage/AdminMessageController.cs +++ b/src/BE/Controllers/Admin/AdminMessage/AdminMessageController.cs @@ -5,7 +5,7 @@ using Chats.BE.Controllers.Common.Dtos; using Chats.BE.DB; using Chats.BE.Infrastructure; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.FileServices; using Chats.BE.Services.UrlEncryption; using Microsoft.AspNetCore.Mvc; @@ -100,6 +100,7 @@ public async Task> GetAdminMessage(int ch .ToArray(), CreatedAt = x.CreatedAt, SpanId = x.SpanId, + Edited = x.Edited, Usage = x.Usage == null ? null : new ChatMessageTempUsage() { InputTokens = x.Usage.InputTokens, diff --git a/src/BE/Controllers/Admin/AdminModels/AdminModelsController.cs b/src/BE/Controllers/Admin/AdminModels/AdminModelsController.cs index 5081b438..e3d289db 100644 --- a/src/BE/Controllers/Admin/AdminModels/AdminModelsController.cs +++ b/src/BE/Controllers/Admin/AdminModels/AdminModelsController.cs @@ -5,7 +5,7 @@ using Chats.BE.DB.Jsons; using Chats.BE.Infrastructure; using Chats.BE.Services; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore; @@ -122,13 +122,6 @@ public async Task> FastCreateModel([FromBody] ValidateModelReq return BadRequest($"Invalid ModelReferenceId: {req.ModelReferenceId}"); } - bool hasExistingModel = await db.Models - .AnyAsync(x => x.ModelKeyId == req.ModelKeyId && x.ModelReferenceId == req.ModelReferenceId && x.DeploymentName == req.DeploymentName, cancellationToken); - if (hasExistingModel) - { - return BadRequest("Model already exists"); - } - Model toCreate = new() { ModelKeyId = req.ModelKeyId, @@ -182,7 +175,7 @@ public async Task DeleteModel(short modelId, CancellationToken can [HttpPost("models/validate")] public async Task> ValidateModel( [FromBody] ValidateModelRequest req, - [FromServices] ChatFactory conversationFactory, + [FromServices] ChatFactory chatFactory, CancellationToken cancellationToken) { ModelKey? modelKey = await db.ModelKeys @@ -204,12 +197,12 @@ public async Task> ValidateModel( return this.BadRequestMessage($"Model reference id: {req.ModelReferenceId} not found"); } - ModelValidateResult result = await conversationFactory.ValidateModel(modelKey, modelReference, req.DeploymentName, cancellationToken); + ModelValidateResult result = await chatFactory.ValidateModel(modelKey, modelReference, req.DeploymentName, cancellationToken); return Ok(result); } [HttpGet("user-models/{userId:int}")] - public async Task>> GetUserModels(int userId, CancellationToken cancellationToken) + public async Task> GetUserModels(int userId, CancellationToken cancellationToken) { UserModelDto[] userModels = await db.Models .Where(x => !x.IsDeleted) diff --git a/src/BE/Controllers/Admin/GlobalConfigs/GlobalConfigController.cs b/src/BE/Controllers/Admin/GlobalConfigs/GlobalConfigController.cs index 453eb99f..b09da0a5 100644 --- a/src/BE/Controllers/Admin/GlobalConfigs/GlobalConfigController.cs +++ b/src/BE/Controllers/Admin/GlobalConfigs/GlobalConfigController.cs @@ -2,6 +2,7 @@ using Chats.BE.Controllers.Admin.GlobalConfigs.Dtos; using Chats.BE.Controllers.Common; using Chats.BE.DB; +using Chats.BE.Services.Configs; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Text.Json; diff --git a/src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs b/src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs index 361000a1..12f169ee 100644 --- a/src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs +++ b/src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs @@ -7,16 +7,17 @@ namespace Chats.BE.Controllers.Admin.GlobalConfigs; [AuthorizeAdmin, Route("api/version")] public class VersionController : ControllerBase { + // it will be replaced by CI/CD pipeline const int buildVersion = 0; - [HttpGet("current")] - public ActionResult GetCurrentVersion() + [HttpGet] + public ActionResult GetCurrentVersion() { return Ok(buildVersion); } [HttpPost("check-update")] - public async Task CheckUpdate(CancellationToken cancellationToken) + public async Task> CheckUpdate(CancellationToken cancellationToken) { string tagName = await GitHubReleaseChecker.SdcbChats.GetLatestReleaseTagNameAsync(cancellationToken); bool hasNewVersion = GitHubReleaseChecker.IsNewVersionAvailableAsync(tagName, buildVersion); diff --git a/src/BE/Controllers/Admin/ModelKeys/Dtos/AutoCreateModelResult.cs b/src/BE/Controllers/Admin/ModelKeys/Dtos/AutoCreateModelResult.cs index 9e976dbc..619920f6 100644 --- a/src/BE/Controllers/Admin/ModelKeys/Dtos/AutoCreateModelResult.cs +++ b/src/BE/Controllers/Admin/ModelKeys/Dtos/AutoCreateModelResult.cs @@ -1,5 +1,5 @@ using Chats.BE.DB; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using System.Text.Json.Serialization; namespace Chats.BE.Controllers.Admin.ModelKeys.Dtos; diff --git a/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs b/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs index 8a01c9cb..0b3905e9 100644 --- a/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs +++ b/src/BE/Controllers/Admin/ModelKeys/ModelKeysController.cs @@ -2,8 +2,9 @@ using Chats.BE.Controllers.Admin.ModelKeys.Dtos; using Chats.BE.Controllers.Common; using Chats.BE.DB; +using Chats.BE.DB.Enums; using Chats.BE.Services.Common; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -114,75 +115,8 @@ public async Task DeleteModelKey(short modelKeyId, CancellationTok return NoContent(); } - [HttpPost("{modelKeyId:int}/auto-create-models")] - public async Task> AutoCreateModels(short modelKeyId, [FromServices] ChatFactory conversationFactory, CancellationToken cancellationToken) - { - ModelKey? modelKey = await db - .ModelKeys - .Include(x => x.Models) - .AsSplitQuery() - .FirstOrDefaultAsync(x => x.Id == modelKeyId, cancellationToken); - - if (modelKey == null) - { - return NotFound(); - } - - HashSet existingModelRefIds = modelKey.Models - .Select(x => x.ModelReferenceId) - .ToHashSet(); - - ModelReference[] readyRefs = await db.ModelReferences - .Include(x => x.CurrencyCodeNavigation) - .Where(x => !x.IsLegacy && x.ProviderId == modelKey.ModelProviderId) - .ToArrayAsync(cancellationToken); - - ParepareAutoCreateModelResult[] scanedModels = await Task.WhenAll(readyRefs - .Select(async r => existingModelRefIds.Contains(r.Id) - ? ParepareAutoCreateModelResult.ModelAlreadyExists(r) - : ParepareAutoCreateModelResult.FromModelValidateResult(await conversationFactory.ValidateModel(modelKey, r, r.Name, cancellationToken), r))); - - FileService? fileService = await db.FileServices - .OrderByDescending(x => x.Id) - .FirstOrDefaultAsync(cancellationToken); - AutoCreateModelResult[] results = await scanedModels - .ToAsyncEnumerable() - .SelectAwait(async (m) => - { - if (!m.IsValidationPassed) - { - return m.ToResult(null); - } - - db.Models.Add(new Model - { - ModelKeyId = modelKeyId, - ModelReferenceId = m.ModelReference.Id, - Name = m.ModelReference.Name, - CreatedAt = DateTime.UtcNow, - UpdatedAt = DateTime.UtcNow, - DeploymentName = null, // don't need to specify deploymentName because it's auto created - IsDeleted = false, - InputTokenPrice1M = m.ModelReference.InputTokenPrice1M * m.ModelReference.CurrencyCodeNavigation.ExchangeRate, - OutputTokenPrice1M = m.ModelReference.OutputTokenPrice1M * m.ModelReference.CurrencyCodeNavigation.ExchangeRate, - }); - try - { - await db.SaveChangesAsync(cancellationToken); - return m.ToResult(null); - } - catch (Exception ex) - { - return m.ToResult(ex.Message); - } - }) - .ToArrayAsync(cancellationToken); - - return Ok(results); - } - [HttpGet("{modelKeyId:int}/possible-models")] - public async Task> ListModelKeyPossibleModels(short modelKeyId, CancellationToken cancellationToken) + public async Task> ListModelKeyPossibleModels(short modelKeyId, [FromServices] ChatFactory cf, CancellationToken cancellationToken) { ModelKey? modelKey = await db .ModelKeys @@ -195,21 +129,77 @@ public async Task> ListModelKeyPossibleModels(s return NotFound(); } - PossibleModelDto[] readyRefs = await db.ModelReferences - .Where(x => x.ProviderId == modelKey.ModelProviderId) - .OrderBy(x => x.Name) - .Select(x => new PossibleModelDto() + DBModelProvider modelProvider = (DBModelProvider)modelKey.ModelProviderId; + ModelLoader? loader = cf.CreateModelLoader(modelProvider); + if (loader != null) + { + string[] models = await loader.ListModels(modelKey, cancellationToken); + HashSet existsDeploymentNames = await db.Models + .Where(x => x.ModelKeyId == modelKeyId && x.DeploymentName != null) + .Select(x => x.DeploymentName!) + .ToHashSetAsync(cancellationToken); + + if (modelProvider == DBModelProvider.Ollama) { - DeploymentName = x.Models.FirstOrDefault(m => m.ModelKeyId == modelKeyId)!.DeploymentName, - ReferenceId = x.Id, - ReferenceName = x.Name, - IsLegacy = x.IsLegacy, - IsExists = x.Models.Any(m => m.ModelKeyId == modelKeyId), - }) - .OrderBy(x => (x.IsLegacy ? 1 : 0) + (x.IsExists ? 2 : 0)) - .ThenByDescending(x => x.ReferenceId) - .ToArrayAsync(cancellationToken); + Dictionary referenceOptions = await db.ModelReferences + .Where(x => x.ProviderId == modelKey.ModelProviderId) + .ToDictionaryAsync(k => k.Id, v => v, cancellationToken); - return Ok(readyRefs); + return Ok(models.Select(model => + { + bool isVision = model.Contains("qvq", StringComparison.OrdinalIgnoreCase) || + model.Contains("vision", StringComparison.OrdinalIgnoreCase); + short modelReferenceId = isVision ? (short)1401 : (short)1400; + return new PossibleModelDto() + { + DeploymentName = model, + ReferenceId = modelReferenceId, + ReferenceName = referenceOptions[modelReferenceId].Name, + IsLegacy = referenceOptions[modelReferenceId].IsLegacy, + IsExists = existsDeploymentNames.Contains(model), + }; + })); + } + else + { + Dictionary referenceOptions = await db.ModelReferences + .Where(x => x.ProviderId == modelKey.ModelProviderId) + .ToDictionaryAsync(k => k.Name, v => v, cancellationToken); + HashSet referenceOptionNames = [.. referenceOptions.Keys]; + + return Ok(models.Select(model => + { + string bestMatch = FuzzyMatcher.FindBestMatch(model, referenceOptionNames); + + return new PossibleModelDto() + { + DeploymentName = model, + ReferenceId = referenceOptions[bestMatch].Id, + ReferenceName = referenceOptions[bestMatch].Name, + IsLegacy = false, + IsExists = existsDeploymentNames.Contains(model), + }; + }).ToArray()); + } + } + else + { + PossibleModelDto[] readyRefs = await db.ModelReferences + .Where(x => x.ProviderId == modelKey.ModelProviderId) + .OrderBy(x => x.Name) + .Select(x => new PossibleModelDto() + { + DeploymentName = x.Models.FirstOrDefault(m => m.ModelKeyId == modelKeyId)!.DeploymentName, + ReferenceId = x.Id, + ReferenceName = x.Name, + IsLegacy = x.IsLegacy, + IsExists = x.Models.Any(m => m.ModelKeyId == modelKeyId), + }) + .OrderBy(x => (x.IsLegacy ? 1 : 0) + (x.IsExists ? 2 : 0)) + .ThenByDescending(x => x.ReferenceId) + .ToArrayAsync(cancellationToken); + + return Ok(readyRefs); + } } } diff --git a/src/BE/Controllers/Chats/Chats/ChatController.cs b/src/BE/Controllers/Chats/Chats/ChatController.cs index 43f6f3b0..279a318d 100644 --- a/src/BE/Controllers/Chats/Chats/ChatController.cs +++ b/src/BE/Controllers/Chats/Chats/ChatController.cs @@ -2,9 +2,9 @@ using Chats.BE.DB; using Chats.BE.Infrastructure; using Chats.BE.Services; -using Chats.BE.Services.ChatServices; -using Chats.BE.Services.ChatServices.Dtos; -using Chats.BE.Services.ChatServices.Implementations.Test; +using Chats.BE.Services.Models; +using Chats.BE.Services.Models.Dtos; +using Chats.BE.Services.Models.ChatServices.Test; using Chats.BE.Services.FileServices; using Chats.BE.Services.UrlEncryption; using Microsoft.AspNetCore.Authorization; @@ -399,7 +399,7 @@ private static async Task ProcessChatSpan( string? errorText = null; try { - using ChatService s = chatFactory.CreateConversationService(userModel.Model); + using ChatService s = chatFactory.CreateChatService(userModel.Model); await foreach (InternalChatSegment seg in icc.Run(userBalance.Balance, userModel, s.ChatStreamedFEProcessed(messageToSend, cco, extraDetails, cancellationToken))) { if (seg.TextSegment == string.Empty) continue; diff --git a/src/BE/Controllers/Chats/Chats/Dtos/ChatRequest.cs b/src/BE/Controllers/Chats/Chats/Dtos/ChatRequest.cs index 491cf2dd..1ac45113 100644 --- a/src/BE/Controllers/Chats/Chats/Dtos/ChatRequest.cs +++ b/src/BE/Controllers/Chats/Chats/Dtos/ChatRequest.cs @@ -1,6 +1,6 @@ using Chats.BE.Controllers.Chats.Messages.Dtos; using Chats.BE.DB; -using Chats.BE.Services.ChatServices.Extensions; +using Chats.BE.Services.Models.Extensions; using OpenAI.Chat; using System.Text.Json.Serialization; diff --git a/src/BE/Controllers/Chats/Chats/Dtos/MessageLiteDto.cs b/src/BE/Controllers/Chats/Chats/Dtos/MessageLiteDto.cs index 5acbc5b6..899fd3ad 100644 --- a/src/BE/Controllers/Chats/Chats/Dtos/MessageLiteDto.cs +++ b/src/BE/Controllers/Chats/Chats/Dtos/MessageLiteDto.cs @@ -1,6 +1,6 @@ using Chats.BE.DB; using Chats.BE.DB.Enums; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.FileServices; using OpenAI.Chat; diff --git a/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs b/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs index a781fe89..edffeb0a 100644 --- a/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs +++ b/src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs @@ -1,6 +1,6 @@ using Chats.BE.Controllers.Chats.Messages.Dtos; using Chats.BE.DB; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.FileServices; using Chats.BE.Services.UrlEncryption; using System.Text.Json.Serialization; @@ -52,6 +52,7 @@ public static SseResponseLine ResponseMessage( ParentId = assistantMessage.ParentId, Role = (DBChatRole)assistantMessage.ChatRoleId, SpanId = assistantMessage.SpanId, + Edited = assistantMessage.Edited, Usage = assistantMessage.Usage == null ? null : new ChatMessageTempUsage() { Duration = assistantMessage.Usage.TotalDurationMs - assistantMessage.Usage.PreprocessDurationMs, @@ -89,6 +90,7 @@ public static SseResponseLine UserMessage( ParentId = userMessage.ParentId, Role = (DBChatRole)userMessage.ChatRoleId, SpanId = userMessage.SpanId, + Edited = userMessage.Edited, Usage = null, }; MessageDto userMessageDto = userMessageTemp.ToDto(urlEncryptionService, fup); diff --git a/src/BE/Controllers/Chats/Chats/InsufficientBalanceException.cs b/src/BE/Controllers/Chats/Chats/InsufficientBalanceException.cs index 728e6cbb..ad65abcf 100644 --- a/src/BE/Controllers/Chats/Chats/InsufficientBalanceException.cs +++ b/src/BE/Controllers/Chats/Chats/InsufficientBalanceException.cs @@ -1,4 +1,4 @@ -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; namespace Chats.BE.Controllers.Chats.Chats; diff --git a/src/BE/Controllers/Chats/Messages/Dtos/MessageDto.cs b/src/BE/Controllers/Chats/Messages/Dtos/MessageDto.cs index 6d4748e8..eeea197d 100644 --- a/src/BE/Controllers/Chats/Messages/Dtos/MessageDto.cs +++ b/src/BE/Controllers/Chats/Messages/Dtos/MessageDto.cs @@ -1,6 +1,6 @@ using Chats.BE.DB; using Chats.BE.DB.Enums; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.FileServices; using Chats.BE.Services.UrlEncryption; using System.Text.Json.Serialization; @@ -29,9 +29,27 @@ public abstract record MessageDto [JsonPropertyName("spanId")] public required byte? SpanId { get; init; } + + [JsonPropertyName("edited")] + public required bool Edited { get; init; } } -public record RequestMessageDto : MessageDto; +public record RequestMessageDto : MessageDto +{ + public static RequestMessageDto FromDB(Message message, FileUrlProvider fup) + { + return new RequestMessageDto() + { + Id = message.Id.ToString(), + ParentId = message.ParentId?.ToString(), + Role = (DBChatRole)message.ChatRoleId, + Content = MessageContentResponse.FromSegments([.. message.MessageContents], fup), + CreatedAt = message.CreatedAt, + SpanId = message.SpanId, + Edited = message.Edited, + }; + } +} public record ResponseMessageDto : MessageDto { @@ -128,8 +146,10 @@ public static MessageContentResponse FromSegments(MessageContent[] segments, Fil public record FileDto { + [JsonPropertyName("id")] public required string Id { get; init; } + [JsonPropertyName("url")] public required Uri Url { get; init; } } @@ -157,6 +177,7 @@ public record ChatMessageTemp public required MessageContent[] Content { get; init; } public required DateTime CreatedAt { get; init; } public required byte? SpanId { get; init; } + public required bool Edited { get; init; } public required ChatMessageTempUsage? Usage { get; init; } public MessageDto ToDto(IUrlEncryptionService urlEncryption, FileUrlProvider fup) @@ -171,6 +192,7 @@ public MessageDto ToDto(IUrlEncryptionService urlEncryption, FileUrlProvider fup Content = MessageContentResponse.FromSegments(Content, fup), CreatedAt = CreatedAt, SpanId = SpanId, + Edited = Edited, }; } else @@ -183,6 +205,7 @@ public MessageDto ToDto(IUrlEncryptionService urlEncryption, FileUrlProvider fup Content = MessageContentResponse.FromSegments(Content, fup), CreatedAt = CreatedAt, SpanId = SpanId, + Edited = Edited, InputTokens = Usage.InputTokens, OutputTokens = Usage.OutputTokens, diff --git a/src/BE/Controllers/Chats/Messages/MessagesController.cs b/src/BE/Controllers/Chats/Messages/MessagesController.cs index 27fcea1c..95232933 100644 --- a/src/BE/Controllers/Chats/Messages/MessagesController.cs +++ b/src/BE/Controllers/Chats/Messages/MessagesController.cs @@ -2,7 +2,7 @@ using Chats.BE.DB; using Chats.BE.DB.Enums; using Chats.BE.Infrastructure; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.FileServices; using Chats.BE.Services.UrlEncryption; using Microsoft.AspNetCore.Authorization; @@ -32,6 +32,7 @@ public async Task> GetMessages(string chatId, [FromSe .ToArray(), CreatedAt = x.CreatedAt, SpanId = x.SpanId, + Edited = x.Edited, Usage = x.Usage == null ? null : new ChatMessageTempUsage() { InputTokens = x.Usage.InputTokens, @@ -112,8 +113,8 @@ private async Task ReactionPrivate(string encryptedMessageId, bool return Ok(); } - [HttpPut("{encryptedMessageId}/edit")] - public async Task EditMessage(string encryptedMessageId, [FromBody] MessageContentRequest content, + [HttpPut("{encryptedMessageId}/edit-in-place")] + public async Task EditMessageInPlace(string encryptedMessageId, [FromBody] MessageContentRequest content, [FromServices] FileUrlProvider fup, CancellationToken cancellationToken) { @@ -131,14 +132,19 @@ public async Task EditMessage(string encryptedMessageId, [FromBody return Forbid(); } - message.MessageContents = await content.ToMessageContents(fup, cancellationToken); + message.MessageContents.Clear(); + foreach (MessageContent c in await content.ToMessageContents(fup, cancellationToken)) + { + message.MessageContents.Add(c); + } message.Chat.UpdatedAt = DateTime.UtcNow; + message.Edited = true; await db.SaveChangesAsync(cancellationToken); return Ok(); } [HttpPut("{encryptedMessageId}/edit-and-save-new")] - public async Task EditAndSaveNew(string encryptedMessageId, [FromBody] MessageContentRequest content, + public async Task> EditAndSaveNew(string encryptedMessageId, [FromBody] MessageContentRequest content, [FromServices] FileUrlProvider fup, CancellationToken cancellationToken) { @@ -170,7 +176,7 @@ public async Task EditAndSaveNew(string encryptedMessageId, [FromB db.Messages.Add(newMessage); message.Chat.UpdatedAt = DateTime.UtcNow; await db.SaveChangesAsync(cancellationToken); - return Ok(); + return Ok(RequestMessageDto.FromDB(newMessage, fup)); } [HttpDelete("{encryptedMessageId}")] diff --git a/src/BE/Controllers/Chats/Prompts/PromptsController.cs b/src/BE/Controllers/Chats/Prompts/PromptsController.cs index 498f0b0e..c9dee318 100644 --- a/src/BE/Controllers/Chats/Prompts/PromptsController.cs +++ b/src/BE/Controllers/Chats/Prompts/PromptsController.cs @@ -1,7 +1,7 @@ using Chats.BE.Controllers.Chats.Prompts.Dtos; using Chats.BE.DB; using Chats.BE.Infrastructure; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; diff --git a/src/BE/Controllers/Chats/UserChats/UserChatsController.cs b/src/BE/Controllers/Chats/UserChats/UserChatsController.cs index 22113e67..34b985cd 100644 --- a/src/BE/Controllers/Chats/UserChats/UserChatsController.cs +++ b/src/BE/Controllers/Chats/UserChats/UserChatsController.cs @@ -4,7 +4,7 @@ using Chats.BE.DB; using Chats.BE.Infrastructure; using Chats.BE.Services; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.UrlEncryption; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; diff --git a/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs b/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs index 3f57a8c4..4e46e035 100644 --- a/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs +++ b/src/BE/Controllers/OpenAICompatible/OpenAICompatibleController.cs @@ -1,8 +1,8 @@ using Chats.BE.Controllers.Chats.Chats; using Chats.BE.DB; using Chats.BE.Services; -using Chats.BE.Services.ChatServices; -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models; +using Chats.BE.Services.Models.Dtos; using Chats.BE.Services.OpenAIApiKeySession; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -37,7 +37,7 @@ public async Task ChatCompletion([FromBody] JsonObject json, [From if (userModel == null) return InvalidModel(cco.Model); Model cm = userModel.Model; - using ChatService s = cf.CreateConversationService(cm); + using ChatService s = cf.CreateChatService(cm); UserBalance userBalance = await db.UserBalances .Where(x => x.UserId == currentApiKey.User.Id) diff --git a/src/BE/DB/ChatsDB.cs b/src/BE/DB/ChatsDB.cs index 0ab8b5f9..06a506fa 100644 --- a/src/BE/DB/ChatsDB.cs +++ b/src/BE/DB/ChatsDB.cs @@ -158,9 +158,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { - entity.HasOne(d => d.Chat).WithMany(p => p.ChatShares) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("FK_ChatShare_Chat"); + entity.HasOne(d => d.Chat).WithMany(p => p.ChatShares).HasConstraintName("FK_ChatShare_Chat"); }); modelBuilder.Entity(entity => diff --git a/src/BE/DB/Design/CustomDesignTimeServices.cs b/src/BE/DB/Design/CustomDesignTimeServices.cs deleted file mode 100644 index 61c53ef3..00000000 --- a/src/BE/DB/Design/CustomDesignTimeServices.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.EntityFrameworkCore.Design; - -namespace Chats.BE.DB.Design; - -public class CustomDesignTimeServices : IDesignTimeServices -{ - public void ConfigureDesignTimeServices(IServiceCollection services) - { - } -} \ No newline at end of file diff --git a/src/BE/DB/Enums/DBModelProvider.cs b/src/BE/DB/Enums/DBModelProvider.cs index af64bcff..173336c4 100644 --- a/src/BE/DB/Enums/DBModelProvider.cs +++ b/src/BE/DB/Enums/DBModelProvider.cs @@ -16,4 +16,6 @@ public enum DBModelProvider xAI = 11, GithubModels = 12, GoogleAI = 13, + Ollama = 14, + MiniMax = 15, } diff --git a/src/BE/DB/Init/BasicData.cs b/src/BE/DB/Init/BasicData.cs index b62b4bb3..45baad70 100644 --- a/src/BE/DB/Init/BasicData.cs +++ b/src/BE/DB/Init/BasicData.cs @@ -107,7 +107,7 @@ private static void InsertTransactionTypes(ChatsDB db) private static void InsertModelReferences(ChatsDB db) { - // Generated from data, hash: 9a260fb4f9f10c860ddfa1eba88bb4f3097caddf475eb39618431604002b38aa + // Generated from data, hash: 88ff31dc432a7309dd0e65065384a0d87443fca7838456ac41c2d179bcbfa93c db.ModelReferences.AddRange( [ new(){ Id=0, ProviderId=0, Name="Test", ShortName=null, IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=2048, MaxResponseTokens=2048, TokenizerId=1, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, @@ -248,13 +248,19 @@ private static void InsertModelReferences(ChatsDB db) new(){ Id=1217, ProviderId=12, Name="Phi-3.5-vision-instruct", ShortName="Phi-3.5", IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=131072, MaxResponseTokens=4096, TokenizerId=null, InputTokenPrice1M=1.00000M, OutputTokenPrice1M=2.00000M, CurrencyCode="USD", }, new(){ Id=1218, ProviderId=12, Name="o1-preview", ShortName=null, IsLegacy=true, MinTemperature=1.00M, MaxTemperature=1.00M, AllowSearch=false, AllowVision=false, AllowSystemPrompt=false, AllowStreaming=false, ContextWindow=128000, MaxResponseTokens=32768, TokenizerId=null, InputTokenPrice1M=15.00000M, OutputTokenPrice1M=60.00000M, CurrencyCode="USD", }, new(){ Id=1219, ProviderId=12, Name="o1-mini", ShortName=null, IsLegacy=false, MinTemperature=1.00M, MaxTemperature=1.00M, AllowSearch=false, AllowVision=false, AllowSystemPrompt=false, AllowStreaming=false, ContextWindow=128000, MaxResponseTokens=65536, TokenizerId=null, InputTokenPrice1M=3.00000M, OutputTokenPrice1M=12.00000M, CurrencyCode="USD", }, - new(){ Id=1220, ProviderId=12, Name="o1-2024-12-17", ShortName="o1", IsLegacy=false, MinTemperature=1.00M, MaxTemperature=1.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=false, ContextWindow=200000, MaxResponseTokens=100000, TokenizerId=2, InputTokenPrice1M=15.00000M, OutputTokenPrice1M=60.00000M, CurrencyCode="USD", } + new(){ Id=1220, ProviderId=12, Name="o1-2024-12-17", ShortName="o1", IsLegacy=false, MinTemperature=1.00M, MaxTemperature=1.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=false, ContextWindow=200000, MaxResponseTokens=100000, TokenizerId=2, InputTokenPrice1M=15.00000M, OutputTokenPrice1M=60.00000M, CurrencyCode="USD", }, + new(){ Id=1300, ProviderId=13, Name="gemini-2.0-flash-thinking-exp", ShortName="gemini", IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=40000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, + new(){ Id=1301, ProviderId=13, Name="gemini-2.0-flash-exp", ShortName="gemini", IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1048576, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, + new(){ Id=1302, ProviderId=13, Name="gemini-exp-1206", ShortName="gemini", IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=2097152, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="USD", }, + new(){ Id=1400, ProviderId=14, Name="general", ShortName=null, IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=false, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=128000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, + new(){ Id=1401, ProviderId=14, Name="general-vision", ShortName=null, IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=128000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=0.00000M, OutputTokenPrice1M=0.00000M, CurrencyCode="RMB", }, + new(){ Id=1500, ProviderId=15, Name="MiniMax-Text-01", ShortName=null, IsLegacy=false, MinTemperature=0.00M, MaxTemperature=2.00M, AllowSearch=false, AllowVision=true, AllowSystemPrompt=true, AllowStreaming=true, ContextWindow=1000000, MaxResponseTokens=8000, TokenizerId=null, InputTokenPrice1M=1.00000M, OutputTokenPrice1M=8.00000M, CurrencyCode="RMB", } ]); } private static void InsertModelProviders(ChatsDB db) { - // Generated from data, hash: 615290985867eb3467e7889a31c4edf5dcdbd113930d67df8e19b6cd27c955ca + // Generated from data, hash: 3746cc1a6f22a05f83d27814361078fef50de0cd7b5e777e0c55e1840525f843 db.ModelProviders.AddRange( [ new(){ Id=0, Name="Test", InitialHost=null, InitialSecret=null, }, @@ -269,7 +275,10 @@ private static void InsertModelProviders(ChatsDB db) new(){ Id=9, Name="Zhipu AI", InitialHost=null, InitialSecret="", }, new(){ Id=10, Name="DeepSeek", InitialHost=null, InitialSecret="", }, new(){ Id=11, Name="x.ai", InitialHost=null, InitialSecret="xai-yourkey", }, - new(){ Id=12, Name="Github Models", InitialHost=null, InitialSecret="ghp_yourkey", } + new(){ Id=12, Name="Github Models", InitialHost=null, InitialSecret="ghp_yourkey", }, + new(){ Id=13, Name="Google AI", InitialHost=null, InitialSecret="", }, + new(){ Id=14, Name="Ollama", InitialHost="http://localhost:11434/v1", InitialSecret="ollama", }, + new(){ Id=15, Name="MiniMax", InitialHost=null, InitialSecret="your-key", } ]); } }; \ No newline at end of file diff --git a/src/BE/DB/Init/InitService.cs b/src/BE/DB/Init/InitService.cs index bc54235d..7aa74b29 100644 --- a/src/BE/DB/Init/InitService.cs +++ b/src/BE/DB/Init/InitService.cs @@ -23,9 +23,6 @@ public async Task Init(CancellationToken cancellationToken = default) private static async Task InsertInitialData(IServiceScope scope, ChatsDB db, CancellationToken cancellationToken) { - JwtKeyManager jwtKeyManager = scope.ServiceProvider.GetRequiredService(); - await jwtKeyManager.GetOrCreateSecretKey(cancellationToken); - BasicData.InsertAll(db); await db.SaveChangesAsync(cancellationToken); diff --git a/src/BE/DB/ModelKey.cs b/src/BE/DB/ModelKey.cs index 34ebe1d6..23aedb00 100644 --- a/src/BE/DB/ModelKey.cs +++ b/src/BE/DB/ModelKey.cs @@ -22,7 +22,7 @@ public partial class ModelKey [Unicode(false)] public string? Host { get; set; } - [StringLength(500)] + [StringLength(1000)] [Unicode(false)] public string? Secret { get; set; } diff --git a/src/BE/Program.cs b/src/BE/Program.cs index 690d1416..f33f2088 100644 --- a/src/BE/Program.cs +++ b/src/BE/Program.cs @@ -3,7 +3,7 @@ using Chats.BE.Infrastructure; using Chats.BE.Services; using Chats.BE.Services.Configs; -using Chats.BE.Services.ChatServices; +using Chats.BE.Services.Models; using Chats.BE.Services.UrlEncryption; using Chats.BE.Services.OpenAIApiKeySession; using Chats.BE.Services.Sessions; @@ -45,7 +45,7 @@ public static async Task Main(string[] args) builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/src/BE/Services/Common/FuzzyMatcher.cs b/src/BE/Services/Common/FuzzyMatcher.cs new file mode 100644 index 00000000..6fba0892 --- /dev/null +++ b/src/BE/Services/Common/FuzzyMatcher.cs @@ -0,0 +1,60 @@ +namespace Chats.BE.Services.Common; + +using System; +using System.Collections.Generic; + +public class FuzzyMatcher +{ + // 主函数:找到最佳匹配 + public static string FindBestMatch(string input, HashSet options) + { + string? bestMatch = null; + int bestDistance = int.MaxValue; + + foreach (string option in options) + { + int distance = LevenshteinDistance(input, option); + if (distance < bestDistance) + { + bestDistance = distance; + bestMatch = option; + } + } + + return bestMatch!; + } + + // 计算 Levenshtein 距离 + private static int LevenshteinDistance(string s1, string s2) + { + int len1 = s1.Length; + int len2 = s2.Length; + + var dp = new int[len1 + 1, len2 + 1]; + + for (int i = 0; i <= len1; i++) + { + for (int j = 0; j <= len2; j++) + { + if (i == 0) + { + dp[i, j] = j; + } + else if (j == 0) + { + dp[i, j] = i; + } + else + { + int cost = s1[i - 1] == s2[j - 1] ? 0 : 1; + dp[i, j] = Math.Min( + Math.Min(dp[i - 1, j] + 1, dp[i, j - 1] + 1), + dp[i - 1, j - 1] + cost + ); + } + } + } + + return dp[len1, len2]; + } +} diff --git a/src/BE/Services/Configs/DBConfigKey.cs b/src/BE/Services/Configs/DBConfigKey.cs index 22e0b883..971c9932 100644 --- a/src/BE/Services/Configs/DBConfigKey.cs +++ b/src/BE/Services/Configs/DBConfigKey.cs @@ -2,8 +2,6 @@ public static class DBConfigKey { - public const string JwtSecretKey = "JwtSecretKey"; - public const string TencentSms = "tencentSms"; public const string SiteInfo = "siteInfo"; diff --git a/src/BE/Services/ChatServices/CcoWrapper.cs b/src/BE/Services/Models/CcoWrapper.cs similarity index 97% rename from src/BE/Services/ChatServices/CcoWrapper.cs rename to src/BE/Services/Models/CcoWrapper.cs index 89160158..b3fa9b90 100644 --- a/src/BE/Services/ChatServices/CcoWrapper.cs +++ b/src/BE/Services/Models/CcoWrapper.cs @@ -3,7 +3,7 @@ using System.Text.Json; using System.Text.Json.Nodes; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public class CcoWrapper(JsonObject json) { diff --git a/src/BE/Services/ChatServices/ChatExtraDetails.cs b/src/BE/Services/Models/ChatExtraDetails.cs similarity index 85% rename from src/BE/Services/ChatServices/ChatExtraDetails.cs rename to src/BE/Services/Models/ChatExtraDetails.cs index 81edbc6c..43799313 100644 --- a/src/BE/Services/ChatServices/ChatExtraDetails.cs +++ b/src/BE/Services/Models/ChatExtraDetails.cs @@ -1,4 +1,4 @@ -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public record ChatExtraDetails { diff --git a/src/BE/Services/ChatServices/ChatFactory.cs b/src/BE/Services/Models/ChatFactory.cs similarity index 58% rename from src/BE/Services/ChatServices/ChatFactory.cs rename to src/BE/Services/Models/ChatFactory.cs index fcffa1e0..d4017af2 100644 --- a/src/BE/Services/ChatServices/ChatFactory.cs +++ b/src/BE/Services/Models/ChatFactory.cs @@ -1,17 +1,18 @@ using Chats.BE.DB; using Chats.BE.DB.Enums; -using Chats.BE.Services.ChatServices.Implementations.DashScope; -using Chats.BE.Services.ChatServices.Implementations.Hunyuan; -using Chats.BE.Services.ChatServices.Implementations.OpenAI; -using Chats.BE.Services.ChatServices.Implementations.QianFan; -using Chats.BE.Services.ChatServices.Implementations.Test; +using Chats.BE.Services.Models.ChatServices.DashScope; +using Chats.BE.Services.Models.ChatServices.Hunyuan; +using Chats.BE.Services.Models.ChatServices.OpenAI; +using Chats.BE.Services.Models.ChatServices.QianFan; +using Chats.BE.Services.Models.ChatServices.Test; +using Chats.BE.Services.Models.ModelLoaders; using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public class ChatFactory(ILogger logger) { - public ChatService CreateConversationService(Model model) + public ChatService CreateChatService(Model model) { DBModelProvider modelProvider = (DBModelProvider)model.ModelKey.ModelProviderId; ChatService cs = modelProvider switch @@ -30,14 +31,41 @@ public ChatService CreateConversationService(Model model) DBModelProvider.xAI => new XAIChatService(model), DBModelProvider.GithubModels => new GithubModelsChatService(model), DBModelProvider.GoogleAI => new GoogleAIChatService(model), + DBModelProvider.Ollama => new OllamaChatService(model), + DBModelProvider.MiniMax => new MiniMaxChatService(model), _ => throw new NotSupportedException($"Unknown model provider: {modelProvider}") }; return cs; } + public ModelLoader? CreateModelLoader(DBModelProvider modelProvider) + { + ModelLoader? ml = modelProvider switch + { + DBModelProvider.Test => null, + DBModelProvider.OpenAI => null, + DBModelProvider.AzureOpenAI => null, + DBModelProvider.WenXinQianFan => null, + DBModelProvider.AliyunDashscope => null, + DBModelProvider.ZhiPuAI => null, + DBModelProvider.Moonshot => null, + DBModelProvider.HunYuan => null, + DBModelProvider.Sparkdesk => null, + DBModelProvider.LingYi => null, + DBModelProvider.DeepSeek => null, + DBModelProvider.xAI => null, + DBModelProvider.GithubModels => null, + DBModelProvider.GoogleAI => null, + DBModelProvider.Ollama => new OpenAIModelLoader(), + DBModelProvider.MiniMax => null, + _ => throw new NotSupportedException($"Unknown model provider: {modelProvider}") + }; + return ml; + } + public async Task ValidateModel(ModelKey modelKey, ModelReference modelReference, string? deploymentName, CancellationToken cancellationToken) { - using ChatService cs = CreateConversationService(new Model + using ChatService cs = CreateChatService(new Model { ModelKey = modelKey, ModelReference = modelReference, diff --git a/src/BE/Services/ChatServices/ChatService.cs b/src/BE/Services/Models/ChatService.cs similarity index 93% rename from src/BE/Services/ChatServices/ChatService.cs rename to src/BE/Services/Models/ChatService.cs index f461741d..be87d92d 100644 --- a/src/BE/Services/ChatServices/ChatService.cs +++ b/src/BE/Services/Models/ChatService.cs @@ -1,12 +1,12 @@ using Chats.BE.DB; -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using Tokenizer = Microsoft.ML.Tokenizers.Tokenizer; using OpenAI.Chat; using System.Text; using Microsoft.ML.Tokenizers; -using Chats.BE.Services.ChatServices.Extensions; +using Chats.BE.Services.Models.Extensions; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public abstract partial class ChatService : IDisposable { diff --git a/src/BE/Services/ChatServices/ChatServiceExtensions.cs b/src/BE/Services/Models/ChatServiceExtensions.cs similarity index 97% rename from src/BE/Services/ChatServices/ChatServiceExtensions.cs rename to src/BE/Services/Models/ChatServiceExtensions.cs index 5f245925..357cbe48 100644 --- a/src/BE/Services/ChatServices/ChatServiceExtensions.cs +++ b/src/BE/Services/Models/ChatServiceExtensions.cs @@ -1,9 +1,9 @@ -using Chats.BE.Services.ChatServices.Dtos; -using Chats.BE.Services.ChatServices.Extensions; +using Chats.BE.Services.Models.Dtos; +using Chats.BE.Services.Models.Extensions; using OpenAI.Chat; using System.Runtime.CompilerServices; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public abstract partial class ChatService { diff --git a/src/BE/Services/ChatServices/Implementations/DashScope/DashScopeChatService.cs b/src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs similarity index 97% rename from src/BE/Services/ChatServices/Implementations/DashScope/DashScopeChatService.cs rename to src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs index 9b425946..17c4bc51 100644 --- a/src/BE/Services/ChatServices/Implementations/DashScope/DashScopeChatService.cs +++ b/src/BE/Services/Models/ChatServices/DashScope/DashScopeChatService.cs @@ -1,4 +1,4 @@ -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using Sdcb.DashScope; using Sdcb.DashScope.TextGeneration; using OpenAIChatMessage = OpenAI.Chat.ChatMessage; @@ -9,11 +9,11 @@ using System.Runtime.CompilerServices; using OpenAI.Chat; using Chats.BE.DB; -using Chats.BE.Services.ChatServices.Extensions; +using Chats.BE.Services.Models.Extensions; using ChatTokenUsage = Sdcb.DashScope.TextGeneration.ChatTokenUsage; using ChatMessage = Sdcb.DashScope.TextGeneration.ChatMessage; -namespace Chats.BE.Services.ChatServices.Implementations.DashScope; +namespace Chats.BE.Services.Models.ChatServices.DashScope; public class DashScopeChatService : ChatService { diff --git a/src/BE/Services/ChatServices/Implementations/DashScope/JsonDashScopeConfig.cs b/src/BE/Services/Models/ChatServices/DashScope/JsonDashScopeConfig.cs similarity index 77% rename from src/BE/Services/ChatServices/Implementations/DashScope/JsonDashScopeConfig.cs rename to src/BE/Services/Models/ChatServices/DashScope/JsonDashScopeConfig.cs index 945184c1..f315feaa 100644 --- a/src/BE/Services/ChatServices/Implementations/DashScope/JsonDashScopeConfig.cs +++ b/src/BE/Services/Models/ChatServices/DashScope/JsonDashScopeConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices.Implementations.DashScope; +namespace Chats.BE.Services.Models.ChatServices.DashScope; public class JsonDashScopeConfig { diff --git a/src/BE/Services/ChatServices/Implementations/DashScope/JsonDashScopeModelConfig.cs b/src/BE/Services/Models/ChatServices/DashScope/JsonDashScopeModelConfig.cs similarity index 86% rename from src/BE/Services/ChatServices/Implementations/DashScope/JsonDashScopeModelConfig.cs rename to src/BE/Services/Models/ChatServices/DashScope/JsonDashScopeModelConfig.cs index b52ee969..b9f9a6ff 100644 --- a/src/BE/Services/ChatServices/Implementations/DashScope/JsonDashScopeModelConfig.cs +++ b/src/BE/Services/Models/ChatServices/DashScope/JsonDashScopeModelConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices.Implementations.DashScope; +namespace Chats.BE.Services.Models.ChatServices.DashScope; public record JsonDashScopeModelConfig { diff --git a/src/BE/Services/ChatServices/Implementations/Hunyuan/HunyuanChatService.cs b/src/BE/Services/Models/ChatServices/Hunyuan/HunyuanChatService.cs similarity index 97% rename from src/BE/Services/ChatServices/Implementations/Hunyuan/HunyuanChatService.cs rename to src/BE/Services/Models/ChatServices/Hunyuan/HunyuanChatService.cs index fcda09f0..cfef14c2 100644 --- a/src/BE/Services/ChatServices/Implementations/Hunyuan/HunyuanChatService.cs +++ b/src/BE/Services/Models/ChatServices/Hunyuan/HunyuanChatService.cs @@ -1,4 +1,4 @@ -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using OpenAI.Chat; using System.Text.Json; using TencentCloud.Common; @@ -14,7 +14,7 @@ using Chats.BE.DB; using Message = TencentCloud.Hunyuan.V20230901.Models.Message; -namespace Chats.BE.Services.ChatServices.Implementations.Hunyuan; +namespace Chats.BE.Services.Models.ChatServices.Hunyuan; public class HunyuanChatService : ChatService { diff --git a/src/BE/Services/ChatServices/Implementations/Hunyuan/HuyuanChatSegment.cs b/src/BE/Services/Models/ChatServices/Hunyuan/HuyuanChatSegment.cs similarity index 94% rename from src/BE/Services/ChatServices/Implementations/Hunyuan/HuyuanChatSegment.cs rename to src/BE/Services/Models/ChatServices/Hunyuan/HuyuanChatSegment.cs index 420ff1a4..54be984a 100644 --- a/src/BE/Services/ChatServices/Implementations/Hunyuan/HuyuanChatSegment.cs +++ b/src/BE/Services/Models/ChatServices/Hunyuan/HuyuanChatSegment.cs @@ -1,4 +1,4 @@ -namespace Chats.BE.Services.ChatServices.Implementations.Hunyuan; +namespace Chats.BE.Services.Models.ChatServices.Hunyuan; using System.Text.Json.Serialization; diff --git a/src/BE/Services/ChatServices/Implementations/Hunyuan/JsonHunyuanKeyConfig.cs b/src/BE/Services/Models/ChatServices/Hunyuan/JsonHunyuanKeyConfig.cs similarity index 79% rename from src/BE/Services/ChatServices/Implementations/Hunyuan/JsonHunyuanKeyConfig.cs rename to src/BE/Services/Models/ChatServices/Hunyuan/JsonHunyuanKeyConfig.cs index b681046c..12d8e548 100644 --- a/src/BE/Services/ChatServices/Implementations/Hunyuan/JsonHunyuanKeyConfig.cs +++ b/src/BE/Services/Models/ChatServices/Hunyuan/JsonHunyuanKeyConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices.Implementations.Hunyuan; +namespace Chats.BE.Services.Models.ChatServices.Hunyuan; public record JsonHunyuanKeyConfig { diff --git a/src/BE/Services/ChatServices/Implementations/Hunyuan/UnixDateTimeOffsetConverter.cs b/src/BE/Services/Models/ChatServices/Hunyuan/UnixDateTimeOffsetConverter.cs similarity index 91% rename from src/BE/Services/ChatServices/Implementations/Hunyuan/UnixDateTimeOffsetConverter.cs rename to src/BE/Services/Models/ChatServices/Hunyuan/UnixDateTimeOffsetConverter.cs index a968e35c..4e473339 100644 --- a/src/BE/Services/ChatServices/Implementations/Hunyuan/UnixDateTimeOffsetConverter.cs +++ b/src/BE/Services/Models/ChatServices/Hunyuan/UnixDateTimeOffsetConverter.cs @@ -1,7 +1,7 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices.Implementations.Hunyuan; +namespace Chats.BE.Services.Models.ChatServices.Hunyuan; internal class UnixDateTimeOffsetConverter : JsonConverter { diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/AzureChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/AzureChatService.cs similarity index 94% rename from src/BE/Services/ChatServices/Implementations/OpenAI/AzureChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/AzureChatService.cs index 9ef20529..044ef800 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/AzureChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/AzureChatService.cs @@ -5,7 +5,7 @@ using System.ClientModel; using System.Reflection; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class AzureChatService(Model model) : OpenAIChatService(model, CreateChatClient(model)) { @@ -15,13 +15,13 @@ static ChatClient CreateChatClient(Model model) ArgumentException.ThrowIfNullOrWhiteSpace(model.ModelKey.Secret, nameof(model.ModelKey.Secret)); AzureOpenAIClientOptions options = new(); - if (model.ModelReference.IsSdkUnsupportedO1) - { - // o1 only supports api version: 2024-12-01-preview + if (model.ModelReference.IsSdkUnsupportedO1) + { + // o1 only supports api version: 2024-12-01-preview options .GetType() .GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance) - !.SetValue(options, "2024-12-01-preview"); + !.SetValue(options, "2024-12-01-preview"); } OpenAIClient api = new AzureOpenAIClient( diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/DeepSeekChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/DeepSeekChatService.cs similarity index 67% rename from src/BE/Services/ChatServices/Implementations/OpenAI/DeepSeekChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/DeepSeekChatService.cs index 893faa92..b57593aa 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/DeepSeekChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/DeepSeekChatService.cs @@ -1,5 +1,5 @@ using Chats.BE.DB; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class DeepSeekChatService(Model model) : OpenAIChatService(model, new Uri("https://api.deepseek.com/v1")); \ No newline at end of file diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/GLMChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/GLMChatService.cs similarity index 69% rename from src/BE/Services/ChatServices/Implementations/OpenAI/GLMChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/GLMChatService.cs index 0e62e4dc..ca0cb42f 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/GLMChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/GLMChatService.cs @@ -1,6 +1,6 @@ using Chats.BE.DB; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class GLMChatService(Model model) : OpenAIChatService(model, new Uri("https://open.bigmodel.cn/api/paas/v4/")) { diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/GithubModelsChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/GithubModelsChatService.cs similarity index 90% rename from src/BE/Services/ChatServices/Implementations/OpenAI/GithubModelsChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/GithubModelsChatService.cs index e45a76e3..76da78e5 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/GithubModelsChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/GithubModelsChatService.cs @@ -1,7 +1,7 @@ using Chats.BE.DB; using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class GithubModelsChatService(Model model) : OpenAIChatService(model, new Uri("https://models.inference.ai.azure.com")) { diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/GoogleAIChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/GoogleAIChatService.cs similarity index 90% rename from src/BE/Services/ChatServices/Implementations/OpenAI/GoogleAIChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/GoogleAIChatService.cs index eac83285..879946d0 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/GoogleAIChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/GoogleAIChatService.cs @@ -2,7 +2,7 @@ using OpenAI.Chat; using System.Runtime.CompilerServices; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class GoogleAIChatService(Model model) : OpenAIChatService(model, new Uri("https://generativelanguage.googleapis.com/v1beta/openai/")) { diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/KimiChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/KimiChatService.cs similarity index 67% rename from src/BE/Services/ChatServices/Implementations/OpenAI/KimiChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/KimiChatService.cs index b909ee68..f8e0b2bc 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/KimiChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/KimiChatService.cs @@ -1,5 +1,5 @@ using Chats.BE.DB; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class KimiChatService(Model model) : OpenAIChatService(model, new Uri("https://api.moonshot.cn/v1")); \ No newline at end of file diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/LingYiChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/LingYiChatService.cs similarity index 68% rename from src/BE/Services/ChatServices/Implementations/OpenAI/LingYiChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/LingYiChatService.cs index 7380a7fd..ab090dd5 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/LingYiChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/LingYiChatService.cs @@ -1,5 +1,5 @@ using Chats.BE.DB; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class LingYiChatService(Model model) : OpenAIChatService(model, new Uri("https://api.lingyiwanwu.com/v1")); diff --git a/src/BE/Services/Models/ChatServices/OpenAI/MiniMaxChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/MiniMaxChatService.cs new file mode 100644 index 00000000..f46096b8 --- /dev/null +++ b/src/BE/Services/Models/ChatServices/OpenAI/MiniMaxChatService.cs @@ -0,0 +1,5 @@ +using Chats.BE.DB; + +namespace Chats.BE.Services.Models.ChatServices.OpenAI; + +public class MiniMaxChatService(Model model) : OpenAIChatService(model, new Uri("https://api.minimax.chat/v1")); diff --git a/src/BE/Services/Models/ChatServices/OpenAI/OllamaChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/OllamaChatService.cs new file mode 100644 index 00000000..02468f83 --- /dev/null +++ b/src/BE/Services/Models/ChatServices/OpenAI/OllamaChatService.cs @@ -0,0 +1,5 @@ +using Chats.BE.DB; + +namespace Chats.BE.Services.Models.ChatServices.OpenAI; + +public class OllamaChatService(Model model) : OpenAIChatService(model); \ No newline at end of file diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/OpenAIChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/OpenAIChatService.cs similarity index 97% rename from src/BE/Services/ChatServices/Implementations/OpenAI/OpenAIChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/OpenAIChatService.cs index caf945af..2acc5235 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/OpenAIChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/OpenAIChatService.cs @@ -1,4 +1,4 @@ -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using OpenAI.Chat; using OpenAI; using System.Runtime.CompilerServices; @@ -7,7 +7,7 @@ using System.ClientModel.Primitives; using System.Text.Json; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public partial class OpenAIChatService : ChatService { diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/SparkDeskChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/SparkDeskChatService.cs similarity index 93% rename from src/BE/Services/ChatServices/Implementations/OpenAI/SparkDeskChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/SparkDeskChatService.cs index 570c2d88..aae1981c 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/SparkDeskChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/SparkDeskChatService.cs @@ -1,7 +1,7 @@ using Chats.BE.DB; using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class SparkDeskChatService(Model model) : OpenAIChatService(model, new Uri("https://spark-api-open.xf-yun.com/v1")) { diff --git a/src/BE/Services/ChatServices/Implementations/OpenAI/XAIChatService.cs b/src/BE/Services/Models/ChatServices/OpenAI/XAIChatService.cs similarity index 65% rename from src/BE/Services/ChatServices/Implementations/OpenAI/XAIChatService.cs rename to src/BE/Services/Models/ChatServices/OpenAI/XAIChatService.cs index 338018ab..e600e66c 100644 --- a/src/BE/Services/ChatServices/Implementations/OpenAI/XAIChatService.cs +++ b/src/BE/Services/Models/ChatServices/OpenAI/XAIChatService.cs @@ -1,5 +1,5 @@ using Chats.BE.DB; -namespace Chats.BE.Services.ChatServices.Implementations.OpenAI; +namespace Chats.BE.Services.Models.ChatServices.OpenAI; public class XAIChatService(Model model) : OpenAIChatService(model, new Uri("https://api.x.ai/v1")); \ No newline at end of file diff --git a/src/BE/Services/ChatServices/Implementations/QianFan/JsonQianFanApiConfig.cs b/src/BE/Services/Models/ChatServices/QianFan/JsonQianFanApiConfig.cs similarity index 78% rename from src/BE/Services/ChatServices/Implementations/QianFan/JsonQianFanApiConfig.cs rename to src/BE/Services/Models/ChatServices/QianFan/JsonQianFanApiConfig.cs index 36fdb87d..b2561eaf 100644 --- a/src/BE/Services/ChatServices/Implementations/QianFan/JsonQianFanApiConfig.cs +++ b/src/BE/Services/Models/ChatServices/QianFan/JsonQianFanApiConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices.Implementations.QianFan; +namespace Chats.BE.Services.Models.ChatServices.QianFan; public record JsonQianFanApiConfig { diff --git a/src/BE/Services/ChatServices/Implementations/QianFan/JsonQianFanModelConfig.cs b/src/BE/Services/Models/ChatServices/QianFan/JsonQianFanModelConfig.cs similarity index 86% rename from src/BE/Services/ChatServices/Implementations/QianFan/JsonQianFanModelConfig.cs rename to src/BE/Services/Models/ChatServices/QianFan/JsonQianFanModelConfig.cs index 851a1596..083176e1 100644 --- a/src/BE/Services/ChatServices/Implementations/QianFan/JsonQianFanModelConfig.cs +++ b/src/BE/Services/Models/ChatServices/QianFan/JsonQianFanModelConfig.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices.Implementations.QianFan; +namespace Chats.BE.Services.Models.ChatServices.QianFan; public record JsonQianFanModelConfig { diff --git a/src/BE/Services/ChatServices/Implementations/QianFan/QianFanChatService.cs b/src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs similarity index 95% rename from src/BE/Services/ChatServices/Implementations/QianFan/QianFanChatService.cs rename to src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs index 2a1780d7..378a96c8 100644 --- a/src/BE/Services/ChatServices/Implementations/QianFan/QianFanChatService.cs +++ b/src/BE/Services/Models/ChatServices/QianFan/QianFanChatService.cs @@ -1,4 +1,4 @@ -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using Sdcb.WenXinQianFan; using System.Runtime.CompilerServices; using System.Text.Json; @@ -10,9 +10,9 @@ using ChatMessage = Sdcb.WenXinQianFan.ChatMessage; using Chats.BE.DB; using OpenAI.Chat; -using Chats.BE.Services.ChatServices.Extensions; +using Chats.BE.Services.Models.Extensions; -namespace Chats.BE.Services.ChatServices.Implementations.QianFan; +namespace Chats.BE.Services.Models.ChatServices.QianFan; public class QianFanChatService : ChatService { diff --git a/src/BE/Services/ChatServices/Implementations/Test/TestChatService.cs b/src/BE/Services/Models/ChatServices/Test/TestChatService.cs similarity index 97% rename from src/BE/Services/ChatServices/Implementations/Test/TestChatService.cs rename to src/BE/Services/Models/ChatServices/Test/TestChatService.cs index 1f707212..864b4bf8 100644 --- a/src/BE/Services/ChatServices/Implementations/Test/TestChatService.cs +++ b/src/BE/Services/Models/ChatServices/Test/TestChatService.cs @@ -1,10 +1,10 @@ using Chats.BE.DB; -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using OpenAI.Chat; using System.Runtime.CompilerServices; using System.Text; -namespace Chats.BE.Services.ChatServices.Implementations.Test; +namespace Chats.BE.Services.Models.ChatServices.Test; public class TestChatService(Model model) : ChatService(model) { diff --git a/src/BE/Services/ChatServices/ChatStopService.cs b/src/BE/Services/Models/ChatStopService.cs similarity index 97% rename from src/BE/Services/ChatServices/ChatStopService.cs rename to src/BE/Services/Models/ChatStopService.cs index 00359617..33fef8a3 100644 --- a/src/BE/Services/ChatServices/ChatStopService.cs +++ b/src/BE/Services/Models/ChatStopService.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.WebUtilities; using System.Runtime.Caching; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public class ChatStopService { diff --git a/src/BE/Services/ChatServices/DBChatRole.cs b/src/BE/Services/Models/DBChatRole.cs similarity index 63% rename from src/BE/Services/ChatServices/DBChatRole.cs rename to src/BE/Services/Models/DBChatRole.cs index 2b490202..2b024a4c 100644 --- a/src/BE/Services/ChatServices/DBChatRole.cs +++ b/src/BE/Services/Models/DBChatRole.cs @@ -1,4 +1,4 @@ -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public enum DBChatRole { diff --git a/src/BE/Services/ChatServices/DBFinishReason.cs b/src/BE/Services/Models/DBFinishReason.cs similarity index 87% rename from src/BE/Services/ChatServices/DBFinishReason.cs rename to src/BE/Services/Models/DBFinishReason.cs index 58ae1e7d..d7f1a95c 100644 --- a/src/BE/Services/ChatServices/DBFinishReason.cs +++ b/src/BE/Services/Models/DBFinishReason.cs @@ -1,4 +1,4 @@ -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public enum DBFinishReason : byte { diff --git a/src/BE/Services/ChatServices/Dtos/ChatSegment.cs b/src/BE/Services/Models/Dtos/ChatSegment.cs similarity index 95% rename from src/BE/Services/ChatServices/Dtos/ChatSegment.cs rename to src/BE/Services/Models/Dtos/ChatSegment.cs index 76439448..65607f8f 100644 --- a/src/BE/Services/ChatServices/Dtos/ChatSegment.cs +++ b/src/BE/Services/Models/Dtos/ChatSegment.cs @@ -1,6 +1,6 @@ using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices.Dtos; +namespace Chats.BE.Services.Models.Dtos; public record ChatSegment { diff --git a/src/BE/Services/ChatServices/Dtos/ChatTokenUsage.cs b/src/BE/Services/Models/Dtos/ChatTokenUsage.cs similarity index 87% rename from src/BE/Services/ChatServices/Dtos/ChatTokenUsage.cs rename to src/BE/Services/Models/Dtos/ChatTokenUsage.cs index 2bbdec75..d5b2e322 100644 --- a/src/BE/Services/ChatServices/Dtos/ChatTokenUsage.cs +++ b/src/BE/Services/Models/Dtos/ChatTokenUsage.cs @@ -1,4 +1,4 @@ -namespace Chats.BE.Services.ChatServices.Dtos; +namespace Chats.BE.Services.Models.Dtos; public record ChatTokenUsage { diff --git a/src/BE/Services/ChatServices/Dtos/InternalChatSegment.cs b/src/BE/Services/Models/Dtos/InternalChatSegment.cs similarity index 98% rename from src/BE/Services/ChatServices/Dtos/InternalChatSegment.cs rename to src/BE/Services/Models/Dtos/InternalChatSegment.cs index 63d89e17..9493973f 100644 --- a/src/BE/Services/ChatServices/Dtos/InternalChatSegment.cs +++ b/src/BE/Services/Models/Dtos/InternalChatSegment.cs @@ -1,7 +1,7 @@ using Chats.BE.Controllers.OpenAICompatible.Dtos; using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices.Dtos; +namespace Chats.BE.Services.Models.Dtos; public record InternalChatSegment { diff --git a/src/BE/Services/ChatServices/Extensions/ChatCompletionOptionsExtensions.cs b/src/BE/Services/Models/Extensions/ChatCompletionOptionsExtensions.cs similarity index 98% rename from src/BE/Services/ChatServices/Extensions/ChatCompletionOptionsExtensions.cs rename to src/BE/Services/Models/Extensions/ChatCompletionOptionsExtensions.cs index d1ee37d8..9e9d4609 100644 --- a/src/BE/Services/ChatServices/Extensions/ChatCompletionOptionsExtensions.cs +++ b/src/BE/Services/Models/Extensions/ChatCompletionOptionsExtensions.cs @@ -2,7 +2,7 @@ using System.Reflection; using System.Runtime.CompilerServices; -namespace Chats.BE.Services.ChatServices.Extensions; +namespace Chats.BE.Services.Models.Extensions; public static class ChatCompletionOptionsExtensions { diff --git a/src/BE/Services/ChatServices/Extensions/ChatMessageContentPartExtensions.cs b/src/BE/Services/Models/Extensions/ChatMessageContentPartExtensions.cs similarity index 92% rename from src/BE/Services/ChatServices/Extensions/ChatMessageContentPartExtensions.cs rename to src/BE/Services/Models/Extensions/ChatMessageContentPartExtensions.cs index 07b35aef..17d715a7 100644 --- a/src/BE/Services/ChatServices/Extensions/ChatMessageContentPartExtensions.cs +++ b/src/BE/Services/Models/Extensions/ChatMessageContentPartExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.ML.Tokenizers; using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices.Extensions; +namespace Chats.BE.Services.Models.Extensions; public static class ChatMessageContentPartExtensions { diff --git a/src/BE/Services/ChatServices/Extensions/ChatMessageExtensions.cs b/src/BE/Services/Models/Extensions/ChatMessageExtensions.cs similarity index 85% rename from src/BE/Services/ChatServices/Extensions/ChatMessageExtensions.cs rename to src/BE/Services/Models/Extensions/ChatMessageExtensions.cs index c5a1f845..f5ee3c58 100644 --- a/src/BE/Services/ChatServices/Extensions/ChatMessageExtensions.cs +++ b/src/BE/Services/Models/Extensions/ChatMessageExtensions.cs @@ -1,7 +1,7 @@ using Microsoft.ML.Tokenizers; using OpenAI.Chat; -namespace Chats.BE.Services.ChatServices.Extensions; +namespace Chats.BE.Services.Models.Extensions; public static class ChatMessageExtensions { diff --git a/src/BE/Services/ChatServices/InChatContext.cs b/src/BE/Services/Models/InChatContext.cs similarity index 98% rename from src/BE/Services/ChatServices/InChatContext.cs rename to src/BE/Services/Models/InChatContext.cs index 46069208..a56442d9 100644 --- a/src/BE/Services/ChatServices/InChatContext.cs +++ b/src/BE/Services/Models/InChatContext.cs @@ -3,11 +3,11 @@ using Chats.BE.DB.Enums; using Chats.BE.DB.Jsons; using Chats.BE.Services.Common; -using Chats.BE.Services.ChatServices.Dtos; +using Chats.BE.Services.Models.Dtos; using System.Diagnostics; using System.Text; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public class InChatContext(long firstTick) { diff --git a/src/BE/Services/Models/ModelLoader.cs b/src/BE/Services/Models/ModelLoader.cs new file mode 100644 index 00000000..abf993e3 --- /dev/null +++ b/src/BE/Services/Models/ModelLoader.cs @@ -0,0 +1,8 @@ +using Chats.BE.DB; + +namespace Chats.BE.Services.Models; + +public abstract class ModelLoader +{ + public abstract Task ListModels(ModelKey modelKey, CancellationToken cancellationToken); +} diff --git a/src/BE/Services/Models/ModelLoaders/OpenAIModelLoader.cs b/src/BE/Services/Models/ModelLoaders/OpenAIModelLoader.cs new file mode 100644 index 00000000..f66f8ef7 --- /dev/null +++ b/src/BE/Services/Models/ModelLoaders/OpenAIModelLoader.cs @@ -0,0 +1,21 @@ +using Chats.BE.DB; +using OpenAI; +using OpenAI.Models; +using System.ClientModel; + +namespace Chats.BE.Services.Models.ModelLoaders; + +public class OpenAIModelLoader : ModelLoader +{ + public override async Task ListModels(ModelKey modelKey, CancellationToken cancellationToken) + { + ArgumentException.ThrowIfNullOrWhiteSpace(modelKey.Secret, nameof(modelKey.Secret)); + + OpenAIClient api = new(new ApiKeyCredential(modelKey.Secret), new OpenAIClientOptions() + { + Endpoint = !string.IsNullOrWhiteSpace(modelKey.Host) ? new Uri(modelKey.Host) : null, + }); + ClientResult result = await api.GetOpenAIModelClient().GetModelsAsync(cancellationToken); + return result.Value.Select(m => m.Id).ToArray(); + } +} diff --git a/src/BE/Services/ChatServices/ModelValidateResult.cs b/src/BE/Services/Models/ModelValidateResult.cs similarity index 93% rename from src/BE/Services/ChatServices/ModelValidateResult.cs rename to src/BE/Services/Models/ModelValidateResult.cs index 78ba76f1..066361eb 100644 --- a/src/BE/Services/ChatServices/ModelValidateResult.cs +++ b/src/BE/Services/Models/ModelValidateResult.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json.Serialization; -namespace Chats.BE.Services.ChatServices; +namespace Chats.BE.Services.Models; public record ModelValidateResult { diff --git a/src/BE/Services/Sessions/JwtKeyManager.cs b/src/BE/Services/Sessions/JwtKeyManager.cs index f476cb4d..73715904 100644 --- a/src/BE/Services/Sessions/JwtKeyManager.cs +++ b/src/BE/Services/Sessions/JwtKeyManager.cs @@ -1,28 +1,21 @@ -using Chats.BE.DB; -using Chats.BE.Services.Configs; -using Microsoft.EntityFrameworkCore; +using System.Configuration; namespace Chats.BE.Services.Sessions; -public class JwtKeyManager(ChatsDB db) +public class JwtKeyManager(IConfiguration configuration) { - public async Task GetOrCreateSecretKey(CancellationToken cancellationToken) + public string GetOrCreateSecretKey() { - // check environment variable first - // if it's not set, then generate a new one and store into database - string? secretKey = Environment.GetEnvironmentVariable("JWT_SECRET_KEY"); - if (secretKey != null) return secretKey; - - string? configText = await db.Configs - .Where(s => s.Key == DBConfigKey.JwtSecretKey) - .Select(x => x.Value) - .SingleOrDefaultAsync(cancellationToken); - if (configText != null) return configText; - - string generated = Guid.NewGuid().ToString(); - db.Configs.Add(new Config { Key = DBConfigKey.JwtSecretKey, Value = generated, Description = $"Generated at {DateTime.Now:O}" }); - await db.SaveChangesAsync(cancellationToken); - - return generated; + string? secretKey = configuration["JWT_SECRET_KEY"]; + if (secretKey != null) + { + return secretKey; + } + else + { + string generated = Guid.NewGuid().ToString(); + configuration["JWT_SECRET_KEY"] = generated; + return generated; + } } } diff --git a/src/BE/Services/Sessions/SessionManager.cs b/src/BE/Services/Sessions/SessionManager.cs index 53382df2..a1324268 100644 --- a/src/BE/Services/Sessions/SessionManager.cs +++ b/src/BE/Services/Sessions/SessionManager.cs @@ -13,10 +13,10 @@ public class SessionManager(JwtKeyManager jwtKeyManager) private const string ValidAudience = "chats"; private static readonly TimeSpan ValidPeriod = TimeSpan.FromHours(8); - public async Task GetCachedUserInfoBySession(string jwt, CancellationToken cancellationToken = default) + public Task GetCachedUserInfoBySession(string jwt, CancellationToken _ = default) { - ClaimsPrincipal claims = ValidateJwt(jwt, await GetSecurityKey(cancellationToken)); - return SessionEntry.FromClaims(claims); + ClaimsPrincipal claims = ValidateJwt(jwt, GetSecurityKey()); + return Task.FromResult(SessionEntry.FromClaims(claims)); } private static ClaimsPrincipal ValidateJwt(string jwt, SecurityKey signingKey) @@ -51,11 +51,11 @@ internal static byte[] Pdkdf2StringToByte32(string input) return new Rfc2898DeriveBytes(input, salt, 10000, HashAlgorithmName.SHA256).GetBytes(32); } - private async Task GetSecurityKey(CancellationToken cancellationToken) => new(Pdkdf2StringToByte32(await jwtKeyManager.GetOrCreateSecretKey(cancellationToken))); + private SymmetricSecurityKey GetSecurityKey() => new(Pdkdf2StringToByte32(jwtKeyManager.GetOrCreateSecretKey())); - public async Task GenerateSessionForUser(User user, CancellationToken cancellationToken) + public Task GenerateSessionForUser(User user, CancellationToken _) { - SigningCredentials cred = new(await GetSecurityKey(cancellationToken), SecurityAlgorithms.HmacSha256); + SigningCredentials cred = new(GetSecurityKey(), SecurityAlgorithms.HmacSha256); SessionEntry sessionEntry = new() { UserId = user.Id, @@ -73,12 +73,12 @@ public async Task GenerateSessionForUser(User user, CancellationT string jwt = new JwtSecurityTokenHandler().WriteToken(token); bool hasPayService = false; - return new LoginResponse + return Task.FromResult(new LoginResponse { SessionId = jwt, UserName = user.DisplayName, Role = user.Role, CanReCharge = hasPayService, - }; + }); } } diff --git a/src/FE/apis/adminApis.ts b/src/FE/apis/adminApis.ts index ca938b28..ceb05194 100644 --- a/src/FE/apis/adminApis.ts +++ b/src/FE/apis/adminApis.ts @@ -262,13 +262,6 @@ export const deleteModelKeys = (id: number) => { return fetchService.delete(`/api/admin/model-keys/${id}`); }; -export const postAutoCreateModels = (modelKey: number) => { - const fetchService = useFetch(); - return fetchService.post( - `/api/admin/model-keys/${modelKey}/auto-create-models`, - ); -}; - export const getUserInitialConfig = () => { const fetchServer = useFetch(); return fetchServer.get( diff --git a/src/FE/locales/zh-CN.json b/src/FE/locales/zh-CN.json index dbdf03f2..ac5c169f 100644 --- a/src/FE/locales/zh-CN.json +++ b/src/FE/locales/zh-CN.json @@ -213,6 +213,7 @@ "Rank": "#", "ID": "模型", "Model Display Name": "模型显示名称", + "Model Reference Name": "模型参考名称", "Model Key": "模型密钥", "Model Type": "模型类型", "Model Provider": "模型提供商", diff --git a/src/FE/pages/admin/_components/ModelKeys/ConfigModelModal.tsx b/src/FE/pages/admin/_components/ModelKeys/ConfigModelModal.tsx index 98916bba..0059aa84 100644 --- a/src/FE/pages/admin/_components/ModelKeys/ConfigModelModal.tsx +++ b/src/FE/pages/admin/_components/ModelKeys/ConfigModelModal.tsx @@ -128,6 +128,10 @@ const ConfigModelModal = (props: IProps) => { setModels(modelList); } + function needDeploymentName(modelProviderId: DBModelProvider) { + return modelProviderId === DBModelProvider.AzureOpenAI || modelProviderId === DBModelProvider.Ollama; + } + return ( @@ -138,16 +142,28 @@ const ConfigModelModal = (props: IProps) => { - {t('Model Display Name')} - {modelProverId == DBModelProvider.Azure && ( + {needDeploymentName(modelProverId) && ( {t('Deployment Name')} )} + {t('Model Reference Name')} {t('Actions')} {models.map((model, index) => ( + {needDeploymentName(modelProverId) && ( + + { + handleChangeDeploymentName(index, e.target.value); + }} + /> + + )} {model.referenceName} {model.isExists && ( @@ -161,18 +177,6 @@ const ConfigModelModal = (props: IProps) => { )} - {modelProverId == DBModelProvider.Azure && ( - - { - handleChangeDeploymentName(index, e.target.value); - }} - /> - - )} {!model.isExists && (