diff --git a/blazor/smart-ai-solutions/ai-samples/kanban/sentiment-analysis.md b/blazor/smart-ai-solutions/ai-samples/kanban/sentiment-analysis.md index d66c1982d1..e35814a715 100644 --- a/blazor/smart-ai-solutions/ai-samples/kanban/sentiment-analysis.md +++ b/blazor/smart-ai-solutions/ai-samples/kanban/sentiment-analysis.md @@ -228,9 +228,21 @@ await builder.Build().RunAsync(); {% endhighlight %} {% endtabs %} -## Razor Component +## AI-powered Sentiment Analysis in Kanban -This section demonstrates how to implement sentiment analysis in the Syncfusion Blazor Kanban component using AI. The AI Assistant evaluates the emotional tone of each task description and displays a corresponding emoji (๐Ÿ˜Š, ๐Ÿ˜, ๐Ÿ˜ž) to help teams quickly assess the mood or urgency of tasks. This can be especially useful in agile workflows where emotional context can influence task priority and team communication. +The AI-powered sentiment analysis feature in Syncfusion Blazor Kanban evaluates customer feedback for delivered tasks and displays emojis (๐Ÿ˜Š, ๐Ÿ˜, ๐Ÿ˜ž) to indicate positive, neutral, or negative sentiment. This helps teams quickly assess satisfaction levels and prioritize follow-up actions, improving customer experience in service workflows. + +### UI Structure and User Interaction (`Home.razor`) + +The Razor page includes a Kanban board for managing pizza orders across columns like Menu, Order, Ready to Serve, and Delivered. A progress button triggers sentiment analysis, while custom card templates display task details such as title, description, and delivery date. After analysis, an emoji appears on delivered tasks to represent sentiment. A dialog template supports editing fields like category, size, and feedback, with conditional rendering based on task type. + +### AI Sentiment Analysis Logic (`Home.razor.cs`) + +The `GetScore` method serializes Kanban data to JSON and sends it to the AI service with a prompt requesting a `SentimentScore` (1โ€“5) based on feedback. The AI response is cleaned and deserialized into the data model, updating each taskโ€™s score and assigning emojis: ๐Ÿ˜ข for 1โ€“2, ๐Ÿ˜ for 3, and ๐Ÿ˜€ for 4โ€“5. State flags like `ShowScore` control rendering, while progress events (`Begin`, `End`) update button text during processing. + +### Data Binding and Error Handling + +The `SfKanban` binds to the `Pizza` list using `Category` as the `KeyField`, with dynamic columns from `columnData`. Card templates conditionally render sentiment emojis for delivered tasks. If AI analysis fails, the spinner state prevents premature interactions, and `StateHasChanged()` refreshes the UI after completion. (`Home.razor`) @@ -520,10 +532,6 @@ If the AI service fails to return a valid response, the Kanban will display an e - **Network Issues**: Check connectivity to the AI service endpoint, especially for self-hosted Ollama instances. - **Large Prompts**: Processing large text inputs may cause timeouts. Consider reducing the prompt size or optimizing the request for efficiency. -## Performance Considerations - -When handling large text content, ensure the Ollama server has sufficient resources (CPU/GPU) to process requests efficiently. For long-form content or batch operations, consider splitting the input into smaller segments to avoid performance bottlenecks. Test the application with your specific use case to determine optimal performance. - ## Sample Code A complete working example is available in the [Syncfusion Blazor AI Samples GitHub repository](https://github.com/syncfusion/smart-ai-samples). diff --git a/blazor/smart-ai-solutions/ai-samples/kanban/smart-task-suggestion.md b/blazor/smart-ai-solutions/ai-samples/kanban/smart-task-suggestion.md index a8034ddae5..a4ad47896a 100644 --- a/blazor/smart-ai-solutions/ai-samples/kanban/smart-task-suggestion.md +++ b/blazor/smart-ai-solutions/ai-samples/kanban/smart-task-suggestion.md @@ -228,342 +228,215 @@ await builder.Build().RunAsync(); {% endhighlight %} {% endtabs %} -## Razor Component +## AI-powered Kanban Smart Card Generation -This section demonstrates how to implement the Syncfusion Blazor Kanban component with AI-powered task generation. The AI Assistant analyzes the provided project description and automatically suggests relevant tasks, which are then displayed in the Kanban board. +The AI-powered Kanban feature integrates Blazor Kanban with an AI service to automatically generate structured task cards based on user input. This functionality simplifies project planning by allowing users to enter basic project details and the desired number of tasks. The backend logic then uses AI to create well-defined tasks, including fields like **Title**, **Description**, **Status**, and **Story Points**, and dynamically updates the Kanban board. This approach ensures faster task creation, consistent formatting, and an interactive experience for managing projects. -(`Home.razor`) +### UI Structure + +The Kanban AI interface starts with a simple form where users provide **Project Details** and the **Number of Tasks** to generate. Below these inputs, a Progress Button labeled โ€œGenerate Tasksโ€ triggers the AI process. This button shows a loading state during task generation for better user feedback. + +- **Project Details:** A multiline text box for describing the project. +- **Number of Tasks:** A numeric input for specifying how many tasks to generate. +- **Generate Button:** A progress-enabled button that calls `GenerateTasks()` to start AI-based task creation. + +{% tabs %} +{% highlight %} + +
+ +
+
+ +
+
+ +
+
+ +
+
+ + + +
+ +{% endhighlight %} +{% endtabs %} + +### Generating Tasks with AI + +After the user enters project details and the number of tasks, clicking Generate Tasks calls `GenerateTasks()`, which triggers `GenerateProjectTasks()` to build a detailed prompt for the AI service. This prompt includes the project description, task count, and strict instructions to return data in JSON format with fields like Id, Title, Description, Status, StoryPoints, and Color. + +The prompt is then sent to the AI using `GetCompletionAsync()`, which processes the request and returns a JSON response containing the generated tasks. The response is cleaned to remove unnecessary formatting and deserialized into a list of `SmartSuggestionDataModel` objects. These tasks are stored in `smartSuggestion` and displayed in the Kanban board or backlog view. ```csharp -@rendermode InteractiveServer -@inject AzureAIService OpenAIService -@using Syncfusion.Blazor.Kanban -@using Syncfusion.Blazor.Buttons -@using Syncfusion.Blazor.SplitButtons -@using Syncfusion.Blazor.Popups -@using Syncfusion.Blazor.Inputs -@using Syncfusion.Blazor.Notifications -@using BlazorApp4.Components.Models -@using BlazorApp4.Components.Service -@using Syncfusion.Blazor.Grids - -@if (isHomapage) +private async Task GenerateProjectTasks() { -
-
-
-
-

