Skip to content

Commit

Permalink
Merge pull request #37 from sdcb/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
sdcb authored Jan 16, 2025
2 parents 15a04df + 340d686 commit dc8044d
Show file tree
Hide file tree
Showing 45 changed files with 1,003 additions and 243 deletions.
20 changes: 10 additions & 10 deletions src/BE/Chats.BE.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@

<ItemGroup>
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.410.6" />
<PackageReference Include="AWSSDK.S3" Version="3.7.412" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" /> <!-- fix security issue from Azure.AI.OpenAI -->
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
<PackageReference Include="Microsoft.ML.Tokenizers" Version="1.0.0" />
<PackageReference Include="Microsoft.ML.Tokenizers.Data.Cl100kBase" Version="1.0.0" />
<PackageReference Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="1.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.1" />
<PackageReference Include="Microsoft.ML.Tokenizers" Version="1.0.1" />
<PackageReference Include="Microsoft.ML.Tokenizers.Data.Cl100kBase" Version="1.0.1" />
<PackageReference Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="1.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageReference Include="OpenAI" Version="2.1.0" />
<PackageReference Include="Sdcb.DashScope" Version="2.0.0" />
<PackageReference Include="Sdcb.WenXinQianFan" Version="1.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="System.Runtime.Caching" Version="9.0.0" />
<PackageReference Include="TencentCloudSDK.Hunyuan" Version="3.0.1143" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1143" />
<PackageReference Include="System.Runtime.Caching" Version="9.0.1" />
<PackageReference Include="TencentCloudSDK.Hunyuan" Version="3.0.1165" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1165" />
</ItemGroup>

