Skip to content

Commit 5688c30

Browse files
committed
Replace Visual flag with ImageGen and add vision support
Rename the old "Visual" concept to a clearer "ImageGen" across the codebase and add vision detection/flags. Key changes: - Domain & storage: Chat.Visual -> Chat.ImageGen, ChatDocument.ImageGen, DTO and DB mappings updated (SQL/SQLite repos). - API: removed EnableVisual(); added WithModel(AIModel model, bool? imageGen = null) to allow explicit imageGen override (defaults to model capability). - Interfaces: removed EnableVisual from builder interfaces. - Services/handlers: ChatService, AgentService, StartCommandHandler, AnswerCommandHandler and step handlers updated to use ImageGen logic when routing to image-gen or LLM services; TTS gating now checks ImageGen. - Mappers: ChatMapper and DTO mappings updated to use ImageGen. - UI: InferPage and NavBar updated to show Image Gen and Vision badges; Home.razor now computes message content/reasoning via MessageExt.ComputedContent/ComputedReasoning; MessageExt gains HasReasoning and computed fields. - Utils: Reason is now computed from registered model capabilities and ImageGen is mutually exclusive with reasoning; added Vision detection and model lists. - Examples & tests: updated to call WithModel(..., imageGen: true) or model-based API accordingly. Why: clarifies semantics between image generation and visual/vision capabilities, centralizes model-driven behavior, and enables explicit overrides for image generation behavior.
1 parent 07034d1 commit 5688c30

File tree

20 files changed

+90
-68
lines changed

20 files changed

+90
-68
lines changed

Examples/Examples/Chat/ChatWithImageGenExample.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Examples.Utils;
22
using MaIN.Core.Hub;
3+
using MaIN.Domain.Models.Abstract;
34

45
namespace Examples.Chat;
56

@@ -10,7 +11,7 @@ public async Task Start()
1011
Console.WriteLine("ChatExample with image gen is running!");
1112

1213
var result = await AIHub.Chat()
13-
.EnableVisual()
14+
.WithModel(new GenericLocalModel("FLUX.1_Shnell"), imageGen: true)
1415
.WithMessage("Generate cyberpunk godzilla cat warrior")
1516
.CompleteAsync();
1617

Examples/Examples/Chat/ChatWithImageGenGeminiExample.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Examples.Utils;
22
using MaIN.Core.Hub;
3+
using MaIN.Domain.Configuration;
4+
using MaIN.Domain.Models.Abstract;
35

46
namespace Examples.Chat;
57

@@ -11,7 +13,7 @@ public async Task Start()
1113
GeminiExample.Setup(); // We need to provide Gemini API key
1214

1315
var result = await AIHub.Chat()
14-
.EnableVisual()
16+
.WithModel(new GenericCloudModel("imagen-3", BackendType.Gemini), imageGen: true)
1517
.WithMessage("Generate hamster as a astronaut on the moon")
1618
.CompleteAsync();
1719

Examples/Examples/Chat/ChatWithImageGenOpenAiExample.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public async Task Start()
1313

1414
var result = await AIHub.Chat()
1515
.WithModel<DallE3>()
16-
.EnableVisual()
1716
.WithMessage("Generate rock style cow playing guitar")
1817
.CompleteAsync();
1918

MaIN.Core.IntegrationTests/ChatTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using MaIN.Core.Hub;
22
using MaIN.Domain.Entities;
3+
using MaIN.Domain.Models.Abstract;
34
using MaIN.Domain.Models.Concrete;
45

56
namespace MaIN.Core.IntegrationTests;
@@ -88,7 +89,7 @@ public async Task Should_GenerateImage_BasedOnPrompt()
8889
const string extension = "png";
8990

9091
var result = await AIHub.Chat()
91-
.EnableVisual()
92+
.WithModel(new GenericLocalModel("FLUX.1_Shnell"), imageGen: true)
9293
.WithMessage("Generate cat in Rome. Sightseeing, colloseum, ancient builidngs, Italy.")
9394
.CompleteAsync();
9495

src/MaIN.Core/Hub/Contexts/ChatContext.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,10 @@ internal ChatContext(IChatService chatService, Chat existingChat)
3939
_chat = existingChat;
4040
}
4141

42-
public IChatMessageBuilder WithModel(AIModel model)
42+
public IChatMessageBuilder WithModel(AIModel model, bool? imageGen = null)
4343
{
4444
SetModel(model);
45+
_chat.ImageGen = imageGen ?? model is IImageGenerationModel;
4546
return this;
4647
}
4748