AI Smart Task Suggestion

-
-
-
-
-
-
- -
-
- -
-
- -
-
- -
-
- - - -
-
-
-
-
-
-
-
+ try + { + if (!string.IsNullOrEmpty(TextBoxValue) && !string.IsNullOrEmpty(TasksValue)) + { + string result = ""; + var description = $"Generate {TasksValue} task recommendations for {TextBoxValue}. Each task should include the following fields: Id (like example: ID should be in project name simple 4char word - 1), Title, Status, Description, Assignee, StoryPoints, Color and Due Date, formatted according to the dataset. Assign each task to the Assignee: empty string, set the Status to 'Open', and use black for the Color. Use the dataset provided below to create your recommendations. IMPORTANT: Return the data strictly in JSON format with all the required fields. Only the JSON data is needed, no additional text.Return only the JSON array format without any explanations."; + result = await OpenAIService.GetCompletionAsync(description); + if (result != null) + { + string data = result.Replace("```json", "").Replace("```", "").Replace("\r", "").Replace("\n", "").Replace("\t", "").Trim(); + List modifiedData; + modifiedData = JsonSerializer.Deserialize>(data); + smartSuggestion = modifiedData != null ? smartSuggestion.Concat(modifiedData).ToList() : smartSuggestion; + this.isGeneratedProjectTasks = true; + } + ContentGenerateTask = "Generate Tasks"; + } + else + { + waringText = string.IsNullOrEmpty(TextBoxValue) && string.IsNullOrEmpty(TasksValue) ? "Enter the required task creation details" : !string.IsNullOrEmpty(TasksValue) ? "Enter the Project Details" : "Enter the number of tasks"; + } + } + catch + { + await this.ToastObj.ShowAsync(new ToastModel { ContentTemplate = @GetTemplate(), ShowCloseButton = true, Timeout = 0 }); + } } -else -{ -
-
-
- @if (showBacklogs) - { -
-

Kanban Board

-
- Add New Projects -
-
- + +``` + +### Displaying AI-Generated Tasks in Kanban Cards + +Once the AI-generated tasks are processed and stored in `smartSuggestion`, they are displayed in two ways: + +1. Kanban Board View +2. Backlog Grid View + +#### Kanban Board View + +The Kanban board uses `SfKanban` to organize tasks into columns like **To Do**, **In Progress**, **Review**, and **Done**, based on the Status field. Each card shows the task title, description, and story points using a custom template for better readability. + +- **Kanban Columns:** Defined by KeyField values such as Open, InProgress, Review, and Close. +- **Card Template:** Displays Title, Description, and StoryPoints in a structured layout. +- **Dynamic Binding:** The [DataSource](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Kanban.SfKanban-1.html#Syncfusion_Blazor_Kanban_SfKanban_1_DataSource) property binds to `smartSuggestion`, ensuring that newly generated tasks appear instantly. + +{% tabs %} +{% highlight %} + + + + + + + + + + + + + +{% endhighlight %} +{% endtabs %} + +#### Backlog Grid View + +When users switch to **Backlog View**, tasks are displayed in a grid using `SfGrid`. This view allows adding, deleting, and editing tasks through a **dialog popup** for structured input. + +- **Dialog Editing:** Uses [GridEditSettings](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_EditSettings) with [Dialog](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.GridEditSettings.html#Syncfusion_Blazor_Grids_GridEditSettings_Dialog) mode for structured editing. +- **Validation:** Ensures required fields like Task ID, Title, and Status are filled. +- **Data Binding:** The [DataSource](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.Grids.SfGrid-1.html#Syncfusion_Blazor_Grids_SfGrid_1_DataSource) property binds to `smartSuggestion`, just like Kanban. + +{% tabs %} +{% highlight %} + + + + + + + + + + + + + + -``` +{% endhighlight %} +{% endtabs %} + +**View Toggle:** Users can switch between **Kanban** and **Grid** using the button below: + +{% tabs %} +{% highlight %} -`Home.razor.cs` + + +{% endhighlight %} +{% endtabs %} ```csharp -using System.Text.Json; -using AISamples.Components.Models; -using Microsoft.AspNetCore.Components; -using Syncfusion.Blazor.Grids; -using Syncfusion.Blazor.Kanban; -using Syncfusion.Blazor.Notifications; - -namespace AISamples.Components.Pages +private void GoToBacklogBoardView() { - public partial class Home + if (BacklogButtonViewContent == "View as Board") { - SfToast ToastObj; - public string[] SelectedAssignees { get; set; } = new string[] { }; - private string ToastTarget { get; set; } = "#toast-kanban-observable"; - SfKanban kanbanObj; - public string ContentGenerateTask = "Generate Tasks"; - public string BacklogButtonViewContent = "View as Backlog"; - private string TextBoxValue = string.Empty; - private string TasksValue = string.Empty; - private bool enableProjectDetailsDialog = false; - private bool isGeneratedProjectTasks = false; - private bool enableTaskEditing = false; - private bool isHomapage = true; - private bool showSprintBoard = false; - private bool showBacklogs = false; - private bool showBacklogBoard = true; - private List smartSuggestion = new List(); - private DialogSettings DialogParams = new DialogSettings { MinHeight = "400px", Width = "450px" }; - private string waringText = "Click \"Generate Tasks\" to preview"; - private void GoToBacklogBoardView() - { - if (BacklogButtonViewContent == "View as Board") - { - BacklogButtonViewContent = "View as Backlog"; - showBacklogBoard = true; - } - else - { - showBacklogBoard = false; - BacklogButtonViewContent = "View as Board"; - } - showSprintBoard = false; - } - private RenderFragment GetTemplate() => builder => - { - builder.OpenElement(0, "div"); - builder.AddContent(1, "An error occurred during the AI process, Please try again."); - builder.CloseElement(); - }; - public void BeginGenerateTasks(Syncfusion.Blazor.SplitButtons.ProgressEventArgs args) - { - ContentGenerateTask = "Generating..."; - } - public async Task EndGenerateTasks(Syncfusion.Blazor.SplitButtons.ProgressEventArgs args) - { - while (!isGeneratedProjectTasks) - { - await Task.Delay(1000); - } - this.isHomapage = false; - this.CloseDialog(); - showBacklogs = true; - } - private async Task GenerateProjectTasks() - { - try - { - if (!string.IsNullOrEmpty(TextBoxValue) && !string.IsNullOrEmpty(TasksValue)) - { - string result = ""; - var description = $"Generate {TasksValue} task recommendations for {TextBoxValue}. Each task should include the following fields: Id (like example: ID should be in project name simple 4char word - 1), Title, Status, Description, Assignee, StoryPoints, Color and Due Date, formatted according to the dataset. Assign each task to the Assignee: empty string, set the Status to 'Open', and use black for the Color. Use the dataset provided below to create your recommendations. IMPORTANT: Return the data strictly in JSON format with all the required fields. Only the JSON data is needed, no additional text.Return only the JSON array format without any explanations."; - result = await OpenAIService.GetCompletionAsync(description); - if (result != null) - { - string data = result.Replace("```json", "").Replace("```", "").Replace("\r", "").Replace("\n", "").Replace("\t", "").Trim(); - List modifiedData; - modifiedData = JsonSerializer.Deserialize>(data); - smartSuggestion = modifiedData != null ? smartSuggestion.Concat(modifiedData).ToList() : smartSuggestion; - this.isGeneratedProjectTasks = true; - } - ContentGenerateTask = "Generate Tasks"; - } - else - { - waringText = string.IsNullOrEmpty(TextBoxValue) && string.IsNullOrEmpty(TasksValue) ? "Enter the required task creation details" : !string.IsNullOrEmpty(TasksValue) ? "Enter the Project Details" : "Enter the number of tasks"; - } - } - catch - { - await this.ToastObj.ShowAsync(new ToastModel { ContentTemplate = @GetTemplate(), ShowCloseButton = true, Timeout = 0 }); - } - } - private void OpenProjectDetailsDialog() - { - this.enableProjectDetailsDialog = true; - } - private async Task GenerateTasks() - { - this.isGeneratedProjectTasks = false; - await this.GenerateProjectTasks(); - } - private void SaveTask() - { - this.enableProjectDetailsDialog = false; - this.TasksValue = string.Empty; - this.TextBoxValue = string.Empty; - this.isGeneratedProjectTasks = false; - StateHasChanged(); - } - private void CloseDialog() - { - this.enableProjectDetailsDialog = false; - this.TasksValue = string.Empty; - this.TextBoxValue = string.Empty; - this.isGeneratedProjectTasks = false; - StateHasChanged(); - } - public void TaskEditingHandler(Syncfusion.Blazor.Grids.ActionEventArgs args) - { - if (args.RequestType.ToString() == "Add") - { - enableTaskEditing = true; - } - else - { - enableTaskEditing = false; - } - } - public void RowCreatedHandler(RowCreatedEventArgs args) - { - args.Data.Status = "Open"; - args.Data.Color = "#000000"; - } + BacklogButtonViewContent = "View as Backlog"; + showBacklogBoard = true; + } + else + { + showBacklogBoard = false; + BacklogButtonViewContent = "View as Board"; } } - ``` +## Sample Code + +A complete working example is available in the [Syncfusion Blazor AI Samples GitHub repository](https://github.com/syncfusion/smart-ai-samples). + +![Kanban AI Assistant - Output](../../ai/images/smart-task-suggestion.png) + ## Error Handling and Troubleshooting If the AI service fails to return a valid response, the Kanban will display an error message ("Oops! Please try again!"). Common issues include: @@ -573,12 +446,3 @@ If the AI service fails to return a valid response, the Kanban will display an e - **Network Issues**: Check connectivity to the AI service endpoint, especially for self-hosted Ollama instances. - **Large Prompts**: Processing large text inputs may cause timeouts. Consider reducing the prompt size or optimizing the request for efficiency. -## Performance Considerations - -When handling large text content, ensure the Ollama server has sufficient resources (CPU/GPU) to process requests efficiently. For long-form content or batch operations, consider splitting the input into smaller segments to avoid performance bottlenecks. Test the application with your specific use case to determine optimal performance. - -## Sample Code - -A complete working example is available in the [Syncfusion Blazor AI Samples GitHub repository](https://github.com/syncfusion/smart-ai-samples). - -![Kanban AI Assistant - Output](../../ai/images/smart-task-suggestion.png) \ No newline at end of file diff --git a/blazor/smart-ai-solutions/ai-samples/rich-text-editor/writting-assistance.md b/blazor/smart-ai-solutions/ai-samples/rich-text-editor/writting-assistance.md index 3074d17360..f6c75df29c 100644 --- a/blazor/smart-ai-solutions/ai-samples/rich-text-editor/writting-assistance.md +++ b/blazor/smart-ai-solutions/ai-samples/rich-text-editor/writting-assistance.md @@ -228,432 +228,376 @@ await builder.Build().RunAsync(); {% endhighlight %} {% endtabs %} -## Razor Component +## AI-powered Rich Text Editor in Blazor -This section implements the Syncfusion Blazor Rich Text Editor with AI-powered content assistance features such as rephrase, correct grammar, summarize, elaborate, and translate. +This guide explains how to integrate a full-featured AI writing assistant into the Blazor Rich Text Editor using Azure OpenAI (via Semantic Kernel). The implementation supports **Rephrase**, **Correct Grammar**, **Summarize**, **Elaborate**, and **Translate** with live preview, dynamic tone and language controls, skeleton loading, and safe content replacement with undo support. -(`Home.razor`) +### How the Custom Toolbar is Rendered -```csharp -@inject AzureAIService semanticKernelAI -@inject IJSRuntime JSRuntime -@using AISamples.Components.Service -@using Syncfusion.Blazor.RichTextEditor -@using Syncfusion.Blazor.SplitButtons -@using Syncfusion.Blazor.Buttons -@using Syncfusion.Blazor.Popups -@using Syncfusion.Blazor.Notifications -@using Syncfusion.Blazor.DropDowns - -
- - - - - - - - - - - - - -
-
- - -
AI Assistant
- -
-
-
- - +The toolbar uses [RichTextEditorCustomToolbarItems](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.RichTextEditor.RichTextEditorCustomToolbarItems.html) to inject a custom item named AI. + +Inside the template, an `SfDropDownButton` is rendered with: + +- **Icon**: AI icon (e-icons e-ai-chat) +- **Dropdown Items**: Rephrase, Grammar, Summarize, Elaborate, Translate +- **Event Binding**: ItemSelected="AIQuerySelectedMenu" triggers the AI workflow when an option is selected. + +An additional `SfButton` for **Rephrase** is provided for quick access. + +{% tabs %} +{% highlight razor %} + + + + + + + + + + + + +{% endhighlight %} +{% endtabs %} + +### AI Assistant Dialog โ€“ Layout and Dynamic Controls + +The AI Assistant dialog provides a responsive, user-friendly interface for interacting with AI-generated content. It is designed as a modal dialog that overlays the editor and ensures proper z-index stacking for seamless integration. + +#### Dialog Structure + +The dialog is divided into two main rows: + +**Row 1:** Action Selection and Dynamic Controls + +- **Action Dropdown:** Allows users to switch between AI actions (Rephrase, Grammar, Summarize, Elaborate, Translate). +- **Dynamic Controls:** + - **Tone Chips** (Standard, Fluent, Professional) appear when the selected action is Rephrase. + - **Language Dropdown** (15+ languages) appears when the selected action is Translate. + +**Row 2:** Original Text vs AI Result + +- **Left Panel:** Displays the original selected text in a read-only Rich Text Editor. +- **Right Panel:** Shows AI-generated content in another Rich Text Editor. +- **Loading State:** While AI processes the request, a progressive skeleton loader animates from 100% to 10% width for visual feedback. +- **Fallback State:** If no result is returned, a โ€œNo results foundโ€ message with a warning icon is displayed. + +**Footer Actions** + +- **Regenerate:** Requests a new AI output for the same prompt. +- **Copy:** Copies the AI-generated content to the clipboard. +- **Replace:** Inserts the AI-generated content back into the original editor with undo support. + +{% tabs %} +{% highlight %} + + + + +
+ AI Assistant +
+ + + +
+
+
+ + + + + + +
+
+ +
+ @if (this.enableRephraseChips) + { + + + + + + + + + + } + else if (this.enableLanguageList) + { +
+ Target Language +
+
+ + - +
-
-
- @if (this.enableRephraseChips) - { - - - - - - - - - } - else if (this.enableLanguageList) - { -
- Target Language -
-
- - - - -
- } -
+ }
-
-
-
- - - -
+
+ +
+
+
+ + + +
- @if (!isContentGenerating) +
+ + @if (!isContentGenerating) + { + @if (noResultsFound) { - @if (noResultsFound) - { -
-
-
- -
No results found
-
-
-
- } - else { -
-
- - - + +
+
+
+ +
No results found
- } - } else { +
+ } + else {
-
-
-
-
-
-
- -
+ + + +
} -
- - -
-
+ } else { +
- Regenerate +
+ +
+
+
+
+
+ +
-
- @if (!string.IsNullOrEmpty(sentiment)) { - - } - Copy - Replace + } +
+ + + +
+
+
+ + Regenerate
- - - - -
- - - +
+ @if (!string.IsNullOrEmpty(sentiment)) { + + } + + Copy + Replace +
+
+ + + + + +{% endhighlight %} +{% endtabs %} + +### Opening the AI Dialog and Preserving Selection + +The `DialogueOpen()` method opens the AI dialog when an action is selected and prepares the editor for processing. It first uses `GetSelectedHtmlAsync()` to capture the highlighted content with its formatting. If no text is selected, a toast message alerts the user to select content before proceeding. + +When valid text is found, the method makes the dialog visible, stores the selected HTML for AI processing, and calls `SaveSelectionAsync()` to preserve the cursor position so the AI output can replace only the selected text later. It then refreshes the dialogโ€™s editor and updates AI suggestions based on the chosen action. + + +```csharp +private async Task Rephrase() +{ + // Directly open dialog for Rephrase action + await DialogueOpen("Rephrase"); +} +private async Task AIQuerySelectedMenu(MenuEventArgs args) +{ + // Open dialog based on selected dropdown item + await DialogueOpen(args.Item.Text); +} +private async Task DialogueOpen(string selectedQuery) +{ + // Get the selected text from the RichTextEditor + var selectionText = await rteObj.GetSelectedHtmlAsync(); + + if (!string.IsNullOrEmpty(selectionText)) + { + // Make the dialog visible + dialogVisible = true; + + // Identify the selected query and map it to its ID + + dropVal = QueryList.FirstOrDefault(q => q.Text.Equals(selectedQuery, StringComparison.OrdinalIgnoreCase))?.ID; + + // Store the selected text for AI processing + promptQuery = selectionText; + + // Save the current selection so AI output can be inserted later + await this.rteObj.SaveSelectionAsync(); + + // Refresh the left-side RichTextEditor in the dialog to show original text + await this.leftRteChildObj.RefreshUIAsync(); + + // Update AI suggestions based on the selected query + await UpdateAISuggestionsData(selectedQuery); + } + else + { + // Show a toast notification if no text is selected + await this.ToastObj.ShowAsync(new ToastModel { ContentTemplate = @GetTemplate(true), ShowCloseButton = true, Timeout = 0 }); + } +} ``` -`Home.razor.cs` +### Building and Sending the AI + +The AI prompt is built in two parts: **user intent** and **system instruction**. + +1. **User Intent (subQuery)** - Defines what the AI should do, such as summarizing, rephrasing with a specific tone, or translating into a target language. This makes the prompt context-aware and action-specific. + +2. **System Instruction (HTML preservation)** - Enforces formatting rules, for example: โ€œRetain existing HTML structure. Modify content only.โ€ This ensures that the AI preserves elements like bold text, lists, and links while updating the content. + +Both parts are combined and sent to `semanticKernelAI.GetCompletionAsync()` via Semantic Kernel, resulting in an AI-generated output that is accurate and safe to insert back into the editor. ```csharp -using Markdig; -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using Syncfusion.Blazor.DropDowns; -using Syncfusion.Blazor.Inputs; -using Syncfusion.Blazor.Notifications; -using Syncfusion.Blazor.RichTextEditor; -using Syncfusion.Blazor.SplitButtons; - -namespace AISamples.Components.Pages + +private async Task UpdateAISuggestionsData(string selectedQuery) { - public partial class Home + enableRephraseChips = false; + enableLanguageList = false; + isSentimentCheck = false; + switch (selectedQuery) { - SfToast ToastObj; - private string ToastTarget { get; set; } = "#scroll-restricted"; - SfRichTextEditor rteObj; - SfRichTextEditor leftRteChildObj; - SfRichTextEditor rightRteChildObj; - private string Value { get; set; } = "
Integrate AI with the Editor

Integrate the AI assistant into the rich text editor by capturing the content from the editor, sending it to the AI service, and displaying the results or suggestions back in the editor.

Summarize

This function condenses the selected content into a brief summary, capturing the main points succinctly.

Elaborate

This function expands the selected content, adding additional details and context.

Rephrase

This function rewrites the selected content to convey the same meaning using different words or structures. It also enables rephrase options and disables language selection.

Correct Grammar

This function reviews and corrects the grammar of the selected content, ensuring it adheres to standard grammatical rules.

Translate

This function translates the selected content into the specified language, enabling language selection and disabling rephrase options.

"; - private bool dialogVisible { get; set; } - private bool enabelAIAssitantButton { get; set; } = false; - private bool enabelRegenerateContentButton { get; set; } = false; - private bool enabelContentButton { get; set; } = true; - private string promptQuery = string.Empty; - private string subQuery = string.Empty; - private string[] chipValue = new[] { "Standard" }; - private string translatelanguage = "EN"; - private string dropVal { get; set; } = "Rephrase"; - private bool enableRephraseChips { get; set; } = true; - private bool enableLanguageList { get; set; } = false; - private bool noResultsFound { get; set; } = false; - public bool isContentGenerating { get; set; } = true; - private string AIResult { get; set; } = string.Empty; - private bool isSentimentCheck { get; set; } = false; - private MarkdownPipeline pipeline { get; set; } = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); - private string sentiment = ""; - private string apiResultData = ""; - private string ButtonClass = "e-tbar-btn"; - private void UpdateStatus(Syncfusion.Blazor.RichTextEditor.ChangeEventArgs args) - { - Value = args.Value; - enabelAIAssitantButton = string.IsNullOrWhiteSpace(Value); - } - private void OnActionCompleteHandler(Syncfusion.Blazor.RichTextEditor.ActionCompleteEventArgs args) - { - if (args.RequestType == "SourceCode") - { - this.ButtonClass = "e-tbar-btn e-overlay"; - } - if (args.RequestType == "Preview") - { - this.ButtonClass = "e-tbar-btn"; - } - } - private void UpdateTextAreaStatus(InputEventArgs args) - { - Value = args.Value; - enabelRegenerateContentButton = string.IsNullOrWhiteSpace(Value); - } - private async Task AIQuerySelectedMenu(MenuEventArgs args) - { - await DialogueOpen(args.Item.Text); - } - private async Task Rephrase() - { - await DialogueOpen("Rephrase"); - } - private async Task DialogueOpen(string selectedQuery) - { - var selectionText = await rteObj.GetSelectedHtmlAsync(); - if (!string.IsNullOrEmpty(selectionText)) - { - dialogVisible = true; - dropVal = QueryList.FirstOrDefault(q => q.Text.Equals(selectedQuery, StringComparison.OrdinalIgnoreCase))?.ID; - promptQuery = selectionText; - await this.rteObj.SaveSelectionAsync(); - await this.leftRteChildObj.RefreshUIAsync(); - await UpdateAISuggestionsData(selectedQuery); - } - else - { - await this.ToastObj.ShowAsync(new ToastModel { ContentTemplate = @GetTemplate(true), ShowCloseButton = true, Timeout = 0 }); - } - } - private async Task SelectedChipsChanged(Syncfusion.Blazor.Buttons.SelectionChangedEventArgs args) - { - if (chipValue.Length == 0 && args != null && args.RemovedItems.Length > 0) - { - chipValue = new [] { args.RemovedItems[0] }; - } - await UpdateAISuggestionsData("Rephrase"); - } - private async Task AITranslateDropdownList(ChangeEventArgs args) - { - await UpdateAISuggestionsData("Translate"); - } - private async Task AIQuerySelectedDropdownList(ChangeEventArgs args) - { - if (!string.IsNullOrEmpty(dropVal)) - { - chipValue = new[] { "Standard" }; - translatelanguage = "EN"; - var selectedQuery = QueryList.FirstOrDefault(q => q.ID.Equals(dropVal, StringComparison.OrdinalIgnoreCase))?.Text; - await UpdateAISuggestionsData(selectedQuery); - } - } - private async Task UpdateAISuggestionsData(string selectedQuery) - { - enableRephraseChips = false; - enableLanguageList = false; - isSentimentCheck = false; - switch (selectedQuery) - { - case "Summarize": - subQuery = "Briefly summarize the following text in a short and concise manner."; - break; - case "Elaborate": - subQuery = "Elaborate/Expand on the following text, providing more detail and context."; - break; - case "Rephrase": - enableRephraseChips = true; - enableLanguageList = false; - subQuery = $"Rephrase the following text in a {chipValue[0]} [tone/style], ensuring clarity and maintaining the original meaning."; - break; - case "Correct Grammar": - subQuery = "Correct any grammatical errors in the following text, ensuring it is clear and well-structured."; - break; - case "Translate": - enableLanguageList = true; - enableRephraseChips = false; - subQuery = $"Translate the following text into {translatelanguage}, preserving the original meaning and tone."; - break; - } - UpdateAISuggestionsData(); - } - private async Task RegenerateContent() - { - UpdateAISuggestionsData(); - } - private async Task ReplaceContent() - { - ExecuteCommandOption executeCommandOption = new ExecuteCommandOption(); - executeCommandOption.Undo = true; - await this.rteObj.RestoreSelectionAsync(); - await this.rteObj.ExecuteCommandAsync(CommandName.InsertHTML, this.apiResultData, executeCommandOption); - await CloseDialog(); - } - private async Task CopyContent() - { - await JSRuntime.InvokeVoidAsync("copyToClipboard", Markdig.Markdown.ToPlainText(AIResult, pipeline)); - } - private async Task CloseDialog() - { - dialogVisible = false; - promptQuery = string.Empty; - AIResult = string.Empty; - chipValue = new[] { "Standard" }; - dropVal = "Query1"; + case "Summarize": + subQuery = "Briefly summarize the following text in a short and concise manner."; + break; + case "Elaborate": + subQuery = "Elaborate/Expand on the following text, providing more detail and context."; + break; + case "Rephrase": enableRephraseChips = true; enableLanguageList = false; - sentiment = ""; - apiResultData = ""; - } - private async Task UpdateAISuggestionsData() + subQuery = $"Rephrase the following text in a {chipValue[0]} [tone/style], ensuring clarity and maintaining the original meaning."; + break; + case "Correct Grammar": + subQuery = "Correct any grammatical errors in the following text, ensuring it is clear and well-structured."; + break; + case "Translate": + enableLanguageList = true; + enableRephraseChips = false; + subQuery = $"Translate the following text into {translatelanguage}, preserving the original meaning and tone."; + break; + } + UpdateAISuggestionsData(); +} + +private async Task UpdateAISuggestionsData() +{ + try + { + if (!string.IsNullOrEmpty(promptQuery)) { - try - { - if (!string.IsNullOrEmpty(promptQuery)) - { - enabelRegenerateContentButton = isContentGenerating = enabelContentButton = true; - string systemPrompt = subQuery.Contains("emoji followed by the sentiment in the format") ? "You are a helpful assistant. Please respond in string format." : "NOTE:Please retain the existing HTML structure and modify the content only. Ensure that the response adheres to the specified formatting."; - apiResultData = await semanticKernelAI.GetCompletionAsync(promptQuery, false, false, (subQuery + systemPrompt)); - if (apiResultData != null) - { - isContentGenerating = false; - sentiment = isSentimentCheck ? apiResultData.Replace("\"", "").Replace("'", "") : ""; - AIResult = isSentimentCheck ? promptQuery : apiResultData; - noResultsFound = string.IsNullOrEmpty(AIResult) || string.IsNullOrEmpty(promptQuery); - enabelRegenerateContentButton = enabelContentButton = noResultsFound; - await InvokeAsync(StateHasChanged); - } - else - { - isContentGenerating = false; - await InvokeAsync(StateHasChanged); - } - } - } - catch - { - await this.ToastObj.ShowAsync(new ToastModel { ContentTemplate = @GetTemplate(), ShowCloseButton = true, Timeout = 0 }); - } - } - private RenderFragment GetTemplate(bool hasTextSelection = false) => builder => - { - builder.OpenElement(0, "div"); - builder.AddContent(1, hasTextSelection ? "Please select the content to perform the AI operation." : "An error occurred during the AI process, Please try again."); - builder.CloseElement(); - }; - public class SubQuery + enabelRegenerateContentButton = isContentGenerating = enabelContentButton = true; + string systemPrompt = subQuery.Contains("emoji followed by the sentiment in the format") ? "You are a helpful assistant. Please respond in string format." : "NOTE:Please retain the existing HTML structure and modify the content only. Ensure that the response adheres to the specified formatting."; + apiResultData = await semanticKernelAI.GetCompletionAsync(promptQuery, false, false, (subQuery + systemPrompt)); + if (apiResultData != null) { - public string ID { get; set; } - public string Text { get; set; } + isContentGenerating = false; + sentiment = isSentimentCheck ? apiResultData.Replace("\"", "").Replace("'", "") : ""; + AIResult = isSentimentCheck ? promptQuery : apiResultData; + noResultsFound = string.IsNullOrEmpty(AIResult) || string.IsNullOrEmpty(promptQuery); + enabelRegenerateContentButton = enabelContentButton = noResultsFound; + await InvokeAsync(StateHasChanged); } - public class Languages + else { - public string ID { get; set; } - public string Text { get; set; } + isContentGenerating = false; + await InvokeAsync(StateHasChanged); } - public List QueryList = new List - { - new SubQuery { ID = "Rephrase", Text = "Rephrase" }, - new SubQuery { ID = "Grammar", Text = "Correct Grammar" }, - new SubQuery { ID = "Summarize", Text = "Summarize" }, - new SubQuery { ID = "Elaborate", Text = "Elaborate" }, - new SubQuery { ID = "Translate", Text = "Translate" } - }; - public List LanguageList = new List - { - new Languages { ID = "EN", Text = "English" }, - new Languages { ID = "ZH", Text = "Chinese (Simplified)" }, - new Languages { ID = "ZHT", Text = "Chinese (Traditional)" }, - new Languages { ID = "ES", Text = "Spanish" }, - new Languages { ID = "HI", Text = "Hindi" }, - new Languages { ID = "AR", Text = "Arabic" }, - new Languages { ID = "BN", Text = "Bengali" }, - new Languages { ID = "PT", Text = "Portuguese" }, - new Languages { ID = "RU", Text = "Russian" }, - new Languages { ID = "JA", Text = "Japanese" }, - new Languages { ID = "DE", Text = "German" }, - new Languages { ID = "KO", Text = "Korean" }, - new Languages { ID = "FR", Text = "French" }, - new Languages { ID = "IT", Text = "Italian" }, - new Languages { ID = "TR", Text = "Turkish" } - }; - private List Tools = new List() - { - new ToolbarItemModel() { Name = "AIAssistant", TooltipText = "AI Assistant" }, - new ToolbarItemModel() { Name = "Rephrase", TooltipText = "Rephrase" }, - new ToolbarItemModel() { Command = ToolbarCommand.Bold }, - new ToolbarItemModel() { Command = ToolbarCommand.Italic }, - new ToolbarItemModel() { Command = ToolbarCommand.Underline }, - new ToolbarItemModel() { Command = ToolbarCommand.Separator }, - new ToolbarItemModel() { Command = ToolbarCommand.FontName }, - new ToolbarItemModel() { Command = ToolbarCommand.FontSize }, - new ToolbarItemModel() { Command = ToolbarCommand.FontColor }, - new ToolbarItemModel() { Command = ToolbarCommand.Separator }, - new ToolbarItemModel() { Command = ToolbarCommand.BackgroundColor }, - new ToolbarItemModel() { Command = ToolbarCommand.Formats }, - new ToolbarItemModel() { Command = ToolbarCommand.Alignments }, - new ToolbarItemModel() { Command = ToolbarCommand.Separator }, - new ToolbarItemModel() { Command = ToolbarCommand.NumberFormatList }, - new ToolbarItemModel() { Command = ToolbarCommand.BulletFormatList }, - new ToolbarItemModel() { Command = ToolbarCommand.CreateLink }, - new ToolbarItemModel() { Command = ToolbarCommand.Image }, - new ToolbarItemModel() { Command = ToolbarCommand.Separator }, - new ToolbarItemModel() { Command = ToolbarCommand.CreateTable }, - new ToolbarItemModel() { Command = ToolbarCommand.SourceCode }, - new ToolbarItemModel() { Command = ToolbarCommand.Undo }, - new ToolbarItemModel() { Command = ToolbarCommand.Redo }, - }; + } + } + catch + { + await this.ToastObj.ShowAsync(new ToastModel { ContentTemplate = @GetTemplate(), ShowCloseButton = true, Timeout = 0 }); } } + +``` + +### Replacing AI Result Back into the Editor (with Undo Support) + +Once the AI-generated content is ready, it needs to be inserted exactly where the user originally selected text, without disturbing the rest of the document. This is achieved using the [ExecuteCommandAsync](https://help.syncfusion.com/cr/blazor/Syncfusion.Blazor.RichTextEditor.SfRichTextEditor.html#Syncfusion_Blazor_RichTextEditor_SfRichTextEditor_ExecuteCommandAsync_Syncfusion_Blazor_RichTextEditor_CommandName_System_String_Syncfusion_Blazor_RichTextEditor_ExecuteCommandOption_) method with the `InsertHTML` command. The `Undo` option is enabled to allow users to revert changes if needed. + +Additionally, users can copy the AI-generated content to the clipboard for use elsewhere. This is handled via a `JavaScript interop` function that copies plain text extracted from the AI result. + +{% tabs %} +{% highlight %} + + + Copy + Replace + + +{% endhighlight %} +{% endtabs %} + +```csharp + +private async Task ReplaceContent() +{ + ExecuteCommandOption executeCommandOption = new ExecuteCommandOption(); + executeCommandOption.Undo = true; + await this.rteObj.RestoreSelectionAsync(); // Return to original selection + await this.rteObj.ExecuteCommandAsync(CommandName.InsertHTML, this.apiResultData, executeCommandOption); + await CloseDialog(); +} +private async Task CopyContent() +{ + await JSRuntime.InvokeVoidAsync("copyToClipboard", Markdig.Markdown.ToPlainText(AIResult, pipeline)); +} + +``` + -``` +## Workflow Summary + +Select text โ†’ Choose AI action โ†’ Dialog opens โ†’ AI processes โ†’ Updated content displayed โ†’ User copies or replaces content. + +## Sample Code + +A complete working example is available in the [Syncfusion Blazor AI Samples GitHub repository](https://github.com/syncfusion/smart-ai-samples). + +![Rich Text Editor AI Assistant - Output](../../ai/images/richtexteditor-ai-assistant.png) ## Error Handling and Troubleshooting @@ -695,13 +647,3 @@ If the AI service fails to return a valid response, the Rich Text Editor will di - **Model Unavailable**: Ensure the specified `openAIModel`, `azureOpenAIModel`, or `ModelName` is deployed and supported. - **Network Issues**: Check connectivity to the AI service endpoint, especially for self-hosted Ollama instances. - **Large Prompts**: Processing large text inputs may cause timeouts. Consider reducing the prompt size or optimizing the request for efficiency. - -## Performance Considerations - -When handling large text content, ensure the Ollama server has sufficient resources (CPU/GPU) to process requests efficiently. For long-form content or batch operations, consider splitting the input into smaller segments to avoid performance bottlenecks. Test the application with your specific use case to determine optimal performance. - -## Sample Code - -A complete working example is available in the [Syncfusion Blazor AI Samples GitHub repository](https://github.com/syncfusion/smart-ai-samples). - -![Rich Text Editor AI Assistant - Output](../../ai/images/richtexteditor-ai-assistant.png) \ No newline at end of file