diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index eeb90c5b..d3a4c32b 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -3532,6 +3532,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Failed to l
-- Please enter a model name.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name."
+-- Additional API parameters must form a JSON object.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2051143391"] = "Additional API parameters must form a JSON object."
+
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model"
@@ -3553,12 +3556,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instance Na
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
+-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Invalid JSON: Add the parameters in proper JSON formatting, e.g., \\\"temperature\\\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though."
+
-- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
-- This host uses the model configured at the provider level. No model selection is available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
+-- Duplicate key '{0}' found.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Duplicate key '{0}' found."
+
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
index 96e94a2f..4c09da2f 100644
--- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
+++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor
@@ -160,7 +160,7 @@
@T("Please be aware: This section is for experts only. You are responsible for verifying the correctness of the additional parameters you provide to the API call. By default, AI Studio uses the OpenAI-compatible chat completions API, when that it is supported by the underlying service and model.")
-
+
@@ -181,4 +181,4 @@
}
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
index 216f49ee..9e84bea8 100644
--- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs
@@ -1,3 +1,6 @@
+using System.Text;
+using System.Text.Json;
+
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Provider.HuggingFace;
@@ -334,7 +337,168 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
private void OnInputChangeExpertSettings()
{
- this.AdditionalJsonApiParameters = this.AdditionalJsonApiParameters.Trim().TrimEnd(',', ' ');
+ this.AdditionalJsonApiParameters = NormalizeAdditionalJsonApiParameters(this.AdditionalJsonApiParameters)
+ .Trim()
+ .TrimEnd(',', ' ');
+ }
+
+ private string? ValidateAdditionalJsonApiParameters(string additionalParams)
+ {
+ if (string.IsNullOrWhiteSpace(additionalParams))
+ return null;
+
+ var normalized = NormalizeAdditionalJsonApiParameters(additionalParams);
+ if (!string.Equals(normalized, additionalParams, StringComparison.Ordinal))
+ this.AdditionalJsonApiParameters = normalized;
+
+ var json = $"{{{normalized}}}";
+ try
+ {
+ if (!this.TryValidateJsonObjectWithDuplicateCheck(json, out var errorMessage))
+ return errorMessage;
+
+ return null;
+ }
+ catch (JsonException)
+ {
+ return T("Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.");
+ }
+ }
+
+ private static string NormalizeAdditionalJsonApiParameters(string input)
+ {
+ var sb = new StringBuilder(input.Length);
+ var inString = false;
+ var escape = false;
+ for (var i = 0; i < input.Length; i++)
+ {
+ var c = input[i];
+ if (inString)
+ {
+ sb.Append(c);
+ if (escape)
+ {
+ escape = false;
+ continue;
+ }
+
+ if (c == '\\')
+ {
+ escape = true;
+ continue;
+ }
+
+ if (c == '"')
+ inString = false;
+
+ continue;
+ }
+
+ if (c == '"')
+ {
+ inString = true;
+ sb.Append(c);
+ continue;
+ }
+
+ if (TryReadToken(input, i, "True", out var tokenLength))
+ {
+ sb.Append("true");
+ i += tokenLength - 1;
+ continue;
+ }
+
+ if (TryReadToken(input, i, "False", out tokenLength))
+ {
+ sb.Append("false");
+ i += tokenLength - 1;
+ continue;
+ }
+
+ if (TryReadToken(input, i, "Null", out tokenLength))
+ {
+ sb.Append("null");
+ i += tokenLength - 1;
+ continue;
+ }
+
+ sb.Append(c);
+ }
+
+ return sb.ToString();
+ }
+
+ private static bool TryReadToken(string input, int startIndex, string token, out int tokenLength)
+ {
+ tokenLength = 0;
+ if (startIndex + token.Length > input.Length)
+ return false;
+
+ if (!input.AsSpan(startIndex, token.Length).SequenceEqual(token))
+ return false;
+
+ var beforeIndex = startIndex - 1;
+ if (beforeIndex >= 0 && IsIdentifierChar(input[beforeIndex]))
+ return false;
+
+ var afterIndex = startIndex + token.Length;
+ if (afterIndex < input.Length && IsIdentifierChar(input[afterIndex]))
+ return false;
+
+ tokenLength = token.Length;
+ return true;
+ }
+
+ private static bool IsIdentifierChar(char c) => char.IsLetterOrDigit(c) || c == '_';
+
+ private bool TryValidateJsonObjectWithDuplicateCheck(string json, out string? errorMessage)
+ {
+ errorMessage = null;
+ var bytes = Encoding.UTF8.GetBytes(json);
+ var reader = new Utf8JsonReader(bytes, new JsonReaderOptions
+ {
+ AllowTrailingCommas = false,
+ CommentHandling = JsonCommentHandling.Disallow
+ });
+
+ var objectStack = new Stack>();
+ while (reader.Read())
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.StartObject:
+ objectStack.Push(new HashSet(StringComparer.Ordinal));
+ break;
+
+ case JsonTokenType.EndObject:
+ if (objectStack.Count > 0)
+ objectStack.Pop();
+ break;
+
+ case JsonTokenType.PropertyName:
+ if (objectStack.Count == 0)
+ {
+ errorMessage = T("Additional API parameters must form a JSON object.");
+ return false;
+ }
+
+ var name = reader.GetString() ?? string.Empty;
+ if (!objectStack.Peek().Add(name))
+ {
+ errorMessage = string.Format(T("Duplicate key '{0}' found."), name);
+ return false;
+ }
+ break;
+ }
+ }
+
+ if (objectStack.Count != 0)
+ {
+ errorMessage = T("Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.");
+ return false;
+ }
+
+ return true;
}
private string GetExpertStyles => this.showExpertSettings ? "border-2 border-dashed rounded pa-2" : string.Empty;
@@ -345,4 +509,4 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
"top_p": 0.9,
"frequency_penalty": 0.0
""";
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
index 08d78e5a..339d8507 100644
--- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua
@@ -3534,6 +3534,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Der API-Sch
-- Please enter a model name.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Bitte geben Sie einen Modellnamen ein."
+-- Additional API parameters must form a JSON object.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2051143391"] = "Zusätzliche API-Parameter müssen ein JSON-Objekt bilden."
+
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Modell"
@@ -3555,12 +3558,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instanzname
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Experten-Einstellungen anzeigen"
+-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Ungültiges JSON: Fügen Sie die Parameter in korrektem JSON-Format hinzu, z. B. \"temperature\": 0.5. Entfernen Sie abschließende Kommas. Die üblichen umgebenden geschweiften Klammern {} dürfen jedoch nicht verwendet werden."
+
-- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen"
-- This host uses the model configured at the provider level. No model selection is available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Es ist keine Modellauswahl verfügbar."
+-- Duplicate key '{0}' found.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Doppelter Schlüssel '{0}' gefunden."
+
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Derzeit können wir die Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
index 5ab2e446..d5da8afc 100644
--- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
+++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua
@@ -3534,6 +3534,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Failed to l
-- Please enter a model name.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name."
+-- Additional API parameters must form a JSON object.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2051143391"] = "Additional API parameters must form a JSON object."
+
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model"
@@ -3555,12 +3558,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instance Na
-- Show Expert Settings
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert Settings"
+-- Invalid JSON: Add the parameters in proper JSON formatting, e.g., \"temperature\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3502745319"] = "Invalid JSON: Add the parameters in proper JSON formatting, e.g., \\\"temperature\\\": 0.5. Remove trailing commas. The usual surrounding curly brackets {} must not be used, though."
+
-- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
-- This host uses the model configured at the provider level. No model selection is available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
+-- Duplicate key '{0}' found.
+UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3804472591"] = "Duplicate key '{0}' found."
+
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
diff --git a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs
index 5eb8fe2b..49a0e6ea 100644
--- a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs
+++ b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs
@@ -29,6 +29,9 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
// Parse the API parameters:
var apiParameters = this.ParseAdditionalApiParameters("system");
+ var maxTokens = 4_096;
+ if (TryPopIntParameter(apiParameters, "max_tokens", out var parsedMaxTokens))
+ maxTokens = parsedMaxTokens;
// Build the list of messages:
var messages = await chatThread.Blocks.BuildMessagesAsync(
@@ -73,7 +76,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
Messages = [..messages],
System = chatThread.PrepareSystemPrompt(settingsManager),
- MaxTokens = apiParameters.TryGetValue("max_tokens", out var value) && value is int intValue ? intValue : 4_096,
+ MaxTokens = maxTokens,
// Right now, we only support streaming completions:
Stream = true,
@@ -188,4 +191,4 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
var modelResponse = await response.Content.ReadFromJsonAsync(JSON_SERIALIZER_OPTIONS, token);
return modelResponse.Data;
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs
index 4acefc62..9b729824 100644
--- a/app/MindWork AI Studio/Provider/BaseProvider.cs
+++ b/app/MindWork AI Studio/Provider/BaseProvider.cs
@@ -731,7 +731,7 @@ public abstract class BaseProvider : IProvider, ISecretId
/// Optional list of keys to remove from the final dictionary
/// (case-insensitive). The parameters stream, model, and messages are removed by default.
protected IDictionary ParseAdditionalApiParameters(
- params List keysToRemove)
+ params string[] keysToRemove)
{
if(string.IsNullOrWhiteSpace(this.AdditionalJsonApiParameters))
return new Dictionary();
@@ -744,14 +744,23 @@ public abstract class BaseProvider : IProvider, ISecretId
var dict = ConvertToDictionary(jsonDoc);
// Some keys are always removed because we set them:
- keysToRemove.Add("stream");
- keysToRemove.Add("model");
- keysToRemove.Add("messages");
+ var removeSet = new HashSet(StringComparer.OrdinalIgnoreCase);
+ if (keysToRemove.Length > 0)
+ removeSet.UnionWith(keysToRemove);
+
+ removeSet.Add("stream");
+ removeSet.Add("model");
+ removeSet.Add("messages");
// Remove the specified keys (case-insensitive):
- var removeSet = new HashSet(keysToRemove, StringComparer.OrdinalIgnoreCase);
- foreach (var key in removeSet)
- dict.Remove(key);
+ if (removeSet.Count > 0)
+ {
+ foreach (var key in dict.Keys.ToList())
+ {
+ if (removeSet.Contains(key))
+ dict.Remove(key);
+ }
+ }
return dict;
}
@@ -761,6 +770,85 @@ public abstract class BaseProvider : IProvider, ISecretId
return new Dictionary();
}
}
+
+ protected static bool TryPopIntParameter(IDictionary parameters, string key, out int value)
+ {
+ value = default;
+ if (!TryPopParameter(parameters, key, out var raw) || raw is null)
+ return false;
+
+ switch (raw)
+ {
+ case int i:
+ value = i;
+ return true;
+
+ case long l when l is >= int.MinValue and <= int.MaxValue:
+ value = (int)l;
+ return true;
+
+ case double d when d is >= int.MinValue and <= int.MaxValue:
+ value = (int)d;
+ return true;
+
+ case decimal m when m is >= int.MinValue and <= int.MaxValue:
+ value = (int)m;
+ return true;
+ }
+
+ return false;
+ }
+
+ protected static bool TryPopBoolParameter(IDictionary parameters, string key, out bool value)
+ {
+ value = default;
+ if (!TryPopParameter(parameters, key, out var raw) || raw is null)
+ return false;
+
+ switch (raw)
+ {
+ case bool b:
+ value = b;
+ return true;
+
+ case string s when bool.TryParse(s, out var parsed):
+ value = parsed;
+ return true;
+
+ case int i:
+ value = i != 0;
+ return true;
+
+ case long l:
+ value = l != 0;
+ return true;
+
+ case double d:
+ value = Math.Abs(d) > double.Epsilon;
+ return true;
+
+ case decimal m:
+ value = m != 0;
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TryPopParameter(IDictionary parameters, string key, out object? value)
+ {
+ value = null;
+ if (parameters.Count == 0)
+ return false;
+
+ var foundKey = parameters.Keys.FirstOrDefault(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase));
+ if (foundKey is null)
+ return false;
+
+ value = parameters[foundKey];
+ parameters.Remove(foundKey);
+ return true;
+ }
private static IDictionary ConvertToDictionary(JsonElement element)
{
@@ -785,4 +873,4 @@ public abstract class BaseProvider : IProvider, ISecretId
_ => string.Empty,
};
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs b/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs
index 01a45a89..1d42081f 100644
--- a/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs
+++ b/app/MindWork AI Studio/Provider/Mistral/ChatRequest.cs
@@ -14,11 +14,12 @@ public readonly record struct ChatRequest(
string Model,
IList Messages,
bool Stream,
- int RandomSeed,
+ [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ int? RandomSeed,
bool SafePrompt = false
)
{
// Attention: The "required" modifier is not supported for [JsonExtensionData].
[JsonExtensionData]
public IDictionary AdditionalApiParameters { get; init; } = new Dictionary();
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
index f4cb07f4..485729fb 100644
--- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
+++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
@@ -36,6 +36,8 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
// Parse the API parameters:
var apiParameters = this.ParseAdditionalApiParameters();
+ var safePrompt = TryPopBoolParameter(apiParameters, "safe_prompt", out var parsedSafePrompt) && parsedSafePrompt;
+ var randomSeed = TryPopIntParameter(apiParameters, "random_seed", out var parsedRandomSeed) ? parsedRandomSeed : (int?)null;
// Build the list of messages:
var messages = await chatThread.Blocks.BuildMessagesUsingDirectImageUrlAsync(this.Provider, chatModel);
@@ -52,7 +54,8 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
// Right now, we only support streaming completions:
Stream = true,
- SafePrompt = apiParameters.TryGetValue("safe_prompt", out var value) && value is true,
+ RandomSeed = randomSeed,
+ SafePrompt = safePrompt,
AdditionalApiParameters = apiParameters
}, JSON_SERIALIZER_OPTIONS);
@@ -165,4 +168,4 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
var modelResponse = await response.Content.ReadFromJsonAsync(token);
return modelResponse;
}
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md
index 3cbd4960..25befa8a 100644
--- a/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md
+++ b/app/MindWork AI Studio/wwwroot/changelog/v26.3.1.md
@@ -7,4 +7,5 @@
- Improved the user-language logging by limiting language detection logs to a single entry per app start.
- Improved the logbook readability by removing non-readable special characters from log entries.
- Improved the logbook reliability by significantly reducing duplicate log entries.
+- Improved the validation of additional API parameters in the advanced provider settings to help catch formatting mistakes earlier.
- Fixed an issue where the app could turn white or appear invisible in certain chats after HTML-like content was shown. Thanks Inga for reporting this issue and providing some context on how to reproduce it.
\ No newline at end of file