diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
index eeb90c5b..862fb9f6 100644
--- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
+++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua
@@ -1594,15 +1594,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you s
-- Stop generation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Stop generation"
+-- Insert heading formatting
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1347686985"] = "Insert heading formatting"
+
-- Save chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1516264254"] = "Save chat"
+-- Insert italic formatting
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T182467363"] = "Insert italic formatting"
+
-- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..."
+-- Insert bulleted list formatting
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1850228954"] = "Insert bulleted list formatting"
+
-- Your Prompt (use selected instance '{0}', provider '{1}')
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')"
+-- Insert code formatting
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2278685708"] = "Insert code formatting"
+
+-- Insert bold formatting
+UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T265791106"] = "Insert bold formatting"
+
-- Profile usage is disabled according to your chat template settings.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile usage is disabled according to your chat template settings."
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor
index 3c49a4b5..5686f49c 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor
@@ -55,6 +55,22 @@
Style="@this.UserInputStyle"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@if (
this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY)
@@ -127,4 +143,4 @@
-
\ No newline at end of file
+
diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
index 9c2b38a0..79f065ba 100644
--- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs
+++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs
@@ -6,6 +6,7 @@ using AIStudio.Settings.DataModel;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
+using Microsoft.JSInterop;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
@@ -13,6 +14,13 @@ namespace AIStudio.Components;
public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
{
+ private const string CHAT_INPUT_ID = "chat-user-input";
+ private const string MARKDOWN_CODE = "code";
+ private const string MARKDOWN_BOLD = "bold";
+ private const string MARKDOWN_ITALIC = "italic";
+ private const string MARKDOWN_HEADING = "heading";
+ private const string MARKDOWN_BULLET_LIST = "bullet_list";
+
[Parameter]
public ChatThread? ChatThread { get; set; }
@@ -36,6 +44,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
[Inject]
private IDialogService DialogService { get; init; } = null!;
+
+ [Inject]
+ private IJSRuntime JsRuntime { get; init; } = null!;
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
private static readonly Dictionary USER_INPUT_ATTRIBUTES = new();
@@ -73,6 +84,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
+ USER_INPUT_ATTRIBUTES["id"] = CHAT_INPUT_ID;
// Get the preselected profile:
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
@@ -463,6 +475,18 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
break;
}
}
+
+ private async Task ApplyMarkdownFormat(string formatType)
+ {
+ if (this.IsInputForbidden())
+ return;
+
+ if(this.dataSourceSelectionComponent?.IsVisible ?? false)
+ this.dataSourceSelectionComponent.Hide();
+
+ this.userInput = await this.JsRuntime.InvokeAsync("formatChatInputMarkdown", CHAT_INPUT_ID, formatType);
+ this.hasUnsavedChanges = true;
+ }
private async Task SendMessage(bool reuseLastUserPrompt = false)
{
@@ -1018,4 +1042,4 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
}
#endregion
-}
\ No newline at end of file
+}
diff --git a/app/MindWork AI Studio/wwwroot/app.js b/app/MindWork AI Studio/wwwroot/app.js
index aa6b8e2b..0db881e9 100644
--- a/app/MindWork AI Studio/wwwroot/app.js
+++ b/app/MindWork AI Studio/wwwroot/app.js
@@ -25,4 +25,109 @@ window.clearDiv = function (divName) {
window.scrollToBottom = function(element) {
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
-}
\ No newline at end of file
+}
+
+window.formatChatInputMarkdown = function (inputId, formatType) {
+ let input = document.getElementById(inputId)
+ if (input && input.tagName !== 'TEXTAREA' && input.tagName !== 'INPUT')
+ input = input.querySelector('textarea, input')
+
+ if (!input)
+ return ''
+
+ input.focus()
+
+ const value = input.value ?? ''
+ const start = input.selectionStart ?? value.length
+ const end = input.selectionEnd ?? value.length
+ const hasSelection = end > start
+ const selectedText = value.substring(start, end)
+
+ let insertedText = ''
+ let selectionStart = start
+ let selectionEnd = start
+
+ switch (formatType) {
+ case 'bold': {
+ const text = hasSelection ? selectedText : 'bold text'
+ insertedText = `**${text}**`
+ selectionStart = start + 2
+ selectionEnd = selectionStart + text.length
+ break
+ }
+
+ case 'italic': {
+ const text = hasSelection ? selectedText : 'italic text'
+ insertedText = `*${text}*`
+ selectionStart = start + 1
+ selectionEnd = selectionStart + text.length
+ break
+ }
+
+ case 'heading': {
+ if (hasSelection) {
+ insertedText = selectedText
+ .split('\n')
+ .map(line => line.startsWith('# ') ? line : `# ${line}`)
+ .join('\n')
+
+ selectionStart = start
+ selectionEnd = start + insertedText.length
+ } else {
+ const text = 'Heading'
+ insertedText = `# ${text}`
+ selectionStart = start + 2
+ selectionEnd = selectionStart + text.length
+ }
+
+ break
+ }
+
+ case 'bullet_list': {
+ if (hasSelection) {
+ insertedText = selectedText
+ .split('\n')
+ .map(line => line.startsWith('- ') ? line : `- ${line}`)
+ .join('\n')
+
+ selectionStart = start
+ selectionEnd = start + insertedText.length
+ } else {
+ insertedText = '- '
+ selectionStart = start + 2
+ selectionEnd = start + insertedText.length
+ }
+
+ break
+ }
+
+ case 'code':
+ default: {
+ if (hasSelection) {
+ if (selectedText.includes('\n')) {
+ insertedText = `\`\`\`\n${selectedText}\n\`\`\``
+ selectionStart = start + 4
+ selectionEnd = selectionStart + selectedText.length
+ } else {
+ insertedText = `\`${selectedText}\``
+ selectionStart = start + 1
+ selectionEnd = selectionStart + selectedText.length
+ }
+ } else {
+ const text = 'code'
+ insertedText = `\`\`\`\n${text}\n\`\`\``
+ selectionStart = start + 4
+ selectionEnd = selectionStart + text.length
+ }
+
+ break
+ }
+ }
+
+ const nextValue = value.slice(0, start) + insertedText + value.slice(end)
+ input.value = nextValue
+ input.setSelectionRange(selectionStart, selectionEnd)
+ input.dispatchEvent(new Event('input', { bubbles: true }))
+
+ return nextValue
+}