├── src ├── client-opensilver │ ├── OllamaHub.Browser │ │ ├── wwwroot │ │ │ ├── favicon.ico │ │ │ ├── modern │ │ │ │ ├── loading-indicator.css │ │ │ │ └── loading-animation.js │ │ │ ├── libs │ │ │ │ ├── FileSaver.min.js │ │ │ │ └── cshtml5.css │ │ │ ├── index.html │ │ │ └── loading-indicator.css │ │ ├── Program.cs │ │ ├── Pages │ │ │ └── Index.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── OllamaHub.Browser.csproj │ │ └── App.cs │ ├── NuGet.Config │ ├── OllamaHub.Support │ │ ├── Local │ │ │ ├── Models │ │ │ │ ├── UserMessage.cs │ │ │ │ ├── ApiResponse.cs │ │ │ │ ├── AIMessage.cs │ │ │ │ └── ModelItem.cs │ │ │ ├── Converters │ │ │ │ ├── NullToBooleanConverter.cs │ │ │ │ └── EqualToVisibilityConverter.cs │ │ │ └── Services │ │ │ │ └── ApiClient.cs │ │ ├── UI │ │ │ └── Units │ │ │ │ ├── ModelStatus.cs │ │ │ │ ├── SendButton.cs │ │ │ │ ├── DeleteButton.cs │ │ │ │ ├── SearchTextBox.cs │ │ │ │ ├── ModelTagButton.cs │ │ │ │ ├── ApplicationHeader.cs │ │ │ │ ├── ModelListBoxItem.cs │ │ │ │ ├── PopularModelsPanel.cs │ │ │ │ ├── OllamaComboBoxItem.cs │ │ │ │ ├── AIMessageListBoxItem.cs │ │ │ │ ├── NavigationListBoxItem.cs │ │ │ │ ├── NavigationRadioButton.cs │ │ │ │ ├── UserMessageListBoxItem.cs │ │ │ │ ├── GlobalStatus.cs │ │ │ │ ├── ModelListBox.cs │ │ │ │ ├── OllamaComboBox.cs │ │ │ │ ├── NavigationListBox.cs │ │ │ │ ├── ChatListBox.cs │ │ │ │ ├── PlayStopButton.cs │ │ │ │ ├── ModelStatusBadge.cs │ │ │ │ └── GlobalStatusBadge.cs │ │ ├── Themes │ │ │ ├── Units │ │ │ │ ├── SearchTextBox.xaml │ │ │ │ ├── ModelListBox.xaml │ │ │ │ ├── UserMessageListBoxItem.xaml │ │ │ │ ├── ChatListBox.xaml │ │ │ │ ├── ModelListBoxItem.xaml │ │ │ │ ├── ApplicationHeader.xaml │ │ │ │ ├── NavigationRadioButton.xaml │ │ │ │ └── AIMessageListBoxItem.xaml │ │ │ └── Generic.xaml │ │ └── OllamaHub.Support.csproj │ ├── OllamaHub │ │ ├── MainPage.xaml.cs │ │ ├── App.xaml.cs │ │ ├── MainPage.xaml │ │ ├── OllamaHub.csproj │ │ └── OllamaHubBootstrapper.cs │ ├── OllamaHub.Simulator │ │ ├── Startup.cs │ │ └── OllamaHub.Simulator.csproj │ └── OllamaHub.Main │ │ ├── UI │ │ └── Views │ │ │ └── MainContent.cs │ │ ├── Themes │ │ ├── Generic.xaml │ │ └── Views │ │ │ └── MainContent.xaml │ │ ├── OllamaHub.Main.csproj │ │ └── Local │ │ └── ViewModels │ │ └── MainViewModel.cs ├── server-minimalapi │ └── LocalLLMServer │ │ ├── Model │ │ ├── Dto │ │ │ ├── OllamaResponse.cs │ │ │ └── ChatRequest.cs │ │ └── Entity │ │ │ └── FileName.cs │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ ├── LocalLLMServer.csproj │ │ ├── SignalRHub │ │ └── ModelHub.cs │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── Program.cs │ │ ├── Service │ │ └── Background │ │ │ └── ModelMonitorService.cs │ │ └── Endpoint │ │ └── EndPointExtension.cs ├── client-wpf │ ├── OllamaHub.Support │ │ ├── Local │ │ │ ├── Models │ │ │ │ ├── UserMessage.cs │ │ │ │ ├── AIMessage.cs │ │ │ │ └── ModelItem.cs │ │ │ ├── Converters │ │ │ │ ├── NullToBooleanConverter.cs │ │ │ │ └── EqualToVisibilityConverter.cs │ │ │ └── Services │ │ │ │ └── ApiClient.cs │ │ ├── UI │ │ │ └── Units │ │ │ │ ├── ModelStatus.cs │ │ │ │ ├── SendButton.cs │ │ │ │ ├── DeleteButton.cs │ │ │ │ ├── ModelTagButton.cs │ │ │ │ ├── ApplicationHeader.cs │ │ │ │ ├── ModelListBoxItem.cs │ │ │ │ ├── PopularModelsPanel.cs │ │ │ │ ├── OllamaComboBoxItem.cs │ │ │ │ ├── AIMessageListBoxItem.cs │ │ │ │ ├── NavigationListBoxItem.cs │ │ │ │ ├── NavigationRadioButton.cs │ │ │ │ ├── UserMessageListBoxItem.cs │ │ │ │ ├── GlobalStatus.cs │ │ │ │ ├── ModelListBox.cs │ │ │ │ ├── OllamaComboBox.cs │ │ │ │ ├── NavigationListBox.cs │ │ │ │ ├── SearchTextBox.cs │ │ │ │ ├── PlayStopButton.cs │ │ │ │ ├── ModelStatusBadge.cs │ │ │ │ ├── ChatListBox.cs │ │ │ │ └── GlobalStatusBadge.cs │ │ ├── OllamaHub.Support.csproj │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ └── Themes │ │ │ ├── Units │ │ │ ├── SearchTextBox.xaml │ │ │ ├── ModelListBox.xaml │ │ │ ├── UserMessageListBoxItem.xaml │ │ │ ├── ModelListBoxItem.xaml │ │ │ ├── AIMessageListBoxItem.xaml │ │ │ ├── ChatListBox.xaml │ │ │ ├── ApplicationHeader.xaml │ │ │ ├── NavigationRadioButton.xaml │ │ │ └── SendButton.xaml │ │ │ └── Generic.xaml │ ├── OllamaHub │ │ ├── App.xaml.cs │ │ ├── MainWindow.xaml.cs │ │ ├── MainWindow.xaml │ │ ├── OllamaHub.csproj │ │ ├── AssemblyInfo.cs │ │ └── OllamaHubBootstrapper.cs │ └── OllamaHub.Main │ │ ├── Themes │ │ ├── Generic.xaml │ │ └── Views │ │ │ └── MainContent.xaml │ │ ├── UI │ │ └── Views │ │ │ └── MainContent.cs │ │ ├── OllamaHub.Main.csproj │ │ ├── Properties │ │ └── AssemblyInfo.cs │ │ └── Local │ │ └── ViewModels │ │ └── MainViewModel.cs ├── OllamaServer.sln ├── OllamaWPF.sln └── OllamaOpenSilver.sln ├── LICENSE ├── README_ko.md └── README.md /src/client-opensilver/OllamaHub.Browser/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenSilver/OllamaManager/HEAD/src/client-opensilver/OllamaHub.Browser/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Model/Dto/OllamaResponse.cs: -------------------------------------------------------------------------------- 1 | namespace LocalLLMServer.Endpoint.Dto; 2 | 3 | public class OllamaResponse 4 | { 5 | public string response { get; set; } = ""; 6 | } 7 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Local/Models/UserMessage.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.Local.Models; 2 | 3 | public class UserMessage 4 | { 5 | public string Content { get; set; } 6 | public DateTime Timestamp { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Model/Dto/ChatRequest.cs: -------------------------------------------------------------------------------- 1 | namespace LocalLLMServer.Endpoint.Dto; 2 | 3 | public class ChatRequest 4 | { 5 | public string Message { get; set; } = ""; 6 | public string Model { get; set; } = ""; 7 | } 8 | -------------------------------------------------------------------------------- /src/client-opensilver/NuGet.Config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ModelStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.UI.Units; 2 | 3 | public enum ModelStatus 4 | { 5 | Stopped, 6 | Running, 7 | Loading, 8 | Starting, 9 | Stopping, 10 | Error, 11 | Downloading 12 | } 13 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Models/UserMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OllamaHub.Support.Local.Models; 4 | 5 | public class UserMessage 6 | { 7 | public string Content { get; set; } 8 | public DateTime Timestamp { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ModelStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.UI.Units; 2 | 3 | public enum ModelStatus 4 | { 5 | Stopped, 6 | Running, 7 | Loading, 8 | Starting, 9 | Stopping, 10 | Error, 11 | Downloading 12 | } 13 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/SendButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class SendButton : Button 6 | { 7 | public SendButton() 8 | { 9 | DefaultStyleKey = typeof(SendButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/SendButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class SendButton : Button 6 | { 7 | public SendButton() 8 | { 9 | DefaultStyleKey = typeof(SendButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub 4 | { 5 | public partial class MainPage : Page 6 | { 7 | public MainPage() 8 | { 9 | this.InitializeComponent(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/DeleteButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class DeleteButton : Button 6 | { 7 | public DeleteButton() 8 | { 9 | DefaultStyleKey = typeof(DeleteButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/DeleteButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class DeleteButton : Button 6 | { 7 | public DeleteButton() 8 | { 9 | DefaultStyleKey = typeof(DeleteButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ModelTagButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class ModelTagButton : Button 6 | { 7 | public ModelTagButton() 8 | { 9 | DefaultStyleKey = typeof(ModelTagButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/LocalLLMServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/SearchTextBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class SearchTextBox : TextBox 6 | { 7 | public SearchTextBox() 8 | { 9 | DefaultStyleKey = typeof(SearchTextBox); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ModelTagButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class ModelTagButton : Button 6 | { 7 | public ModelTagButton() 8 | { 9 | DefaultStyleKey = typeof(ModelTagButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ApplicationHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class ApplicationHeader : Control 6 | { 7 | public ApplicationHeader() 8 | { 9 | DefaultStyleKey = typeof(ApplicationHeader); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ModelListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class ModelListBoxItem : ListBoxItem 6 | { 7 | public ModelListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(ModelListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ApplicationHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class ApplicationHeader : Control 6 | { 7 | public ApplicationHeader() 8 | { 9 | DefaultStyleKey = typeof(ApplicationHeader); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ModelListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class ModelListBoxItem : ListBoxItem 6 | { 7 | public ModelListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(ModelListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/PopularModelsPanel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class PopularModelsPanel : Control 6 | { 7 | public PopularModelsPanel() 8 | { 9 | DefaultStyleKey = typeof(PopularModelsPanel); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/PopularModelsPanel.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class PopularModelsPanel : Control 6 | { 7 | public PopularModelsPanel() 8 | { 9 | DefaultStyleKey = typeof(PopularModelsPanel); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/OllamaComboBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class OllamaComboBoxItem : ComboBoxItem 6 | { 7 | public OllamaComboBoxItem() 8 | { 9 | DefaultStyleKey = typeof(OllamaComboBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Models/ApiResponse.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.Local.Models; 2 | 3 | public class ApiResponse 4 | { 5 | public string Message { get; set; } = ""; 6 | public int Count { get; set; } 7 | public T Models { get; set; } = default!; 8 | public bool Success { get; set; } = true; 9 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/OllamaComboBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class OllamaComboBoxItem : ComboBoxItem 6 | { 7 | public OllamaComboBoxItem() 8 | { 9 | DefaultStyleKey = typeof(OllamaComboBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/AIMessageListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class AIMessageListBoxItem : ListBoxItem 6 | { 7 | public AIMessageListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(AIMessageListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/AIMessageListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class AIMessageListBoxItem : ListBoxItem 6 | { 7 | public AIMessageListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(AIMessageListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/NavigationListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class NavigationListBoxItem : ListBoxItem 6 | { 7 | public NavigationListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(NavigationListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/NavigationRadioButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class NavigationRadioButton : RadioButton 6 | { 7 | public NavigationRadioButton() 8 | { 9 | DefaultStyleKey = typeof(NavigationRadioButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/NavigationListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class NavigationListBoxItem : ListBoxItem 6 | { 7 | public NavigationListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(NavigationListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/NavigationRadioButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class NavigationRadioButton : RadioButton 6 | { 7 | public NavigationRadioButton() 8 | { 9 | DefaultStyleKey = typeof(NavigationRadioButton); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/UserMessageListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class UserMessageListBoxItem : ListBoxItem 6 | { 7 | public UserMessageListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(UserMessageListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace OllamaHub 4 | { 5 | public partial class App : Application 6 | { 7 | public App() 8 | { 9 | OllamaHubBootstrapper bootstrapper = new OllamaHubBootstrapper(); 10 | bootstrapper.Run(); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Model/Entity/FileName.cs: -------------------------------------------------------------------------------- 1 | namespace LocalLLMServer.Model.Entity; 2 | 3 | public class OllamaModel 4 | { 5 | public string Name { get; set; } = ""; 6 | public string Size { get; set; } = ""; 7 | public string LastUsed { get; set; } = ""; 8 | public string Status { get; set; } = ""; 9 | } 10 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/UserMessageListBoxItem.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace OllamaHub.Support.UI.Units; 4 | 5 | public class UserMessageListBoxItem : ListBoxItem 6 | { 7 | public UserMessageListBoxItem() 8 | { 9 | DefaultStyleKey = typeof(UserMessageListBoxItem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/GlobalStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.UI.Units; 2 | 3 | public enum GlobalStatus 4 | { 5 | NoModelsRunning, 6 | SingleModelRunning, 7 | MultipleModelsRunning, 8 | SystemLoading, 9 | SystemError, 10 | SystemIdle, 11 | AllModelsDownloading 12 | } 13 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Simulator/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenSilver.Simulator; 3 | 4 | namespace OllamaHub.Simulator 5 | { 6 | internal static class Startup 7 | { 8 | [STAThread] 9 | static int Main(string[] args) 10 | { 11 | return SimulatorLauncher.Start(typeof(App)); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/GlobalStatus.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.UI.Units; 2 | 3 | public enum GlobalStatus 4 | { 5 | NoModelsRunning, 6 | SingleModelRunning, 7 | MultipleModelsRunning, 8 | SystemLoading, 9 | SystemError, 10 | SystemIdle, 11 | AllModelsDownloading 12 | } 13 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace OllamaHub 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Main/UI/Views/MainContent.cs: -------------------------------------------------------------------------------- 1 | using Jamesnet.Foundation; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Main.UI.Views 5 | { 6 | public class MainContent : ContentControl, IView 7 | { 8 | public MainContent() 9 | { 10 | DefaultStyleKey = typeof(MainContent); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Local/Models/AIMessage.cs: -------------------------------------------------------------------------------- 1 | namespace OllamaHub.Support.Local.Models; 2 | 3 | public class AIMessage 4 | { 5 | public string Content { get; set; } 6 | public DateTime Timestamp { get; set; } 7 | public string ModelName { get; set; } 8 | public bool IsCodeBlock { get; set; } 9 | public string CodeLanguage { get; set; } 10 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Models/AIMessage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace OllamaHub.Support.Local.Models; 4 | 5 | public class AIMessage 6 | { 7 | public string Content { get; set; } 8 | public DateTime Timestamp { get; set; } 9 | public string ModelName { get; set; } 10 | public bool IsCodeBlock { get; set; } 11 | public string CodeLanguage { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Main/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Main/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/OllamaHub.Support.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0-windows 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/SignalRHub/ModelHub.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Microsoft.AspNetCore.SignalR; 3 | namespace LocalLLMServer.SignalRHub; 4 | 5 | public class ModelHub : Hub 6 | { 7 | public override async Task OnConnectedAsync() 8 | { 9 | await Groups.AddToGroupAsync(Context.ConnectionId, "ModelUpdates"); 10 | await base.OnConnectedAsync(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Main/UI/Views/MainContent.cs: -------------------------------------------------------------------------------- 1 | using Jamesnet.Foundation; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | 5 | namespace OllamaHub.Main.UI.Views; 6 | 7 | public class MainContent : ContentControl, IView 8 | { 9 | static MainContent() 10 | { 11 | DefaultStyleKeyProperty.OverrideMetadata(typeof(MainContent), new FrameworkPropertyMetadata(typeof(MainContent))); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ModelListBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class ModelListBox : ListBox 7 | { 8 | public ModelListBox() 9 | { 10 | DefaultStyleKey = typeof(ModelListBox); 11 | } 12 | 13 | protected override DependencyObject GetContainerForItemOverride() 14 | { 15 | return new ModelListBoxItem(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ModelListBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class ModelListBox : ListBox 7 | { 8 | public ModelListBox() 9 | { 10 | DefaultStyleKey = typeof(ModelListBox); 11 | } 12 | 13 | protected override DependencyObject GetContainerForItemOverride() 14 | { 15 | return new ModelListBoxItem(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Models/ModelItem.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace OllamaHub.Support.Local.Models; 4 | 5 | public partial class ModelItem : ObservableObject 6 | { 7 | [ObservableProperty] 8 | private string _name; 9 | [ObservableProperty] 10 | private string _size; 11 | [ObservableProperty] 12 | private string _lastUsed; 13 | [ObservableProperty] 14 | private string _status; 15 | } 16 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/OllamaComboBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class OllamaComboBox : ComboBox 7 | { 8 | public OllamaComboBox() 9 | { 10 | DefaultStyleKey = typeof(OllamaComboBox); 11 | } 12 | 13 | protected override DependencyObject GetContainerForItemOverride() 14 | { 15 | return new OllamaComboBoxItem(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/OllamaComboBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class OllamaComboBox : ComboBox 7 | { 8 | public OllamaComboBox() 9 | { 10 | DefaultStyleKey = typeof(OllamaComboBox); 11 | } 12 | 13 | protected override DependencyObject GetContainerForItemOverride() 14 | { 15 | return new OllamaComboBoxItem(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/NavigationListBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class NavigationListBox : ListBox 7 | { 8 | public NavigationListBox() 9 | { 10 | DefaultStyleKey = typeof(NavigationListBox); 11 | } 12 | 13 | protected override DependencyObject GetContainerForItemOverride() 14 | { 15 | return new NavigationListBoxItem(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Simulator/OllamaHub.Simulator.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net9.0-windows 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/NavigationListBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class NavigationListBox : ListBox 7 | { 8 | public NavigationListBox() 9 | { 10 | DefaultStyleKey = typeof(NavigationListBox); 11 | } 12 | 13 | protected override DependencyObject GetContainerForItemOverride() 14 | { 15 | return new NavigationListBoxItem(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace OllamaHub 4 | { 5 | public sealed partial class App : Application 6 | { 7 | public App() 8 | { 9 | this.InitializeComponent(); 10 | 11 | OllamaHubBootstrapper bootstrapper = new OllamaHubBootstrapper(); 12 | bootstrapper.Run(); 13 | 14 | var mainPage = new MainPage(); 15 | Window.Current.Content = mainPage; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | 4 | namespace OllamaHub.Browser 5 | { 6 | public class Program 7 | { 8 | public static async Task Main(string[] args) 9 | { 10 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 11 | builder.RootComponents.Add("#app"); 12 | var host = builder.Build(); 13 | await host.RunAsync(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Converters/NullToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace OllamaHub.Support.Local.Converters; 6 | 7 | public class NullToBooleanConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return value != null; 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub/MainPage.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Main/OllamaHub.Main.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0-windows 5 | true 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Local/Converters/NullToBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | 4 | namespace OllamaHub.Support.Local.Converters; 5 | 6 | public class NullToBooleanConverter : IValueConverter 7 | { 8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 9 | { 10 | // CurrentModel이 null이면 false, null이 아니면 true 반환 11 | return value != null; 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/Pages/Index.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | using OpenSilver.WebAssembly; 5 | 6 | namespace OllamaHub.Browser.Pages 7 | { 8 | [Route("/")] 9 | public class Index : ComponentBase 10 | { 11 | protected override void BuildRenderTree(RenderTreeBuilder __builder) 12 | { 13 | } 14 | 15 | protected async override Task OnInitializedAsync() 16 | { 17 | await base.OnInitializedAsync(); 18 | await Runner.RunApplicationAsync(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub/OllamaHub.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net9.0-windows 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Main/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "applicationUrl": "http://localhost:5081", 9 | "environmentVariables": { 10 | "ASPNETCORE_ENVIRONMENT": "Development" 11 | } 12 | }, 13 | "https": { 14 | "commandName": "Project", 15 | "dotnetRunMessages": true, 16 | "launchBrowser": true, 17 | "applicationUrl": "https://localhost:7262;http://localhost:5081", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Program.cs: -------------------------------------------------------------------------------- 1 | using LocalLLMServer.Endpoint; 2 | using LocalLLMServer.Service.Background; 3 | using LocalLLMServer.SignalRHub; 4 | 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Services.AddCors(options => 9 | { 10 | options.AddDefaultPolicy(policy => policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); 11 | }); 12 | 13 | builder.Services.AddSignalR(); 14 | builder.Services.AddHttpClient("Ollama", client => 15 | { 16 | client.BaseAddress = new Uri("http://localhost:11434/"); 17 | client.Timeout = TimeSpan.FromMinutes(5); 18 | }); 19 | builder.Services.AddHostedService(); 20 | 21 | var app = builder.Build(); 22 | 23 | app.UseCors(); 24 | app.MapHub("/modelhub"); 25 | 26 | app.AddModelEndPoints(); 27 | app.AddChatEndPoints(); 28 | 29 | app.Run(); -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Local/Models/ModelItem.cs: -------------------------------------------------------------------------------- 1 | using Jamesnet.Foundation; 2 | 3 | namespace OllamaHub.Support.Local.Models; 4 | 5 | public partial class ModelItem : ViewModelBase 6 | { 7 | private string _name; 8 | private string _size; 9 | private string _lastUsed; 10 | private string _status; 11 | 12 | public string Name 13 | { 14 | get => _name; 15 | set => SetProperty(ref _name, value); 16 | } 17 | 18 | public string Size 19 | { 20 | get => _size; 21 | set => SetProperty(ref _size, value); 22 | } 23 | 24 | public string LastUsed 25 | { 26 | get => _lastUsed; 27 | set => SetProperty(ref _lastUsed, value); 28 | } 29 | 30 | public string Status 31 | { 32 | get => _status; 33 | set => SetProperty(ref _status, value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Local/Converters/EqualToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Data; 3 | using System.Windows; 4 | 5 | namespace OllamaHub.Support.Local.Converters; 6 | 7 | public class EqualToVisibilityConverter : IValueConverter 8 | { 9 | public string Id { get; set; } 10 | 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is bool isChecked && isChecked) 14 | { 15 | if (parameter.Equals(Id)) 16 | { 17 | return Visibility.Visible; 18 | } 19 | } 20 | return Visibility.Hidden; 21 | } 22 | 23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Converters/EqualToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows; 5 | 6 | namespace OllamaHub.Support.Local.Converters; 7 | 8 | public class EqualToVisibilityConverter : IValueConverter 9 | { 10 | public string Id { get; set; } 11 | 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is bool isChecked && isChecked) 15 | { 16 | if (parameter.Equals(Id)) 17 | { 18 | return Visibility.Visible; 19 | } 20 | } 21 | return Visibility.Collapsed; 22 | } 23 | 24 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 25 | { 26 | throw new NotImplementedException(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55591/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "OllamaHub.Browser": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "applicationUrl": "http://localhost:55592/" 20 | }, 21 | "IIS Express": { 22 | "commandName": "IISExpress", 23 | "launchBrowser": true, 24 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/OllamaHub.Browser.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | 7 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | True 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub/OllamaHub.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | true 7 | 12 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | MSBuild:Compile 18 | 19 | 20 | MSBuild:Compile 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 OpenSilver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Main/OllamaHub.Main.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | true 7 | 12 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Designer 27 | MSBuild:Compile 28 | 29 | 30 | Designer 31 | MSBuild:Compile 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/OllamaServer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35919.96 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocalLLMServer", "server-minimalapi\LocalLLMServer\LocalLLMServer.csproj", "{C846FE1B-9DD7-20A8-66B1-46CF7BFD0463}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {C846FE1B-9DD7-20A8-66B1-46CF7BFD0463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {C846FE1B-9DD7-20A8-66B1-46CF7BFD0463}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {C846FE1B-9DD7-20A8-66B1-46CF7BFD0463}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {C846FE1B-9DD7-20A8-66B1-46CF7BFD0463}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {39A72B66-BD23-4C85-A993-C6A5100B3888} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ChatListBox.cs: -------------------------------------------------------------------------------- 1 | using OllamaHub.Support.Local.Models; 2 | using System.Collections.Specialized; 3 | using System.Linq; 4 | using System.Windows; 5 | using System.Windows.Controls; 6 | 7 | namespace OllamaHub.Support.UI.Units; 8 | 9 | public class ChatListBox : ListBox 10 | { 11 | private int _currentItemIndex = -1; 12 | 13 | public ChatListBox() 14 | { 15 | DefaultStyleKey = typeof(ChatListBox); 16 | _currentItemIndex = -1; 17 | } 18 | 19 | protected override DependencyObject GetContainerForItemOverride() 20 | { 21 | _currentItemIndex++; 22 | 23 | if (_currentItemIndex >= 0 && _currentItemIndex < Items.Count) 24 | { 25 | var item = Items.LastOrDefault(); 26 | if (item is UserMessage) 27 | { 28 | return new UserMessageListBoxItem(); 29 | } 30 | else if (item is AIMessage) 31 | { 32 | return new AIMessageListBoxItem(); 33 | } 34 | } 35 | return base.GetContainerForItemOverride(); 36 | } 37 | 38 | protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 39 | { 40 | base.OnItemsChanged(e); 41 | _currentItemIndex = -1; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub/OllamaHubBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Jamesnet.Foundation; 2 | using Microsoft.AspNetCore.SignalR.Client; 3 | using OllamaHub.Main.Local.ViewModels; 4 | using OllamaHub.Main.UI.Views; 5 | using OllamaHub.Support.Local.Services; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace OllamaHub 13 | { 14 | internal class OllamaHubBootstrapper : AppBootstrapper 15 | { 16 | protected override void RegisterDependencies(IContainer container) 17 | { 18 | HubConnection _hubConnection = new HubConnectionBuilder() 19 | .WithUrl("https://localhost:7262/modelhub") 20 | .WithAutomaticReconnect() 21 | .Build(); 22 | 23 | container.RegisterInstance(_hubConnection); 24 | container.RegisterSingleton(); 25 | container.RegisterSingleton(nameof(MainContent)); 26 | } 27 | 28 | protected override void RegisterViewModels(IViewModelMapper viewModelMapper) 29 | { 30 | viewModelMapper.Register(); 31 | } 32 | 33 | protected override void SettingsLayer(ILayerManager layer, IContainer container) 34 | { 35 | layer.Mapping("MAIN", container.Resolve(nameof(MainContent))); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub/OllamaHubBootstrapper.cs: -------------------------------------------------------------------------------- 1 | using Jamesnet.Foundation; 2 | using Microsoft.AspNetCore.SignalR.Client; 3 | using OllamaHub.Main.Local.ViewModels; 4 | using OllamaHub.Main.UI.Views; 5 | using OllamaHub.Support.Local.Services; 6 | using System; 7 | 8 | namespace OllamaHub 9 | { 10 | internal class OllamaHubBootstrapper : AppBootstrapper 11 | { 12 | protected override void RegisterDependencies(IContainer container) 13 | { 14 | var hubConnection = new HubConnectionBuilder() 15 | .WithUrl("https://localhost:7262/modelhub") 16 | .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10) }) 17 | .Build(); 18 | 19 | container.RegisterInstance(hubConnection); 20 | 21 | var apiClient = new ApiClient("https://localhost:7262/api/"); 22 | container.RegisterInstance(apiClient); 23 | 24 | container.RegisterSingleton(nameof(MainContent)); 25 | } 26 | 27 | protected override void RegisterViewModels(IViewModelMapper viewModelMapper) 28 | { 29 | viewModelMapper.Register(); 30 | } 31 | 32 | protected override void SettingsLayer(ILayerManager layer, IContainer container) 33 | { 34 | layer.Mapping("MAIN", container.Resolve(nameof(MainContent))); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/SearchTextBox.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Input; 4 | 5 | namespace OllamaHub.Support.UI.Units; 6 | 7 | public class SearchTextBox : TextBox 8 | { 9 | public static readonly DependencyProperty EnterCommandProperty = 10 | DependencyProperty.Register ( 11 | "EnterCommand", 12 | typeof (ICommand), 13 | typeof (SearchTextBox), 14 | new PropertyMetadata (null)); 15 | 16 | public ICommand EnterCommand 17 | { 18 | get => (ICommand)GetValue (EnterCommandProperty); 19 | set => SetValue (EnterCommandProperty, value); 20 | } 21 | 22 | protected override void OnPreviewKeyDown(KeyEventArgs e) 23 | { 24 | base.OnPreviewKeyDown (e); 25 | 26 | if (e.Key == Key.Enter && Keyboard.Modifiers == ModifierKeys.None) 27 | { 28 | if (EnterCommand?.CanExecute (null) == true) 29 | { 30 | EnterCommand.Execute (null); 31 | e.Handled = true; // 기본 줄바꿈 막기 32 | } 33 | } 34 | } 35 | protected override void OnPreviewMouseDown(MouseButtonEventArgs e) 36 | { 37 | base.OnPreviewMouseDown (e); 38 | 39 | if(e.LeftButton == MouseButtonState.Pressed) 40 | { 41 | this.Focus (); 42 | } 43 | } 44 | 45 | public SearchTextBox() 46 | { 47 | DefaultStyleKey = typeof(SearchTextBox); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/SearchTextBox.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 29 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/SearchTextBox.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 30 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Local/Services/ApiClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text; 3 | using System.Text.Json; 4 | 5 | namespace OllamaHub.Support.Local.Services; 6 | 7 | public class ApiClient 8 | { 9 | private readonly HttpClient _http; 10 | 11 | public ApiClient() 12 | { 13 | _http = new(); 14 | _http.Timeout = TimeSpan.FromMinutes(5); 15 | } 16 | 17 | public async Task> GetAsync(string url) 18 | { 19 | var json = await _http.GetStringAsync(url); 20 | var options = new JsonSerializerOptions 21 | { 22 | PropertyNameCaseInsensitive = true 23 | }; 24 | var response = JsonSerializer.Deserialize>>(json, options); 25 | return response?.Models ?? new List(); 26 | } 27 | 28 | public async Task PostAsync(string url) 29 | { 30 | var response = await _http.PostAsync(url, null); 31 | return await response.Content.ReadAsStringAsync(); 32 | } 33 | 34 | public async Task PostAsync(string url, object data) 35 | { 36 | var json = JsonSerializer.Serialize(data); 37 | var content = new StringContent(json, Encoding.UTF8, "application/json"); 38 | var response = await _http.PostAsync(url, content); 39 | return await response.Content.ReadAsStringAsync(); 40 | } 41 | 42 | public void Dispose() 43 | { 44 | _http?.Dispose(); 45 | } 46 | } 47 | 48 | public class ApiResponse 49 | { 50 | public string Message { get; set; } = ""; 51 | public int Count { get; set; } 52 | public T Models { get; set; } = default!; 53 | public bool Success { get; set; } = true; 54 | } -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/PlayStopButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public enum ModelButtonState 7 | { 8 | Stopped, 9 | Starting, 10 | Running, 11 | Stopping 12 | } 13 | 14 | public class PlayStopButton : Button 15 | { 16 | public static readonly DependencyProperty ModelStatusProperty = 17 | DependencyProperty.Register( 18 | nameof(ModelStatus), 19 | typeof(string), 20 | typeof(PlayStopButton), 21 | new PropertyMetadata(string.Empty, OnModelStatusChanged)); 22 | 23 | public string ModelStatus 24 | { 25 | get => (string)GetValue(ModelStatusProperty); 26 | set => SetValue(ModelStatusProperty, value); 27 | } 28 | 29 | public PlayStopButton() 30 | { 31 | DefaultStyleKey = typeof(PlayStopButton); 32 | } 33 | 34 | private static void OnModelStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 35 | { 36 | if (d is PlayStopButton button) 37 | { 38 | button.UpdateVisualState((string)e.NewValue); 39 | } 40 | } 41 | 42 | public override void OnApplyTemplate() 43 | { 44 | base.OnApplyTemplate(); 45 | UpdateVisualState(ModelStatus); 46 | } 47 | 48 | private void UpdateVisualState(string status) 49 | { 50 | var stateName = status switch 51 | { 52 | "Running" => "Running", 53 | "Starting" => "Starting", 54 | "Stopping" => "Stopping", 55 | _ => "Stopped" 56 | }; 57 | 58 | VisualStateManager.GoToState(this, stateName, false); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/PlayStopButton.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public enum ModelButtonState 7 | { 8 | Stopped, 9 | Starting, 10 | Running, 11 | Stopping 12 | } 13 | 14 | public class PlayStopButton : Button 15 | { 16 | public static readonly DependencyProperty ModelStatusProperty = 17 | DependencyProperty.Register( 18 | nameof(ModelStatus), 19 | typeof(string), 20 | typeof(PlayStopButton), 21 | new PropertyMetadata(string.Empty, OnModelStatusChanged)); 22 | 23 | public string ModelStatus 24 | { 25 | get => (string)GetValue(ModelStatusProperty); 26 | set => SetValue(ModelStatusProperty, value); 27 | } 28 | 29 | public PlayStopButton() 30 | { 31 | DefaultStyleKey = typeof(PlayStopButton); 32 | } 33 | 34 | private static void OnModelStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 35 | { 36 | if (d is PlayStopButton button) 37 | { 38 | button.UpdateVisualState((string)e.NewValue); 39 | } 40 | } 41 | 42 | public override void OnApplyTemplate() 43 | { 44 | base.OnApplyTemplate(); 45 | UpdateVisualState(ModelStatus); 46 | } 47 | 48 | private void UpdateVisualState(string status) 49 | { 50 | var stateName = status switch 51 | { 52 | "Running" => "Running", 53 | "Starting" => "Starting", 54 | "Stopping" => "Stopping", 55 | _ => "Stopped" 56 | }; 57 | 58 | VisualStateManager.GoToState(this, stateName, false); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/App.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.CompilerServices; 3 | using Microsoft.AspNetCore.Components.Rendering; 4 | using Microsoft.AspNetCore.Components.Routing; 5 | 6 | namespace OllamaHub.Browser 7 | { 8 | public class App : ComponentBase 9 | { 10 | protected override void BuildRenderTree(RenderTreeBuilder builder) 11 | { 12 | builder.OpenComponent(0); 13 | builder.AddAttribute(1, "AppAssembly", RuntimeHelpers.TypeCheck( 14 | typeof(Program).Assembly 15 | )); 16 | builder.AddAttribute(2, "PreferExactMatches", RuntimeHelpers.TypeCheck( 17 | true 18 | )); 19 | builder.AddAttribute(3, "Found", (RenderFragment)(routeData => builder2 => 20 | { 21 | builder2.OpenComponent(4); 22 | builder2.AddAttribute(5, "RouteData", RuntimeHelpers.TypeCheck( 23 | routeData 24 | )); 25 | builder2.CloseComponent(); 26 | } 27 | )); 28 | builder.AddAttribute(7, "NotFound", (RenderFragment)(builder2 => 29 | { 30 | builder2.OpenComponent(8); 31 | builder2.AddAttribute(9, "ChildContent", (RenderFragment)(builder3 => 32 | { 33 | builder3.AddMarkupContent(10, "

Sorry, there\'s nothing at this address.

"); 34 | } 35 | )); 36 | builder2.CloseComponent(); 37 | } 38 | )); 39 | builder.CloseComponent(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Generic.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ModelStatusBadge.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class ModelStatusBadge : Control 7 | { 8 | public ModelStatusBadge() 9 | { 10 | DefaultStyleKey = typeof(ModelStatusBadge); 11 | } 12 | 13 | public static readonly DependencyProperty CornerRadiusProperty = 14 | DependencyProperty.Register( 15 | nameof(CornerRadius), 16 | typeof(CornerRadius), 17 | typeof(ModelStatusBadge), 18 | new PropertyMetadata(new CornerRadius(0))); 19 | 20 | public static readonly DependencyProperty StatusProperty = 21 | DependencyProperty.Register( 22 | nameof(Status), 23 | typeof(ModelStatus), 24 | typeof(ModelStatusBadge), 25 | new PropertyMetadata(ModelStatus.Stopped, OnStatusChanged)); 26 | 27 | public CornerRadius CornerRadius 28 | { 29 | get { return (CornerRadius)GetValue(CornerRadiusProperty); } 30 | set { SetValue(CornerRadiusProperty, value); } 31 | } 32 | 33 | public ModelStatus Status 34 | { 35 | get => (ModelStatus)GetValue(StatusProperty); 36 | set => SetValue(StatusProperty, value); 37 | } 38 | 39 | private static void OnStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 40 | { 41 | if (d is ModelStatusBadge badge) 42 | { 43 | badge.UpdateVisualState(); 44 | } 45 | } 46 | 47 | private void UpdateVisualState() 48 | { 49 | var state = Status.ToString(); 50 | VisualStateManager.GoToState(this, state, true); 51 | } 52 | 53 | public override void OnApplyTemplate() 54 | { 55 | base.OnApplyTemplate(); 56 | UpdateVisualState(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/ModelStatusBadge.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class ModelStatusBadge : Control 7 | { 8 | public ModelStatusBadge() 9 | { 10 | DefaultStyleKey = typeof(ModelStatusBadge); 11 | } 12 | 13 | public static readonly DependencyProperty CornerRadiusProperty = 14 | DependencyProperty.Register( 15 | nameof(CornerRadius), 16 | typeof(CornerRadius), 17 | typeof(ModelStatusBadge), 18 | new PropertyMetadata(new CornerRadius(0))); 19 | 20 | public static readonly DependencyProperty StatusProperty = 21 | DependencyProperty.Register( 22 | nameof(Status), 23 | typeof(ModelStatus), 24 | typeof(ModelStatusBadge), 25 | new PropertyMetadata(ModelStatus.Stopped, OnStatusChanged)); 26 | 27 | public CornerRadius CornerRadius 28 | { 29 | get { return (CornerRadius)GetValue(CornerRadiusProperty); } 30 | set { SetValue(CornerRadiusProperty, value); } 31 | } 32 | 33 | public ModelStatus Status 34 | { 35 | get => (ModelStatus)GetValue(StatusProperty); 36 | set => SetValue(StatusProperty, value); 37 | } 38 | 39 | private static void OnStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 40 | { 41 | if (d is ModelStatusBadge badge) 42 | { 43 | badge.UpdateVisualState(); 44 | } 45 | } 46 | 47 | private void UpdateVisualState() 48 | { 49 | var state = Status.ToString(); 50 | VisualStateManager.GoToState(this, state, true); 51 | } 52 | 53 | public override void OnApplyTemplate() 54 | { 55 | base.OnApplyTemplate(); 56 | UpdateVisualState(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/ModelListBox.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 38 | 39 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/ModelListBox.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 38 | 39 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/wwwroot/modern/loading-indicator.css: -------------------------------------------------------------------------------- 1 | html body { 2 | background: var(--opensilver-loading-background-color); 3 | } 4 | 5 | .opensilver-loading-indicator { 6 | position: fixed; 7 | top: 0; 8 | left: 0; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | pointer-events: none; 13 | height: 100vh; 14 | width: 100vw; 15 | overflow: hidden; 16 | background: var(--opensilver-loading-background-color); 17 | } 18 | 19 | .opensilver-loading-indicator .opensilver-loader-container { 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | gap: 8px; 24 | } 25 | 26 | .opensilver-loading-indicator .opensilver-loader { 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: center; 30 | align-items: center; 31 | gap: 10px; 32 | width: 100%; 33 | height: 100%; 34 | margin-left: 20px; 35 | } 36 | 37 | .opensilver-loading-indicator .opensilver-loader-progress { 38 | display: flex; 39 | justify-content: flex-start; 40 | align-items: center; 41 | width: 0; 42 | max-width: 200px; 43 | height: 4px; 44 | border-radius: 12px; 45 | background-color: var(--opensilver-loading-progress-bg); 46 | border-bottom: 1px var(--opensilver-loading-progress-border-color) solid; 47 | border-right: 1px var(--opensilver-loading-progress-border-color) solid; 48 | } 49 | 50 | .opensilver-loading-indicator .opensilver-loader-progress-bar { 51 | width: 0%; 52 | height: 5px; 53 | border-radius: 12px; 54 | background-color: var(--opensilver-loading-progress-bar-color); 55 | } 56 | 57 | .opensilver-loading-indicator .opensilver-counter-container { 58 | align-self: end; 59 | display: flex; 60 | justify-content: end; 61 | align-items: center; 62 | font-family: sans-serif; 63 | font-size: 0.8rem; 64 | font-weight: 500; 65 | width: 100%; 66 | min-width: 26px; 67 | line-height: 1; 68 | gap: 1px; 69 | color: var(--opensilver-loading-counter-color); 70 | } 71 | -------------------------------------------------------------------------------- /src/OllamaWPF.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.14.36221.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub", "client-wpf\OllamaHub\OllamaHub.csproj", "{AB0CFB89-1BEB-97B9-18D6-428E585FEDB0}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub.Main", "client-wpf\OllamaHub.Main\OllamaHub.Main.csproj", "{9600DDE8-A452-2E7D-C30B-17F3402727D3}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub.Support", "client-wpf\OllamaHub.Support\OllamaHub.Support.csproj", "{94E02CA6-4496-878D-F6C2-B8AEAF9D4F9C}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {AB0CFB89-1BEB-97B9-18D6-428E585FEDB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {AB0CFB89-1BEB-97B9-18D6-428E585FEDB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {AB0CFB89-1BEB-97B9-18D6-428E585FEDB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {AB0CFB89-1BEB-97B9-18D6-428E585FEDB0}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {9600DDE8-A452-2E7D-C30B-17F3402727D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {9600DDE8-A452-2E7D-C30B-17F3402727D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {9600DDE8-A452-2E7D-C30B-17F3402727D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9600DDE8-A452-2E7D-C30B-17F3402727D3}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {94E02CA6-4496-878D-F6C2-B8AEAF9D4F9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {94E02CA6-4496-878D-F6C2-B8AEAF9D4F9C}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {94E02CA6-4496-878D-F6C2-B8AEAF9D4F9C}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {94E02CA6-4496-878D-F6C2-B8AEAF9D4F9C}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {E5FEB065-1766-4791-B8A6-3467CA24D472} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/UserMessageListBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 41 | 42 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/UserMessageListBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 41 | 42 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Local/Services/ApiClient.cs: -------------------------------------------------------------------------------- 1 | using OllamaHub.Support.Local.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Text.Json; 7 | using System.Threading.Tasks; 8 | 9 | namespace OllamaHub.Support.Local.Services; 10 | 11 | public class ApiClient : IDisposable 12 | { 13 | private readonly HttpClient _http; 14 | private readonly JsonSerializerOptions _jsonOptions; 15 | 16 | public ApiClient(string baseUrl) 17 | { 18 | _http = new HttpClient 19 | { 20 | BaseAddress = new Uri(baseUrl), 21 | Timeout = TimeSpan.FromMinutes(5) 22 | }; 23 | 24 | _jsonOptions = new JsonSerializerOptions 25 | { 26 | PropertyNameCaseInsensitive = true 27 | }; 28 | } 29 | 30 | public async Task> GetAsync(string endpoint) 31 | { 32 | try 33 | { 34 | var response = await _http.GetAsync(endpoint); 35 | response.EnsureSuccessStatusCode(); 36 | 37 | var json = await response.Content.ReadAsStringAsync(); 38 | var apiResponse = JsonSerializer.Deserialize>>(json, _jsonOptions); 39 | return apiResponse?.Models ?? new List(); 40 | } 41 | catch (HttpRequestException ex) 42 | { 43 | Console.WriteLine($"API GET 오류: {ex.Message}"); 44 | return new List(); 45 | } 46 | } 47 | 48 | public async Task PostAsync(string endpoint, object data = null) 49 | { 50 | try 51 | { 52 | HttpContent content = null; 53 | if (data != null) 54 | { 55 | var json = JsonSerializer.Serialize(data); 56 | content = new StringContent(json, Encoding.UTF8, "application/json"); 57 | } 58 | 59 | var response = await _http.PostAsync(endpoint, content); 60 | response.EnsureSuccessStatusCode(); 61 | 62 | return await response.Content.ReadAsStringAsync(); 63 | } 64 | catch (HttpRequestException ex) 65 | { 66 | Console.WriteLine($"API POST 오류: {ex.Message}"); 67 | return string.Empty; 68 | } 69 | } 70 | 71 | public void Dispose() 72 | { 73 | _http?.Dispose(); 74 | } 75 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/wwwroot/libs/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | /* FileSaver.js 2.0.5, @license MIT */ 2 | (function(a,b){if("function"==typeof define&&define.amd)define([],b);else if("undefined"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){"use strict";function b(a,b){return"undefined"==typeof b?b={autoBom:!1}:"object"!=typeof b&&(console.warn("Deprecated: Expected third argument to be a object"),b={autoBom:!b}),b.autoBom&&/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(a.type)?new Blob(["\uFEFF",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open("GET",a),d.responseType="blob",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error("could not download file")},d.send()}function d(a){var b=new XMLHttpRequest;b.open("HEAD",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent("click"))}catch(c){var b=document.createEvent("MouseEvents");b.initMouseEvent("click",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f="object"==typeof window&&window.window===window?window:"object"==typeof self&&self.self===self?self:"object"==typeof global&&global.global===global?global:void 0,a=f.navigator&&/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||("object"!=typeof window||window!==f?function(){}:"download"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement("a");g=g||b.name||"download",j.download=g,j.rel="noopener","string"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target="_blank")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:"msSaveOrOpenBlob"in navigator?function(f,g,h){if(g=g||f.name||"download","string"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement("a");i.href=f,i.target="_blank",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open("","_blank"),g&&(g.document.title=g.document.body.innerText="downloading..."),"string"==typeof b)return c(b,d,e);var h="application/octet-stream"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\/[\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&"undefined"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,"data:attachment/file;"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,"undefined"!=typeof module&&(module.exports=g)}); 3 | 4 | //# sourceMappingURL=FileSaver.min.js.map -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/ChatListBox.cs: -------------------------------------------------------------------------------- 1 | using OllamaHub.Support.Local.Models; 2 | using System.Collections.Specialized; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | 6 | namespace OllamaHub.Support.UI.Units; 7 | 8 | public class ChatListBox : ListBox 9 | { 10 | private int _currentItemIndex = -1; // 생성 중인 아이템 인덱스 추적 11 | 12 | public ChatListBox() 13 | { 14 | DefaultStyleKey = typeof(ChatListBox); 15 | _currentItemIndex = -1; 16 | 17 | ListBoxAutoScrollBehavior.SetAutoScroll (this, true); 18 | } 19 | 20 | protected override DependencyObject GetContainerForItemOverride() 21 | { 22 | if (Items.Count > 0) 23 | { 24 | var currentItem = Items[Items.Count - 1]; 25 | if (currentItem is UserMessage) 26 | { 27 | return new UserMessageListBoxItem(); 28 | } 29 | else if (currentItem is AIMessage) 30 | { 31 | return new AIMessageListBoxItem(); 32 | } 33 | } 34 | return base.GetContainerForItemOverride(); 35 | } 36 | 37 | protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) 38 | { 39 | base.OnItemsChanged(e); 40 | _currentItemIndex = -1; 41 | } 42 | } 43 | 44 | public static class ListBoxAutoScrollBehavior 45 | { 46 | public static readonly DependencyProperty AutoScrollProperty = 47 | DependencyProperty.RegisterAttached ( 48 | "AutoScroll", 49 | typeof (bool), 50 | typeof (ListBoxAutoScrollBehavior), 51 | new PropertyMetadata (false, OnAutoScrollChanged)); 52 | 53 | public static bool GetAutoScroll(DependencyObject obj) => 54 | (bool)obj.GetValue (AutoScrollProperty); 55 | 56 | public static void SetAutoScroll(DependencyObject obj, bool value) => 57 | obj.SetValue (AutoScrollProperty, value); 58 | 59 | private static void OnAutoScrollChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 60 | { 61 | if (d is ListBox listBox) 62 | { 63 | if ((bool)e.NewValue) 64 | { 65 | var items = listBox.Items; 66 | var ic = items as INotifyCollectionChanged; 67 | if (ic != null) 68 | { 69 | ic.CollectionChanged += (sender, args) => 70 | { 71 | if (args.Action == NotifyCollectionChangedAction.Add) 72 | { 73 | if (listBox.Items.Count > 0) 74 | { 75 | listBox.ScrollIntoView (listBox.Items[listBox.Items.Count - 1]); 76 | } 77 | } 78 | }; 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OllamaHub 7 | 8 | 17 | 31 | 32 | 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 | 44 | 45 |
46 |
47 |
48 |
49 |
50 | 51 | 65 | 66 | -------------------------------------------------------------------------------- /README_ko.md: -------------------------------------------------------------------------------- 1 | # Ollama Manager 2 | 3 | OpenSilver로 개발된 Ollama 모델 관리 웹 애플리케이션입니다. 4 | 5 | OpenSilver 버전을 WPF로 마이그레이션한 데스크톱 버전도 함께 제공하여 두 플랫폼 간의 개발 경험을 비교하고 학습할 수 있도록 구성했습니다. 6 | 7 | ## 목차 8 | 9 | - [스크린샷](#스크린샷) 10 | - [주요 기능](#주요-기능) 11 | - [프로젝트 구조](#프로젝트-구조) 12 | - [기술 스택](#기술-스택) 13 | - [서버 아키텍처](#서버-아키텍처) 14 | - [실행 방법](#실행-방법) 15 | - [개발 계획](#개발-계획) 16 | - [기여하기](#기여하기) 17 | - [라이센스](#라이센스) 18 | - [참고사항](#참고사항) 19 | 20 | ## 스크린샷 21 | 22 | 직관적인 인터페이스로 Ollama 모델을 쉽게 관리하고 실시간으로 채팅할 수 있습니다. 23 | 24 | | 메인 화면 | 채팅 화면 | 25 | |-----------|-----------| 26 | | ![메인 화면](https://github.com/user-attachments/assets/8c3bcfc6-ae3f-4d58-9cce-f18285506f1c) | ![채팅 화면](https://github.com/user-attachments/assets/1daeb5bd-a1d9-4cd0-bc15-3fd779950a4b) | 27 | 28 | ## 주요 기능 29 | 30 | - 설치된 모델 목록 조회 31 | - 모델 시작/중지 32 | - 실시간 채팅 33 | - 모델 상태 모니터링 34 | 35 | ## 프로젝트 구조 36 | 37 | ``` 38 | src/ 39 | ├── client-opensilver/ # OpenSilver 웹 클라이언트 40 | ├── client-wpf/ # WPF 데스크톱 클라이언트 41 | └── server-minimalapi/ # 공유 백엔드 서버 42 | ``` 43 | 44 | ## 기술 스택 45 | 46 | - **웹 클라이언트**: OpenSilver (.NET Standard 2.0) 47 | - **데스크톱 클라이언트**: WPF (.NET 9.0) 48 | - **백엔드**: ASP.NET Core Minimal API (.NET 9.0) 49 | - **실시간 통신**: SignalR 50 | 51 | ## 서버 아키텍처 52 | 53 | ### Minimal API 구조 54 | - **GET** `/api/models` - 설치된 모델 목록 및 상태 조회 55 | - **POST** `/api/models/{modelName}/start` - 모델 시작 56 | - **POST** `/api/models/{modelName}/stop` - 모델 중지 57 | - **POST** `/api/chat` - 모델과 채팅 58 | 59 | ### Ollama API 연동 60 | 백엔드 서버는 Ollama API를 활용하여 모델을 관리합니다: 61 | - `http://localhost:11434/api/tags` - 설치된 모델 목록 62 | - `http://localhost:11434/api/ps` - 실행 중인 모델 확인 63 | - `http://localhost:11434/api/generate` - 모델 로드/언로드 및 채팅 64 | 65 | ### 실시간 모니터링 66 | - SignalR Hub를 통한 실시간 모델 상태 업데이트 67 | - 백그라운드 서비스로 모델 상태 변화 감지 68 | 69 | ## 실행 방법 70 | 71 | ### 사전 요구사항 72 | - **Ollama**: [ollama.com](https://ollama.com)에서 설치 73 | - **모델 설치**: 예시로 `ollama pull llama3.2` 실행 74 | - **Visual Studio 2022** 75 | - **.NET 9.0 SDK** 76 | - **WASM Tools**: `dotnet workload install wasm-tools` 77 | - **OpenSilver SDK**: [www.opensilver.net](https://www.opensilver.net)에서 `OpenSilver_SDK_v3.2.0.4.vsix` 다운로드 후 설치 78 | 79 | ### 서버 실행 80 | 81 | 먼저 백엔드 서버를 실행합니다: 82 | ```bash 83 | cd src/server-minimalapi/LocalLLMServer 84 | dotnet run --launch-profile https 85 | ``` 86 | 서버가 `https://localhost:7262`에서 실행됩니다. 87 | 88 | ### OpenSilver 웹 클라이언트 89 | 90 | ```bash 91 | cd src/client-opensilver/OllamaHub.Browser 92 | dotnet run 93 | ``` 94 | 95 | 브라우저에서 `http://localhost:55592` 접속 96 | 97 | ### WPF 버전 98 | 99 | 동일한 서버를 사용하여 WPF 버전도 실행할 수 있습니다: 100 | ```bash 101 | cd src/client-wpf/OllamaHub 102 | dotnet run 103 | ``` 104 | 105 | ## 개발 계획 106 | 107 | ### 현재 구현됨 108 | - 모델 목록 조회 109 | - 모델 제어 (시작/중지) 110 | - 실시간 채팅 111 | 112 | ### 예정 기능 113 | - 모델 다운로드 114 | - 모델 삭제 115 | - 다중 세션 채팅 116 | - 성능 모니터링 117 | 118 | ## 기여하기 119 | 120 | 1. 저장소 포크 121 | 2. 기능 브랜치 생성 122 | 3. 변경사항 커밋 123 | 4. Pull Request 생성 124 | 125 | ## 라이센스 126 | 127 | MIT License 128 | 129 | ## 참고사항 130 | 131 | - WPF와 OpenSilver 간의 코드 호환성을 확인할 수 있는 좋은 예제입니다 132 | - OpenSilver는 .NET Standard 2.0을 기반으로 하여 웹에서 WPF와 유사한 개발 경험을 제공합니다 133 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Main/Themes/Views/MainContent.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 52 | 53 | -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Service/Background/ModelMonitorService.cs: -------------------------------------------------------------------------------- 1 | using LocalLLMServer.SignalRHub; 2 | using Microsoft.AspNetCore.SignalR; 3 | using System.Diagnostics; 4 | 5 | namespace LocalLLMServer.Service.Background; 6 | public class ModelMonitorService : BackgroundService 7 | { 8 | private readonly IHubContext _hubContext; 9 | private readonly Dictionary _lastStatus = new(); 10 | 11 | public ModelMonitorService(IHubContext hubContext) => _hubContext = hubContext; 12 | 13 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 14 | { 15 | while (!stoppingToken.IsCancellationRequested) 16 | { 17 | var currentStatus = await GetModelStatus(); 18 | 19 | foreach (var (modelName, status) in currentStatus) 20 | { 21 | if (!_lastStatus.TryGetValue(modelName, out var prevStatus) || prevStatus != status) 22 | { 23 | _lastStatus[modelName] = status; 24 | await _hubContext.Clients.Group("ModelUpdates").SendAsync("ModelStatusChanged", modelName, status); 25 | } 26 | } 27 | 28 | foreach (var removed in _lastStatus.Keys.Except(currentStatus.Keys).ToList()) 29 | _lastStatus.Remove(removed); 30 | 31 | await Task.Delay(3000, stoppingToken); 32 | } 33 | } 34 | 35 | private async Task> GetModelStatus() 36 | { 37 | var result = new Dictionary(); 38 | var running = new HashSet(); 39 | 40 | var psProcess = new Process 41 | { 42 | StartInfo = new ProcessStartInfo 43 | { 44 | FileName = "ollama", 45 | Arguments = "ps", 46 | RedirectStandardOutput = true, 47 | UseShellExecute = false, 48 | CreateNoWindow = true 49 | } 50 | }; 51 | psProcess.Start(); 52 | var psOutput = await psProcess.StandardOutput.ReadToEndAsync(); 53 | await psProcess.WaitForExitAsync(); 54 | 55 | foreach (var line in psOutput.Split('\n').Skip(1)) 56 | { 57 | var parts = line.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); 58 | if (parts.Length > 0) running.Add(parts[0]); 59 | } 60 | 61 | var listProcess = new Process 62 | { 63 | StartInfo = new ProcessStartInfo 64 | { 65 | FileName = "ollama", 66 | Arguments = "list", 67 | RedirectStandardOutput = true, 68 | UseShellExecute = false, 69 | CreateNoWindow = true 70 | } 71 | }; 72 | listProcess.Start(); 73 | var listOutput = await listProcess.StandardOutput.ReadToEndAsync(); 74 | await listProcess.WaitForExitAsync(); 75 | 76 | foreach (var line in listOutput.Split('\n').Skip(1)) 77 | { 78 | var parts = line.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); 79 | if (parts.Length >= 1) 80 | { 81 | var modelName = parts[0]; 82 | result[modelName] = running.Contains(modelName) ? "Running" : "Stopped"; 83 | } 84 | } 85 | 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Main/Themes/Views/MainContent.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 56 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/ChatListBox.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 60 | 61 | -------------------------------------------------------------------------------- /src/OllamaOpenSilver.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.13.35919.96 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub", "client-opensilver\OllamaHub\OllamaHub.csproj", "{6D7DC9D8-127D-A873-B5DC-67DDA6A9E73E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub.Browser", "client-opensilver\OllamaHub.Browser\OllamaHub.Browser.csproj", "{027ADC29-76E8-1B3F-3464-C54C0E9417C9}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub.Support", "client-opensilver\OllamaHub.Support\OllamaHub.Support.csproj", "{33586B4F-83EF-9A03-C6C7-4959C83D11BF}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{ED3DE717-1672-4FD7-AD3D-EE8A681B78AB}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Based", "Based", "{FFF3EAF6-7F8A-4C6F-841C-C389C50A7FC7}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Partial", "Partial", "{CF04B1B3-6F0C-4513-8FE9-A05F8D9896E9}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OllamaHub.Main", "client-opensilver\OllamaHub.Main\OllamaHub.Main.csproj", "{4E08E2F1-C3D9-492C-9DC0-C23F8A17B691}" 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Release|Any CPU = Release|Any CPU 26 | EndGlobalSection 27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 28 | {6D7DC9D8-127D-A873-B5DC-67DDA6A9E73E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {6D7DC9D8-127D-A873-B5DC-67DDA6A9E73E}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {6D7DC9D8-127D-A873-B5DC-67DDA6A9E73E}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {6D7DC9D8-127D-A873-B5DC-67DDA6A9E73E}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {027ADC29-76E8-1B3F-3464-C54C0E9417C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {027ADC29-76E8-1B3F-3464-C54C0E9417C9}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {027ADC29-76E8-1B3F-3464-C54C0E9417C9}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {027ADC29-76E8-1B3F-3464-C54C0E9417C9}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {33586B4F-83EF-9A03-C6C7-4959C83D11BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {33586B4F-83EF-9A03-C6C7-4959C83D11BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {33586B4F-83EF-9A03-C6C7-4959C83D11BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {33586B4F-83EF-9A03-C6C7-4959C83D11BF}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {4E08E2F1-C3D9-492C-9DC0-C23F8A17B691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {4E08E2F1-C3D9-492C-9DC0-C23F8A17B691}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {4E08E2F1-C3D9-492C-9DC0-C23F8A17B691}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {4E08E2F1-C3D9-492C-9DC0-C23F8A17B691}.Release|Any CPU.Build.0 = Release|Any CPU 44 | EndGlobalSection 45 | GlobalSection(SolutionProperties) = preSolution 46 | HideSolutionNode = FALSE 47 | EndGlobalSection 48 | GlobalSection(NestedProjects) = preSolution 49 | {6D7DC9D8-127D-A873-B5DC-67DDA6A9E73E} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} 50 | {027ADC29-76E8-1B3F-3464-C54C0E9417C9} = {ED3DE717-1672-4FD7-AD3D-EE8A681B78AB} 51 | {33586B4F-83EF-9A03-C6C7-4959C83D11BF} = {FFF3EAF6-7F8A-4C6F-841C-C389C50A7FC7} 52 | {4E08E2F1-C3D9-492C-9DC0-C23F8A17B691} = {CF04B1B3-6F0C-4513-8FE9-A05F8D9896E9} 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {8842B7F8-0001-4090-A300-290B774D96BB} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/ModelListBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 62 | 63 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/ModelListBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 62 | 63 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/wwwroot/loading-indicator.css: -------------------------------------------------------------------------------- 1 | @keyframes loading-indicator-ball-anim { 2 | 0% { 3 | transform: translate(-50%, -50%) scale(0); 4 | opacity: 0; 5 | } 6 | 7 | 25% { 8 | transform: translate(-50%, -50%) scale(1); 9 | opacity: 1; 10 | } 11 | 12 | 32% { 13 | transform: translate(-50%, -50%) scale(0.5); 14 | opacity: 0; 15 | } 16 | 17 | 100% { 18 | transform: translate(-50%, -50%) scale(0); 19 | opacity: 0; 20 | } 21 | } 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | .loading-indicator-wrapper { 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | width: 100vw; 32 | height: 100vh; 33 | background-color: #f0f0f0; /* Lighter background */ 34 | } 35 | 36 | .loading-indicator { 37 | position: relative; 38 | width: 80px; 39 | height: 80px; 40 | pointer-events: none; 41 | } 42 | 43 | .loading-indicator-ball { 44 | will-change: transform, opacity; 45 | position: absolute; 46 | width: 16%; 47 | height: 16%; 48 | border-radius: 50%; 49 | background: #3b8eea; /* Light blue */ 50 | filter: blur(3px); /* Slightly lighter blur */ 51 | opacity: 0; 52 | animation: loading-indicator-ball-anim 9s infinite; 53 | } 54 | 55 | .loading-indicator-ball:nth-child(1) { 56 | left: 85.3553390593%; 57 | top: 85.3553390593%; 58 | animation-delay: 0s; 59 | } 60 | 61 | .loading-indicator-ball:nth-child(2) { 62 | left: 100%; 63 | top: 50%; 64 | animation-delay: 0.2s; 65 | } 66 | 67 | .loading-indicator-ball:nth-child(3) { 68 | left: 85.3553390593%; 69 | top: 14.6446609407%; 70 | --rotation: calc(-45deg * 3); 71 | animation-delay: 0.4s; 72 | } 73 | 74 | .loading-indicator-ball:nth-child(4) { 75 | left: 50%; 76 | top: 0%; 77 | animation-delay: 0.6s; 78 | } 79 | 80 | .loading-indicator-ball:nth-child(5) { 81 | left: 14.6446609407%; 82 | top: 14.6446609407%; 83 | animation-delay: 0.8s; 84 | } 85 | 86 | .loading-indicator-ball:nth-child(6) { 87 | left: 0%; 88 | top: 50%; 89 | animation-delay: 1.0s; 90 | } 91 | 92 | .loading-indicator-ball:nth-child(7) { 93 | left: 14.6446609407%; 94 | top: 85.3553390593%; 95 | animation-delay: 1.2s; 96 | } 97 | 98 | .loading-indicator-ball:nth-child(8) { 99 | left: 50%; 100 | top: 100%; 101 | animation-delay: 1.4s; 102 | } 103 | 104 | .loading-indicator-ball:nth-child(9) { 105 | left: 50%; 106 | top: 100%; 107 | animation-delay: 4.5s; 108 | } 109 | 110 | .loading-indicator-ball:nth-child(10) { 111 | left: 14.6446609407%; 112 | top: 85.3553390593%; 113 | animation-delay: 4.7s; 114 | } 115 | 116 | .loading-indicator-ball:nth-child(11) { 117 | left: 0%; 118 | top: 50%; 119 | animation-delay: 4.9s; 120 | } 121 | 122 | .loading-indicator-ball:nth-child(12) { 123 | left: 14.6446609407%; 124 | top: 14.6446609407%; 125 | animation-delay: 5.1s; 126 | } 127 | 128 | .loading-indicator-ball:nth-child(13) { 129 | left: 50%; 130 | top: 0%; 131 | animation-delay: 5.3s; 132 | } 133 | 134 | .loading-indicator-ball:nth-child(14) { 135 | left: 85.3553390593%; 136 | top: 14.6446609407%; 137 | animation-delay: 5.5s; 138 | } 139 | 140 | .loading-indicator-ball:nth-child(15) { 141 | left: 100%; 142 | top: 50%; 143 | animation-delay: 5.7s; 144 | } 145 | 146 | .loading-indicator-ball:nth-child(16) { 147 | left: 85.3553390593%; 148 | top: 85.3553390593%; 149 | animation-delay: 5.9s; 150 | } 151 | 152 | .loading-indicator-text { 153 | display: flex; 154 | justify-content: center; 155 | align-items: center; 156 | width: 100%; 157 | height: 100%; 158 | } 159 | 160 | .loading-indicator-text:after { 161 | content: var(--blazor-load-percentage-text, "Loading..."); 162 | color: #555; /* Darker text for contrast */ 163 | font-size: 1.2rem; 164 | font-family: 'Arial', sans-serif; 165 | } 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ollama Manager 2 | 3 | A web-based Ollama model management application developed with OpenSilver. 4 | 5 | This project also includes a WPF desktop version migrated from the OpenSilver version, allowing developers to compare and learn from the development experience across both platforms. 6 | 7 | OllamaManager 8 | 9 | 10 | ## Table of Contents 11 | 12 | - [Screenshots](#screenshots) 13 | - [Key Features](#key-features) 14 | - [Project Structure](#project-structure) 15 | - [Tech Stack](#tech-stack) 16 | - [Server Architecture](#server-architecture) 17 | - [Getting Started](#getting-started) 18 | - [Development Roadmap](#development-roadmap) 19 | - [Contributing](#contributing) 20 | - [License](#license) 21 | - [Notes](#notes) 22 | 23 | ## Screenshots 24 | 25 | Manage Ollama models easily with an intuitive interface and chat with them in real-time. 26 | 27 | | Main Interface | Chat Interface | 28 | |----------------|----------------| 29 | | ![Main Interface](https://github.com/user-attachments/assets/8c3bcfc6-ae3f-4d58-9cce-f18285506f1c) | ![Chat Interface](https://github.com/user-attachments/assets/1daeb5bd-a1d9-4cd0-bc15-3fd779950a4b) | 30 | 31 | ## Key Features 32 | 33 | - View installed model list 34 | - Start/stop models 35 | - Real-time chat 36 | - Model status monitoring 37 | 38 | ## Project Structure 39 | 40 | ``` 41 | src/ 42 | ├── client-opensilver/ # OpenSilver web client 43 | ├── client-wpf/ # WPF desktop client 44 | └── server-minimalapi/ # Shared backend server 45 | ``` 46 | 47 | ## Tech Stack 48 | 49 | - **Web Client**: OpenSilver (.NET Standard 2.0) 50 | - **Desktop Client**: WPF (.NET 9.0) 51 | - **Backend**: ASP.NET Core Minimal API (.NET 9.0) 52 | - **Real-time Communication**: SignalR 53 | 54 | ## Server Architecture 55 | 56 | ### Minimal API Structure 57 | - **GET** `/api/models` - Retrieve installed models list and status 58 | - **POST** `/api/models/{modelName}/start` - Start model 59 | - **POST** `/api/models/{modelName}/stop` - Stop model 60 | - **POST** `/api/chat` - Chat with model 61 | 62 | ### Ollama API Integration 63 | The backend server manages models using the Ollama API: 64 | - `http://localhost:11434/api/tags` - Installed models list 65 | - `http://localhost:11434/api/ps` - Check running models 66 | - `http://localhost:11434/api/generate` - Model load/unload and chat 67 | 68 | ### Real-time Monitoring 69 | - Real-time model status updates via SignalR Hub 70 | - Background service for detecting model status changes 71 | 72 | ## Getting Started 73 | 74 | ### Prerequisites 75 | - **Ollama**: Install from [ollama.com](https://ollama.com) 76 | - **Model Installation**: Example: run `ollama pull llama3.2` 77 | - **Visual Studio 2022** 78 | - **.NET 9.0 SDK** 79 | - **WASM Tools**: `dotnet workload install wasm-tools` 80 | - **OpenSilver SDK**: Download `OpenSilver_SDK_v3.2.0.4.vsix` from [www.opensilver.net](https://www.opensilver.net) and install 81 | 82 | ### Server Execution 83 | 84 | First, run the backend server: 85 | ```bash 86 | cd src/server-minimalapi/LocalLLMServer 87 | dotnet run --launch-profile https 88 | ``` 89 | The server will run at `https://localhost:7262`. 90 | 91 | ### OpenSilver Web Client 92 | 93 | ```bash 94 | cd src/client-opensilver/OllamaHub.Browser 95 | dotnet run 96 | ``` 97 | 98 | Access via browser at `http://localhost:55592` 99 | 100 | ### WPF Version 101 | 102 | You can also run the WPF version using the same server: 103 | ```bash 104 | cd src/client-wpf/OllamaHub 105 | dotnet run 106 | ``` 107 | 108 | ## Development Roadmap 109 | 110 | ### Currently Implemented 111 | - Model list retrieval 112 | - Model control (start/stop) 113 | - Real-time chat 114 | 115 | ### Planned Features 116 | - Model downloads 117 | - Model deletion 118 | - Multi-session chat 119 | - Performance monitoring 120 | 121 | ## Contributing 122 | 123 | 1. Fork the repository 124 | 2. Create a feature branch 125 | 3. Commit your changes 126 | 4. Create a Pull Request 127 | 128 | ## License 129 | 130 | MIT License 131 | 132 | ## Notes 133 | 134 | - This is an excellent example for verifying code compatibility between WPF and OpenSilver 135 | - OpenSilver is based on .NET Standard 2.0 and provides a WPF-like development experience on the web 136 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/AIMessageListBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 82 | 83 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/ApplicationHeader.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 74 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/UI/Units/GlobalStatusBadge.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class GlobalStatusBadge : Control 7 | { 8 | public GlobalStatusBadge() 9 | { 10 | DefaultStyleKey = typeof(GlobalStatusBadge); 11 | } 12 | 13 | public static readonly DependencyProperty CornerRadiusProperty = 14 | DependencyProperty.Register( 15 | "CornerRadius", 16 | typeof(CornerRadius), 17 | typeof(GlobalStatusBadge), 18 | new PropertyMetadata(new CornerRadius(0))); 19 | 20 | 21 | 22 | public static readonly DependencyProperty StatusProperty = 23 | DependencyProperty.Register( 24 | nameof(Status), 25 | typeof(GlobalStatus), 26 | typeof(GlobalStatusBadge), 27 | new PropertyMetadata(GlobalStatus.NoModelsRunning, OnStatusChanged)); 28 | 29 | public CornerRadius CornerRadius 30 | { 31 | get { return (CornerRadius)GetValue(CornerRadiusProperty); } 32 | set { SetValue(CornerRadiusProperty, value); } 33 | } 34 | 35 | public GlobalStatus Status 36 | { 37 | get => (GlobalStatus)GetValue(StatusProperty); 38 | set => SetValue(StatusProperty, value); 39 | } 40 | 41 | public static readonly DependencyProperty ModelNameProperty = 42 | DependencyProperty.Register( 43 | nameof(ModelName), 44 | typeof(string), 45 | typeof(GlobalStatusBadge), 46 | new PropertyMetadata(string.Empty, OnModelNameChanged)); 47 | 48 | public string ModelName 49 | { 50 | get => (string)GetValue(ModelNameProperty); 51 | set => SetValue(ModelNameProperty, value); 52 | } 53 | 54 | public static readonly DependencyProperty RunningCountProperty = 55 | DependencyProperty.Register( 56 | nameof(RunningCount), 57 | typeof(int), 58 | typeof(GlobalStatusBadge), 59 | new PropertyMetadata(0, OnRunningCountChanged)); 60 | 61 | public int RunningCount 62 | { 63 | get => (int)GetValue(RunningCountProperty); 64 | set => SetValue(RunningCountProperty, value); 65 | } 66 | 67 | private static void OnStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 68 | { 69 | if (d is GlobalStatusBadge badge) 70 | { 71 | badge.UpdateVisualState(); 72 | } 73 | } 74 | 75 | private static void OnModelNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 76 | { 77 | if (d is GlobalStatusBadge badge) 78 | { 79 | badge.UpdateVisualState(); 80 | } 81 | } 82 | 83 | private static void OnRunningCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 84 | { 85 | if (d is GlobalStatusBadge badge) 86 | { 87 | badge.UpdateVisualState(); 88 | } 89 | } 90 | 91 | private void UpdateVisualState() 92 | { 93 | var state = Status.ToString(); 94 | VisualStateManager.GoToState(this, state, true); 95 | 96 | UpdateStatusText(); 97 | } 98 | 99 | private void UpdateStatusText() 100 | { 101 | if (GetTemplateChild("StatusText") is TextBlock statusText) 102 | { 103 | switch (Status) 104 | { 105 | case GlobalStatus.NoModelsRunning: 106 | statusText.Text = "No models running"; 107 | break; 108 | case GlobalStatus.SingleModelRunning: 109 | statusText.Text = string.IsNullOrEmpty(ModelName) ? "Model running" : $"{ModelName} Running"; 110 | break; 111 | case GlobalStatus.MultipleModelsRunning: 112 | statusText.Text = $"{RunningCount} models running"; 113 | break; 114 | case GlobalStatus.SystemLoading: 115 | statusText.Text = "System loading"; 116 | break; 117 | case GlobalStatus.SystemError: 118 | statusText.Text = "System error"; 119 | break; 120 | case GlobalStatus.SystemIdle: 121 | statusText.Text = "System idle"; 122 | break; 123 | case GlobalStatus.AllModelsDownloading: 124 | statusText.Text = "Downloading models"; 125 | break; 126 | } 127 | } 128 | } 129 | 130 | public override void OnApplyTemplate() 131 | { 132 | base.OnApplyTemplate(); 133 | UpdateVisualState(); 134 | UpdateStatusText(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/OllamaHub.Support.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | false 6 | true 7 | 12 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Designer 50 | MSBuild:Compile 51 | 52 | 53 | Designer 54 | MSBuild:Compile 55 | 56 | 57 | Designer 58 | MSBuild:Compile 59 | 60 | 61 | Designer 62 | MSBuild:Compile 63 | 64 | 65 | Designer 66 | MSBuild:Compile 67 | 68 | 69 | Designer 70 | MSBuild:Compile 71 | 72 | 73 | Designer 74 | MSBuild:Compile 75 | 76 | 77 | Designer 78 | MSBuild:Compile 79 | 80 | 81 | Designer 82 | MSBuild:Compile 83 | 84 | 85 | Designer 86 | MSBuild:Compile 87 | 88 | 89 | Designer 90 | MSBuild:Compile 91 | 92 | 93 | Designer 94 | MSBuild:Compile 95 | 96 | 97 | Designer 98 | MSBuild:Compile 99 | 100 | 101 | Designer 102 | MSBuild:Compile 103 | 104 | 105 | MSBuild:Compile 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/UI/Units/GlobalStatusBadge.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace OllamaHub.Support.UI.Units; 5 | 6 | public class GlobalStatusBadge : Control 7 | { 8 | public GlobalStatusBadge() 9 | { 10 | DefaultStyleKey = typeof(GlobalStatusBadge); 11 | } 12 | 13 | public static readonly DependencyProperty CornerRadiusProperty = 14 | DependencyProperty.Register( 15 | "CornerRadius", 16 | typeof(CornerRadius), 17 | typeof(GlobalStatusBadge), 18 | new PropertyMetadata(new CornerRadius(0))); 19 | 20 | 21 | 22 | public static readonly DependencyProperty StatusProperty = 23 | DependencyProperty.Register( 24 | nameof(Status), 25 | typeof(GlobalStatus), 26 | typeof(GlobalStatusBadge), 27 | new PropertyMetadata(GlobalStatus.NoModelsRunning, OnStatusChanged)); 28 | 29 | public CornerRadius CornerRadius 30 | { 31 | get { return (CornerRadius)GetValue(CornerRadiusProperty); } 32 | set { SetValue(CornerRadiusProperty, value); } 33 | } 34 | 35 | public GlobalStatus Status 36 | { 37 | get => (GlobalStatus)GetValue(StatusProperty); 38 | set => SetValue(StatusProperty, value); 39 | } 40 | 41 | public static readonly DependencyProperty ModelNameProperty = 42 | DependencyProperty.Register( 43 | nameof(ModelName), 44 | typeof(string), 45 | typeof(GlobalStatusBadge), 46 | new PropertyMetadata(string.Empty, OnModelNameChanged)); 47 | 48 | public string ModelName 49 | { 50 | get => (string)GetValue(ModelNameProperty); 51 | set => SetValue(ModelNameProperty, value); 52 | } 53 | 54 | public static readonly DependencyProperty RunningCountProperty = 55 | DependencyProperty.Register( 56 | nameof(RunningCount), 57 | typeof(int), 58 | typeof(GlobalStatusBadge), 59 | new PropertyMetadata(0, OnRunningCountChanged)); 60 | 61 | public int RunningCount 62 | { 63 | get => (int)GetValue(RunningCountProperty); 64 | set => SetValue(RunningCountProperty, value); 65 | } 66 | 67 | private static void OnStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 68 | { 69 | if (d is GlobalStatusBadge badge) 70 | { 71 | badge.UpdateVisualState(); 72 | } 73 | } 74 | 75 | private static void OnModelNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 76 | { 77 | if (d is GlobalStatusBadge badge) 78 | { 79 | badge.UpdateVisualState(); 80 | } 81 | } 82 | 83 | private static void OnRunningCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 84 | { 85 | if (d is GlobalStatusBadge badge) 86 | { 87 | badge.UpdateVisualState(); 88 | } 89 | } 90 | 91 | private void UpdateVisualState() 92 | { 93 | var state = Status.ToString(); 94 | VisualStateManager.GoToState(this, state, true); 95 | 96 | UpdateStatusText(); 97 | } 98 | 99 | private void UpdateStatusText() 100 | { 101 | if (GetTemplateChild("StatusText") is TextBlock statusText) 102 | { 103 | switch (Status) 104 | { 105 | case GlobalStatus.NoModelsRunning: 106 | statusText.Text = "No models running"; 107 | break; 108 | case GlobalStatus.SingleModelRunning: 109 | statusText.Text = string.IsNullOrEmpty(ModelName) ? "Model running" : $"{ModelName} Running"; 110 | break; 111 | case GlobalStatus.MultipleModelsRunning: 112 | statusText.Text = $"{RunningCount} models running"; 113 | break; 114 | case GlobalStatus.SystemLoading: 115 | statusText.Text = "System loading"; 116 | break; 117 | case GlobalStatus.SystemError: 118 | statusText.Text = "System error"; 119 | break; 120 | case GlobalStatus.SystemIdle: 121 | statusText.Text = "System idle"; 122 | break; 123 | case GlobalStatus.AllModelsDownloading: 124 | statusText.Text = "Downloading models"; 125 | break; 126 | } 127 | } 128 | } 129 | 130 | public override void OnApplyTemplate() 131 | { 132 | base.OnApplyTemplate(); 133 | UpdateVisualState(); 134 | UpdateStatusText(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/NavigationRadioButton.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 71 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/wwwroot/modern/loading-animation.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let lastAnimationTime = 0; 3 | const ANIMATION_THROTTLE_DELAY = 150; // Minimum time between animations 4 | const ANIMATION_DURATION = 150; // Total animation duration 5 | 6 | const odometerClass = "opensilver-odometer"; 7 | 8 | function startLoader() { 9 | const count = document.querySelectorAll("." + odometerClass); 10 | const loader = document.querySelector(".opensilver-loader-progress-bar"); 11 | const loaderProgress = document.querySelector(".opensilver-loader-progress"); 12 | if (!count || !loader) return; 13 | 14 | loader.style.width = "0%"; 15 | const observer = new MutationObserver(updateCount); 16 | 17 | function updateCount() { 18 | const loadPercentageText = getComputedStyle(document.documentElement) 19 | .getPropertyValue("--blazor-load-percentage-text") 20 | .trim(); 21 | const loadPercentage = parseInt(loadPercentageText.replace(/"/g, "")); 22 | const currentValue = isNaN(loadPercentage) ? 0 : loadPercentage; 23 | 24 | // Always animate 100 regardless of throttling 25 | if (currentValue === 100) { 26 | animateCounter(currentValue, true); 27 | loader.style.width = "100%"; 28 | loaderProgress.style["border-right"] = "none"; 29 | observer.disconnect(); 30 | return; 31 | } 32 | 33 | // Throttle animations to prevent too frequent updates 34 | const now = Date.now(); 35 | if (now - lastAnimationTime >= ANIMATION_THROTTLE_DELAY) { 36 | animateCounter(currentValue); 37 | lastAnimationTime = now; 38 | } 39 | 40 | loader.style.width = currentValue + "%"; 41 | } 42 | 43 | observer.observe(document.documentElement, { 44 | attributes: true, 45 | attributeFilter: ["style"], 46 | }); 47 | updateCount(); 48 | } 49 | 50 | function animateCounter(newValue, force = false) { 51 | const odometers = Array.from(document.querySelectorAll("." + odometerClass)); 52 | const newValueString = String(newValue).padStart(3, "0"); 53 | 54 | for (let index = odometers.length - 1; index > -1; index--) { 55 | const element = odometers[index]; 56 | 57 | if (force) { 58 | const finalOdometer = document.createElement("div"); 59 | finalOdometer.textContent = newValueString[index]; 60 | finalOdometer.classList.add(odometerClass); 61 | element.replaceWith(finalOdometer); 62 | continue; 63 | } 64 | 65 | if (element.textContent === "") { 66 | element.textContent = "0"; 67 | } 68 | const currentValue = element.textContent || "0"; 69 | 70 | if (newValueString[index] !== currentValue) { 71 | element.style.transition = `transform ${ANIMATION_DURATION}ms, opacity ${ANIMATION_DURATION}ms`; 72 | element.style.transform = "translateY(-4px)"; 73 | element.style.opacity = "0.5"; 74 | 75 | setTimeout(() => { 76 | element.textContent = newValueString[index]; 77 | element.style.transform = "translateY(4px)"; 78 | element.style.opacity = "0.5"; 79 | 80 | setTimeout(() => { 81 | element.style.transform = "translateY(0)"; 82 | element.style.opacity = "1"; 83 | }, ANIMATION_DURATION / 2); 84 | }, ANIMATION_DURATION / 2); 85 | } 86 | }; 87 | } 88 | 89 | function onDomReady() { 90 | startLoader(); 91 | startAnimations(); 92 | } 93 | 94 | function startAnimations() { 95 | const loaderProgress = document.querySelector(".opensilver-loader-progress"); 96 | const counterContainer = document.querySelector(".opensilver-counter-container"); 97 | 98 | if (loaderProgress) { 99 | loaderProgress.style.transition = "width 1.25s, opacity 1.25s"; 100 | loaderProgress.style.width = "60vw"; 101 | loaderProgress.style.opacity = "1"; 102 | } 103 | 104 | if (counterContainer) { 105 | counterContainer.style.transition = "opacity 0.3s"; 106 | counterContainer.style.opacity = "1"; 107 | } 108 | } 109 | 110 | if (document.readyState === 'loading') { 111 | document.addEventListener('DOMContentLoaded', onDomReady); 112 | } else { 113 | onDomReady(); 114 | } 115 | })(); -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/ChatListBox.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 92 | 93 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Support/Themes/Units/AIMessageListBoxItem.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 75 | 76 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/ApplicationHeader.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 95 | -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Browser/wwwroot/libs/cshtml5.css: -------------------------------------------------------------------------------- 1 | 2 | /*=================================================================================== 3 | * 4 | * Copyright (c) Userware (OpenSilver.net, CSHTML5.com) 5 | * 6 | * This file is part of both the OpenSilver Runtime (https://opensilver.net), which 7 | * is licensed under the MIT license (https://opensource.org/licenses/MIT), and the 8 | * CSHTML5 Runtime (http://cshtml5.com), which is dual-licensed (MIT + commercial). 9 | * 10 | * As stated in the MIT license, "the above copyright notice and this permission 11 | * notice shall be included in all copies or substantial portions of the Software." 12 | * 13 | \*====================================================================================*/ 14 | 15 | * { 16 | -webkit-tap-highlight-color: rgba(0,0,0,0); 17 | } 18 | 19 | html { 20 | height: 100%; 21 | width: 100%; 22 | margin: 0px; 23 | } 24 | 25 | body { 26 | background-color: white; 27 | margin: 0px; 28 | padding: 0px; 29 | height: 100%; 30 | width: 100%; 31 | font-size: 11px; 32 | overflow-x: hidden; 33 | overflow-y: hidden; 34 | cursor: default; 35 | font-family: 'Segoe UI', Verdana, 'DejaVu Sans', Lucida, 'MS Sans Serif', sans-serif; 36 | -webkit-touch-callout: none; /* prevents callout to copy image, etc when tap to hold */ 37 | -webkit-text-size-adjust: none; /* prevents webkit from resizing text to fit */ 38 | -webkit-user-select: text; /* 'none' prevents copy paste. 'text' allows it. */ 39 | } 40 | 41 | .opensilver-pointer-captured { 42 | -moz-user-select: none; /* Firefox */ 43 | -webkit-user-select: none; /* Chrome, Safari, and Opera */ 44 | -ms-user-select: none; /* Internet Explorer/Edge */ 45 | user-select: none; 46 | } 47 | 48 | .opensilver-root-element { 49 | touch-action: none; /* prevents the browser from cancelling pointermove events */ 50 | } 51 | 52 | .uielement-collapsed { 53 | display: none !important; 54 | } 55 | 56 | .uielement-unarranged { 57 | opacity: 0 !important; 58 | } 59 | 60 | .opensilver-hyperlink { 61 | cursor: pointer; 62 | } 63 | 64 | .opensilver-hyperlink:hover { 65 | color: var(--mouse-over-color, rgb(237, 110, 0)) !important; 66 | text-decoration: var(--mouse-over-decoration, underline) !important; 67 | } 68 | 69 | .opensilver-uielement { 70 | position: absolute; 71 | box-sizing: border-box; 72 | pointer-events: none; 73 | z-index: 0; 74 | outline: none; 75 | transform-origin: 0% 0%; 76 | } 77 | 78 | .opensilver-shape { 79 | pointer-events: none !important; 80 | fill: none; /* CSS default value is black */ 81 | fill-rule: evenodd; /* CSS default value is nonzero, and evenodd is the default value of every shape */ 82 | stroke: none; /* CSS default value is black */ 83 | stroke-miterlimit: 10; /* CSS default value is 4 */ 84 | } 85 | 86 | .opensilver-border { 87 | border-style: solid; 88 | border-color: transparent; /* CSS default value matches the color property (currentcolor) */ 89 | border-width: 0px; /* CSS default value is medium */ 90 | background-clip: padding-box !important; 91 | } 92 | 93 | .opensilver-window { 94 | width: 100%; 95 | height: 100%; 96 | overflow: hidden; 97 | transform-origin: 0% 0%; 98 | } 99 | 100 | .opensilver-popup { 101 | position: absolute; 102 | width: 100%; 103 | height: 100%; 104 | overflow: clip; 105 | z-index: 2147483647; 106 | } 107 | 108 | .opensilver-inkpresenter { 109 | width: 100%; 110 | height: 100%; 111 | position: absolute; 112 | pointer-events: none; 113 | } 114 | 115 | .opensilver-textblock { 116 | text-overflow: ellipsis; 117 | text-align: start; 118 | white-space: pre; 119 | overflow: visible; 120 | } 121 | 122 | .opensilver-inline { 123 | display: inline; 124 | line-height: var(--line-stacking-strategy, normal); 125 | } 126 | 127 | .opensilver-block { 128 | display: block; 129 | box-sizing: border-box; 130 | border-style: solid; 131 | border-color: transparent; /* CSS default value matches the color property (currentcolor) */ 132 | border-width: 0px; /* CSS default value is medium */ 133 | background-clip: padding-box !important; 134 | } 135 | 136 | .opensilver-textboxview { 137 | font-size: inherit; 138 | font-family: inherit; 139 | color: inherit; 140 | letter-spacing: inherit; 141 | border: none; 142 | background: transparent; 143 | padding: 0; 144 | resize: none; 145 | cursor: text; 146 | overflow: hidden; 147 | tab-size: 4; 148 | } 149 | 150 | .opensilver-textboxview:focus::selection { 151 | color: var(--selection-color, HighlightText); 152 | background-color: var(--selection-bg-color, Highlight); 153 | } 154 | 155 | .opensilver-passwordboxview { 156 | font-size: inherit; 157 | font-family: inherit; 158 | color: inherit; 159 | letter-spacing: inherit; 160 | border: none; 161 | background: transparent; 162 | padding: 0; 163 | } 164 | 165 | .opensilver-passwordboxview:focus::selection { 166 | color: var(--selection-color, HighlightText); 167 | background-color: var(--selection-bg-color, Highlight); 168 | } 169 | 170 | /* Media query used for printing (cf. "CSHTML5.Native.Html.Printing.PrintManager") */ 171 | @media print { 172 | body { 173 | -webkit-print-color-adjust: exact; 174 | } 175 | 176 | body * { 177 | visibility: hidden; 178 | } 179 | 180 | .section-to-print, .section-to-print * { 181 | visibility: visible; 182 | } 183 | 184 | .section-to-print { 185 | position: fixed; 186 | left: 0; 187 | top: 0; 188 | height: 100%; 189 | width: 100%; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/NavigationRadioButton.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 88 | 89 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Main/Local/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using Jamesnet.Foundation; 2 | using Microsoft.AspNetCore.SignalR.Client; 3 | using OllamaHub.Support.Local.Models; 4 | using OllamaHub.Support.Local.Services; 5 | using System.Collections.ObjectModel; 6 | using System.Windows.Input; 7 | using System.Linq; 8 | using System.Windows; 9 | 10 | namespace OllamaHub.Main.Local.ViewModels; 11 | 12 | public partial class MainViewModel : ViewModelBase 13 | { 14 | private readonly ApiClient _apiClient; 15 | private readonly HubConnection _hubConnection; 16 | 17 | private ObservableCollection _models; 18 | private ObservableCollection _runningModels; 19 | private bool _isLoading; 20 | private ObservableCollection _chatMessages; 21 | private string _inputText; 22 | private bool _isChatLoading; 23 | private ModelItem _currentModel; 24 | 25 | public ObservableCollection Models 26 | { 27 | get => _models; 28 | set => SetProperty(ref _models, value); 29 | } 30 | 31 | public ObservableCollection RunningModels 32 | { 33 | get => _runningModels; 34 | set => SetProperty(ref _runningModels, value); 35 | } 36 | 37 | public bool IsLoading 38 | { 39 | get => _isLoading; 40 | set => SetProperty(ref _isLoading, value); 41 | } 42 | 43 | public ObservableCollection ChatMessages 44 | { 45 | get => _chatMessages; 46 | set => SetProperty(ref _chatMessages, value); 47 | } 48 | 49 | public string InputText 50 | { 51 | get => _inputText; 52 | set => SetProperty(ref _inputText, value); 53 | } 54 | 55 | public bool IsChatLoading 56 | { 57 | get => _isChatLoading; 58 | set => SetProperty(ref _isChatLoading, value); 59 | } 60 | 61 | public ModelItem CurrentModel 62 | { 63 | get => _currentModel; 64 | set => SetProperty(ref _currentModel, value); 65 | } 66 | 67 | public ICommand LoadModelsCommand { get; } 68 | public ICommand SendMessageCommand { get; } 69 | public ICommand ToggleModelCommand { get; } 70 | 71 | public MainViewModel( 72 | ApiClient apiClient, 73 | HubConnection hubConnection) 74 | { 75 | Models = []; 76 | RunningModels = []; 77 | ChatMessages = []; 78 | InputText = ""; 79 | IsLoading = false; 80 | IsChatLoading = false; 81 | CurrentModel = null; 82 | 83 | _apiClient = apiClient; 84 | _hubConnection = hubConnection; 85 | 86 | LoadModelsCommand = new RelayCommand(async () => await LoadModelsAsync()); 87 | SendMessageCommand = new RelayCommand(async () => await SendMessageAsync()); 88 | ToggleModelCommand = new RelayCommand(OnPlayStop); 89 | 90 | _ = InitializeAsync(); 91 | } 92 | 93 | private async Task InitializeAsync() 94 | { 95 | await LoadModelsAsync(); 96 | await ConnectToSignalRAsync(); 97 | } 98 | 99 | private async Task LoadModelsAsync() 100 | { 101 | IsLoading = true; 102 | 103 | var modelList = await _apiClient.GetAsync("https://localhost:7262/api/models"); 104 | 105 | Models = new ObservableCollection(modelList); 106 | RunningModels = new ObservableCollection(modelList.Where(m => m.Status == "Running")); 107 | CurrentModel = RunningModels.FirstOrDefault(); 108 | 109 | IsLoading = false; 110 | } 111 | 112 | private async Task SendMessageAsync() 113 | { 114 | if (string.IsNullOrWhiteSpace(InputText) || IsChatLoading) return; 115 | 116 | var userMessage = InputText.Trim(); 117 | InputText = ""; 118 | 119 | ChatMessages.Add(new UserMessage 120 | { 121 | Content = userMessage, 122 | Timestamp = DateTime.Now, 123 | }); 124 | 125 | IsChatLoading = true; 126 | await _apiClient.PostAsync("https://localhost:7262/api/chat", new 127 | { 128 | message = userMessage, 129 | model = CurrentModel?.Name 130 | }); 131 | } 132 | 133 | private async Task ConnectToSignalRAsync() 134 | { 135 | _hubConnection.On("ModelStatusChanged", (modelName, status) => 136 | { 137 | var model = Models.FirstOrDefault(m => m.Name == modelName); 138 | if (model != null) 139 | { 140 | model.Status = status; 141 | 142 | RunningModels = new ObservableCollection(Models.Where(m => m.Status == "Running")); 143 | 144 | if (CurrentModel?.Status != "Running") 145 | { 146 | CurrentModel = RunningModels.FirstOrDefault(); 147 | } 148 | } 149 | }); 150 | 151 | _hubConnection.On("ChatMessageReceived", (message) => 152 | { 153 | if (!string.IsNullOrEmpty(message)) 154 | { 155 | Application.Current.Dispatcher.BeginInvoke(() => 156 | { 157 | ChatMessages.Add(new AIMessage 158 | { 159 | Content = message, 160 | Timestamp = DateTime.Now 161 | }); 162 | }); 163 | } 164 | IsChatLoading = false; 165 | }); 166 | 167 | _hubConnection.Closed += async (error) => 168 | { 169 | await Task.Delay(new Random().Next(0, 5) * 1000); 170 | await _hubConnection.StartAsync(); 171 | }; 172 | 173 | _hubConnection.Reconnecting += (error) => Task.CompletedTask; 174 | _hubConnection.Reconnected += (connectionId) => Task.CompletedTask; 175 | 176 | await _hubConnection.StartAsync(); 177 | } 178 | 179 | private async void OnPlayStop(ModelItem model) 180 | { 181 | if (model == null) return; 182 | 183 | var action = model.Status == "Running" ? "stop" : "start"; 184 | model.Status = model.Status == "Running" ? "Stopping" : "Starting"; 185 | await _apiClient.PostAsync($"https://localhost:7262/api/models/{model.Name}/{action}"); 186 | } 187 | 188 | public async Task DisconnectAsync() 189 | { 190 | if (_hubConnection != null) 191 | { 192 | await _hubConnection.DisposeAsync(); 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /src/client-opensilver/OllamaHub.Main/Local/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Threading.Tasks; 4 | using System.Linq; 5 | using System.Windows.Input; 6 | using OllamaHub.Support.Local.Services; 7 | using Microsoft.AspNetCore.SignalR.Client; 8 | using OllamaHub.Support.Local.Models; 9 | using Jamesnet.Foundation; 10 | 11 | namespace OllamaHub.Main.Local.ViewModels; 12 | 13 | public class MainViewModel : ViewModelBase, IDisposable 14 | { 15 | private readonly ApiClient _apiClient; 16 | private readonly HubConnection _hubConnection; 17 | 18 | private ObservableCollection _models; 19 | private ObservableCollection _runningModels; 20 | private bool _isLoading; 21 | private ObservableCollection _chatMessages; 22 | private string _inputText; 23 | private bool _isChatLoading; 24 | private ModelItem _currentModel; 25 | 26 | public ObservableCollection Models 27 | { 28 | get => _models; 29 | set => SetProperty(ref _models, value); 30 | } 31 | 32 | public ObservableCollection RunningModels 33 | { 34 | get => _runningModels; 35 | set => SetProperty(ref _runningModels, value); 36 | } 37 | 38 | public bool IsLoading 39 | { 40 | get => _isLoading; 41 | set => SetProperty(ref _isLoading, value); 42 | } 43 | 44 | public ObservableCollection ChatMessages 45 | { 46 | get => _chatMessages; 47 | set => SetProperty(ref _chatMessages, value); 48 | } 49 | 50 | public string InputText 51 | { 52 | get => _inputText; 53 | set => SetProperty(ref _inputText, value); 54 | } 55 | 56 | public bool IsChatLoading 57 | { 58 | get => _isChatLoading; 59 | set => SetProperty(ref _isChatLoading, value); 60 | } 61 | 62 | public ModelItem CurrentModel 63 | { 64 | get => _currentModel; 65 | set => SetProperty(ref _currentModel, value); 66 | } 67 | 68 | public ICommand LoadModelsCommand { get; set; } 69 | public ICommand SendMessageCommand { get; set; } 70 | public ICommand ToggleModelCommand { get; set; } 71 | 72 | public MainViewModel( 73 | ApiClient apiClient, 74 | HubConnection hubConnection) 75 | { 76 | Models = []; 77 | RunningModels = []; 78 | ChatMessages = []; 79 | InputText = ""; 80 | IsLoading = false; 81 | IsChatLoading = false; 82 | CurrentModel = null; 83 | 84 | _apiClient = apiClient; 85 | _hubConnection = hubConnection; 86 | 87 | LoadModelsCommand = new RelayCommand(async () => await LoadModelsAsync()); 88 | SendMessageCommand = new RelayCommand(async () => await SendMessageAsync()); 89 | ToggleModelCommand = new RelayCommand(async (model) => await ToggleModelAsync(model)); 90 | 91 | _ = InitializeAsync(); 92 | } 93 | 94 | private async Task InitializeAsync() 95 | { 96 | await LoadModelsAsync(); 97 | await ConnectToSignalRAsync(); 98 | } 99 | 100 | private async Task LoadModelsAsync() 101 | { 102 | IsLoading = true; 103 | try 104 | { 105 | var modelList = await _apiClient.GetAsync("models"); 106 | Models.Clear(); 107 | foreach (var model in modelList) 108 | { 109 | Models.Add(model); 110 | } 111 | UpdateRunningModels(); 112 | CurrentModel = RunningModels.FirstOrDefault(); 113 | } 114 | catch (Exception ex) 115 | { 116 | Console.WriteLine($"Load models error: {ex.Message}"); 117 | } 118 | finally 119 | { 120 | IsLoading = false; 121 | } 122 | } 123 | 124 | private async Task SendMessageAsync() 125 | { 126 | if (string.IsNullOrWhiteSpace(InputText) || IsChatLoading) return; 127 | 128 | var userMessage = InputText.Trim(); 129 | InputText = ""; 130 | 131 | ChatMessages.Add(new UserMessage 132 | { 133 | Content = userMessage, 134 | Timestamp = DateTime.Now, 135 | }); 136 | 137 | IsChatLoading = true; 138 | try 139 | { 140 | await _apiClient.PostAsync("chat", new 141 | { 142 | message = userMessage, 143 | model = CurrentModel?.Name 144 | }); 145 | } 146 | catch (Exception ex) 147 | { 148 | Console.WriteLine($"Send message error: {ex.Message}"); 149 | IsChatLoading = false; 150 | } 151 | } 152 | 153 | private async Task ConnectToSignalRAsync() 154 | { 155 | try 156 | { 157 | _hubConnection.On("ModelStatusChanged", (modelName, status) => 158 | { 159 | var model = Models.FirstOrDefault(m => m.Name == modelName); 160 | if (model != null) 161 | { 162 | model.Status = status; 163 | UpdateRunningModels(); 164 | if (CurrentModel?.Status != "Running") 165 | { 166 | CurrentModel = RunningModels.FirstOrDefault(); 167 | } 168 | } 169 | }); 170 | 171 | _hubConnection.On("ChatMessageReceived", (message) => 172 | { 173 | if (!string.IsNullOrEmpty(message)) 174 | { 175 | ChatMessages.Add(new AIMessage 176 | { 177 | Content = message, 178 | Timestamp = DateTime.Now 179 | }); 180 | } 181 | IsChatLoading = false; 182 | }); 183 | 184 | _hubConnection.Closed += async (error) => 185 | { 186 | await Task.Delay(new Random().Next(0, 5) * 1000); 187 | await _hubConnection.StartAsync(); 188 | }; 189 | 190 | _hubConnection.Reconnecting += (error) => Task.CompletedTask; 191 | _hubConnection.Reconnected += (connectionId) => Task.CompletedTask; 192 | 193 | await _hubConnection.StartAsync(); 194 | } 195 | catch (Exception ex) 196 | { 197 | Console.WriteLine($"SignalR connection error: {ex.Message}"); 198 | } 199 | } 200 | 201 | private async Task ToggleModelAsync(ModelItem model) 202 | { 203 | if (model == null) return; 204 | 205 | var action = model.Status == "Running" ? "stop" : "start"; 206 | model.Status = model.Status == "Running" ? "Stopping" : "Starting"; 207 | try 208 | { 209 | await _apiClient.PostAsync($"models/{model.Name}/{action}"); 210 | } 211 | catch (Exception ex) 212 | { 213 | Console.WriteLine($"Toggle model error: {ex.Message}"); 214 | } 215 | } 216 | 217 | private void UpdateRunningModels() 218 | { 219 | var running = Models.Where(m => m.Status == "Running").ToList(); 220 | RunningModels.Clear(); 221 | foreach (var model in running) 222 | { 223 | RunningModels.Add(model); 224 | } 225 | } 226 | 227 | public async Task DisconnectAsync() 228 | { 229 | if (_hubConnection != null) 230 | { 231 | await _hubConnection.DisposeAsync(); 232 | } 233 | } 234 | 235 | public void Dispose() 236 | { 237 | _apiClient.Dispose(); 238 | DisconnectAsync().GetAwaiter().GetResult(); 239 | } 240 | } -------------------------------------------------------------------------------- /src/server-minimalapi/LocalLLMServer/Endpoint/EndPointExtension.cs: -------------------------------------------------------------------------------- 1 | using LocalLLMServer.Endpoint.Dto; 2 | using LocalLLMServer.Model.Entity; 3 | using LocalLLMServer.SignalRHub; 4 | using Microsoft.AspNetCore.SignalR; 5 | using System.Diagnostics; 6 | using System.Text.Json; 7 | 8 | namespace LocalLLMServer.Endpoint; 9 | 10 | public static class EndPointExtension 11 | { 12 | public static WebApplication AddChatEndPoints(this WebApplication app) 13 | { 14 | app.MapPost("/api/chat", async (ChatRequest request, IHubContext hubContext, IHttpClientFactory httpClientFactory) => 15 | { 16 | if (!await CheckModelRunning(request.Model)) 17 | { 18 | await hubContext.Clients.All.SendAsync("ChatMessageReceived", $"모델 {request.Model}을 시작하는 중입니다..."); 19 | 20 | var startProcess = new Process 21 | { 22 | StartInfo = new ProcessStartInfo 23 | { 24 | FileName = "ollama", 25 | Arguments = $"run {request.Model}", 26 | RedirectStandardOutput = true, 27 | UseShellExecute = false, 28 | CreateNoWindow = true 29 | } 30 | }; 31 | startProcess.Start(); 32 | await startProcess.WaitForExitAsync(); 33 | 34 | await Task.Delay(3000); 35 | 36 | if (!await CheckModelRunning(request.Model)) 37 | { 38 | await hubContext.Clients.All.SendAsync("ChatMessageReceived", $"모델 {request.Model}을 시작할 수 없습니다."); 39 | return Results.BadRequest(new { success = false }); 40 | } 41 | } 42 | 43 | var client = httpClientFactory.CreateClient("Ollama"); 44 | var content = new StringContent( 45 | JsonSerializer.Serialize(new { model = request.Model, prompt = request.Message, stream = false }), 46 | System.Text.Encoding.UTF8, "application/json"); 47 | 48 | var response = await client.PostAsync("api/generate", content); 49 | var responseContent = await response.Content.ReadAsStringAsync(); 50 | 51 | if (response.IsSuccessStatusCode) 52 | { 53 | var ollamaResponse = JsonSerializer.Deserialize(responseContent); 54 | var message = ollamaResponse?.response ?? "응답을 생성할 수 없습니다."; 55 | await hubContext.Clients.All.SendAsync("ChatMessageReceived", message); 56 | return Results.Ok(new { success = true, response = message }); 57 | } 58 | 59 | var testResponse = $"테스트 응답: '{request.Message}'에 대한 답변입니다."; 60 | await hubContext.Clients.All.SendAsync("ChatMessageReceived", testResponse); 61 | return Results.Ok(new { success = true, response = testResponse }); 62 | }); 63 | 64 | return app; 65 | 66 | } 67 | 68 | public static WebApplication AddModelEndPoints(this WebApplication app) 69 | { 70 | app.MapGet("/api/models", async () => 71 | { 72 | var listProcess = new Process 73 | { 74 | StartInfo = new ProcessStartInfo 75 | { 76 | FileName = "ollama", 77 | Arguments = "list", 78 | RedirectStandardOutput = true, 79 | UseShellExecute = false, 80 | CreateNoWindow = true 81 | } 82 | }; 83 | listProcess.Start(); 84 | var listOutput = await listProcess.StandardOutput.ReadToEndAsync(); 85 | await listProcess.WaitForExitAsync(); 86 | 87 | var psProcess = new Process 88 | { 89 | StartInfo = new ProcessStartInfo 90 | { 91 | FileName = "ollama", 92 | Arguments = "ps", 93 | RedirectStandardOutput = true, 94 | UseShellExecute = false, 95 | CreateNoWindow = true 96 | } 97 | }; 98 | psProcess.Start(); 99 | var psOutput = await psProcess.StandardOutput.ReadToEndAsync(); 100 | await psProcess.WaitForExitAsync(); 101 | 102 | var models = ParseOllamaModels(listOutput, psOutput); 103 | return Results.Ok(new { models, count = models.Count }); 104 | }); 105 | 106 | app.MapPost("/api/models/{modelName}/start", async (string modelName, IHubContext hubContext) => 107 | { 108 | var process = new Process 109 | { 110 | StartInfo = new ProcessStartInfo 111 | { 112 | FileName = "ollama", 113 | Arguments = $"run {modelName}", 114 | RedirectStandardOutput = true, 115 | UseShellExecute = false, 116 | CreateNoWindow = true 117 | } 118 | }; 119 | process.Start(); 120 | await process.WaitForExitAsync(); 121 | 122 | await Task.Delay(2000); 123 | var status = await CheckModelRunning(modelName) ? "Running" : "Error"; 124 | await hubContext.Clients.Group("ModelUpdates").SendAsync("ModelStatusChanged", modelName, status); 125 | 126 | return Results.Ok(new { success = true }); 127 | }); 128 | 129 | app.MapPost("/api/models/{modelName}/stop", async (string modelName, IHubContext hubContext, IHttpClientFactory httpClientFactory) => 130 | { 131 | var client = httpClientFactory.CreateClient("Ollama"); 132 | var content = new StringContent(JsonSerializer.Serialize(new { model = modelName, keep_alive = 0 }), System.Text.Encoding.UTF8, "application/json"); 133 | 134 | await client.PostAsync("api/generate", content); 135 | await Task.Delay(1000); 136 | 137 | var status = await CheckModelRunning(modelName) ? "Running" : "Stopped"; 138 | await hubContext.Clients.Group("ModelUpdates").SendAsync("ModelStatusChanged", modelName, status); 139 | 140 | return Results.Ok(new { success = true }); 141 | }); 142 | 143 | 144 | 145 | return app; 146 | } 147 | 148 | 149 | 150 | 151 | private static async Task CheckModelRunning(string modelName) 152 | { 153 | var process = new Process 154 | { 155 | StartInfo = new ProcessStartInfo 156 | { 157 | FileName = "ollama", 158 | Arguments = "ps", 159 | RedirectStandardOutput = true, 160 | UseShellExecute = false, 161 | CreateNoWindow = true 162 | } 163 | }; 164 | process.Start(); 165 | var output = await process.StandardOutput.ReadToEndAsync(); 166 | await process.WaitForExitAsync(); 167 | return output.Contains(modelName); 168 | } 169 | 170 | private static List ParseOllamaModels(string listOutput, string psOutput) 171 | { 172 | var runningModels = new HashSet(); 173 | 174 | foreach (var line in psOutput.Split('\n').Skip(1)) 175 | { 176 | var parts = line.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); 177 | if (parts.Length > 0) runningModels.Add(parts[0]); 178 | } 179 | 180 | var models = new List(); 181 | foreach (var line in listOutput.Split('\n').Skip(1)) 182 | { 183 | var parts = line.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); 184 | if (parts.Length >= 4) 185 | { 186 | models.Add(new OllamaModel 187 | { 188 | Name = parts[0], 189 | Size = $"{parts[2]} {parts[3]}", 190 | LastUsed = string.Join(" ", parts.Skip(4)), 191 | Status = runningModels.Contains(parts[0]) ? "Running" : "Stopped" 192 | }); 193 | } 194 | } 195 | return models; 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/client-wpf/OllamaHub.Support/Themes/Units/SendButton.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 147 | --------------------------------------------------------------------------------