@@ -81,14 +82,9 @@ private void SetModel(AIModel model)
8182
_chat.ModelId = model.Id;
8283
_chat.ModelInstance = model;
8384
_chat.Backend = model.Backend;
85+
_chat.ImageGen = model.HasImageGeneration;
8486
}
8587

86-
public IChatMessageBuilder EnableVisual()
87-
{
88-
_chat.Visual = true;
89-
return this;
90-
}
91-
9288
public IChatConfigurationBuilder WithInferenceParams(InferenceParams inferenceParams)
9389
{
9490
_chat.InterferenceParams = inferenceParams;
@@ -109,7 +105,7 @@ public IChatConfigurationBuilder WithMemoryParams(MemoryParams memoryParams)
109105

110106
public IChatConfigurationBuilder Speak(TextToSpeechParams speechParams)
111107
{
112-
_chat.Visual = false;
108+
_chat.ImageGen = false;
113109
_chat.TextToSpeechParams = speechParams;
114110
return this;
115111
}
@@ -246,9 +242,6 @@ private async Task<bool> ChatExists(string id)
246242
}
247243
}
248244

249-
IChatMessageBuilder IChatMessageBuilder.EnableVisual() => EnableVisual();
250-
251-
252245
public string GetChatId() => _chat.Id;
253246

254247
public async Task<Chat> GetCurrentChat()
Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext;
1+
namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext;
22

33
public interface IChatBuilderEntryPoint : IChatActions
44
{
@@ -9,7 +9,7 @@ public interface IChatBuilderEntryPoint : IChatActions
99
/// <param name="model">The name of the AI model to be used.</param>
1010
/// <returns>The context instance implementing <see cref="IChatMessageBuilder"/> for method chaining.</returns>
1111
IChatMessageBuilder WithModel(string model);
12-
12+
1313
/// <summary>
1414
/// Configures a custom model with a specific path and project context.
1515
/// </summary>
@@ -18,18 +18,11 @@ public interface IChatBuilderEntryPoint : IChatActions
1818
/// <param name="mmProject">Optional multi-modal project identifier.</param>
1919
/// <returns>The context instance implementing <see cref="IChatMessageBuilder"/> for method chaining.</returns>
2020
IChatMessageBuilder WithCustomModel(string model, string path, string? mmProject = null);
21-
22-
/// <summary>
23-
/// Enables visual/image generation mode. Use this method now if you do not plan to explicitly define the model.
24-
/// Otherwise, you will be able to use this method in the next step, after defining the model.
25-
/// </summary>
26-
/// <returns>The context instance implementing <see cref="IChatMessageBuilder"/> for method chaining.</returns>
27-
IChatMessageBuilder EnableVisual();
28-
21+
2922
/// <summary>
3023
/// Loads an existing chat session from the database using its unique identifier.
3124
/// </summary>
3225
/// <param name="chatId">The GUID of the existing chat.</param>
3326
/// <returns>The context instance implementing <see cref="IChatConfigurationBuilder"/> for method chaining.</returns>
3427
Task<IChatConfigurationBuilder> FromExisting(string chatId);
35-
}
28+
}

src/MaIN.Core/Hub/Contexts/Interfaces/ChatContext/IChatMessageBuilder.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@ namespace MaIN.Core.Hub.Contexts.Interfaces.ChatContext;
44

55
public interface IChatMessageBuilder : IChatActions
66
{
7-
/// <summary>
8-
/// Enables the visual output for the current chat session. This flag allows the AI to generate and return visual content,
9-
/// such as images or charts, as part of its response.
10-
/// </summary>
11-
/// <returns>The context instance implementing <see cref="IChatMessageBuilder"/> for method chaining.</returns>
12-
IChatMessageBuilder EnableVisual();
13-
147
/// <summary>
158
/// Adds a user message to the chat. This method captures the message content and assigns the "User" role to it.
169
/// It also timestamps the message for proper ordering.

src/MaIN.Domain/Entities/Chat.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public AIModel? ModelInstance
3737
}
3838
public List<Message> Messages { get; set; } = [];
3939
public ChatType Type { get; set; } = ChatType.Conversation;
40-
public bool Visual { get; set; }
40+
public bool ImageGen { get; set; }
4141
public InferenceParams InterferenceParams { get; set; } = new();
4242
public MemoryParams MemoryParams { get; set; } = new();
4343
public ToolsConfiguration? ToolsConfiguration { get; set; }

