Skip to content

Commit dc8044d

Browse files
authored
Merge pull request #37 from sdcb/dev
Dev
2 parents 15a04df + 340d686 commit dc8044d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1003
-243
lines changed

src/BE/Chats.BE.csproj

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,29 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
12-
<PackageReference Include="AWSSDK.S3" Version="3.7.410.6" />
12+
<PackageReference Include="AWSSDK.S3" Version="3.7.412" />
1313
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
1414
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" /> <!-- fix security issue from Azure.AI.OpenAI -->
1515
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
16-
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
16+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1">
1717
<PrivateAssets>all</PrivateAssets>
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
</PackageReference>
20-
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
21-
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
22-
<PackageReference Include="Microsoft.ML.Tokenizers" Version="1.0.0" />
23-
<PackageReference Include="Microsoft.ML.Tokenizers.Data.Cl100kBase" Version="1.0.0" />
24-
<PackageReference Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="1.0.0" />
20+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
21+
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.1" />
22+
<PackageReference Include="Microsoft.ML.Tokenizers" Version="1.0.1" />
23+
<PackageReference Include="Microsoft.ML.Tokenizers.Data.Cl100kBase" Version="1.0.1" />
24+
<PackageReference Include="Microsoft.ML.Tokenizers.Data.O200kBase" Version="1.0.1" />
2525
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
2626
<PackageReference Include="OpenAI" Version="2.1.0" />
2727
<PackageReference Include="Sdcb.DashScope" Version="2.0.0" />
2828
<PackageReference Include="Sdcb.WenXinQianFan" Version="1.2.0" />
2929
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
3030
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
3131
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
32-
<PackageReference Include="System.Runtime.Caching" Version="9.0.0" />
33-
<PackageReference Include="TencentCloudSDK.Hunyuan" Version="3.0.1143" />
34-
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1143" />
32+
<PackageReference Include="System.Runtime.Caching" Version="9.0.1" />
33+
<PackageReference Include="TencentCloudSDK.Hunyuan" Version="3.0.1165" />
34+
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1165" />
3535
</ItemGroup>
3636

3737
</Project>

