Works now

This commit is contained in:
Peer Schütt 2026-03-12 11:57:44 +01:00
parent 1c52d6f199
commit 8a5539cdfd
4 changed files with 163 additions and 3 deletions

View File

@ -1594,15 +1594,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you s
-- Stop generation -- Stop generation
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "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 -- Save chat
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1516264254"] = "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... -- Type your input here...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "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}') -- Your Prompt (use selected instance '{0}', provider '{1}')
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "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. -- 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." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2670286472"] = "Profile usage is disabled according to your chat template settings."

View File

@ -55,6 +55,22 @@
Style="@this.UserInputStyle"/> Style="@this.UserInputStyle"/>
</MudElement> </MudElement>
<MudToolBar WrapContent="true" Gutters="@false" Class="border border-solid rounded" Style="border-color: lightgrey;"> <MudToolBar WrapContent="true" Gutters="@false" Class="border border-solid rounded" Style="border-color: lightgrey;">
<MudTooltip Text="@T("Insert code formatting")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.Code" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_CODE)" Disabled="@this.IsInputForbidden()"/>
</MudTooltip>
<MudTooltip Text="@T("Insert bold formatting")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.FormatBold" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_BOLD)" Disabled="@this.IsInputForbidden()"/>
</MudTooltip>
<MudTooltip Text="@T("Insert italic formatting")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.FormatItalic" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_ITALIC)" Disabled="@this.IsInputForbidden()"/>
</MudTooltip>
<MudTooltip Text="@T("Insert heading formatting")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.TextFields" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_HEADING)" Disabled="@this.IsInputForbidden()"/>
</MudTooltip>
<MudTooltip Text="@T("Insert bulleted list formatting")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
<MudIconButton Icon="@Icons.Material.Filled.FormatListBulleted" OnClick="() => this.ApplyMarkdownFormat(MARKDOWN_BULLET_LIST)" Disabled="@this.IsInputForbidden()"/>
</MudTooltip>
@if ( @if (
this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES
&& this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY) && this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY)
@ -127,4 +143,4 @@
<MudIconButton /> <MudIconButton />
</MudToolBar> </MudToolBar>
</FooterContent> </FooterContent>
</InnerScrolling> </InnerScrolling>

View File

@ -6,6 +6,7 @@ using AIStudio.Settings.DataModel;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using DialogOptions = AIStudio.Dialogs.DialogOptions; using DialogOptions = AIStudio.Dialogs.DialogOptions;
@ -13,6 +14,13 @@ namespace AIStudio.Components;
public partial class ChatComponent : MSGComponentBase, IAsyncDisposable 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] [Parameter]
public ChatThread? ChatThread { get; set; } public ChatThread? ChatThread { get; set; }
@ -36,6 +44,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
[Inject] [Inject]
private IDialogService DialogService { get; init; } = null!; private IDialogService DialogService { get; init; } = null!;
[Inject]
private IJSRuntime JsRuntime { get; init; } = null!;
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top; private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new(); private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
@ -73,6 +84,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// Configure the spellchecking for the user input: // Configure the spellchecking for the user input:
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
USER_INPUT_ATTRIBUTES["id"] = CHAT_INPUT_ID;
// Get the preselected profile: // Get the preselected profile:
this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT); this.currentProfile = this.SettingsManager.GetPreselectedProfile(Tools.Components.CHAT);
@ -463,6 +475,18 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
break; 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<string>("formatChatInputMarkdown", CHAT_INPUT_ID, formatType);
this.hasUnsavedChanges = true;
}
private async Task SendMessage(bool reuseLastUserPrompt = false) private async Task SendMessage(bool reuseLastUserPrompt = false)
{ {
@ -1018,4 +1042,4 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
} }
#endregion #endregion
} }

View File

@ -25,4 +25,109 @@ window.clearDiv = function (divName) {
window.scrollToBottom = function(element) { window.scrollToBottom = function(element) {
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' }); element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
} }
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
}