src/MaIN.InferPage/Components/Layout/NavBar.razor

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,23 @@
2929
Color="#000"
3030
Style="margin-left: 10px">Reasoning ✨</FluentBadge>
3131
}
32-
@if (Utils.Visual)
32+
@if (Utils.ImageGen)
3333
{
3434
<FluentBadge
3535
Appearance="Appearance.Neutral"
3636
Fill="highlight"
3737
BackgroundColor="#7c3aed"
3838
Color="#fff"
39-
Style="margin-left: 10px">Visual 🎨</FluentBadge>
39+
Style="margin-left: 10px">Image Gen 🎨</FluentBadge>
40+
}
41+
@if (Utils.Vision)
42+
{
43+
<FluentBadge
44+
Appearance="Appearance.Neutral"
45+
Fill="highlight"
46+
BackgroundColor="#0ea5e9"
47+
Color="#fff"
48+
Style="margin-left: 10px">Vision 👁️</FluentBadge>
4049
}
4150
<div style="margin-left: auto; align-self: flex-end;">
4251
<FluentButton Style="padding: 10px; background-color: transparent;"

src/MaIN.InferPage/Components/Pages/Home.razor

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
else
9393
{
9494
<div>
95-
@if (_reasoning && conversation.Message.Role == "Assistant")
95+
@if (conversation.HasReasoning)
9696
{
9797
<div class="reasoning-row">
9898
<span class="brain-toggle" title="@(conversation.ShowReason ? "Hide reasoning" : "Show reasoning")"
@@ -104,13 +104,13 @@
104104
{
105105
<div class="thinker reasoning-text"
106106
style="border-radius: 10px; padding: 10px; border-width: 2px; background-color: var(--neutral-fill-hover)">
107-
@((MarkupString)Markdown.ToHtml(GetReasoningContent(conversation.Message), _markdownPipeline))
107+
@((MarkupString)Markdown.ToHtml(conversation.ComputedReasoning, _markdownPipeline))
108108
</div>
109109
}
110110
</div>
111111
<hr class="reasoning-hr"/>
112112
}
113-
@((MarkupString)Markdown.ToHtml(GetMessageContent(conversation.Message), _markdownPipeline))
113+
@((MarkupString)Markdown.ToHtml(conversation.ComputedContent, _markdownPipeline))
114114
</div>
115115
}
116116
}
@@ -119,7 +119,7 @@
119119
}
120120
@if (_isLoading)
121121
{
122-
@if (Utils.Visual)
122+
@if (Utils.ImageGen)
123123
{
124124
<span class="message-role-bot" style="font-style: italic; font-size: small">This might take a while...</span>
125125
}
@@ -207,7 +207,6 @@
207207
private bool _isLoading;
208208
private bool _isThinking;
209209
private bool _isDragging;
210-
private bool _reasoning;
211210
private bool _preserveScroll;
212211
private string _accentColor = "#00cca3";
213212
private string? _errorMessage;
@@ -272,9 +271,7 @@
272271
: new GenericCloudModel(Id: Utils.Model!, Backend: Utils.BackendType);
273272
}
274273

275-
ctx = Utils.Visual
276-
? AIHub.Chat().WithModel(model).EnableVisual()
277-
: AIHub.Chat().WithModel(model);
274+
ctx = AIHub.Chat().WithModel(model, imageGen: Utils.ImageGen);
278275
}
279276
catch (MaINCustomException ex)
280277
{
@@ -285,9 +282,6 @@
285282
_errorMessage = ex.Message;
286283
}
287284

288-
_reasoning = !Utils.Visual && model?.HasReasoning == true;
289-
Utils.Reason = _reasoning;
290-
291285
return base.OnInitializedAsync();
292286
}
293287

@@ -451,7 +445,7 @@
451445
});
452446

453447
Chat.ModelId = Utils.Model!;
454-
Chat.Visual = Utils.Visual;
448+
Chat.ImageGen = Utils.ImageGen;
455449

456450
bool wasAtBottom = await JS.InvokeAsync<bool>("scrollManager.isAtBottom", "messages-container");
457451

@@ -620,7 +614,10 @@
620614
Message = x,
621615
AttachedFiles = existingFilesMap.TryGetValue(x, out var files) ? files : new List<string>(),
622616
AttachedImages = existingImagesMap.TryGetValue(x, out var images) ? images : new List<(string, string)>(),
623-
ShowReason = existingReasonMap.TryGetValue(x, out var show) && show
617+
ShowReason = existingReasonMap.TryGetValue(x, out var show) && show,
618+
HasReasoning = x.Tokens.Any(t => t.Type == TokenType.Reason),
619+
ComputedContent = GetMessageContent(x),
620+
ComputedReasoning = GetReasoningContent(x)
624621
}).ToList();
625622
}
626623

0 commit comments

Comments
 (0)