diff --git a/ollamasharp/.gitignore b/ollamasharp/.gitignore new file mode 100644 index 00000000..2cb90536 --- /dev/null +++ b/ollamasharp/.gitignore @@ -0,0 +1,5 @@ +bin/ +obj/ +*.sln.iml +.idea/**/* +**/.idea/* \ No newline at end of file diff --git a/ollamasharp/Apps/OllamaSharp.cs b/ollamasharp/Apps/OllamaSharp.cs new file mode 100644 index 00000000..57f45eab --- /dev/null +++ b/ollamasharp/Apps/OllamaSharp.cs @@ -0,0 +1,108 @@ +using System.Text; +using OllamaSharp; +using Chat = Ivy.Chat; +using ChatMessage = Ivy.ChatMessage; + +namespace OllamaSharpDemo.Apps; + +[App(icon: Icons.TextQuote, title: "OllamaSharp")] +public class OllamaSharpSample : ViewBase +{ + private const string Url = "http://localhost:11434"; + private IState> _messages; + private IState> _models; + private IState _selectedModel; + private OllamaApiClient? _ollamaApiClient; + + public override object? Build() + { + _messages = UseState(ImmutableArray.Create()); + + _models = UseState(ImmutableArray.Create); + _selectedModel = UseState(); + + if (_selectedModel.Value == null) + return Layout.Center() + | (new Card( + Layout.Vertical().Gap(6).Padding(2) + | new Button("Refresh Models", onClick: _ => OnRefreshClicked()) + | Layout.Vertical(_models.Value) + ) + ) + .Width(Size.Units(120).Max(500)); + + return Layout.Vertical().Padding(0, 10, 0, 10) + | Text.H4($"Chatting with model: {_selectedModel.Value?.ModelName}") + | new Button("Back to Models", onClick: _ => + { + ClearModelSelection(); + }) + | new Chat(_messages.Value.ToArray(), OnSendMessage).Width(Size.Full().Max(200)); + } + + private async ValueTask OnSendMessage(Event @event) + { + if (_ollamaApiClient == null) + { + var clientWarn = UseService(); + clientWarn.Toast("Click 'Refresh Models' first to initialize the API client.", "Not Ready"); + return; + } + if (_selectedModel.Value == null) + { + var clientWarn = UseService(); + clientWarn.Toast("Select a model before chatting.", "Model Required"); + return; + } + _messages.Set(_messages.Value.Add(new ChatMessage(ChatSender.User, @event.Value))); + _ollamaApiClient.SelectedModel = _selectedModel.Value.ModelName; + var chat = new OllamaSharp.Chat(_ollamaApiClient, @event.Value); + var builder = new StringBuilder(); + await foreach (var answerToken in chat.SendAsync(@event.Value)) + { + builder.Append(answerToken); + } + + _messages.Set(_messages.Value.Add(new ChatMessage(ChatSender.Assistant, builder.ToString()))); + } + + private async ValueTask OnRefreshClicked() + { + _ollamaApiClient?.Dispose(); + _ollamaApiClient = new OllamaApiClient(Url); + var connected = await _ollamaApiClient.IsRunningAsync(); + if (!connected) + { + var client = UseService(); + client.Toast($"Ollama API is not running at {Url}", "Connection Error"); + return; + } + + var ollamaModels = await _ollamaApiClient.ListLocalModelsAsync(); + _models.Set(ollamaModels.Select(m => new ModelListItem(s => { _selectedModel.Set(s); }, m.Name)) + .ToImmutableArray()); + ClearModelSelection(); + } + + private void ClearModelSelection() + { + _selectedModel.Set((ModelListItem?)null); + _messages.Set([]); + } +} + +public class ModelListItem(Action modelSelect, string modelName) : ViewBase +{ + public string ModelName { get; } = modelName; + + public override object? Build() + { + return Layout.Vertical( + Layout.Horizontal( + new Button(ModelName, _ => { modelSelect(this); }).Icon(Icons.NotebookText) + .Variant(ButtonVariant.Outline) + ).Align(Align.Left).Width(Size.Units(120).Max(500)), + new Separator() + ); + } +} \ No newline at end of file diff --git a/ollamasharp/GlobalUsings.cs b/ollamasharp/GlobalUsings.cs new file mode 100644 index 00000000..8889495b --- /dev/null +++ b/ollamasharp/GlobalUsings.cs @@ -0,0 +1,26 @@ +global using Ivy; +global using Ivy.Apps; +global using Ivy.Auth; +global using Ivy.Chrome; +global using Ivy.Client; +global using Ivy.Core; +global using Ivy.Core.Hooks; +global using Ivy.Helpers; +global using Ivy.Hooks; +global using Ivy.Shared; +global using Ivy.Views; +global using Ivy.Views.Alerts; +global using Ivy.Views.Blades; +global using Ivy.Views.Builders; +global using Ivy.Views.Charts; +global using Ivy.Views.Dashboards; +global using Ivy.Views.Forms; +global using Ivy.Views.Tables; +global using Ivy.Widgets.Inputs; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using System.Collections.Immutable; +global using System.ComponentModel.DataAnnotations; +global using System.Globalization; +global using System.Reactive.Linq; \ No newline at end of file diff --git a/ollamasharp/OllamaSharpDemo.csproj b/ollamasharp/OllamaSharpDemo.csproj new file mode 100644 index 00000000..7e1b81b1 --- /dev/null +++ b/ollamasharp/OllamaSharpDemo.csproj @@ -0,0 +1,24 @@ + + + + Exe + net9.0 + enable + enable + CS8618;CS8603;CS8602;CS8604;CS9113 + OllamaSharpDemo + + + + + + + + + + + + + + + diff --git a/ollamasharp/OllamaSharpDemo.sln b/ollamasharp/OllamaSharpDemo.sln new file mode 100644 index 00000000..4417834b --- /dev/null +++ b/ollamasharp/OllamaSharpDemo.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello", "OllamaSharpDemo.csproj", "{039638F3-A6D7-FB67-E733-27BD896EFDC4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {039638F3-A6D7-FB67-E733-27BD896EFDC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {039638F3-A6D7-FB67-E733-27BD896EFDC4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {039638F3-A6D7-FB67-E733-27BD896EFDC4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {039638F3-A6D7-FB67-E733-27BD896EFDC4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5F74B1AD-6C2E-4FDA-9DAF-9F7D8053687B} + EndGlobalSection +EndGlobal diff --git a/ollamasharp/Program.cs b/ollamasharp/Program.cs new file mode 100644 index 00000000..ff8aa007 --- /dev/null +++ b/ollamasharp/Program.cs @@ -0,0 +1,11 @@ +using OllamaSharpDemo.Apps; +CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US"); +var server = new Server(); +#if DEBUG +server.UseHotReload(); +#endif +server.AddAppsFromAssembly(); +server.AddConnectionsFromAssembly(); +var chromeSettings = new ChromeSettings().DefaultApp().UseTabs(preventDuplicates: true); +server.UseChrome(chromeSettings); +await server.RunAsync(); diff --git a/ollamasharp/README.md b/ollamasharp/README.md new file mode 100644 index 00000000..eb91183d --- /dev/null +++ b/ollamasharp/README.md @@ -0,0 +1,134 @@ +# Hello + +Web application created using [Ivy](https://github.com/Ivy-Interactive/Ivy). + +Ivy is a web framework for building interactive web applications using C# and .NET. + +## Run + +``` +dotnet watch +``` + +## Deploy + +``` +ivy deploy +``` + +# OllamaSharp Demo + +This project demonstrates how to integrate [Ollama](https://ollama.com/) with an Ivy application using the [OllamaSharp](https://github.com/awaescher/OllamaSharp) library. + +## What is OllamaSharp? + +The `OllamaSharp` is an Ivy application that provides a chat interface for interacting with local Large Language Models (LLMs) through Ollama. The application features: + +- **Model Selection**: Browse and select from locally available Ollama models +- **Interactive Chat**: Real-time chat interface with streaming responses +- **Model Management**: Easy switching between different models +- **Connection Status**: Automatic detection of Ollama service availability + +The sample demonstrates how to: +- Connect to a local Ollama API instance +- List available models +- Send chat messages and receive streaming responses +- Handle connection errors and service availability + +## Prerequisites + +### 1. Install Ollama + +First, you need to install Ollama on your local machine: + +#### Windows +1. Download Ollama from [https://ollama.com/download](https://ollama.com/download) +2. Run the installer +3. Ollama will automatically start as a service + +#### macOS +```bash +# Using Homebrew +brew install ollama + +# Or download from https://ollama.com/download +``` + +#### Linux +```bash +curl -fsSL https://ollama.com/install.sh | sh +``` + +### 2. Download Models + +After installing Ollama, you need to download at least one model to use with the application: + +```bash +# Download a popular model (this may take a while depending on model size) +ollama pull llama2 + +# Or try a smaller model for testing +ollama pull phi3 + +# List other available models +ollama list +``` + +### 3. Start Ollama Service + +Ensure Ollama is running locally: + +```bash +# Start Ollama (if not running as a service) +ollama serve +``` + +By default, Ollama runs on `http://localhost:11434` + +## Running the Application + +1. **Clone and navigate to the project**: + ```bash + cd ollamasharp + ``` + +2. **Restore dependencies**: + ```bash + dotnet restore + ``` + +3. **Run the application**: + ```bash + dotnet run + ``` + +4. **Use the application**: + - Click "Refresh Models" to load available Ollama models + - Select a model from the list + - Start chatting with the selected model + - Use "Back to Models" to switch to a different model + +## Troubleshooting + +### "Ollama API is not running" Error + +If you see this error: +1. Ensure Ollama is installed and running: `ollama serve` +2. Check if Ollama is accessible: `curl http://localhost:11434` +3. Verify you have at least one model downloaded: `ollama list` + +### No Models Available + +If no models appear in the list: +1. Download a model: `ollama pull llama2` +2. Verify models are installed: `ollama list` +3. Restart the application and click "Refresh Models" + +## Dependencies + +- **OllamaSharp**: .NET client library for Ollama API +- **Ivy Framework**: UI framework for building the chat interface + +## Configuration + +The application connects to Ollama on `http://localhost:11434` by default. You can modify the `Url` constant in `OllamaSharp.cs` to connect to a different Ollama instance if needed.