Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/MaIN.Core.UnitTests/ChatContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public async Task CompleteAsync_ShouldCallChatService()
};


_mockChatService.Setup(s => s.Completions(It.IsAny<Chat>(), It.IsAny<bool>(), It.IsAny<bool>(), null))
_mockChatService.Setup(s => s.Completions(It.IsAny<Chat>(), It.IsAny<bool>(), It.IsAny<bool>(), null, It.IsAny<CancellationToken>()))
.ReturnsAsync(chatResult);

_chatContext.WithMessage("User message");
Expand All @@ -98,7 +98,7 @@ public async Task CompleteAsync_ShouldCallChatService()
var result = await _chatContext.CompleteAsync();

// Assert
_mockChatService.Verify(s => s.Completions(It.IsAny<Chat>(), false, false, null), Times.Once);
_mockChatService.Verify(s => s.Completions(It.IsAny<Chat>(), false, false, null, It.IsAny<CancellationToken>()), Times.Once);
Assert.Equal(chatResult, result);
}

Expand Down Expand Up @@ -128,6 +128,6 @@ await _chatContext.WithModel(model)
.CompleteAsync();

// Assert
_mockChatService.Verify(s => s.Completions(It.Is<Chat>(c => c.ModelId == _testModelId && c.ModelInstance == model), It.IsAny<bool>(), It.IsAny<bool>(), null), Times.Once);
_mockChatService.Verify(s => s.Completions(It.Is<Chat>(c => c.ModelId == _testModelId && c.ModelInstance == model), It.IsAny<bool>(), It.IsAny<bool>(), null, It.IsAny<CancellationToken>()), Times.Once);
}
}
5 changes: 3 additions & 2 deletions src/MaIN.Core/Hub/Contexts/ChatContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ public IChatConfigurationBuilder DisableCache()
public async Task<ChatResult> CompleteAsync(
bool translate = false, // Move to WithTranslate
bool interactive = false, // Move to WithInteractive
Func<LLMTokenValue?, Task>? changeOfValue = null)
Func<LLMTokenValue?, Task>? changeOfValue = null,
CancellationToken cancellationToken = default)
{
if (_chat.ModelInstance is null)
{
Expand All @@ -219,7 +220,7 @@ public async Task<ChatResult> CompleteAsync(
{
await _chatService.Create(_chat);
}
var result = await _chatService.Completions(_chat, translate, interactive, changeOfValue);
var result = await _chatService.Completions(_chat, translate, interactive, changeOfValue, cancellationToken);
_files = [];
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,5 @@ public interface IChatConfigurationBuilder : IChatActions
/// <param name="interactive">A flag indicating whether the chat session should be interactive. Default is false.</param>
/// <param name="changeOfValue">An optional callback invoked whenever a new token or update is received during streaming.</param>
/// <returns>A <see cref="ChatResult"/> object containing the result of the completed chat session.</returns>
Task<ChatResult> CompleteAsync(bool translate = false, bool interactive = false, Func<LLMTokenValue?, Task>? changeOfValue = null);
Task<ChatResult> CompleteAsync(bool translate = false, bool interactive = false, Func<LLMTokenValue?, Task>? changeOfValue = null, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace MaIN.Services.Services.LLMService.Utils;
using MaIN.Domain.Configuration;

namespace MaIN.Domain.Models.Concrete;

public static class LLMApiRegistry
{
Expand All @@ -9,6 +11,18 @@ public static class LLMApiRegistry
public static readonly LLMApiRegistryEntry Anthropic = new("Anthropic", "ANTHROPIC_API_KEY");
public static readonly LLMApiRegistryEntry Xai = new("Xai", "XAI_API_KEY");
public static readonly LLMApiRegistryEntry Ollama = new("Ollama", "OLLAMA_API_KEY");

public static LLMApiRegistryEntry? GetEntry(BackendType backendType) => backendType switch
{
BackendType.OpenAi => OpenAi,
BackendType.Gemini => Gemini,
BackendType.DeepSeek => Deepseek,
BackendType.GroqCloud => Groq,
BackendType.Anthropic => Anthropic,
BackendType.Xai => Xai,
BackendType.Ollama => Ollama,
_ => null
};
}

public record LLMApiRegistryEntry(string ApiName, string ApiKeyEnvName);
27 changes: 27 additions & 0 deletions src/MaIN.InferPage/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,39 @@
<link href="_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css" rel="stylesheet" />
<link rel="stylesheet" href="MaIN.InferPage.styles.css"/>
<HeadOutlet/>
<script>
(function () {
try {
var raw = localStorage.getItem('theme');
if (raw) {
var mode = JSON.parse(raw).mode;
if (mode === 'dark') document.documentElement.setAttribute('data-theme', 'dark');
}
} catch (e) {}
})();
</script>
</head>

<body>
<Routes/>
<script src="_framework/blazor.web.js"></script>
<script src="scroll.js"></script>
<script src="editor.js"></script>
<script>
window.themeManager = {
save: function (theme) { localStorage.setItem('theme', theme); },
load: function () {
try {
var raw = localStorage.getItem('theme');
if (!raw) return '';
var parsed = JSON.parse(raw);
return parsed.mode ? parsed.mode.toLowerCase() : raw.toLowerCase();
} catch (e) {
return '';
}
}
};
</script>
</body>

</html>
1 change: 0 additions & 1 deletion src/MaIN.InferPage/Components/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@inherits LayoutComponentBase
<FluentDesignTheme StorageName="theme" />
<NavBar/>
<div class="content">

Expand Down
63 changes: 51 additions & 12 deletions src/MaIN.InferPage/Components/Layout/NavBar.razor
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
@using Microsoft.FluentUI.AspNetCore.Components.Icons.Regular
@using MaIN.Domain.Configuration
@inject NavigationManager _navigationManager
@inject IJSRuntime JS
@rendermode @(new InteractiveServerRenderMode(prerender: false))

<FluentDesignTheme @bind-Mode="@Mode"
StorageName="theme" />

<header class="navbar">
<span class="fancy-text" style="align-self: flex-start;">M.A.I.N</span>
<FluentBadge
<span class="fancy-text" style="align-self: flex-start;">M.A.I.N</span>
<FluentBadge
Appearance="Appearance.Neutral"
Fill="highlight"
Fill="highlight"
BackgroundColor="@GetBackendColor()"
Color="#fff"
Style="margin-left: 10px">@GetBackendDisplayName()</FluentBadge>
<FluentBadge
Appearance="Appearance.Neutral"
Fill="highlight"
BackgroundColor="#ffd800;"
Color="#000"
Style="margin-left: 10px">@Utils.Model</FluentBadge>
Expand All @@ -22,28 +30,59 @@
Style="margin-left: 10px">Reasoning ✨</FluentBadge>
}
<div style="margin-left: auto; align-self: flex-end;">
<FluentButton Style="padding: 10px"
Appearance="Appearance.Stealth"
OnClick="@Reload" IconStart="@(new Icons.Filled.Size20.ArrowClockwise())">
</FluentButton>
<FluentButton
Appearance="Appearance.Stealth"
OnClick="@SetTheme" IconStart="@(new Size24.WeatherMoon())">
</FluentButton>
<FluentButton Style="padding: 10px; background-color: transparent;"
BackgroundColor="rgba(0, 0, 0, 0)"
Appearance="Appearance.Lightweight"
OnClick="@Reload" IconStart="@(new Icons.Filled.Size24.ArrowClockwise().WithColor(AccentColor))">
</FluentButton>
<FluentButton Style="background-color: transparent;"
BackgroundColor="rgba(0, 0, 0, 0)"
Appearance="Appearance.Lightweight"
OnClick="@SetTheme" IconStart="@(new Size24.WeatherMoon().WithColor(AccentColor))">
</FluentButton>
</div>
</header>

@code {
private DesignThemeModes Mode { get; set; }
private string AccentColor => Mode == DesignThemeModes.Dark ? "#00ffcc" : "#00cca3";
private bool _isChangingTheme = false;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
var stored = await JS.InvokeAsync<string>("themeManager.load");
Mode = stored == "dark" ? DesignThemeModes.Dark : DesignThemeModes.Light;
StateHasChanged();
}
}

private void SetTheme()
{
if (_isChangingTheme) return;
_isChangingTheme = true;
Mode = Mode == DesignThemeModes.Dark ? DesignThemeModes.Light : DesignThemeModes.Dark;
_isChangingTheme = false;
}

private void Reload(MouseEventArgs obj)
{
_navigationManager.Refresh(true);
}

}
private string GetBackendColor()
{
return Utils.IsLocal ? "#6b7280" : "#10a37f";
}

private string GetBackendDisplayName()
{
return Utils.BackendType switch
{
BackendType.Self => "Local",
BackendType.Ollama when !Utils.HasApiKey => "Local Ollama",
_ => Utils.BackendType.ToString()
};
}
}
Loading
Loading