src/BE/Controllers/Admin/AdminMessage/AdminMessageController.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ namespace Chats.BE.Controllers.Admin.AdminMessage;
1616
[Route("api/admin"), AuthorizeAdmin]
1717
public class AdminMessageController(ChatsDB db, CurrentUser currentUser, IUrlEncryptionService urlEncryption) : ControllerBase
1818
{
19-
[HttpGet("messages")]
20-
public async Task<ActionResult<PagedResult<AdminChatsDto>>> GetMessages([FromQuery] PagingRequest req, CancellationToken cancellationToken)
19+
[HttpGet("chats")]
20+
public async Task<ActionResult<PagedResult<AdminChatsDto>>> GetAdminChats([FromQuery] PagingRequest req, CancellationToken cancellationToken)
2121
{
2222
IQueryable<Chat> chats = db.Chats
2323
.Where(x => x.User.Role != "admin" || x.UserId == currentUser.Id);
@@ -27,12 +27,13 @@ public async Task<ActionResult<PagedResult<AdminChatsDto>>> GetMessages([FromQue
2727
}
2828

2929
return await PagedResult.FromQuery(chats
30-
.OrderByDescending(x => x.CreatedAt)
30+
.OrderByDescending(x => x.Id)
3131
.Select(x => new AdminChatsDto
3232
{
33-
Id = urlEncryption.EncryptChatId(x.Id),
33+
Id = x.Id.ToString(),
3434
CreatedAt = x.CreatedAt,
3535
IsDeleted = x.IsArchived,
36+
IsShared = x.ChatShares.Any(),
3637
Title = x.Title,
3738
UserName = x.User.UserName,
3839
Spans = x.ChatSpans.Select(s => new ChatSpanDto
@@ -99,19 +100,19 @@ public async Task<ActionResult<ChatsResponseWithMessage>> GetAdminMessage(int ch
99100
.ToArray(),
100101
CreatedAt = x.CreatedAt,
101102
SpanId = x.SpanId,
102-
Usage = x.MessageResponse!.Usage == null ? null : new ChatMessageTempUsage()
103+
Usage = x.Usage == null ? null : new ChatMessageTempUsage()
103104
{
104-
InputTokens = x.MessageResponse.Usage.InputTokens,
105-
OutputTokens = x.MessageResponse.Usage.OutputTokens,
106-
InputPrice = x.MessageResponse.Usage.InputCost,
107-
OutputPrice = x.MessageResponse.Usage.OutputCost,
108-
ReasoningTokens = x.MessageResponse.Usage.ReasoningTokens,
109-
Duration = x.MessageResponse.Usage.TotalDurationMs - x.MessageResponse.Usage.PreprocessDurationMs,
110-
FirstTokenLatency = x.MessageResponse.Usage.FirstResponseDurationMs,
111-
ModelId = x.MessageResponse.Usage.UserModel.ModelId,
112-
ModelName = x.MessageResponse.Usage.UserModel.Model.Name,
113-
ModelProviderId = x.MessageResponse.Usage.UserModel.Model.ModelKey.ModelProviderId,
114-
Reaction = x.MessageResponse.ReactionId,
105+
InputTokens = x.Usage.InputTokens,
106+
OutputTokens = x.Usage.OutputTokens,
107+
InputPrice = x.Usage.InputCost,
108+
OutputPrice = x.Usage.OutputCost,
109+
ReasoningTokens = x.Usage.ReasoningTokens,
110+
Duration = x.Usage.TotalDurationMs - x.Usage.PreprocessDurationMs,
111+
FirstTokenLatency = x.Usage.FirstResponseDurationMs,
112+
ModelId = x.Usage.UserModel.ModelId,
113+
ModelName = x.Usage.UserModel.Model.Name,
114+
ModelProviderId = x.Usage.UserModel.Model.ModelKey.ModelProviderId,
115+
Reaction = x.ReactionId,
115116
},
116117
})
117118
.OrderBy(x => x.CreatedAt)

src/BE/Controllers/Admin/AdminMessage/Dtos/AdminChatsDto.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ public record AdminChatsDto
1717
[JsonPropertyName("isDeleted")]
1818
public required bool IsDeleted { get; init; }
1919

20+
[JsonPropertyName("isShared")]
21+
public required bool IsShared { get; init; }
22+
2023
[JsonPropertyName("spans")]
2124
public required ChatSpanDto[] Spans { get; init; }
2225

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace Chats.BE.Controllers.Admin.GlobalConfigs.Dtos;
4+
5+
public record CheckUpdateResponse
6+
{
7+
[JsonPropertyName("hasNewVersion")]
8+
public required bool HasNewVersion { get; init; }
9+
10+
[JsonPropertyName("tagName")]
11+
public required string TagName { get; init; }
12+
13+
[JsonPropertyName("currentVersion")]
14+
public required int CurrentVersion { get; init; }
15+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System.Net.Http.Headers;
2+
using System.Text.Json;
3+
4+
namespace Chats.BE.Controllers.Admin.GlobalConfigs;
5+
6+
public class GitHubReleaseChecker
7+
{
8+
private readonly HttpClient _httpClient;
9+
private readonly string _owner;
10+
private readonly string _repo;
11+
12+
public static GitHubReleaseChecker SdcbChats => new("sdcb", "chats");
13+
14+
public GitHubReleaseChecker(string owner, string repo)
15+
{
16+
_owner = owner;
17+
_repo = repo;
18+
19+
_httpClient = new HttpClient { BaseAddress = new Uri("https://api.github.com") };
20+
_httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("SdcbChatsVersionChecker", "1.0"));
21+
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
22+
}
23+
24+
public async Task<string> GetLatestReleaseTagNameAsync(CancellationToken cancellationToken)
25+
{
26+
HttpResponseMessage response = await _httpClient.GetAsync($"/repos/{_owner}/{_repo}/releases/latest", cancellationToken);
27+
response.EnsureSuccessStatusCode(); // 如果请求失败,抛出异常
28+
29+
using Stream responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
30+
var jsonDocument = await JsonDocument.ParseAsync(responseStream, cancellationToken: cancellationToken);
31+
32+
return jsonDocument.RootElement.GetProperty("tag_name").GetString()!;
33+
}
34+
35+
public static bool IsNewVersionAvailableAsync(string latestTagName, int currentVersion)
36+
{
37+
try
38+
{
39+
if (latestTagName.StartsWith("r-"))
40+
{
41+
int latestVersion = int.Parse(latestTagName.Substring(2));
42+
return latestVersion > currentVersion;
43+
}
44+
else
45+
{
46+
Console.WriteLine($"Warning: Latest tag name '{latestTagName}' does not follow the expected format 'r-XXX'.");
47+
return false;
48+
}
49+
}
50+
catch (HttpRequestException ex)
51+
{
52+
Console.WriteLine($"Error checking for updates: {ex.Message}");
53+
return false;
54+
}
55+
catch (JsonException ex)
56+
{
57+
Console.WriteLine($"Error parsing JSON response: {ex.Message}");
58+
return false;
59+
}
60+
catch (Exception ex)
61+
{
62+
Console.WriteLine($"An unexpected error occurred: {ex.Message}");
63+
return false;
64+
}
65+
}
66+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Chats.BE.Controllers.Admin.Common;
2+
using Chats.BE.Controllers.Admin.GlobalConfigs.Dtos;
3+
using Microsoft.AspNetCore.Mvc;
4+
5+
namespace Chats.BE.Controllers.Admin.GlobalConfigs;
6+
7+
[AuthorizeAdmin, Route("api/version")]
8+
public class VersionController : ControllerBase
9+
{
10+
const int buildVersion = 0;
11+
12+
[HttpGet("current")]
13+
public ActionResult GetCurrentVersion()
14+
{
15+
return Ok(buildVersion);
16+
}
17+
18+
[HttpPost("check-update")]
19+
public async Task<ActionResult> CheckUpdate(CancellationToken cancellationToken)
20+
{
21+
string tagName = await GitHubReleaseChecker.SdcbChats.GetLatestReleaseTagNameAsync(cancellationToken);
22+
bool hasNewVersion = GitHubReleaseChecker.IsNewVersionAvailableAsync(tagName, buildVersion);
23+
return Ok(new CheckUpdateResponse
24+
{
25+
CurrentVersion = buildVersion,
26+
HasNewVersion = hasNewVersion,
27+
TagName = tagName,
28+
});
29+
}
30+
}

src/BE/Controllers/Chats/Chats/ChatController.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,9 @@ private async Task<IActionResult> ChatPrivate(
308308
}
309309
if (resps.Any(x => x.Cost.CostUsage))
310310
{
311-
foreach (short modelId in userModels.Keys)
311+
foreach (UserModel um in userModels.Values)
312312
{
313-
await balanceService.UpdateUsage(db, modelId, cancellationToken);
313+
await balanceService.UpdateUsage(db, um.Id, cancellationToken);
314314
}
315315
}
316316

@@ -467,10 +467,7 @@ private static async Task<ChatSpanResponse> ProcessChatSpan(
467467
dbAssistantMessage.MessageContents.Add(MessageContent.FromError(errorText));
468468
await writer.WriteAsync(SseResponseLine.Error(span.Id, errorText), cancellationToken);
469469
}
470-
dbAssistantMessage.MessageResponse = new MessageResponse()
471-
{
472-
Usage = icc.ToUserModelUsage(currentUser.Id, await clientInfoTask, isApi: false)
473-
};
470+
dbAssistantMessage.Usage = icc.ToUserModelUsage(currentUser.Id, await clientInfoTask, isApi: false);
474471
await writer.WriteAsync(new SseResponseLine { Kind = SseResponseKind.End, Result = dbAssistantMessage, SpanId = span.Id }, cancellationToken);
475472
writer.Complete();
476473
return new ChatSpanResponse()

src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,19 @@ public static SseResponseLine ResponseMessage(
5252
ParentId = assistantMessage.ParentId,
5353
Role = (DBChatRole)assistantMessage.ChatRoleId,
5454
SpanId = assistantMessage.SpanId,
55-
Usage = assistantMessage.MessageResponse?.Usage == null ? null : new ChatMessageTempUsage()
55+
Usage = assistantMessage.Usage == null ? null : new ChatMessageTempUsage()
5656
{
57-
Duration = assistantMessage.MessageResponse.Usage.TotalDurationMs - assistantMessage.MessageResponse.Usage.PreprocessDurationMs,
58-
FirstTokenLatency = assistantMessage.MessageResponse.Usage.FirstResponseDurationMs,
59-
InputPrice = assistantMessage.MessageResponse.Usage.InputCost,
60-
InputTokens = assistantMessage.MessageResponse.Usage.InputTokens,
61-
ModelId = assistantMessage.MessageResponse.Usage.UserModel.ModelId,
62-
ModelName = assistantMessage.MessageResponse.Usage.UserModel.Model.Name,
63-
OutputPrice = assistantMessage.MessageResponse.Usage.OutputCost,
64-
OutputTokens = assistantMessage.MessageResponse.Usage.OutputTokens,
65-
ReasoningTokens = assistantMessage.MessageResponse.Usage.ReasoningTokens,
66-
ModelProviderId = assistantMessage.MessageResponse.Usage.UserModel.Model.ModelKey.ModelProviderId,
67-
Reaction = assistantMessage.MessageResponse.ReactionId,
57+
Duration = assistantMessage.Usage.TotalDurationMs - assistantMessage.Usage.PreprocessDurationMs,
58+
FirstTokenLatency = assistantMessage.Usage.FirstResponseDurationMs,
59+
InputPrice = assistantMessage.Usage.InputCost,
60+
InputTokens = assistantMessage.Usage.InputTokens,
61+
ModelId = assistantMessage.Usage.UserModel.ModelId,
62+
ModelName = assistantMessage.Usage.UserModel.Model.Name,
63+
OutputPrice = assistantMessage.Usage.OutputCost,
64+
OutputTokens = assistantMessage.Usage.OutputTokens,
65+
ReasoningTokens = assistantMessage.Usage.ReasoningTokens,
66+
ModelProviderId = assistantMessage.Usage.UserModel.Model.ModelKey.ModelProviderId,
67+
Reaction = assistantMessage.ReactionId,
6868
},
6969
};
7070
MessageDto assistantMessageDto = assistantMessageTemp.ToDto(urlEncryptionService, fup);

src/BE/Controllers/Chats/Messages/MessagesController.cs

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ public async Task<ActionResult<MessageDto[]>> GetMessages(string chatId, [FromSe
3232
.ToArray(),
3333
CreatedAt = x.CreatedAt,
3434
SpanId = x.SpanId,
35-
Usage = x.MessageResponse!.Usage == null ? null : new ChatMessageTempUsage()
35+
Usage = x.Usage == null ? null : new ChatMessageTempUsage()
3636
{
37-
InputTokens = x.MessageResponse.Usage.InputTokens,
38-
OutputTokens = x.MessageResponse.Usage.OutputTokens,
39-
InputPrice = x.MessageResponse.Usage.InputCost,
40-
OutputPrice = x.MessageResponse.Usage.OutputCost,
41-
ReasoningTokens = x.MessageResponse.Usage.ReasoningTokens,
42-
Duration = x.MessageResponse.Usage.TotalDurationMs - x.MessageResponse.Usage.PreprocessDurationMs,
43-
FirstTokenLatency = x.MessageResponse.Usage.FirstResponseDurationMs,
44-
ModelId = x.MessageResponse.Usage.UserModel.ModelId,
45-
ModelName = x.MessageResponse.Usage.UserModel.Model.Name,
46-
ModelProviderId = x.MessageResponse.Usage.UserModel.Model.ModelKey.ModelProviderId,
47-
Reaction = x.MessageResponse.ReactionId,
37+
InputTokens = x.Usage.InputTokens,
38+
OutputTokens = x.Usage.OutputTokens,
39+
InputPrice = x.Usage.InputCost,
40+
OutputPrice = x.Usage.OutputCost,
41+
ReasoningTokens = x.Usage.ReasoningTokens,
42+
Duration = x.Usage.TotalDurationMs - x.Usage.PreprocessDurationMs,
43+
FirstTokenLatency = x.Usage.FirstResponseDurationMs,
44+
ModelId = x.Usage.UserModel.ModelId,
45+
ModelName = x.Usage.UserModel.Model.Name,
46+
ModelProviderId = x.Usage.UserModel.Model.ModelKey.ModelProviderId,
47+
Reaction = x.ReactionId,
4848
},
4949
})
5050
.OrderBy(x => x.CreatedAt)
@@ -93,11 +93,10 @@ private async Task<ActionResult> ReactionPrivate(string encryptedMessageId, bool
9393
{
9494
long messageId = urlEncryption.DecryptMessageId(encryptedMessageId);
9595
Message? message = await db.Messages
96-
.Include(x => x.MessageResponse)
9796
.Include(x => x.Chat)
9897
.FirstOrDefaultAsync(x => x.Id == messageId, cancellationToken);
9998

100-
if (message == null || message.MessageResponse == null)
99+
if (message == null)
101100
{
102101
return NotFound();
103102
}
@@ -107,7 +106,7 @@ private async Task<ActionResult> ReactionPrivate(string encryptedMessageId, bool
107106
return Forbid();
108107
}
109108

110-
message.MessageResponse.ReactionId = reactionId;
109+
message.ReactionId = reactionId;
111110
message.Chat.UpdatedAt = DateTime.UtcNow;
112111
await db.SaveChangesAsync(cancellationToken);
113112
return Ok();
@@ -138,6 +137,42 @@ public async Task<ActionResult> EditMessage(string encryptedMessageId, [FromBody
138137
return Ok();
139138
}
140139

140+
[HttpPut("{encryptedMessageId}/edit-and-save-new")]
141+
public async Task<ActionResult> EditAndSaveNew(string encryptedMessageId, [FromBody] MessageContentRequest content,
142+
[FromServices] FileUrlProvider fup,
143+
CancellationToken cancellationToken)
144+
{
145+
long messageId = urlEncryption.DecryptMessageId(encryptedMessageId);
146+
Message? message = await db.Messages
147+
.Include(x => x.Chat)
148+
.FirstOrDefaultAsync(x => x.Id == messageId, cancellationToken);
149+
if (message == null)
150+
{
151+
return NotFound();
152+
}
153+
if (message.Chat.UserId != currentUser.Id)
154+
{
155+
return Forbid();
156+
}
157+
158+
Message newMessage = new()
159+
{
160+
Edited = true,
161+
CreatedAt = DateTime.UtcNow,
162+
SpanId = message.SpanId,
163+
ChatId = message.ChatId,
164+
ParentId = message.ParentId,
165+
ChatRoleId = message.ChatRoleId,
166+
ChatRole = message.ChatRole,
167+
MessageContents = await content.ToMessageContents(fup, cancellationToken),
168+
UsageId = null,
169+
};
170+
db.Messages.Add(newMessage);
171+
message.Chat.UpdatedAt = DateTime.UtcNow;
172+
await db.SaveChangesAsync(cancellationToken);
173+
return Ok();
174+
}
175+
141176
[HttpDelete("{encryptedMessageId}")]
142177
public async Task<ActionResult<string[]>> DeleteMessage(string encryptedMessageId, bool recursive, CancellationToken cancellationToken)
143178
{

0 commit comments

Comments
 (0)