Skip to content

Commit

Permalink
Merge pull request #49 from sdcb/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
sdcb authored Jan 22, 2025
2 parents 31f7dba + 1c490cf commit ebefd93
Show file tree
Hide file tree
Showing 22 changed files with 779 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public async Task<ActionResult<ChatsResponseWithMessage>> GetAdminMessage(int ch
Temperature = s.Temperature,
EnableSearch = s.EnableSearch,
}).ToArray(),
LeafMessageId = x.LeafMessageId != null ? urlEncryption.EncryptMessageId(x.LeafMessageId.Value) : null,
LeafMessageId = urlEncryption.EncryptMessageId(x.LeafMessageId),
UpdatedAt = x.UpdatedAt,
})
.FirstOrDefaultAsync(cancellationToken);
Expand Down
37 changes: 2 additions & 35 deletions src/BE/Controllers/Chats/Chats/Dtos/SseResponseLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,7 @@ public static SseResponseLine ResponseMessage(
IUrlEncryptionService urlEncryptionService,
FileUrlProvider fup)
{
ChatMessageTemp assistantMessageTemp = new()
{
Content = [.. assistantMessage.MessageContents],
CreatedAt = assistantMessage.CreatedAt,
Id = assistantMessage.Id,
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,
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,
},
};
ChatMessageTemp assistantMessageTemp = ChatMessageTemp.FromDB(assistantMessage);
MessageDto assistantMessageDto = assistantMessageTemp.ToDto(urlEncryptionService, fup);
return new SseResponseLine
{
Expand All @@ -82,17 +59,7 @@ public static SseResponseLine UserMessage(
IUrlEncryptionService urlEncryptionService,
FileUrlProvider fup)
{
ChatMessageTemp userMessageTemp = new()
{
Content = [.. userMessage.MessageContents],
CreatedAt = userMessage.CreatedAt,
Id = userMessage.Id,
ParentId = userMessage.ParentId,
Role = (DBChatRole)userMessage.ChatRoleId,
SpanId = userMessage.SpanId,
Edited = userMessage.Edited,
Usage = null,
};
ChatMessageTemp userMessageTemp = ChatMessageTemp.FromDB(userMessage);
MessageDto userMessageDto = userMessageTemp.ToDto(urlEncryptionService, fup);
return new SseResponseLine
{
Expand Down
54 changes: 51 additions & 3 deletions src/BE/Controllers/Chats/Messages/Dtos/MessageDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public abstract record MessageDto

public record RequestMessageDto : MessageDto
{
public static RequestMessageDto FromDB(Message message, FileUrlProvider fup)
public static RequestMessageDto FromDB(Message message, FileUrlProvider fup, IUrlEncryptionService urlEncryption)
{
return new RequestMessageDto()
{
Id = message.Id.ToString(),
ParentId = message.ParentId?.ToString(),
Id = urlEncryption.EncryptMessageId(message.Id),
ParentId = urlEncryption.EncryptMessageId(message.ParentId),
Role = (DBChatRole)message.ChatRoleId,
Content = MessageContentResponse.FromSegments([.. message.MessageContents], fup),
CreatedAt = message.CreatedAt,
Expand Down Expand Up @@ -221,4 +221,52 @@ public MessageDto ToDto(IUrlEncryptionService urlEncryption, FileUrlProvider fup
};
}
}

public static ChatMessageTemp FromDB(Message assistantMessage)
{
if (assistantMessage.ChatRoleId == (byte)DBChatRole.Assistant)
{
if (assistantMessage.Usage == null) throw new InvalidOperationException("Assistant message must have usage data");

return new()
{
Content = [.. assistantMessage.MessageContents],
CreatedAt = assistantMessage.CreatedAt,
Id = assistantMessage.Id,
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,
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,
},
};
}
else
{
// user/system message
return new()
{
Content = [.. assistantMessage.MessageContents],
CreatedAt = assistantMessage.CreatedAt,
Id = assistantMessage.Id,
ParentId = assistantMessage.ParentId,
Role = (DBChatRole)assistantMessage.ChatRoleId,
SpanId = assistantMessage.SpanId,
Edited = assistantMessage.Edited,
Usage = null,
};
}
}
}
77 changes: 53 additions & 24 deletions src/BE/Controllers/Chats/Messages/MessagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.ML.Tokenizers;
using Chats.BE.Services;

namespace Chats.BE.Controllers.Chats.Messages;

Expand Down Expand Up @@ -146,11 +148,13 @@ public async Task<ActionResult> EditMessageInPlace(string encryptedMessageId, [F
[HttpPut("{encryptedMessageId}/edit-and-save-new")]
public async Task<ActionResult<RequestMessageDto>> EditAndSaveNew(string encryptedMessageId, [FromBody] MessageContentRequest content,
[FromServices] FileUrlProvider fup,
[FromServices] ClientInfoManager clientInfoManager,
CancellationToken cancellationToken)
{
long messageId = urlEncryption.DecryptMessageId(encryptedMessageId);
Message? message = await db.Messages
.Include(x => x.Chat)
.Include(x => x.Usage)
.FirstOrDefaultAsync(x => x.Id == messageId, cancellationToken);
if (message == null)
{
Expand All @@ -171,21 +175,45 @@ public async Task<ActionResult<RequestMessageDto>> EditAndSaveNew(string encrypt
ChatRoleId = message.ChatRoleId,
ChatRole = message.ChatRole,
MessageContents = await content.ToMessageContents(fup, cancellationToken),
UsageId = null,
};
if (message.Usage != null)
{
newMessage.Usage = new UserModelUsage()
{
UserModelId = message.Usage.UserModelId,
FinishReasonId = (byte)DBFinishReason.Success,
SegmentCount = 1,
InputTokens = message.Usage.InputTokens,
OutputTokens = TiktokenTokenizer.CreateForEncoding("cl100k_base").CountTokens(content.Text),
ReasoningTokens = 0,
IsUsageReliable = false,
PreprocessDurationMs = 0,
FirstResponseDurationMs = 0,
PostprocessDurationMs = 0,
TotalDurationMs = 0,
InputCost = 0,
OutputCost = 0,
BalanceTransactionId = null,
UsageTransactionId = null,
ClientInfo = await clientInfoManager.GetClientInfo(cancellationToken),
CreatedAt = DateTime.UtcNow,
};
}
db.Messages.Add(newMessage);
message.Chat.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync(cancellationToken);
return Ok(RequestMessageDto.FromDB(newMessage, fup));
ChatMessageTemp temp = ChatMessageTemp.FromDB(message);
return Ok(temp.ToDto(urlEncryption, fup));
}

[HttpDelete("{encryptedMessageId}")]
public async Task<ActionResult<string[]>> DeleteMessage(string encryptedMessageId, bool recursive, CancellationToken cancellationToken)
public async Task<ActionResult<string[]>> DeleteMessage(string encryptedMessageId, string? encryptedLeafMessageId, CancellationToken cancellationToken)
{
long messageId = urlEncryption.DecryptMessageId(encryptedMessageId);
long? leafMessageId = urlEncryption.DecryptMessageIdOrNull(encryptedLeafMessageId);
Message? message = await db.Messages
.Include(x => x.Chat)
.Include(x => x.InverseParent)
.Include(x => x.Chat.Messages)
.FirstOrDefaultAsync(x => x.Id == messageId, cancellationToken);
if (message == null)
{
Expand All @@ -196,33 +224,34 @@ public async Task<ActionResult<string[]>> DeleteMessage(string encryptedMessageI
return Forbid();
}

if (message.InverseParent.Count > 0)
Message? leafMessage = leafMessageId == null ? null : message.Chat.Messages.FirstOrDefault(x => x.Id == leafMessageId);
if (leafMessageId != null)
{
if (!recursive)
if (leafMessage == null)
{
return BadRequest("Cannot delete a message with replies");
return BadRequest("Leaf message not found");
}

List<long> messageIdsQueue = [messageId];
List<long> toDeleteMessageIds = [];
while (messageIdsQueue.Count > 0)
else if (leafMessage.ChatId != message.ChatId)
{
toDeleteMessageIds.AddRange(messageIdsQueue);
messageIdsQueue = await db.Messages
.Where(x => x.ParentId != null && messageIdsQueue.Contains(x.ParentId.Value))
.Select(x => x.Id)
.ToListAsync(cancellationToken);
return BadRequest("Leaf message does not belong to the same chat");
}
await db.Messages
.Where(x => toDeleteMessageIds.Contains(x.Id))
.ExecuteDeleteAsync(cancellationToken);
return Ok(toDeleteMessageIds.Select(urlEncryption.EncryptMessageId));
}
else

List<Message> messagesQueue = [message];
List<Message> toDeleteMessages = [];
while (messagesQueue.Count > 0)
{
toDeleteMessages.AddRange(messagesQueue);
messagesQueue = message.Chat.Messages
.Where(x => x.ParentId != null && messagesQueue.Any(toDelete => toDelete.Id == x.ParentId.Value))
.ToList();
}
foreach (Message toDeleteMessage in toDeleteMessages)
{
db.Messages.Remove(message);
await db.SaveChangesAsync(cancellationToken);
return Ok(new string[] { urlEncryption.EncryptMessageId(messageId) });
message.Chat.Messages.Remove(toDeleteMessage);
}
message.Chat.LeafMessageId = leafMessageId;
await db.SaveChangesAsync(cancellationToken);
return Ok(toDeleteMessages.Select(x => urlEncryption.EncryptMessageId(x.Id)).ToArray());
}
}
8 changes: 4 additions & 4 deletions src/BE/Controllers/Chats/UserChats/UserChatsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public async Task<ActionResult<ChatsResponse>> GetOneChat(string encryptedChatId
Temperature = s.Temperature,
EnableSearch = s.EnableSearch,
}).ToArray(),
LeafMessageId = x.LeafMessageId != null ? idEncryption.EncryptMessageId(x.LeafMessageId.Value) : null,
LeafMessageId = idEncryption.EncryptMessageId(x.LeafMessageId),
UpdatedAt = x.UpdatedAt,
})
.FirstOrDefaultAsync(cancellationToken);
Expand Down Expand Up @@ -87,7 +87,7 @@ internal static async Task<PagedResult<ChatsResponse>> GetChatsForGroupAsync(Cha
Temperature = s.Temperature,
EnableSearch = s.EnableSearch,
}).ToArray(),
LeafMessageId = x.LeafMessageId != null ? idEncryption.EncryptMessageId(x.LeafMessageId.Value) : null,
LeafMessageId = idEncryption.EncryptMessageId(x.LeafMessageId),
UpdatedAt = x.UpdatedAt,
}),
request,
Expand Down Expand Up @@ -176,7 +176,7 @@ public async Task<ActionResult<ChatsResponse>> CreateChat([FromBody] EncryptedCr
Temperature = s.Temperature,
EnableSearch = s.EnableSearch,
}).ToArray(),
LeafMessageId = chat.LeafMessageId != null ? idEncryption.EncryptMessageId(chat.LeafMessageId.Value) : null,
LeafMessageId = idEncryption.EncryptMessageId(chat.LeafMessageId),
UpdatedAt = chat.UpdatedAt,
});
}
Expand Down Expand Up @@ -221,7 +221,7 @@ public async Task<ActionResult<ChatsResponse>> ListArchived(CancellationToken ca
Temperature = s.Temperature,
EnableSearch = s.EnableSearch,
}).ToArray(),
LeafMessageId = x.LeafMessageId != null ? idEncryption.EncryptMessageId(x.LeafMessageId.Value) : null,
LeafMessageId = idEncryption.EncryptMessageId(x.LeafMessageId),
UpdatedAt = x.UpdatedAt,
})
.ToListAsync(cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ public static string EncryptMessageId(this IUrlEncryptionService that, long mess
return that.Encrypt(messageId, EncryptionPurpose.MessageId);
}

public static string? EncryptMessageId(this IUrlEncryptionService that, long? messageId)
{
return messageId == null ? null : that.EncryptMessageId(messageId.Value);
}

public static long DecryptMessageId(this IUrlEncryptionService that, string encryptedMessageId)
{
return that.DecryptAsInt64(encryptedMessageId, EncryptionPurpose.MessageId);
Expand Down
30 changes: 30 additions & 0 deletions src/FE/apis/clientApis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
PutChatGroupParams,
PutChatParams,
PutMoveChatGroupParams,
PutResponseMessageEditAndSaveNewParams,
PutResponseMessageEditAndSaveNewResult,
PutResponseMessageEditInPlaceParams,
SingInParams,
SingInResult,
} from '@/types/clientApis';
Expand Down Expand Up @@ -359,3 +362,30 @@ export const getChatShare = (encryptedChatShareId: string) => {
`/api/public/chat-share/${encryptedChatShareId}`,
);
};

export const putResponseMessageEditAndSaveNew = (
params: PutResponseMessageEditAndSaveNewParams,
) => {
const fetchServer = useFetch();
return fetchServer.put<PutResponseMessageEditAndSaveNewResult>(
`/api/messages/${params.messageId}/edit-and-save-new`,
{
body: params.content,
},
);
};
export const putResponseMessageEditInPlace = (
params: PutResponseMessageEditInPlaceParams,
) => {
const fetchServer = useFetch();
return fetchServer.put(`/api/messages/${params.messageId}/edit-in-place`, {
body: params.content,
});
};

export const deleteMessage = (messageId: string, leafId: string) => {
const fetchServer = useFetch();
return fetchServer.delete(
`/api/messages/${messageId}?encryptedLeafMessageId=${leafId}&recursive=true`,
);
};
Loading

0 comments on commit ebefd93

Please sign in to comment.