diff --git a/ChartGeneratorAISample/ChartGenerator/AIService/AICredentials.cs b/ChartGeneratorAISample/ChartGenerator/AIService/AICredentials.cs new file mode 100644 index 0000000..c1e34ed --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/AIService/AICredentials.cs @@ -0,0 +1,9 @@ +namespace ChartGenerator.AIService +{ + internal abstract class AICredentials + { + internal const string endpoint = "https://YOUR_ACCOUNT.openai.azure.com/"; + internal const string deploymentName = "deployment name"; + internal const string key = "API key"; + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/AIService/ChartsAIService.cs b/ChartGeneratorAISample/ChartGenerator/AIService/ChartsAIService.cs index 19bbf65..856eb95 100644 --- a/ChartGeneratorAISample/ChartGenerator/AIService/ChartsAIService.cs +++ b/ChartGeneratorAISample/ChartGenerator/AIService/ChartsAIService.cs @@ -1,38 +1,14 @@ -using Azure.AI.OpenAI; using Azure; +using Azure.AI.OpenAI; +using ChartGenerator.AIService; using Microsoft.Extensions.AI; namespace ChartGenerator { - internal class ChartAIService + internal class ChartAIService : AICredentials { #region Fields - /// - /// The EndPoint - /// - internal const string endpoint = "https://YOUR_ACCOUNT.openai.azure.com/"; - - /// - /// The Deployment name - /// - internal const string deploymentName = "deployment name"; - - /// - /// The Image Deployment name - /// - internal const string imageDeploymentName = "IMAGE_MODEL_NAME"; - - /// - /// The API key - /// - internal const string key = "API key"; - - /// - /// The already credential validated field - /// - private static bool isAlreadyValidated; - /// /// The uri result field /// @@ -64,11 +40,6 @@ private async void ValidateCredential() { this.GetAzureOpenAIKernal(); - if (isAlreadyValidated) - { - return; - } - try { if (Client != null) @@ -76,7 +47,6 @@ private async void ValidateCredential() await Client!.CompleteAsync("Hello, Test Check"); ChatHistory = string.Empty; IsCredentialValid = true; - isAlreadyValidated = true; } else { @@ -110,16 +80,27 @@ private void GetAzureOpenAIKernal() /// Retrieves an answer from the deployment name model using the provided user prompt. /// /// The user prompt. + /// Whether to include class structure context. /// The AI response. - internal async Task GetAnswerFromGPT(string userPrompt) + internal async Task GetAnswerFromGPT(string userPrompt, bool includeClassContext = false) { try { if (IsCredentialValid && Client != null) { ChatHistory = string.Empty; - // Add the system message and user message to the options - ChatHistory = ChatHistory + userPrompt; + + // Only include class context if specifically requested + if (includeClassContext) + { + var classContext = GetChartClassStructureContext(); + ChatHistory = classContext + "\n\n" + userPrompt; + } + else + { + ChatHistory = userPrompt; + } + var response = await Client.CompleteAsync(ChatHistory); return response.ToString(); } @@ -141,11 +122,104 @@ private async void ShowAlertAsync() var page = Application.Current?.Windows[0].Page; if (page != null && !IsCredentialValid) { - isAlreadyValidated = true; await page.DisplayAlert("Alert", "The Azure API key or endpoint is missing or incorrect. Please verify your credentials. You can also continue with the offline data.", "OK"); } } - } + /// + /// Creates a concise context string that explains the structure of chart-related classes + /// + /// A string containing class structure information + private string GetChartClassStructureContext() + { + return @" + Chart class model reference: + ChartConfig{ChartType:enum, Title:string, Series:Collection, XAxis/YAxis:Collection, ShowLegend:bool, SideBySidePlacement:bool} + SeriesConfig{Type:enum, Name:string, DataSource:Collection, Tooltip:bool} + AxisConfig{Title:string, Type:string('Numerical'|'DateTime'|'Category'|'Logarithmic'), Minimum/Maximum:double?}"; + } + + /// + /// Gets specific context about a particular chart class based on need + /// + /// The class type to get context for ("ChartConfig", "SeriesConfig", or "AxisConfig") + /// Specific context about the requested class + internal string GetSpecificClassContext(string classType) + { + return classType.ToLower() switch + { + "chartconfig" => "ChartConfig: Controls overall chart appearance with properties for chart type, title, axis collections, series data, and display options like legends.", + "seriesconfig" => "SeriesConfig: Defines a data series with properties for series type, name, data source collection, and tooltip visibility.", + "axisconfig" => "AxisConfig: Configures chart axes with title, axis type (Numerical, DateTime, Category, Logarithmic), and optional min/max range values.", + _ => "Unknown class type. Available classes: ChartConfig, SeriesConfig, AxisConfig" + }; + } + + internal async Task AnalyzeImageAzureAsync(ImageSource source, string textInput) + { + byte[] imageBytes = await ConvertImageSourceToByteArray(source); + + // Convert the byte array to a Base64 string + return await InterpretImageBase64(Convert.ToBase64String(imageBytes), textInput); + } + + public static async Task ConvertImageSourceToByteArray(ImageSource imageSource) + { + Stream stream = await ConvertImageSourceToStream(imageSource); + + using (MemoryStream memoryStream = new MemoryStream()) + { + stream.CopyTo(memoryStream); + return memoryStream.ToArray(); + } + } + + private static async Task ConvertImageSourceToStream(ImageSource imageSource) + { + if (imageSource is FileImageSource fileImageSource) + { + return new FileStream(fileImageSource.File, FileMode.Open, FileAccess.Read); + } + else if (imageSource is UriImageSource uriImageSource) + { + var httpClient = new System.Net.Http.HttpClient(); + return await httpClient.GetStreamAsync(uriImageSource.Uri); + } + else if (imageSource is StreamImageSource streamImageSource) + { + return await streamImageSource.Stream(default); + } + else + { + throw new NotSupportedException("Unsupported ImageSource type"); + } + } + + internal async Task InterpretImageBase64(string base64, string textInput) + { + + try + { + var imageDataUri = $"data:image/jpeg;base64,{base64}"; + var chatHistory = new Microsoft.Extensions.AI.ChatMessage(); + chatHistory.Text = "You are an AI assistant that describes images."; + chatHistory.Contents = (new List + { + new TextContent("Describe this image:"), + new TextContent($"{textInput}"), + new ImageContent(imageDataUri) + }); + + var result = await Client.CompleteAsync(new[] { chatHistory }); + return result?.ToString() ?? "No description generated."; + } + catch (Exception ex) + { + return $"Error generating OpenAI response: {ex.Message}"; + } + } + + + } #endregion -} +} \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/AppShell.xaml b/ChartGeneratorAISample/ChartGenerator/AppShell.xaml deleted file mode 100644 index 6333586..0000000 --- a/ChartGeneratorAISample/ChartGenerator/AppShell.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - diff --git a/ChartGeneratorAISample/ChartGenerator/AppShell.xaml.cs b/ChartGeneratorAISample/ChartGenerator/AppShell.xaml.cs deleted file mode 100644 index 48bb3fb..0000000 --- a/ChartGeneratorAISample/ChartGenerator/AppShell.xaml.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChartGenerator -{ - public partial class AppShell : Shell - { - public AppShell() - { - InitializeComponent(); - } - } -} diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianChartExt.xaml b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianCategory.xaml similarity index 50% rename from ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianChartExt.xaml rename to ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianCategory.xaml index 7ac98b5..41cbe14 100644 --- a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianChartExt.xaml +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianCategory.xaml @@ -3,14 +3,16 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:chart="clr-namespace:Syncfusion.Maui.Toolkit.Charts;assembly=Syncfusion.Maui.Toolkit" xmlns:local="clr-namespace:ChartGenerator" - x:Class="ChartGenerator.CartesianChartExt" - Title="{Binding Title}" - Source="{Binding Series}"> + x:Class="ChartGenerator.CartesianCategory" x:DataType="local:ChartConfig" + Source="{Binding Series}" XAxis="{Binding XAxis}" YAxis="{Binding YAxis}" EnableSideBySideSeriesPlacement="{Binding SideBySidePlacement}"> + + - + @@ -19,20 +21,5 @@ - - - - - - - - - - - - - - - diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianCategory.xaml.cs b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianCategory.xaml.cs new file mode 100644 index 0000000..330d9be --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianCategory.xaml.cs @@ -0,0 +1,188 @@ +using Syncfusion.Maui.Toolkit.Charts; +using System.Collections.ObjectModel; + +namespace ChartGenerator; + +public partial class CartesianCategory : SfCartesianChart +{ + public CartesianCategory() + { + InitializeComponent(); + } + + // BindableProperty for the series source + public static readonly BindableProperty SourceProperty = BindableProperty.Create( + nameof(Source), + typeof(ObservableCollection), + typeof(CartesianCategory), + null, + BindingMode.Default, + propertyChanged: OnPropertyChanged); + + public ObservableCollection Source + { + get => (ObservableCollection)GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + + public static readonly BindableProperty XAxisProperty = BindableProperty.Create( + nameof(XAxis), + typeof(ObservableCollection), + typeof(CartesianCategory), + null, + BindingMode.Default, + propertyChanged: XAxisChanged); + + public ObservableCollection XAxis + { + get => (ObservableCollection)GetValue(XAxisProperty); + set => SetValue(XAxisProperty, value); + } + + public static readonly BindableProperty YAxisProperty = BindableProperty.Create( + nameof(YAxis), + typeof(ObservableCollection), + typeof(CartesianCategory), + null, + BindingMode.Default, + propertyChanged: YAxisChanged); + + public ObservableCollection YAxis + { + get => (ObservableCollection)GetValue(YAxisProperty); + set => SetValue(YAxisProperty, value); + } + + private static void XAxisChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is SfCartesianChart cartesianChart && newValue is ObservableCollection xAxisConfigs) + { + // Clear current axes + cartesianChart.XAxes.Clear(); + + // Add new axes based on the new configuration + foreach (var axisConfig in xAxisConfigs) + { + ChartAxis axis = axisConfig.GetXAxis(); + + if (axis is CategoryAxis category) + { + category.LabelRotation = 45; + } + + axis.AxisLineStyle = SetLineStyle(); + axis.MajorGridLineStyle = SetLineStyle(); + axis.MajorTickStyle = SetTickStyle(); + axis.ShowMajorGridLines = false; + cartesianChart.XAxes.Add(axis); + } + } + } + + private static void YAxisChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is SfCartesianChart cartesianChart && newValue is ObservableCollection yAxisConfigs) + { + cartesianChart.YAxes.Clear(); + + foreach (var axisConfig in yAxisConfigs) + { + var axis = axisConfig.GetYAxis(); + if (axis != null) + { + axis.AxisLineStyle = SetLineStyle(); + axis.MajorGridLineStyle = SetLineStyle(); + axis.MajorTickStyle = SetTickStyle(); + axis.ShowMajorGridLines = false; + cartesianChart.YAxes.Add(axis); + } + } + } + } + + private static ChartLineStyle SetLineStyle() + { + ChartLineStyle axisLineStyle = new ChartLineStyle() + { + Stroke = Colors.Transparent, + StrokeWidth = 0, + }; + + return axisLineStyle; + } + + private static ChartAxisTickStyle SetTickStyle() + { + ChartAxisTickStyle tickStyle = new ChartAxisTickStyle() + { + TickSize = 0 + }; + + return tickStyle; + } + + private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is CartesianCategory chart) + { + chart.GenerateSeries(newValue as ObservableCollection); + } + } + + private void GenerateSeries(ObservableCollection configs) + { + Series.Clear(); + if (configs != null) + { + foreach (var config in configs) + { + CreateSeriesFromTemplate(config); + var paletteBrush = GetPaletteBrushes(); + + if (Series.Count == 1 && Series[0] is ColumnSeries series) + { + series.PaletteBrushes = paletteBrush; + } + else + { + this.PaletteBrushes = paletteBrush; + } + } + } + } + + private Brush[] GetPaletteBrushes() + { + var random = new Random(); + switch (random.Next(1, 6)) + { + case 1: + return Resources["Pallet1"] as Brush[]; + case 2: + return Resources["Pallet2"] as Brush[]; + case 3: + return Resources["Pallet3"] as Brush[]; + case 5: + return Resources["Pallet5"] as Brush[]; + default: + return Resources["Pallet6"] as Brush[]; + } + } + + private void CreateSeriesFromTemplate(SeriesConfig seriesConfig) + { + var templateSelector = (SeriesTemplateSelector)Resources["seriesTemplateSelector"]; + var template = templateSelector.SelectTemplate(seriesConfig, null); + + if (template != null) + { + ChartSeries series = (ChartSeries)template.CreateContent(); + + if (series != null) + { + series.BindingContext = seriesConfig; + this.Series.Add(series); + } + } + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianChartExt.xaml.cs b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianChartExt.xaml.cs deleted file mode 100644 index 43c0f08..0000000 --- a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CartesianChartExt.xaml.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Syncfusion.Maui.Toolkit.Charts; -using System.Collections.ObjectModel; - -namespace ChartGenerator; - -public partial class CartesianChartExt : SfCartesianChart -{ - public CartesianChartExt() - { - InitializeComponent(); - } - - public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ObservableCollection), typeof(CartesianChartExt), null, BindingMode.Default, null, OnPropertyChanged); - public ObservableCollection Source - { - get => (ObservableCollection)GetValue(SourceProperty); - set => SetValue(SourceProperty, value); - } - - private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) - { - if (Equals(oldValue, newValue)) - { - return; - } - - if (bindable is CartesianChartExt chart) - { - chart.GenerateSeries(newValue as ObservableCollection); - } - } - - private void GenerateSeries(ObservableCollection configs) - { - if (configs != null) - { - this.Series.Clear(); - foreach (var config in configs) - { - CreateSeriesFromTemplate(config); - } - } - } - - private void CreateSeriesFromTemplate(SeriesConfig seriesConfig) - { - var templateSelector = (SeriesTemplateSelector)Resources["seriesTemplateSelector"]; - var template = templateSelector.SelectTemplate(seriesConfig, null); - - if (template != null) - { - ChartSeries series = (ChartSeries)template.CreateContent(); - - if (series != null) - { - series.BindingContext = seriesConfig; - this.Series.Add(series); - } - } - } -} diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/ChartResources.xaml b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/ChartResources.xaml new file mode 100644 index 0000000..a94c60f --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/ChartResources.xaml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/ChartResources.xaml.cs b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/ChartResources.xaml.cs new file mode 100644 index 0000000..91e1568 --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/ChartResources.xaml.cs @@ -0,0 +1,9 @@ +namespace ChartGenerator; + +public partial class ChartResources : ResourceDictionary +{ + public ChartResources() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularChartExt.xaml b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularCategory.xaml similarity index 76% rename from ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularChartExt.xaml rename to ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularCategory.xaml index aac3863..608576f 100644 --- a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularChartExt.xaml +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularCategory.xaml @@ -3,18 +3,20 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:chart="clr-namespace:Syncfusion.Maui.Toolkit.Charts;assembly=Syncfusion.Maui.Toolkit" xmlns:local="clr-namespace:ChartGenerator" - x:Class="ChartGenerator.CircularChartExt" - Title="{Binding Title}" + x:Class="ChartGenerator.CircularCategory" Source="{Binding Series}" > + + + - + - diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularChartExt.xaml.cs b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularCategory.xaml.cs similarity index 58% rename from ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularChartExt.xaml.cs rename to ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularCategory.xaml.cs index ad313ab..5c61547 100644 --- a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularChartExt.xaml.cs +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/CircularCategory.xaml.cs @@ -3,14 +3,14 @@ namespace ChartGenerator; -public partial class CircularChartExt : SfCircularChart +public partial class CircularCategory : SfCircularChart { - public CircularChartExt() - { - InitializeComponent(); - } + public CircularCategory() + { + InitializeComponent(); + } - public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ObservableCollection), typeof(CircularChartExt), null, BindingMode.Default, null, OnPropertyChanged); + public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(ObservableCollection), typeof(CircularCategory), null, BindingMode.Default, null, OnPropertyChanged); public ObservableCollection Source { get => (ObservableCollection)GetValue(SourceProperty); @@ -19,7 +19,7 @@ public ObservableCollection Source private static void OnPropertyChanged(BindableObject bindable, object oldValue, object newValue) { - if (bindable is CircularChartExt chart) + if (bindable is CircularCategory chart) { chart.GenerateSeries(newValue as ObservableCollection); } @@ -34,6 +34,34 @@ private void GenerateSeries(ObservableCollection configs) { CreateSeriesFromTemplate(config); } + + var paletteBrush = GetPaletteBrushes(); + + if (Series.Count == 1) + { + if (Series[0] is CircularSeries series) + { + series.PaletteBrushes = paletteBrush; + } + } + } + } + + private Brush[] GetPaletteBrushes() + { + var random = new Random(); + switch (random.Next(1, 6)) + { + case 1: + return Resources["Pallet1"] as Brush[]; + case 2: + return Resources["Pallet2"] as Brush[]; + case 3: + return Resources["Pallet3"] as Brush[]; + case 5: + return Resources["Pallet5"] as Brush[]; + default: + return Resources["Pallet6"] as Brush[]; } } diff --git a/ChartGeneratorAISample/ChartGenerator/ChartExtensions/SeriesTemplateSelector.cs b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/SeriesTemplateSelector.cs new file mode 100644 index 0000000..a3451bf --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/ChartExtensions/SeriesTemplateSelector.cs @@ -0,0 +1,41 @@ + +namespace ChartGenerator +{ + public class SeriesTemplateSelector : DataTemplateSelector + { + public DataTemplate LineSeriesTemplate { get; set; } + public DataTemplate SplineSeriesTemplate { get; set; } + public DataTemplate ColumnSeriesTemplate { get; set; } + public DataTemplate AreaSeriesTemplate { get; set; } + public DataTemplate PieSeriesTemplate { get; set; } + public DataTemplate DoughnutSeriesTemplate { get; set; } + public DataTemplate RadialBarSeriesTemplate { get; set; } + + protected override DataTemplate OnSelectTemplate(object item, BindableObject container) + { + if (item is SeriesConfig config) + { + switch (config.Type) + { + case SeriesType.Line: + return LineSeriesTemplate; + case SeriesType.Spline: + return SplineSeriesTemplate; + case SeriesType.Column: + return ColumnSeriesTemplate; + case SeriesType.Area: + return AreaSeriesTemplate; + case SeriesType.Pie: + return PieSeriesTemplate; + case SeriesType.Doughnut: + return DoughnutSeriesTemplate; + case SeriesType.RadialBar: + return RadialBarSeriesTemplate; + default: + throw new ArgumentOutOfRangeException(); + } + } + return null; + } + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/ChartGenerator.csproj b/ChartGeneratorAISample/ChartGenerator/ChartGenerator.csproj index 753a6b3..6057a41 100644 --- a/ChartGeneratorAISample/ChartGenerator/ChartGenerator.csproj +++ b/ChartGeneratorAISample/ChartGenerator/ChartGenerator.csproj @@ -66,8 +66,9 @@ - - + + + diff --git a/ChartGeneratorAISample/ChartGenerator/Helper/CustomEditor.cs b/ChartGeneratorAISample/ChartGenerator/Helper/CustomEditor.cs new file mode 100644 index 0000000..569e39f --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/Helper/CustomEditor.cs @@ -0,0 +1,75 @@ +using Syncfusion.Maui.Core.Internals; + +#if ANDROID +using PlatformView = Android.Widget.EditText; +#elif WINDOWS +using PlatformView = Microsoft.UI.Xaml.Controls.TextBox; +#endif +namespace ChartGenerator +{ + public class CustomEditor : Editor, IKeyboardListener + { + public CustomEditor() + { + this.AddKeyboardListener(this); + } +#if WINDOWS || ANDROID + protected override void OnHandlerChanged() + { +#if WINDOWS + // Hide editor border and underline. + var platformView = this.Handler?.PlatformView as PlatformView; + if (platformView != null) + { + this.ApplyTextBoxStyle(platformView); + } +#else + var platformView = this.Handler?.PlatformView as PlatformView; + if (platformView != null) + { + this.ApplyTextBoxStyle(platformView); + } +#endif + base.OnHandlerChanged(); + } +#endif +#if WINDOWS || ANDROID + private void ApplyTextBoxStyle(PlatformView? platformView) + { + if (platformView != null) + { +#if WINDOWS + var textBoxStyle = new Microsoft.UI.Xaml.Style(typeof(Microsoft.UI.Xaml.Controls.TextBox)); + textBoxStyle.Setters.Add(new Microsoft.UI.Xaml.Setter() { Property = Microsoft.UI.Xaml.Controls.Control.BorderBrushProperty, Value = new Microsoft.UI.Xaml.Media.SolidColorBrush(Windows.UI.Color.FromArgb(0, 0, 0, 0)) }); + textBoxStyle.Setters.Add(new Microsoft.UI.Xaml.Setter() { Property = Microsoft.UI.Xaml.Controls.Control.BorderThicknessProperty, Value = new Thickness(0) }); + + platformView.Resources.Add(typeof(Microsoft.UI.Xaml.Controls.TextBox), textBoxStyle); +#else + platformView.Background = null; + platformView.SetPadding(0, 0, 0, 0); +#endif + } + } +#endif + public void OnKeyDown(KeyEventArgs args) + { + } + + public void OnKeyUp(KeyEventArgs args) + { + } + + public void OnPreviewKeyDown(KeyEventArgs args) + { + if (args.Key == KeyboardKey.Enter && !args.IsShiftKeyPressed) + { + var bindingContext = this.BindingContext as ChatViewModel; + args.Handled = true; + if (!string.IsNullOrWhiteSpace(bindingContext.InputText)) + { + bindingContext.SendButtonCommand.Execute(null); + } + } + } + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/Helper/HtmlConverter.cs b/ChartGeneratorAISample/ChartGenerator/Helper/HtmlConverter.cs new file mode 100644 index 0000000..da847a0 --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/Helper/HtmlConverter.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace ChartGenerator +{ + public class HtmlConverter + { + public string ConvertToHTML(string aiResponse) + { + StringBuilder htmlBuilder = new StringBuilder(); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine("AI Response"); + htmlBuilder.AppendLine(""); + //htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + + var paragraphs = aiResponse.Split(new[] { "
", "\n\n" }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < paragraphs.Count(); i++) + { + var paragraph = paragraphs[i]; + if (IsHeading(paragraph)) + { + int level = 0; + var title = TrimHashFromTitle(paragraph, out level); + title = ConvertToHtmlBold(title); + if (level == 1) + htmlBuilder.AppendLine($"

{title}

"); + else if (level == 2) + htmlBuilder.AppendLine($"

{title}

"); + else if (level == 3) + htmlBuilder.AppendLine($"

{title}

"); + else if (level == 4) + htmlBuilder.AppendLine($"

{title}

"); + else if (level == 5) + htmlBuilder.AppendLine($"
{title}
"); + else if (level == 6) + htmlBuilder.AppendLine($"
{title}
"); + } + // Implement list parsing here + // else if (IsList(paragraph)) + // { + // ConvertToHtmlList(paragraph, htmlBuilder); + // } + else if (IsQuote(paragraph)) + { + htmlBuilder.AppendLine($"
{paragraph.Trim()}
"); + } + else if (IsURL(paragraph)) + { + htmlBuilder.AppendLine($"{paragraph.Trim()}"); + } + else if (HasURLText(paragraph)) + { + ConvertMarkdownToHtml(paragraph, htmlBuilder); + } + else if (ContainsBoldPattern(paragraph)) + { + htmlBuilder.AppendLine($"

{ConvertToHtmlBold(paragraph)}

"); + } + else if (paragraph.Contains("```")) + { + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine("
"); + htmlBuilder.AppendLine("
");
+                    htmlBuilder.AppendLine("");
+                    var codeSnippetIndex = 0;
+                    for (codeSnippetIndex = i + 1; codeSnippetIndex < paragraphs.Count(); codeSnippetIndex++)
+                    {
+                        var codeSnippet = paragraphs[codeSnippetIndex];
+                        if (codeSnippet.Contains("```"))
+                            break;
+                        htmlBuilder.AppendLine(codeSnippet);
+                    }
+                    htmlBuilder.AppendLine("
"); + htmlBuilder.AppendLine("
"); + i = codeSnippetIndex; + } + else if (HasBackticks(paragraph)) + { + FormatBackTicks(htmlBuilder, paragraph); + } + else + { + htmlBuilder.AppendLine($"

{paragraph.Trim()}

"); + } + } + + htmlBuilder.AppendLine(""); + htmlBuilder.AppendLine(""); + return htmlBuilder.ToString(); + } + public string TrimHashFromTitle(string title, out int headingLevel) + { + headingLevel = 0; + + // Loop from 4 to 1 to check for "####", "###", "##", and "#" + for (int i = 4; i > 0; i--) + { + string hashes = new string('#', i); + int index = title.IndexOf(hashes); + if (index != -1) + { + headingLevel = i; + // Trim the found hashes from the title and any leading/trailing spaces + return title.Substring(0, index).Trim() + title.Substring(index + i).Trim(); + } + } + + // If no hashes are found, return the original title + return title; + } + private bool IsHeading(string text) + { + // Detect headings, e.g., all caps or specific prefixes + return text == text.ToUpper() || Regex.IsMatch(text, @"^#"); + } + + private bool IsList(string text) + { + // Detect bullet points or ordered lists + return text.StartsWith("- ");// || Regex.IsMatch(text, @"^\d+\."); + } + + private bool IsQuote(string text) + { + // Simplistic quote detection + return text.StartsWith("\"") && text.EndsWith("\""); + } + + private bool HasBackticks(string text) + { + var backtickPattern = @"`[^`]*`"; + return Regex.IsMatch(text, backtickPattern); + } + + private void FormatBackTicks(StringBuilder htmlBuilder, string text) + { + var backtickPattern = @"`([^`]*)`"; + var formattedText = Regex.Replace(text, backtickPattern, "$1"); + htmlBuilder.AppendLine($"

{formattedText}

"); + } + + private bool IsURL(string text) + { + // URL detection based on simple regex + var directURLPattern = @"^(http|https):\/\/[^\s$.?#].[^\s]*$"; + return Regex.IsMatch(text, directURLPattern); + } + private bool HasURLText(string markdown) + { + // Define the regex pattern to extract URLs from Markdown links. + string pattern = @"\[(.*?)\]\((http[s]?:\/\/[^\s\)]+)\)"; + + // Create a match to extract the text and URL from the Markdown link. + Match match = Regex.Match(markdown, pattern, RegexOptions.IgnoreCase); + if (match.Success) + { + return IsURL(match.Groups[2].Value); + } + return false; + } + private static string ConvertMarkdownToHtml(string markdown, StringBuilder htmlBuilder) + { + // Define the regex pattern to extract URLs from Markdown links. + string pattern = @"\[(.*?)\]\((http[s]?:\/\/[^\s\)]+)\)"; + + // Create a match to extract the text and URL from the Markdown link. + Match match = Regex.Match(markdown, pattern, RegexOptions.IgnoreCase); + if (match.Success) + { + string linkText = match.Groups[1].Value; // Group 1 contains the link text. + string url = match.Groups[2].Value; // Group 2 contains the URL. + + htmlBuilder.AppendLine($"

{linkText.Trim()}

"); + } + + // Return original text if no Markdown link is found. + return markdown; + } + + private string ConvertToHtmlBold(string input) + { + string pattern = @"\*\*(.*?)\*\*"; + string result = Regex.Replace(input, pattern, "$1"); + if (HasBackticks(result)) + { + var backtickPattern = @"`([^`]*)`"; + var formattedText = Regex.Replace(result, backtickPattern, "$1"); + return formattedText; + } + return result; + } + + private bool ContainsBoldPattern(string input) + { + string pattern = @"\*\*.+?\*\*"; + return Regex.IsMatch(input, pattern); + } + + private void ConvertToHtmlList(string paragraph, StringBuilder htmlBuilder) + { + var items = paragraph.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + htmlBuilder.AppendLine("
    "); + foreach (var item in items) + { + htmlBuilder.AppendLine($"
  1. {item.Trim().TrimStart('-').Trim()}
  2. "); + } + htmlBuilder.AppendLine("
"); + } + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/Helper/ImagePickerHelper.cs b/ChartGeneratorAISample/ChartGenerator/Helper/ImagePickerHelper.cs new file mode 100644 index 0000000..7353edd --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/Helper/ImagePickerHelper.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ChartGenerator +{ + public class ImagePickerHelper + { + internal async Task PickImageAsync() + { + try + { + var result = await MediaPicker.PickPhotoAsync(); + + if (result != null) + { + // Open the file stream + using (Stream stream = await result.OpenReadAsync()) + { + // Copy the stream into a memory stream to avoid object disposed exception. + using (MemoryStream memoryStream = new MemoryStream()) + { + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + } + } + } + catch (Exception ex) + { + // Handle exceptions + Console.WriteLine($"Error picking image: {ex.Message}"); + } + + return null; + } + + internal async Task SaveImageAsync(string filePath) + { + try + { + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error deleting image: {ex.Message}"); + } + + try + { + // Get the image from Gallery. + byte[] imageBytes = await PickImageAsync(); + + if (imageBytes != null) + { + // Save the image bytes to a file, database, etc. + File.WriteAllBytes(filePath, imageBytes); + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error saving image: {ex.Message}"); + } + } + + + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/Helper/JsonConverters.cs b/ChartGeneratorAISample/ChartGenerator/Helper/JsonConverters.cs new file mode 100644 index 0000000..dad4d27 --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/Helper/JsonConverters.cs @@ -0,0 +1,139 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ChartGenerator +{ + /// + /// Converter for handling axis type values from string + /// + public class AxisTypeConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + string value = reader.Value.ToString().ToLower(); + + switch (value) + { + case "numerical": + case "linear": + return "Numerical"; + case "datetime": + return "DateTime"; + case "category": + return "Category"; + case "log": + case "logarithmic": + return "Logarithmic"; + default: + return "Numerical"; // Default to numerical + } + } + + // Default if not a string + return "Numerical"; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + string axisType = (string)value; + writer.WriteValue(axisType); + } + } + + /// + /// Converter for handling ChartTypeEnum values from string + /// + public class ChartTypeEnumConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ChartTypeEnum) || objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + string value = reader.Value.ToString().ToLower(); + + if (value == "cartesian") + return ChartTypeEnum.Cartesian; + else if (value == "circular") + return ChartTypeEnum.Circular; + } + else if (reader.TokenType == JsonToken.Integer) + { + int value = Convert.ToInt32(reader.Value); + if (Enum.IsDefined(typeof(ChartTypeEnum), value)) + return (ChartTypeEnum)value; + } + + // Default to Cartesian if can't convert + return ChartTypeEnum.Cartesian; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + ChartTypeEnum chartType = (ChartTypeEnum)value; + writer.WriteValue(chartType.ToString().ToLower()); + } + } + + /// + /// Converter for handling SeriesType values from string + /// + public class SeriesTypeConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SeriesType) || objectType == typeof(string); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.String) + { + string value = reader.Value.ToString().ToLower(); + + switch (value) + { + case "line": return SeriesType.Line; + case "column": return SeriesType.Column; + case "spline": return SeriesType.Spline; + case "area": return SeriesType.Area; + case "pie": return SeriesType.Pie; + case "doughnut": return SeriesType.Doughnut; + case "radialbar": return SeriesType.RadialBar; + default: return SeriesType.Column; // Default to column + } + } + else if (reader.TokenType == JsonToken.Integer) + { + int value = Convert.ToInt32(reader.Value); + if (Enum.IsDefined(typeof(SeriesType), value)) + return (SeriesType)value; + } + + // Default to Column if can't convert + return SeriesType.Column; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + SeriesType seriesType = (SeriesType)value; + writer.WriteValue(seriesType.ToString().ToLower()); + } + } +} \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/MainPage.xaml b/ChartGeneratorAISample/ChartGenerator/MainPage.xaml deleted file mode 100644 index a651caa..0000000 --- a/ChartGeneratorAISample/ChartGenerator/MainPage.xaml +++ /dev/null @@ -1,204 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ChartGeneratorAISample/ChartGenerator/MainPage.xaml.cs b/ChartGeneratorAISample/ChartGenerator/MainPage.xaml.cs deleted file mode 100644 index 4dfea24..0000000 --- a/ChartGeneratorAISample/ChartGenerator/MainPage.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace ChartGenerator -{ - public partial class MainPage : ContentPage - { - public MainPage() - { - InitializeComponent(); - } - } - -} diff --git a/ChartGeneratorAISample/ChartGenerator/Models/ChartConfig.cs b/ChartGeneratorAISample/ChartGenerator/Models/ChartConfig.cs index 124d06c..647da2e 100644 --- a/ChartGeneratorAISample/ChartGenerator/Models/ChartConfig.cs +++ b/ChartGeneratorAISample/ChartGenerator/Models/ChartConfig.cs @@ -6,14 +6,14 @@ namespace ChartGenerator; public class ChartConfig : INotifyPropertyChanged { - private ChartEnums.ChartTypeEnum chartType; + private ChartTypeEnum chartType; private string title; - private Axes xAxis; - private Axes yAxis; private ObservableCollection series; + private ObservableCollection xAxis; + private ObservableCollection yAxis; private bool showLegend; - public ChartEnums.ChartTypeEnum ChartType + public ChartTypeEnum ChartType { get => chartType; set @@ -25,7 +25,20 @@ public ChartEnums.ChartTypeEnum ChartType } } } + private bool sideBySidePlacement = true; + public bool SideBySidePlacement + { + get => sideBySidePlacement; + set + { + if (sideBySidePlacement != value) + { + sideBySidePlacement = value; + OnPropertyChanged(); + } + } + } public string Title { get => title; @@ -39,7 +52,7 @@ public string Title } } - public Axes XAxis + public ObservableCollection XAxis { get => xAxis; set @@ -52,7 +65,7 @@ public Axes XAxis } } - public Axes YAxis + public ObservableCollection YAxis { get => yAxis; set @@ -91,6 +104,12 @@ public ObservableCollection Series } } + public ChartConfig() + { + XAxis = new ObservableCollection(); + YAxis = new ObservableCollection(); + } + public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { @@ -117,7 +136,7 @@ public string? Title } } - public ChartEnums.AxisType Type + public AxisType Type { get; set; @@ -155,5 +174,4 @@ protected virtual void OnPropertyChanged([CallerMemberName] string propertyName { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } -} - +} \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/Models/ChartEnums.cs b/ChartGeneratorAISample/ChartGenerator/Models/ChartEnums.cs index a4c42be..ee5358c 100644 --- a/ChartGeneratorAISample/ChartGenerator/Models/ChartEnums.cs +++ b/ChartGeneratorAISample/ChartGenerator/Models/ChartEnums.cs @@ -1,29 +1,27 @@ namespace ChartGenerator; -public class ChartEnums -{ - public enum ChartTypeEnum - { - Cartesian, - Circular - } - public enum SeriesType - { - Line, - Column, - Spline, - Area, - Pie, - Doughnut, - RadialBar - } +public enum ChartTypeEnum +{ + Cartesian, + Circular +} - public enum AxisType - { - Category, - Numerical, - DateTime, - Logarithmic - } +public enum SeriesType +{ + Line, + Column, + Spline, + Area, + Pie, + Doughnut, + RadialBar } + +public enum AxisType +{ + Category, + Numerical, + DateTime, + Logarithmic +} \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/Models/ContextMenus.cs b/ChartGeneratorAISample/ChartGenerator/Models/ContextMenus.cs new file mode 100644 index 0000000..7c994fb --- /dev/null +++ b/ChartGeneratorAISample/ChartGenerator/Models/ContextMenus.cs @@ -0,0 +1,65 @@ +using System.ComponentModel; + +namespace ChartGenerator +{ + public class Option : INotifyPropertyChanged + { + #region Private Fields + + private bool isEnabled; + private bool isOpen; + + #endregion + + #region Public APIs + + public string Name { get; set; } + public string Icon { get; set; } + public bool CanShowSeparator { get; set; } + + public bool IsEnabled + { + get { return isEnabled; } + set + { + isEnabled = value; + RaisePropertyChanged(nameof(IsEnabled)); + } + } + public bool IsOpen + { + get { return isOpen; } + set + { + isOpen = value; + RaisePropertyChanged(nameof(IsOpen)); + } + } + + #endregion + + #region Constructor + public Option() + { + IsEnabled = true; + } + + #endregion + + #region Property Changed + + public event PropertyChangedEventHandler? PropertyChanged; + /// + /// Occurs when property is changed. + /// + /// changed property name + public void RaisePropertyChanged(string propName) + { + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propName)); + } + } + #endregion + } +} diff --git a/ChartGeneratorAISample/ChartGenerator/Models/SeriesConfig.cs b/ChartGeneratorAISample/ChartGenerator/Models/SeriesConfig.cs index e6923c3..6156d6b 100644 --- a/ChartGeneratorAISample/ChartGenerator/Models/SeriesConfig.cs +++ b/ChartGeneratorAISample/ChartGenerator/Models/SeriesConfig.cs @@ -1,24 +1,26 @@ +using Syncfusion.Maui.Toolkit.Charts; using System.Collections.ObjectModel; namespace ChartGenerator; + public class SeriesConfig { - public ChartEnums.SeriesType Type + public SeriesType Type { - get; + get; set; } - public string XPath + public string Name { get; set; } - public ObservableCollection DataSource + public ObservableCollection DataSource { - get; + get; set; } @@ -27,31 +29,97 @@ public bool Tooltip get; set; } + public string? XPath { get; internal set; } +} + +public class AxisConfig +{ + public string Title { get; set; } + public string Type { get; set; } = "Numerical"; // default to Numerical + public double? Minimum { get; set; } + public double? Maximum { get; set; } + + public ChartAxis GetXAxis() + { + var title = new ChartAxisTitle() { Text = Title }; + return Type.ToLower() switch + { + + "datetime" => new DateTimeAxis { Title = title }, + "category" => new CategoryAxis { Title = title }, + "log" or "logarithmic" => new LogarithmicAxis { Title = title }, + "numerical" or "linear" => new NumericalAxis { Title = title }, + _ => new NumericalAxis { Title = title } + }; + } + public ChartAxis GetAxis() + { + var title = new ChartAxisTitle() { Text = Title }; + switch (Type?.ToLower()) + { + case "category": + return new CategoryAxis { Title = title }; + case "numerical": + return new NumericalAxis { Title = title }; + case "datetime": + return new DateTimeAxis { Title = title }; + case "logarithmic": + return new LogarithmicAxis { Title = title }; + default: + return new NumericalAxis(); // fallback + } + } + + public RangeAxisBase? GetYAxis() + { + var title = new ChartAxisTitle() { Text = Title }; + return Type.ToLower() switch + { + "datetime" => new DateTimeAxis { Title = title }, + "log" or "logarithmic" => new LogarithmicAxis { Title = title }, + "numerical" or "linear" => new NumericalAxis { Title = title }, + _ => null // CategoryAxis is not valid for YAxes (not RangeAxisBase) + }; + } + } + public class DataModel { - public string xvalue + public object xvalue { - get; + get; set; } public double yvalue { - get; + get; set; } public DateTime? date { - get; + get; set; } public double? xval { - get; + get; + set; + } + + public string Category + { + get; + set; + } + + public double Value + { + get; set; } } \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/Models/SeriesTemplates.xaml b/ChartGeneratorAISample/ChartGenerator/Models/SeriesTemplates.xaml deleted file mode 100644 index 9e376d5..0000000 --- a/ChartGeneratorAISample/ChartGenerator/Models/SeriesTemplates.xaml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ChartGeneratorAISample/ChartGenerator/Models/SeriesTemplates.xaml.cs b/ChartGeneratorAISample/ChartGenerator/Models/SeriesTemplates.xaml.cs deleted file mode 100644 index af93727..0000000 --- a/ChartGeneratorAISample/ChartGenerator/Models/SeriesTemplates.xaml.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace ChartGenerator; - -public partial class SeriesTemplates : ResourceDictionary -{ - public SeriesTemplates() - { - InitializeComponent(); - } -} - -public class SeriesTemplateSelector : DataTemplateSelector -{ - public DataTemplate LineSeriesTemplate { get; set; } - public DataTemplate SplineSeriesTemplate { get; set; } - public DataTemplate ColumnSeriesTemplate { get; set; } - public DataTemplate AreaSeriesTemplate { get; set; } - public DataTemplate PieSeriesTemplate { get; set; } - public DataTemplate DoughnutSeriesTemplate { get; set; } - public DataTemplate RadialBarSeriesTemplate { get; set; } - - protected override DataTemplate OnSelectTemplate(object item, BindableObject container) - { - if (item is SeriesConfig config) - { - switch (config.Type) - { - case ChartEnums.SeriesType.Line: - return LineSeriesTemplate; - case ChartEnums.SeriesType.Spline: - return SplineSeriesTemplate; - case ChartEnums.SeriesType.Column: - return ColumnSeriesTemplate; - case ChartEnums.SeriesType.Area: - return AreaSeriesTemplate; - case ChartEnums.SeriesType.Pie: - return PieSeriesTemplate; - case ChartEnums.SeriesType.Doughnut: - return DoughnutSeriesTemplate; - case ChartEnums.SeriesType.RadialBar: - return RadialBarSeriesTemplate; - default: - throw new ArgumentOutOfRangeException(); - } - } - return null; - } -} diff --git a/ChartGeneratorAISample/ChartGenerator/Platforms/Android/AndroidManifest.xml b/ChartGeneratorAISample/ChartGenerator/Platforms/Android/AndroidManifest.xml index e9937ad..ba759d3 100644 --- a/ChartGeneratorAISample/ChartGenerator/Platforms/Android/AndroidManifest.xml +++ b/ChartGeneratorAISample/ChartGenerator/Platforms/Android/AndroidManifest.xml @@ -1,6 +1,8 @@ - + + + \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml b/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml index 403c16d..733373d 100644 --- a/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml +++ b/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml @@ -12,11 +12,11 @@ - + - + - + - - + @@ -127,88 +127,71 @@ RequestCommand="{Binding RequestCommand}" ShowHeader="{Binding ShowHeader}" AssistItems="{Binding Messages}"> - - - + + - - - - - - - - - + + + + + + + + + - - + + + + - diff --git a/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml.cs b/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml.cs index 96f6713..7ecccd2 100644 --- a/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml.cs +++ b/ChartGeneratorAISample/ChartGenerator/View/ChartView.xaml.cs @@ -1,33 +1,193 @@ using Syncfusion.Maui.AIAssistView; +using Syncfusion.Maui.Core; +using Syncfusion.Maui.Core.Internals; +using Syncfusion.Pdf; +using Syncfusion.Pdf.Graphics; namespace ChartGenerator; public partial class ChartView : ContentPage { - ChartViewModel ViewModel; - public ChartView(ChartViewModel viewModel) + int count = 0; + ChatViewModel ViewModel; + public ChartView(ChatViewModel viewModel) { - InitializeComponent(); BindingContext = ViewModel = viewModel; + InitializeComponent(); } private async void TapGestureRecognizer_Tapped(object sender, TappedEventArgs e) { - var appointmentData = (sender as Border)?.BindingContext as string; - AssistItem botMessage = new AssistItem() { Text = appointmentData, IsRequested = true, ShowAssistItemFooter = false }; + var previousData = (sender as Border)?.BindingContext as string; + AssistItem botMessage = new AssistItem() { Text = previousData, IsRequested = true, ShowAssistItemFooter = false }; ViewModel.Messages.Add(botMessage); ViewModel.ShowHeader = false; - ViewModel.OnRequest(appointmentData); + ViewModel.OnRequest(previousData); } private void close_Clicked(object sender, EventArgs e) { - if ((sender as Button)?.BindingContext is ChartViewModel model) + if ((sender as Button)?.BindingContext is ChatViewModel model) { model.ShowAssistView = false; headerView.IsVisible = false; } } + + private async void Exportchartbutton_Clicked(object sender, EventArgs e) + { + ActivityIndicator? indicator = null; + PdfDocument? document = null; + MemoryStream? stream = null; + Stream? imageStream = null; + + try + { + System.Diagnostics.Debug.WriteLine("Starting PDF export process..."); + + // Display activity indicator + indicator = new ActivityIndicator + { + IsRunning = true, + Color = Color.FromArgb("#6750a4"), + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center + }; + + Grid parentGrid = (Grid)this.Content; + parentGrid.Children.Add(indicator); + + System.Diagnostics.Debug.WriteLine("Creating PDF document..."); + // Create PDF document + document = new PdfDocument(); + PdfPage page = document.Pages.Add(); + PdfGraphics graphics = page.Graphics; + + // Calculate dimensions + float width = (float)templatedItemView.Width + 75; + float height = (float)templatedItemView.Height + 125; + + //To reduce the width and height of the Windows and MAC platform +#if !IOS && !ANDROID + width = (float)templatedItemView.Width / 2.5f; + height = (float)templatedItemView.Height / 1.5f; +#endif + System.Diagnostics.Debug.WriteLine($"PDF dimensions: {width}x{height}"); + + // Get the image stream + System.Diagnostics.Debug.WriteLine("Getting chart image stream..."); + imageStream = await templatedItemView.GetStreamAsync(ImageFileFormat.Png); + if (imageStream == null) + { + System.Diagnostics.Debug.WriteLine("Failed to get chart image stream - it's null"); + await Application.Current.MainPage.DisplayAlert("Error", "Failed to get chart image", "OK"); + parentGrid.Children.Remove(indicator); + return; + } + + System.Diagnostics.Debug.WriteLine($"Chart image stream obtained. Length: {imageStream.Length} bytes"); + + // Create PDF image + System.Diagnostics.Debug.WriteLine("Creating PDF image from bitmap..."); + PdfImage img = new PdfBitmap(imageStream); + graphics.DrawImage(img, 0, 0, width, height); + + // Save to memory stream + System.Diagnostics.Debug.WriteLine("Saving PDF to memory stream..."); + stream = new MemoryStream(); + document.Save(stream); + document.Close(true); + stream.Position = 0; + + System.Diagnostics.Debug.WriteLine($"PDF created in memory. Size: {stream.Length} bytes"); + + // Save to file + System.Diagnostics.Debug.WriteLine("Saving PDF to file..."); + SavePDF("ChartAsPDF.pdf", stream); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"PDF Export Exception: {ex}"); + await Application.Current.MainPage.DisplayAlert("Error", $"Exception during PDF export: {ex.Message}", "OK"); + } + finally + { + // Clean up resources + if (stream != null) + { + stream.Dispose(); + System.Diagnostics.Debug.WriteLine("Memory stream disposed"); + } + + if (imageStream != null) + { + imageStream.Dispose(); + System.Diagnostics.Debug.WriteLine("Image stream disposed"); + } + + if (document != null) + { + document = null; + System.Diagnostics.Debug.WriteLine("PDF document nullified"); + } + + // Remove activity indicator + if (indicator != null && this.Content is Grid parentGrid) + { + parentGrid.Children.Remove(indicator); + System.Diagnostics.Debug.WriteLine("Activity indicator removed"); + } + + System.Diagnostics.Debug.WriteLine("PDF export process completed"); + } + } + + private async void SavePDF(string fileName, Stream stream) + { + try + { + fileName = Path.GetFileNameWithoutExtension(fileName) + ".pdf"; + string filePath; + +#if ANDROID || IOS + // For mobile platforms, use the app's cache directory + string folderPath = Path.Combine(FileSystem.CacheDirectory, "PDFExports"); + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + filePath = Path.Combine(folderPath, fileName); +#else + // For desktop platforms, save to bin/exports + string binPath = AppDomain.CurrentDomain.BaseDirectory; + string exportsFolder = Path.Combine(binPath, "exports"); + if (!Directory.Exists(exportsFolder)) + { + Directory.CreateDirectory(exportsFolder); + } + filePath = Path.Combine(exportsFolder, fileName); +#endif + + using (FileStream fileStream = new(filePath, FileMode.Create, FileAccess.ReadWrite)) + { + await stream.CopyToAsync(fileStream); + fileStream.Flush(); + } + + await Application.Current.MainPage.DisplayAlert("Success", $"PDF saved to {filePath}", "OK"); + } + catch (Exception ex) + { + await Application.Current.MainPage.DisplayAlert("Error", $"Failed to save PDF: {ex.Message}", "OK"); + // Log more detailed error information + System.Diagnostics.Debug.WriteLine($"PDF Export Error: {ex.ToString()}"); + } + } + + private void Saveasimage_Clicked(object sender, EventArgs e) + { + templatedItemView.SaveAsImage("AiBlog"); + } } public class ChartTemplateSelector : DataTemplateSelector @@ -41,8 +201,8 @@ protected override DataTemplate OnSelectTemplate(object item, BindableObject con { return chartConfig.ChartType switch { - ChartEnums.ChartTypeEnum.Cartesian => CartesianChartTemplate, - ChartEnums.ChartTypeEnum.Circular => CircularChartTemplate, + ChartTypeEnum.Cartesian => CartesianChartTemplate, + ChartTypeEnum.Circular => CircularChartTemplate, _ => null }; } diff --git a/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml b/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml index 67cfb16..39d881a 100644 --- a/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml +++ b/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml @@ -16,7 +16,7 @@ - + @@ -33,37 +33,134 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AnimationType="SingleCircle"/> - diff --git a/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml.cs b/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml.cs index 643f494..edfe9da 100644 --- a/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml.cs +++ b/ChartGeneratorAISample/ChartGenerator/View/DesktopUI.xaml.cs @@ -1,3 +1,5 @@ +using Syncfusion.Maui.Core; + namespace ChartGenerator; public partial class DesktopUI : ContentPage @@ -6,4 +8,26 @@ public DesktopUI() { InitializeComponent(); } + private void OnChipClicked(object sender, EventArgs e) + { + var viewmodel = this.BindingContext as ChatViewModel; + var chip = (sender as SfChip); + var layout = chip.Children[0] as HorizontalStackLayout; + + Option option; + if (layout != null) + { + option = layout.BindingContext as Option; + } + else + { + var label = chip.Children[0] as Grid; + option = label.BindingContext as Option; + } + + if (string.IsNullOrEmpty(option.Name) || !option.IsEnabled) + return; + + viewmodel.EditorOptionsComamnd.Execute(option); + } } \ No newline at end of file diff --git a/ChartGeneratorAISample/ChartGenerator/View/MobileUI.xaml b/ChartGeneratorAISample/ChartGenerator/View/MobileUI.xaml index 37a464b..64b3450 100644 --- a/ChartGeneratorAISample/ChartGenerator/View/MobileUI.xaml +++ b/ChartGeneratorAISample/ChartGenerator/View/MobileUI.xaml @@ -14,7 +14,7 @@ - + diff --git a/ChartGeneratorAISample/ChartGenerator/ViewModel/ChartViewModel.cs b/ChartGeneratorAISample/ChartGenerator/ViewModel/ChartViewModel.cs index b1e73e3..85cf46a 100644 --- a/ChartGeneratorAISample/ChartGenerator/ViewModel/ChartViewModel.cs +++ b/ChartGeneratorAISample/ChartGenerator/ViewModel/ChartViewModel.cs @@ -1,27 +1,132 @@ -using Newtonsoft.Json; + +using Newtonsoft.Json; using Syncfusion.Maui.AIAssistView; +using Syncfusion.Maui.Popup; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Text.RegularExpressions; using System.Windows.Input; - namespace ChartGenerator { - public class ChartViewModel : INotifyPropertyChanged + public partial class ChatViewModel : INotifyPropertyChanged { - public event PropertyChangedEventHandler? PropertyChanged; + private readonly ImagePickerHelper _imagePickerHelper; + public event PropertyChangedEventHandler? PropertyChanged; + private bool canStopResponse; + private string sendIconText; + private bool autoSuggestionPopupIsOpen; + private string inputText; + private ObservableCollection imageSourceCollection; + private int imageNo; private string? entryText; private bool showAssistView; private bool showHeader = true; private bool showIndicator = false; - private ChartConfig chartData; private string oldJson; private string newJson; private bool enableAssistant; private bool isLoading; - private ObservableCollection messages = new(); - - private ChartAIService openAIService = new(); + private ObservableCollection messages = new(); + private bool isTemporaryChatEnabled; + private SfPopup OptionsPopup; + private bool isSendIconEnabled; + internal bool isResponseStreaming; + private double sendIconWidth; + private SfPopup ExpandedEditorPopup; + private ObservableCollection