diff --git a/src/Desktop/RodelAgent.UI/App.xaml b/src/Desktop/RodelAgent.UI/App.xaml index 00097adf..8398dd1d 100644 --- a/src/Desktop/RodelAgent.UI/App.xaml +++ b/src/Desktop/RodelAgent.UI/App.xaml @@ -48,6 +48,7 @@ + diff --git a/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionInput.xaml b/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionInput.xaml index 80ff93e0..ffefc9b0 100644 --- a/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionInput.xaml +++ b/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionInput.xaml @@ -141,6 +141,9 @@ Duration="0:0:0.5" /> + + + + + + + + + + + + + + diff --git a/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionTokenUsageControl.xaml.cs b/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionTokenUsageControl.xaml.cs new file mode 100644 index 00000000..3f04c3d4 --- /dev/null +++ b/src/Desktop/RodelAgent.UI/Controls/Chat/ChatSessionTokenUsageControl.xaml.cs @@ -0,0 +1,17 @@ +// Copyright (c) Rodel. All rights reserved. + +namespace RodelAgent.UI.Controls.Chat; + +/// +/// 对话会话令牌使用控件. +/// +public sealed partial class ChatSessionTokenUsageControl : ChatSessionControlBase +{ + /// + /// Initializes a new instance of the class. + /// + public ChatSessionTokenUsageControl() + { + InitializeComponent(); + } +} diff --git a/src/Desktop/RodelAgent.UI/Controls/Chat/SystemInstructionPanel.xaml.cs b/src/Desktop/RodelAgent.UI/Controls/Chat/SystemInstructionPanel.xaml.cs index 9f660fa2..c8cecac9 100644 --- a/src/Desktop/RodelAgent.UI/Controls/Chat/SystemInstructionPanel.xaml.cs +++ b/src/Desktop/RodelAgent.UI/Controls/Chat/SystemInstructionPanel.xaml.cs @@ -39,6 +39,7 @@ private void OnSystemBoxTextChanged(object sender, TextChangedEventArgs e) } ViewModel.Data.SystemInstruction = SystemBox.Text; + ViewModel.CalcTotalTokenCountCommand.Execute(default); _textChanged = true; } diff --git a/src/Desktop/RodelAgent.UI/Converters/TokenCountConverter.cs b/src/Desktop/RodelAgent.UI/Converters/TokenCountConverter.cs new file mode 100644 index 00000000..4a6ea7b2 --- /dev/null +++ b/src/Desktop/RodelAgent.UI/Converters/TokenCountConverter.cs @@ -0,0 +1,14 @@ +// Copyright (c) Rodel. All rights reserved. + +namespace RodelAgent.UI.Converters; + +internal sealed class TokenCountConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) + { + var count = System.Convert.ToInt32(value); + return count < 0 ? "--" : (object)count.ToString("N0"); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); +} diff --git a/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml b/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml index fadf31ad..571db3b0 100644 --- a/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml +++ b/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml @@ -281,5 +281,6 @@ IsHide="{x:Bind ViewModel.IsExtraColumnManualHide, Mode=OneWay}" /> + diff --git a/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml.cs b/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml.cs index 2069ad6a..d62ba9f9 100644 --- a/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml.cs +++ b/src/Desktop/RodelAgent.UI/Pages/ChatServicePage.xaml.cs @@ -25,9 +25,10 @@ public ChatServicePage() /// protected override async void OnPageLoaded() { + InitialRing.IsActive = true; if (ViewModel.IsAvailableServicesEmpty) { - ViewModel.ResetAvailableChatServicesCommand.Execute(default); + await ViewModel.ResetAvailableChatServicesCommand.ExecuteAsync(default); } if (ViewModel.IsAgentsEmpty) @@ -37,17 +38,18 @@ protected override async void OnPageLoaded() if (ViewModel.IsSessionPresetsEmpty) { - ViewModel.ResetSessionPresetsCommand.Execute(default); + await ViewModel.ResetSessionPresetsCommand.ExecuteAsync(default); } if (ViewModel.IsGroupsEmpty) { - ViewModel.ResetGroupsCommand.Execute(default); + await ViewModel.ResetGroupsCommand.ExecuteAsync(default); } InitializeSessionPanelType(); InitializeGroupPanelType(); UpdateExtraSizer(); + InitialRing.IsActive = false; } private void OnSizeChanged(object sender, SizeChangedEventArgs e) diff --git a/src/Desktop/RodelAgent.UI/Resources/en-US/Resources.resw b/src/Desktop/RodelAgent.UI/Resources/en-US/Resources.resw index 73556f71..8e743273 100644 --- a/src/Desktop/RodelAgent.UI/Resources/en-US/Resources.resw +++ b/src/Desktop/RodelAgent.UI/Resources/en-US/Resources.resw @@ -558,6 +558,12 @@ Import plugin + + Input tokens + + + Input words + Insert image @@ -874,6 +880,9 @@ You can manually adjust the limit, and the application will capture the specifie Region + + Remainder + Remove all sessions @@ -1069,6 +1078,12 @@ You can manually adjust the limit, and the application will capture the specifie Tip + + TOKEN + + + Token usage + Tools @@ -1084,6 +1099,9 @@ You can manually adjust the limit, and the application will capture the specifie Only consider higher quality token results, for example, at 0.1, the model only considers tokens within the top 10% of probability mass (usually not mixed with sampling temperature) + + Total usage + Translate diff --git a/src/Desktop/RodelAgent.UI/Resources/zh-Hans-CN/Resources.resw b/src/Desktop/RodelAgent.UI/Resources/zh-Hans-CN/Resources.resw index 9b61512f..57772598 100644 --- a/src/Desktop/RodelAgent.UI/Resources/zh-Hans-CN/Resources.resw +++ b/src/Desktop/RodelAgent.UI/Resources/zh-Hans-CN/Resources.resw @@ -558,6 +558,12 @@ 导入插件 + + 输入标记 + + + 输入字数 + 插入图片 @@ -874,6 +880,9 @@ 位置/区域 + + 剩余可用 + 清除所有会话 @@ -1069,6 +1078,12 @@ 提醒 + + 标记 + + + 标记记录 + 工具 @@ -1084,6 +1099,9 @@ 只考虑较高质量的标记结果,例如 0.1 时,模型仅考虑包含概率质量前 10% 的标记(通常不与采样温度混用) + + 总计使用 + 翻译 diff --git a/src/Desktop/RodelAgent.UI/RodelAgent.UI.csproj b/src/Desktop/RodelAgent.UI/RodelAgent.UI.csproj index b420f132..64bed10f 100644 --- a/src/Desktop/RodelAgent.UI/RodelAgent.UI.csproj +++ b/src/Desktop/RodelAgent.UI/RodelAgent.UI.csproj @@ -65,6 +65,7 @@ + @@ -234,6 +235,7 @@ + @@ -627,6 +629,9 @@ MSBuild:Compile + + MSBuild:Compile + Always diff --git a/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Messages.cs b/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Messages.cs index 5d79731c..791b545e 100644 --- a/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Messages.cs +++ b/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Messages.cs @@ -6,6 +6,7 @@ using RodelAgent.UI.ViewModels.Items; using RodelChat.Models.Client; using RodelChat.Models.Constants; +using Tiktoken; namespace RodelAgent.UI.ViewModels.Components; @@ -217,14 +218,17 @@ private async Task SendMessageInternalAsync(bool addUserMsg = true) } private Task EditMessageAsync(ChatMessage msg) - => SaveSessionToDatabaseAsync(); + { + CalcTotalTokenCount(); + return SaveSessionToDatabaseAsync(); + } private async Task DeleteMessageAsync(ChatMessage msg) { var source = Messages.FirstOrDefault(p => p.Data.Equals(msg)); Messages.Remove(source); Data.Messages.Remove(msg); - + CalcTotalTokenCount(); await SaveSessionToDatabaseAsync(); } @@ -234,4 +238,35 @@ private void HandleSendMessageException(Exception ex) _logger.LogDebug(ex, "Failed to send message."); CancelMessage(); } + + private void CalcBaseTokenCount() + { + if (Data.Messages.Count == 0 && string.IsNullOrEmpty(Data.SystemInstruction)) + { + _baseTokenCount = 0; + return; + } + + var encoder = ModelToEncoder.For("gpt-4o"); + var messages = string.Join("\n\n", Data.Messages.Select(p => p.GetFirstTextContent())); + SystemTokenCount = !string.IsNullOrEmpty(Data.SystemInstruction) ? encoder.CountTokens(Data.SystemInstruction) : 0; + _baseTokenCount = encoder.CountTokens(messages) + SystemTokenCount; + } + + private void CalcUserInputTokenCount() + { + UserInputWordCount = UserInput?.Length ?? 0; + if (!string.IsNullOrEmpty(UserInput)) + { + var encoder = ModelToEncoder.For("gpt-4o"); + UserInputTokenCount = encoder.CountTokens(UserInput); + } + else + { + UserInputTokenCount = 0; + } + + TotalTokenUsage = _baseTokenCount + UserInputTokenCount; + RemainderTokenCount = TotalTokenCount == 0 ? -1 : TotalTokenCount - TotalTokenUsage; + } } diff --git a/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Properties.cs b/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Properties.cs index 3d08788c..182c9b0d 100644 --- a/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Properties.cs +++ b/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.Properties.cs @@ -19,6 +19,8 @@ public sealed partial class ChatSessionViewModel private readonly ILogger _logger; private CancellationTokenSource _cancellationTokenSource; + private int _baseTokenCount; + [ObservableProperty] private ChatServiceItemViewModel _chatService; @@ -76,6 +78,24 @@ public sealed partial class ChatSessionViewModel [ObservableProperty] private string _generatingTipText; + [ObservableProperty] + private int _totalTokenUsage; + + [ObservableProperty] + private int _remainderTokenCount; + + [ObservableProperty] + private int _systemTokenCount; + + [ObservableProperty] + private int _userInputWordCount; + + [ObservableProperty] + private int _userInputTokenCount; + + [ObservableProperty] + private int _totalTokenCount; + /// /// 请求滚动到底部. /// diff --git a/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.cs b/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.cs index 02120e50..f54e0d45 100644 --- a/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.cs +++ b/src/Desktop/RodelAgent.UI/ViewModels/Components/ChatSessionViewModel/ChatSessionViewModel.cs @@ -69,6 +69,7 @@ private void Initialize(ChatSession data) CheckChatEmpty(); CheckLastMessageTime(); CheckRegenerateButtonShown(); + CalcTotalTokenCount(); RequestFocusInput?.Invoke(this, EventArgs.Empty); } @@ -111,6 +112,7 @@ private async Task ChangeTitleAsync(string title) [RelayCommand] private async Task ChangeModelAsync(ChatModelItemViewModel model) { + TotalTokenCount = 0; if (model is null) { return; @@ -118,6 +120,7 @@ private async Task ChangeModelAsync(ChatModelItemViewModel model) Data.Model = model.Id; Model = model.Name; + TotalTokenCount = Convert.ToInt32(model.ContextLength); foreach (var item in Models) { item.IsSelected = item.Equals(model); @@ -152,6 +155,13 @@ private void CheckMaxRounds() private void CheckRegenerateButtonShown() => IsRegenerateButtonShown = !IsChatEmpty && !IsResponding && Messages.Last().IsAssistant; + [RelayCommand] + private void CalcTotalTokenCount() + { + CalcBaseTokenCount(); + CalcUserInputTokenCount(); + } + private void InitializeModels() { Models.Clear(); @@ -184,6 +194,7 @@ private void InitializeModels() { selectedModel.IsSelected = true; Model = selectedModel.Name; + TotalTokenCount = Convert.ToInt32(selectedModel.ContextLength); } } @@ -218,6 +229,7 @@ private void OnMessageCountChanged(object sender, NotifyCollectionChangedEventAr CheckChatEmpty(); CheckRegenerateButtonShown(); CheckLastMessageTime(); + CalcTotalTokenCount(); } partial void OnIsEnterSendChanged(bool value) @@ -225,4 +237,7 @@ partial void OnIsEnterSendChanged(bool value) partial void OnModelChanged(string value) => CheckCurrentModelStatus(); + + partial void OnUserInputChanged(string value) + => CalcUserInputTokenCount(); }