</Project>
33 changes: 17 additions & 16 deletions src/BE/Controllers/Admin/AdminMessage/AdminMessageController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ namespace Chats.BE.Controllers.Admin.AdminMessage;
[Route("api/admin"), AuthorizeAdmin]
public class AdminMessageController(ChatsDB db, CurrentUser currentUser, IUrlEncryptionService urlEncryption) : ControllerBase
{
[HttpGet("messages")]
public async Task<ActionResult<PagedResult<AdminChatsDto>>> GetMessages([FromQuery] PagingRequest req, CancellationToken cancellationToken)
[HttpGet("chats")]
public async Task<ActionResult<PagedResult<AdminChatsDto>>> GetAdminChats([FromQuery] PagingRequest req, CancellationToken cancellationToken)
{
IQueryable<Chat> chats = db.Chats
.Where(x => x.User.Role != "admin" || x.UserId == currentUser.Id);
Expand All @@ -27,12 +27,13 @@ public async Task<ActionResult<PagedResult<AdminChatsDto>>> GetMessages([FromQue
}

return await PagedResult.FromQuery(chats
.OrderByDescending(x => x.CreatedAt)
.OrderByDescending(x => x.Id)
.Select(x => new AdminChatsDto
{
Id = urlEncryption.EncryptChatId(x.Id),
Id = x.Id.ToString(),
CreatedAt = x.CreatedAt,
IsDeleted = x.IsArchived,
IsShared = x.ChatShares.Any(),
Title = x.Title,
UserName = x.User.UserName,
Spans = x.ChatSpans.Select(s => new ChatSpanDto
Expand Down Expand Up @@ -99,19 +100,19 @@ public async Task<ActionResult<ChatsResponseWithMessage>> GetAdminMessage(int ch
.ToArray(),
CreatedAt = x.CreatedAt,
SpanId = x.SpanId,
Usage = x.MessageResponse!.Usage == null ? null : new ChatMessageTempUsage()
Usage = x.Usage == null ? null : new ChatMessageTempUsage()
{
InputTokens = x.MessageResponse.Usage.InputTokens,
OutputTokens = x.MessageResponse.Usage.OutputTokens,
InputPrice = x.MessageResponse.Usage.InputCost,
OutputPrice = x.MessageResponse.Usage.OutputCost,
ReasoningTokens = x.MessageResponse.Usage.ReasoningTokens,
Duration = x.MessageResponse.Usage.TotalDurationMs - x.MessageResponse.Usage.PreprocessDurationMs,
FirstTokenLatency = x.MessageResponse.Usage.FirstResponseDurationMs,
ModelId = x.MessageResponse.Usage.UserModel.ModelId,
ModelName = x.MessageResponse.Usage.UserModel.Model.Name,
ModelProviderId = x.MessageResponse.Usage.UserModel.Model.ModelKey.ModelProviderId,
Reaction = x.MessageResponse.ReactionId,
InputTokens = x.Usage.InputTokens,
OutputTokens = x.Usage.OutputTokens,
InputPrice = x.Usage.InputCost,
OutputPrice = x.Usage.OutputCost,
ReasoningTokens = x.Usage.ReasoningTokens,
Duration = x.Usage.TotalDurationMs - x.Usage.PreprocessDurationMs,
FirstTokenLatency = x.Usage.FirstResponseDurationMs,
ModelId = x.Usage.UserModel.ModelId,
ModelName = x.Usage.UserModel.Model.Name,
ModelProviderId = x.Usage.UserModel.Model.ModelKey.ModelProviderId,
Reaction = x.ReactionId,
},
})
.OrderBy(x => x.CreatedAt)
Expand Down
3 changes: 3 additions & 0 deletions src/BE/Controllers/Admin/AdminMessage/Dtos/AdminChatsDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public record AdminChatsDto
[JsonPropertyName("isDeleted")]
public required bool IsDeleted { get; init; }

[JsonPropertyName("isShared")]
public required bool IsShared { get; init; }

[JsonPropertyName("spans")]
public required ChatSpanDto[] Spans { get; init; }

Expand Down
15 changes: 15 additions & 0 deletions src/BE/Controllers/Admin/GlobalConfigs/Dtos/CheckUpdateResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Text.Json.Serialization;

namespace Chats.BE.Controllers.Admin.GlobalConfigs.Dtos;

public record CheckUpdateResponse
{
[JsonPropertyName("hasNewVersion")]
public required bool HasNewVersion { get; init; }

[JsonPropertyName("tagName")]
public required string TagName { get; init; }

[JsonPropertyName("currentVersion")]
public required int CurrentVersion { get; init; }
}
66 changes: 66 additions & 0 deletions src/BE/Controllers/Admin/GlobalConfigs/GitHubReleaseChecker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System.Net.Http.Headers;
using System.Text.Json;

namespace Chats.BE.Controllers.Admin.GlobalConfigs;

public class GitHubReleaseChecker
{
private readonly HttpClient _httpClient;
private readonly string _owner;
private readonly string _repo;

public static GitHubReleaseChecker SdcbChats => new("sdcb", "chats");

public GitHubReleaseChecker(string owner, string repo)
{
_owner = owner;
_repo = repo;

_httpClient = new HttpClient { BaseAddress = new Uri("https://api.github.com") };
_httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("SdcbChatsVersionChecker", "1.0"));
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
}

public async Task<string> GetLatestReleaseTagNameAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = await _httpClient.GetAsync($"/repos/{_owner}/{_repo}/releases/latest", cancellationToken);
response.EnsureSuccessStatusCode(); // 如果请求失败,抛出异常

using Stream responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
var jsonDocument = await JsonDocument.ParseAsync(responseStream, cancellationToken: cancellationToken);

return jsonDocument.RootElement.GetProperty("tag_name").GetString()!;
}

public static bool IsNewVersionAvailableAsync(string latestTagName, int currentVersion)
{
try
{
if (latestTagName.StartsWith("r-"))
{
int latestVersion = int.Parse(latestTagName.Substring(2));
return latestVersion > currentVersion;
}
else
{
Console.WriteLine($"Warning: Latest tag name '{latestTagName}' does not follow the expected format 'r-XXX'.");
return false;
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error checking for updates: {ex.Message}");
return false;
}
catch (JsonException ex)
{
Console.WriteLine($"Error parsing JSON response: {ex.Message}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
return false;
}
}
}
30 changes: 30 additions & 0 deletions src/BE/Controllers/Admin/GlobalConfigs/VersionController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Chats.BE.Controllers.Admin.Common;
using Chats.BE.Controllers.Admin.GlobalConfigs.Dtos;
using Microsoft.AspNetCore.Mvc;

namespace Chats.BE.Controllers.Admin.GlobalConfigs;

[AuthorizeAdmin, Route("api/version")]
public class VersionController : ControllerBase
{
const int buildVersion = 0;

[HttpGet("current")]
public ActionResult GetCurrentVersion()
{
return Ok(buildVersion);
}

[HttpPost("check-update")]
public async Task<ActionResult> CheckUpdate(CancellationToken cancellationToken)
{
string tagName = await GitHubReleaseChecker.SdcbChats.GetLatestReleaseTagNameAsync(cancellationToken);
bool hasNewVersion = GitHubReleaseChecker.IsNewVersionAvailableAsync(tagName, buildVersion);
return Ok(new CheckUpdateResponse
{
CurrentVersion = buildVersion,
HasNewVersion = hasNewVersion,
TagName = tagName,
});
}
}
9 changes: 3 additions & 6 deletions src/BE/Controllers/Chats/Chats/ChatController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ private async Task<IActionResult> ChatPrivate(
}
if (resps.Any(x => x.Cost.CostUsage))
{
foreach (short modelId in userModels.Keys)
foreach (UserModel um in userModels.Values)
{
await balanceService.UpdateUsage(db, modelId, cancellationToken);
await balanceService.UpdateUsage(db, um.Id, cancellationToken);
}
}

Expand Down Expand Up @@ -467,10 +467,7 @@ private static async Task<ChatSpanResponse> ProcessChatSpan(
dbAssistantMessage.MessageContents.Add(MessageContent.FromError(errorText));
await writer.WriteAsync(SseResponseLine.Error(span.Id, errorText), cancellationToken);
}
dbAssistantMessage.MessageResponse = new MessageResponse()
{
Usage = icc.ToUserModelUsage(currentUser.Id, await clientInfoTask, isApi: false)
};
dbAssistantMessage.Usage = icc.ToUserModelUsage(currentUser.Id, await clientInfoTask, isApi: false);
await writer.WriteAsync(new SseResponseLine { Kind = SseResponseKind.End, Result = dbAssistantMessage, SpanId = span.Id }, cancellationToken);
writer.Complete();
return new ChatSpanResponse()
Expand Down
24 changes: 12 additions & 12 deletions src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ public static SseResponseLine ResponseMessage(
ParentId = assistantMessage.ParentId,
Role = (DBChatRole)assistantMessage.ChatRoleId,
SpanId = assistantMessage.SpanId,
Usage = assistantMessage.MessageResponse?.Usage == null ? null : new ChatMessageTempUsage()
Usage = assistantMessage.Usage == null ? null : new ChatMessageTempUsage()
{
Duration = assistantMessage.MessageResponse.Usage.TotalDurationMs - assistantMessage.MessageResponse.Usage.PreprocessDurationMs,
FirstTokenLatency = assistantMessage.MessageResponse.Usage.FirstResponseDurationMs,
InputPrice = assistantMessage.MessageResponse.Usage.InputCost,
InputTokens = assistantMessage.MessageResponse.Usage.InputTokens,
ModelId = assistantMessage.MessageResponse.Usage.UserModel.ModelId,
ModelName = assistantMessage.MessageResponse.Usage.UserModel.Model.Name,
OutputPrice = assistantMessage.MessageResponse.Usage.OutputCost,
OutputTokens = assistantMessage.MessageResponse.Usage.OutputTokens,
ReasoningTokens = assistantMessage.MessageResponse.Usage.ReasoningTokens,
ModelProviderId = assistantMessage.MessageResponse.Usage.UserModel.Model.ModelKey.ModelProviderId,
Reaction = assistantMessage.MessageResponse.ReactionId,
Duration = assistantMessage.Usage.TotalDurationMs - assistantMessage.Usage.PreprocessDurationMs,
FirstTokenLatency = assistantMessage.Usage.FirstResponseDurationMs,
InputPrice = assistantMessage.Usage.InputCost,
InputTokens = assistantMessage.Usage.InputTokens,
ModelId = assistantMessage.Usage.UserModel.ModelId,
ModelName = assistantMessage.Usage.UserModel.Model.Name,
OutputPrice = assistantMessage.Usage.OutputCost,
OutputTokens = assistantMessage.Usage.OutputTokens,
ReasoningTokens = assistantMessage.Usage.ReasoningTokens,
ModelProviderId = assistantMessage.Usage.UserModel.Model.ModelKey.ModelProviderId,
Reaction = assistantMessage.ReactionId,
},
};
MessageDto assistantMessageDto = assistantMessageTemp.ToDto(urlEncryptionService, fup);
Expand Down
65 changes: 50 additions & 15 deletions src/BE/Controllers/Chats/Messages/MessagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ public async Task<ActionResult<MessageDto[]>> GetMessages(string chatId, [FromSe
.ToArray(),
CreatedAt = x.CreatedAt,
SpanId = x.SpanId,
Usage = x.MessageResponse!.Usage == null ? null : new ChatMessageTempUsage()
Usage = x.Usage == null ? null : new ChatMessageTempUsage()
{
InputTokens = x.MessageResponse.Usage.InputTokens,
OutputTokens = x.MessageResponse.Usage.OutputTokens,
InputPrice = x.MessageResponse.Usage.InputCost,
OutputPrice = x.MessageResponse.Usage.OutputCost,
ReasoningTokens = x.MessageResponse.Usage.ReasoningTokens,
Duration = x.MessageResponse.Usage.TotalDurationMs - x.MessageResponse.Usage.PreprocessDurationMs,
FirstTokenLatency = x.MessageResponse.Usage.FirstResponseDurationMs,
ModelId = x.MessageResponse.Usage.UserModel.ModelId,
ModelName = x.MessageResponse.Usage.UserModel.Model.Name,
ModelProviderId = x.MessageResponse.Usage.UserModel.Model.ModelKey.ModelProviderId,
Reaction = x.MessageResponse.ReactionId,
InputTokens = x.Usage.InputTokens,
OutputTokens = x.Usage.OutputTokens,
InputPrice = x.Usage.InputCost,
OutputPrice = x.Usage.OutputCost,
ReasoningTokens = x.Usage.ReasoningTokens,
Duration = x.Usage.TotalDurationMs - x.Usage.PreprocessDurationMs,
FirstTokenLatency = x.Usage.FirstResponseDurationMs,
ModelId = x.Usage.UserModel.ModelId,
ModelName = x.Usage.UserModel.Model.Name,
ModelProviderId = x.Usage.UserModel.Model.ModelKey.ModelProviderId,
Reaction = x.ReactionId,
},
})
.OrderBy(x => x.CreatedAt)
Expand Down Expand Up @@ -93,11 +93,10 @@ private async Task<ActionResult> ReactionPrivate(string encryptedMessageId, bool
{
long messageId = urlEncryption.DecryptMessageId(encryptedMessageId);
Message? message = await db.Messages
.Include(x => x.MessageResponse)
.Include(x => x.Chat)
.FirstOrDefaultAsync(x => x.Id == messageId, cancellationToken);

if (message == null || message.MessageResponse == null)
if (message == null)
{
return NotFound();
}
Expand All @@ -107,7 +106,7 @@ private async Task<ActionResult> ReactionPrivate(string encryptedMessageId, bool
return Forbid();
}

message.MessageResponse.ReactionId = reactionId;
message.ReactionId = reactionId;
message.Chat.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync(cancellationToken);
return Ok();
Expand Down Expand Up @@ -138,6 +137,42 @@ public async Task<ActionResult> EditMessage(string encryptedMessageId, [FromBody
return Ok();
}

[HttpPut("{encryptedMessageId}/edit-and-save-new")]
public async Task<ActionResult> EditAndSaveNew(string encryptedMessageId, [FromBody] MessageContentRequest content,
[FromServices] FileUrlProvider fup,
CancellationToken cancellationToken)
{
long messageId = urlEncryption.DecryptMessageId(encryptedMessageId);
Message? message = await db.Messages
.Include(x => x.Chat)
.FirstOrDefaultAsync(x => x.Id == messageId, cancellationToken);
if (message == null)
{
return NotFound();
}
if (message.Chat.UserId != currentUser.Id)
{
return Forbid();
}

Message newMessage = new()
{
Edited = true,
CreatedAt = DateTime.UtcNow,
SpanId = message.SpanId,
ChatId = message.ChatId,
ParentId = message.ParentId,
ChatRoleId = message.ChatRoleId,
ChatRole = message.ChatRole,
MessageContents = await content.ToMessageContents(fup, cancellationToken),
UsageId = null,
};
db.Messages.Add(newMessage);
message.Chat.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync(cancellationToken);
return Ok();
}

[HttpDelete("{encryptedMessageId}")]
public async Task<ActionResult<string[]>> DeleteMessage(string encryptedMessageId, bool recursive, CancellationToken cancellationToken)
{
Expand Down
Loading

0 comments on commit dc8044d

Please sign in to comment.