diff --git a/LM-Kit-Maestro/AppShell.xaml b/LM-Kit-Maestro/AppShell.xaml index f7cc67a..bfd513f 100644 --- a/LM-Kit-Maestro/AppShell.xaml +++ b/LM-Kit-Maestro/AppShell.xaml @@ -57,7 +57,7 @@ - + --> - - - - - diff --git a/LM-Kit-Maestro/Services/Interfaces/ILLMFileManager.cs b/LM-Kit-Maestro/Services/Interfaces/ILLMFileManager.cs index 94a0bd7..be2f4f2 100644 --- a/LM-Kit-Maestro/Services/Interfaces/ILLMFileManager.cs +++ b/LM-Kit-Maestro/Services/Interfaces/ILLMFileManager.cs @@ -22,4 +22,4 @@ public interface ILLMFileManager public event PropertyChangedEventHandler PropertyChanged; public event NotifyCollectionChangedEventHandler? SortedModelCollectionChanged; -} \ No newline at end of file +} diff --git a/LM-Kit-Maestro/Services/LLMFileManager.cs b/LM-Kit-Maestro/Services/LLMFileManager.cs index 189d6d0..1e44a88 100644 --- a/LM-Kit-Maestro/Services/LLMFileManager.cs +++ b/LM-Kit-Maestro/Services/LLMFileManager.cs @@ -10,7 +10,7 @@ namespace LMKit.Maestro.Services; /// -/// This service is intended to be used as a singleton via Dependency Injection. +/// This service is intended to be used as a singleton via Dependency Injection. /// Please register with services.AddSingleton<LLMFileManager>(). /// public partial class LLMFileManager : ObservableObject, ILLMFileManager @@ -26,14 +26,14 @@ public partial class LLMFileManager : ObservableObject, ILLMFileManager private readonly FileSystemEntryRecorder _fileSystemEntryRecorder; private readonly IAppSettingsService _appSettingsService; private readonly HttpClient _httpClient; - private bool _enablePredefinedModels = true; //todo: Implement this as a configurable option in the configuration panel - private bool _enableCustomModels = true; //todo: Implement this as a configurable option in the configuration panel + private bool _enablePredefinedModels = true; //todo: Implement this as a configurable option in the configuration panel + private bool _enableCustomModels = true; //todo: Implement this as a configurable option in the configuration panel private bool _isLoaded = false; private readonly Dictionary _fileDownloads = new Dictionary(); private delegate bool ModelDownloadingProgressCallback(string path, long? contentLength, long bytesRead); - public event NotifyCollectionChangedEventHandler? SortedModelCollectionChanged; + public event NotifyCollectionChangedEventHandler? SortedModelCollectionChanged; private CancellationTokenSource? _cancellationTokenSource; private Task? _collectModelFilesTask; @@ -250,7 +250,7 @@ public void DeleteModel(ModelCard modelCard) if (_models.Contains(modelCard)) { _models.Remove(modelCard); - } + } } #endif } diff --git a/LM-Kit-Maestro/Services/LMKitDefaultSettings.cs b/LM-Kit-Maestro/Services/LMKitDefaultSettings.cs index 91d8cf4..e66e8e4 100644 --- a/LM-Kit-Maestro/Services/LMKitDefaultSettings.cs +++ b/LM-Kit-Maestro/Services/LMKitDefaultSettings.cs @@ -23,4 +23,4 @@ public static class LMKitDefaultSettings public const float DefaultMirostat2SamplingTemperature = 0.8f; public const float DefaultMirostat2SamplingTargetEntropy = 5.0f; public const float DefaultMirostat2SamplingLearningRate = 0.1f; -} \ No newline at end of file +} diff --git a/LM-Kit-Maestro/UI/Pages/AssistantsPage.xaml b/LM-Kit-Maestro/UI/Pages/AssistantsPage.xaml index acb386e..b3bb3ef 100644 --- a/LM-Kit-Maestro/UI/Pages/AssistantsPage.xaml +++ b/LM-Kit-Maestro/UI/Pages/AssistantsPage.xaml @@ -30,5 +30,4 @@ - - \ No newline at end of file + diff --git a/LM-Kit-Maestro/UI/Razor/Components/Assistants.razor b/LM-Kit-Maestro/UI/Razor/Components/Assistants.razor new file mode 100644 index 0000000..10f9283 --- /dev/null +++ b/LM-Kit-Maestro/UI/Razor/Components/Assistants.razor @@ -0,0 +1,32 @@ +@inherits MvvmComponentBase + + @page "/assistants" + +
+ +
+ +
+
+ + +@code { +} diff --git a/LM-Kit-Maestro/UI/Razor/Components/Chat.razor b/LM-Kit-Maestro/UI/Razor/Components/Chat.razor index a34ebc9..c5ca753 100644 --- a/LM-Kit-Maestro/UI/Razor/Components/Chat.razor +++ b/LM-Kit-Maestro/UI/Razor/Components/Chat.razor @@ -1,347 +1,347 @@ - @page "/chat" - -@inject LMKitService LMKitService -@inject HttpClient Http -@inject IJSRuntime JS -@inject IScrollHandler ScrollHandler -@inject IResizeHandler ResizeHandler -@inject ILogger Logger - -@inherits MvvmComponentBase - -
-
-
- @if (ViewModel?.ConversationListViewModel.CurrentConversation != null) - { - if (ViewModel.ConversationListViewModel.CurrentConversation.IsEmpty) - { -
-
-
Hello, human.
-
- It is likely that similar software programs are now assuming professional tasks that - were - previously your responsibility. -
-
- Developers and other software programs are working to enhance my - ability to assist you in the process of making sense about this, or to generate witty - jokes - involving robots and humans. -
-
- -
-
- Press Enter to send a new message -
- -
- Hold Shift + Enter to insert a new line -
-
-
- } - else - { -
- @foreach (var message in ViewModel.ConversationListViewModel.CurrentConversation.Messages) - { - - } -
- } - } -
- -
-
- @if (!IsScrolledToEnd) - { - - } -
-
-
- -
- -
- -
- @if (ViewModel?.ConversationListViewModel?.CurrentConversation?.LMKitConversation?.ContextSize > 0) - { - - Tokens: @ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextUsedSpace / - @ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextSize - (@CalculateUsagePercentage(ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextUsedSpace, - ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextSize)%) - - } -
-
- -@code -{ - private const int UIUpdateDelayMilliseconds = 50; - - private ConversationViewModel? _previousConversationViewModel; - private MessageViewModel? _latestAssistantResponse; - private bool _hasPendingScrollEnd; - private bool _ignoreScrollsUntilNextScrollUp; - private double? _previousScrollTop; - private bool _shouldAutoScrollEnd; - private double _scrollTop; - - private bool _isScrolledToEnd = false; - - public bool IsScrolledToEnd - { - get => _isScrolledToEnd; - set - { - _isScrolledToEnd = value; - - if (_isScrolledToEnd && - ViewModel.ConversationListViewModel.CurrentConversation != null && - ViewModel.ConversationListViewModel.CurrentConversation!.AwaitingResponse) - { - // Assistant is currently generating a response, enforce auto-scroll. - _shouldAutoScrollEnd |= true; - } - - UpdateUIAsync(); - } - } - - protected override async void OnInitialized() - { - base.OnInitialized(); - - if (ViewModel.ConversationListViewModel.CurrentConversation != null) - { - OnConversationSet(); - } - - ViewModel.ConversationListViewModel.PropertyChanged += OnConversationListViewModelPropertyChanged; - - ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.PropertyChanged += OnLMKitConversationPropertyChanged; - - await ResizeHandler.RegisterPageResizeAsync(Resized); - await JS.InvokeVoidAsync("initializeScrollHandler", DotNetObjectReference.Create(this)); - } - - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); - - if (_hasPendingScrollEnd) - { - _hasPendingScrollEnd = false; - await ScrollToEnd(); - } - } - - private bool _refreshScheduled = false; - - private Task UpdateUIAsync(bool forceRedraw = false) - { - if (forceRedraw) - { - return InvokeAsync(() => StateHasChanged()); - } - else - { - if (!_refreshScheduled) - { - _refreshScheduled = true; - - return InvokeAsync(async () => - { - await Task.Delay(UIUpdateDelayMilliseconds); - StateHasChanged(); - _refreshScheduled = false; - }); - } - } - - return Task.CompletedTask; - } - - private void OnConversationListViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(ConversationListViewModel.CurrentConversation)) - { - OnConversationSet(); - } - } - - private void OnLMKitConversationPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(LMKitService.Conversation.ContextRemainingSpace)) - { - UpdateUIAsync(); - } - else if (e.PropertyName == nameof(LMKitService.Conversation.InTextCompletion)) - { - UpdateUIAsync(forceRedraw: true); - } - } - - private void OnLatestAssistantMessagePropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(MessageViewModel.Content)) - { - OnLatestAssistantResponseProgressed(); - } - else if (e.PropertyName == nameof(MessageViewModel.MessageInProgress)) - { - if (sender == _latestAssistantResponse && !_latestAssistantResponse!.MessageInProgress && _shouldAutoScrollEnd) - { - Task.Run(async () => - { - // Note: Adding delay to sync with current UI behavior - // (show latest assistant response footer for 3 sec). - await Task.Delay(50); - await ScrollToEnd(); - }); - } - } - } - - private void OnConversationSet() - { - if (_previousConversationViewModel != null) - { - _previousConversationViewModel.Messages.CollectionChanged -= OnConversationMessagesCollectionChanged; - } - - _previousConversationViewModel = ViewModel.ConversationListViewModel.CurrentConversation; - - if (ViewModel.ConversationListViewModel.CurrentConversation != null) - { - ViewModel.ConversationListViewModel.CurrentConversation.Messages.CollectionChanged += OnConversationMessagesCollectionChanged; - _previousScrollTop = null; - _ignoreScrollsUntilNextScrollUp = true; - IsScrolledToEnd = true; - - // Awaiting for the component to be rendered before scrolling to bottom. - _hasPendingScrollEnd = true; - } - } - - private async void OnConversationMessagesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - await UpdateUIAsync(forceRedraw: true); - - await ScrollToEnd(); - - var latestMessage = ViewModel.ConversationListViewModel.CurrentConversation!.Messages.LastOrDefault(); - - if (latestMessage != null && latestMessage.Sender == MessageSender.Assistant && latestMessage.MessageInProgress) - { - _latestAssistantResponse = latestMessage; - _shouldAutoScrollEnd = true; - latestMessage.PropertyChanged += OnLatestAssistantMessagePropertyChanged; - } - } - - private async void OnLatestAssistantResponseProgressed() - { - if (_shouldAutoScrollEnd) - { - await ScrollToEnd(); - } - } - - private async Task Resized(ResizeEventArgs args) - { - if (IsScrolledToEnd) - { - await ScrollToEnd(); - } - else - { - await CheckIsScrolledToEnd(); - } - } - - private async Task ScrollToEnd(bool smooth = false) - { - IsScrolledToEnd = true; - _ignoreScrollsUntilNextScrollUp = true; - - await JS.InvokeVoidAsync("scrollToEnd", smooth); - } - - private async Task CheckIsScrolledToEnd() - { - var viewHeight = await JS.InvokeAsync("getConversationViewHeight"); - var chatContentHeight = await JS.InvokeAsync("getScrollHeight"); - - var value = chatContentHeight - viewHeight - _scrollTop; - IsScrolledToEnd = Math.Abs(chatContentHeight - viewHeight - _scrollTop) < 5 || chatContentHeight <= viewHeight; - } - - public async Task OnScrollToEndButtonClicked() - { - await ScrollToEnd(true); - } - - public void OnSubmit() - { - ViewModel.ConversationListViewModel.CurrentConversation!.Submit(); - } - - private int CalculateUsagePercentage(int used, int total) - { - if (total == 0) return 0; - return (int)(100 * used / total); - } - - - [JSInvokable] - public async Task OnConversationContainerScrolled(double scrollTop) - { - _scrollTop = scrollTop; - - bool shouldCheckIsScrolledToEnd = true; - bool? isScrollUp = null; - - if (_previousScrollTop != null) - { - isScrollUp = scrollTop < _previousScrollTop; - - if (isScrollUp.Value && _shouldAutoScrollEnd) - { - _shouldAutoScrollEnd = false; - } - } - - if (_ignoreScrollsUntilNextScrollUp) - { - if (isScrollUp == null || !isScrollUp.Value) - { - shouldCheckIsScrolledToEnd = false; - } - else - { - _ignoreScrollsUntilNextScrollUp = false; - } - } - - if (shouldCheckIsScrolledToEnd) - { - await CheckIsScrolledToEnd(); - } - - _previousScrollTop = _scrollTop; - } -} \ No newline at end of file + @page "/chat" + +@inject LMKitService LMKitService +@inject HttpClient Http +@inject IJSRuntime JS +@inject IScrollHandler ScrollHandler +@inject IResizeHandler ResizeHandler +@inject ILogger Logger + +@inherits MvvmComponentBase + +
+
+
+ @if (ViewModel?.ConversationListViewModel.CurrentConversation != null) + { + if (ViewModel.ConversationListViewModel.CurrentConversation.IsEmpty) + { +
+
+
Hello, human.
+
+ It is likely that similar software programs are now assuming professional tasks that + were + previously your responsibility. +
+
+ Developers and other software programs are working to enhance my + ability to assist you in the process of making sense about this, or to generate witty + jokes + involving robots and humans. +
+
+ +
+
+ Press Enter to send a new message +
+ +
+ Hold Shift + Enter to insert a new line +
+
+
+ } + else + { +
+ @foreach (var message in ViewModel.ConversationListViewModel.CurrentConversation.Messages) + { + + } +
+ } + } +
+ +
+
+ @if (!IsScrolledToEnd) + { + + } +
+
+
+ +
+ +
+ +
+ @if (ViewModel?.ConversationListViewModel?.CurrentConversation?.LMKitConversation?.ContextSize > 0) + { + + Tokens: @ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextUsedSpace / + @ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextSize + (@CalculateUsagePercentage(ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextUsedSpace, + ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.ContextSize)%) + + } +
+
+ +@code +{ + private const int UIUpdateDelayMilliseconds = 50; + + private ConversationViewModel? _previousConversationViewModel; + private MessageViewModel? _latestAssistantResponse; + private bool _hasPendingScrollEnd; + private bool _ignoreScrollsUntilNextScrollUp; + private double? _previousScrollTop; + private bool _shouldAutoScrollEnd; + private double _scrollTop; + + private bool _isScrolledToEnd = false; + + public bool IsScrolledToEnd + { + get => _isScrolledToEnd; + set + { + _isScrolledToEnd = value; + + if (_isScrolledToEnd && + ViewModel.ConversationListViewModel.CurrentConversation != null && + ViewModel.ConversationListViewModel.CurrentConversation!.AwaitingResponse) + { + // Assistant is currently generating a response, enforce auto-scroll. + _shouldAutoScrollEnd |= true; + } + + UpdateUIAsync(); + } + } + + protected override async void OnInitialized() + { + base.OnInitialized(); + + if (ViewModel.ConversationListViewModel.CurrentConversation != null) + { + OnConversationSet(); + } + + ViewModel.ConversationListViewModel.PropertyChanged += OnConversationListViewModelPropertyChanged; + + ViewModel.ConversationListViewModel.CurrentConversation.LMKitConversation.PropertyChanged += OnLMKitConversationPropertyChanged; + + await ResizeHandler.RegisterPageResizeAsync(Resized); + await JS.InvokeVoidAsync("initializeScrollHandler", DotNetObjectReference.Create(this)); + } + + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (_hasPendingScrollEnd) + { + _hasPendingScrollEnd = false; + await ScrollToEnd(); + } + } + + private bool _refreshScheduled = false; + + private Task UpdateUIAsync(bool forceRedraw = false) + { + if (forceRedraw) + { + return InvokeAsync(() => StateHasChanged()); + } + else + { + if (!_refreshScheduled) + { + _refreshScheduled = true; + + return InvokeAsync(async () => + { + await Task.Delay(UIUpdateDelayMilliseconds); + StateHasChanged(); + _refreshScheduled = false; + }); + } + } + + return Task.CompletedTask; + } + + private void OnConversationListViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ConversationListViewModel.CurrentConversation)) + { + OnConversationSet(); + } + } + + private void OnLMKitConversationPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(LMKitService.Conversation.ContextRemainingSpace)) + { + UpdateUIAsync(); + } + else if (e.PropertyName == nameof(LMKitService.Conversation.InTextCompletion)) + { + UpdateUIAsync(forceRedraw: true); + } + } + + private void OnLatestAssistantMessagePropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(MessageViewModel.Content)) + { + OnLatestAssistantResponseProgressed(); + } + else if (e.PropertyName == nameof(MessageViewModel.MessageInProgress)) + { + if (sender == _latestAssistantResponse && !_latestAssistantResponse!.MessageInProgress && _shouldAutoScrollEnd) + { + Task.Run(async () => + { + // Note: Adding delay to sync with current UI behavior + // (show latest assistant response footer for 3 sec). + await Task.Delay(50); + await ScrollToEnd(); + }); + } + } + } + + private void OnConversationSet() + { + if (_previousConversationViewModel != null) + { + _previousConversationViewModel.Messages.CollectionChanged -= OnConversationMessagesCollectionChanged; + } + + _previousConversationViewModel = ViewModel.ConversationListViewModel.CurrentConversation; + + if (ViewModel.ConversationListViewModel.CurrentConversation != null) + { + ViewModel.ConversationListViewModel.CurrentConversation.Messages.CollectionChanged += OnConversationMessagesCollectionChanged; + _previousScrollTop = null; + _ignoreScrollsUntilNextScrollUp = true; + IsScrolledToEnd = true; + + // Awaiting for the component to be rendered before scrolling to bottom. + _hasPendingScrollEnd = true; + } + } + + private async void OnConversationMessagesCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + await UpdateUIAsync(forceRedraw: true); + + await ScrollToEnd(); + + var latestMessage = ViewModel.ConversationListViewModel.CurrentConversation!.Messages.LastOrDefault(); + + if (latestMessage != null && latestMessage.Sender == MessageSender.Assistant && latestMessage.MessageInProgress) + { + _latestAssistantResponse = latestMessage; + _shouldAutoScrollEnd = true; + latestMessage.PropertyChanged += OnLatestAssistantMessagePropertyChanged; + } + } + + private async void OnLatestAssistantResponseProgressed() + { + if (_shouldAutoScrollEnd) + { + await ScrollToEnd(); + } + } + + private async Task Resized(ResizeEventArgs args) + { + if (IsScrolledToEnd) + { + await ScrollToEnd(); + } + else + { + await CheckIsScrolledToEnd(); + } + } + + private async Task ScrollToEnd(bool smooth = false) + { + IsScrolledToEnd = true; + _ignoreScrollsUntilNextScrollUp = true; + + await JS.InvokeVoidAsync("scrollToEnd", smooth); + } + + private async Task CheckIsScrolledToEnd() + { + var viewHeight = await JS.InvokeAsync("getConversationViewHeight"); + var chatContentHeight = await JS.InvokeAsync("getScrollHeight"); + + var value = chatContentHeight - viewHeight - _scrollTop; + IsScrolledToEnd = Math.Abs(chatContentHeight - viewHeight - _scrollTop) < 5 || chatContentHeight <= viewHeight; + } + + public async Task OnScrollToEndButtonClicked() + { + await ScrollToEnd(true); + } + + public void OnSubmit() + { + ViewModel.ConversationListViewModel.CurrentConversation!.Submit(); + } + + private int CalculateUsagePercentage(int used, int total) + { + if (total == 0) return 0; + return (int)(100 * used / total); + } + + + [JSInvokable] + public async Task OnConversationContainerScrolled(double scrollTop) + { + _scrollTop = scrollTop; + + bool shouldCheckIsScrolledToEnd = true; + bool? isScrollUp = null; + + if (_previousScrollTop != null) + { + isScrollUp = scrollTop < _previousScrollTop; + + if (isScrollUp.Value && _shouldAutoScrollEnd) + { + _shouldAutoScrollEnd = false; + } + } + + if (_ignoreScrollsUntilNextScrollUp) + { + if (isScrollUp == null || !isScrollUp.Value) + { + shouldCheckIsScrolledToEnd = false; + } + else + { + _ignoreScrollsUntilNextScrollUp = false; + } + } + + if (shouldCheckIsScrolledToEnd) + { + await CheckIsScrolledToEnd(); + } + + _previousScrollTop = _scrollTop; + } +} diff --git a/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor b/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor index 46bfa92..842ab0f 100644 --- a/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor +++ b/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor @@ -25,8 +25,8 @@ @if (MessageViewModel.GetResponseCount() > 1) { @@ -34,14 +34,14 @@ } @if (MessageViewModel.Sender == MessageSender.Assistant) @@ -49,8 +49,8 @@ if (MessageViewModel.IsLastAssistantMessage) { } diff --git a/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor.css b/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor.css index edd1771..ee7b180 100644 --- a/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor.css +++ b/LM-Kit-Maestro/UI/Razor/Components/ChatMessage.razor.css @@ -51,10 +51,7 @@ align-self: center; border-radius: 50%; border-width: 0; - font-size: 12px; - padding: 2px; - min-height: 20px; - min-width: 20px; + padding: 6px; display: inline-flex; justify-content: center; align-items: center; @@ -77,7 +74,6 @@ line-height: 20px; color: var(--Outline); display: inline-block; - transform: translateY(-2px); /* tweaking text vertical position to align it with button icons */ align-self: center; margin-inline: 4px; } diff --git a/LM-Kit-Maestro/UI/Razor/Components/IconButton.razor b/LM-Kit-Maestro/UI/Razor/Components/IconButton.razor new file mode 100644 index 0000000..0e1e612 --- /dev/null +++ b/LM-Kit-Maestro/UI/Razor/Components/IconButton.razor @@ -0,0 +1,38 @@ + + +@code { + [Parameter] + public IconButtonStyle Style { get; set; } + + [Parameter] + public string? Icon { get; set; } + + [Parameter] + public EventCallback OnClick { get; set; } + + public enum IconButtonStyle + { + Round, + Square + } + + private void OnButtonClicked() + { + OnClick.InvokeAsync(); + } + + private string GetClasses() + { + switch (Style) + { + default: + case IconButtonStyle.Round: + return "round-button"; + + case IconButtonStyle.Square: + return "square-button"; + } + } +} \ No newline at end of file diff --git a/LM-Kit-Maestro/UI/Razor/Components/IconButton.razor.css b/LM-Kit-Maestro/UI/Razor/Components/IconButton.razor.css new file mode 100644 index 0000000..c3cbb11 --- /dev/null +++ b/LM-Kit-Maestro/UI/Razor/Components/IconButton.razor.css @@ -0,0 +1,14 @@ +button { + cursor: pointer; + justify-content: center; + align-items: center; + display: flex; +} + +.round-button { + border-radius: 50%; +} + +.square-button { + border-radius: 4px; +} diff --git a/LM-Kit-Maestro/UI/Razor/Components/InputBox.razor b/LM-Kit-Maestro/UI/Razor/Components/InputBox.razor new file mode 100644 index 0000000..56f724a --- /dev/null +++ b/LM-Kit-Maestro/UI/Razor/Components/InputBox.razor @@ -0,0 +1,86 @@ +@inject IJSRuntime JS + +
+