Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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";
}
}
152 changes: 113 additions & 39 deletions ChartGeneratorAISample/ChartGenerator/AIService/ChartsAIService.cs
Original file line number Diff line number Diff line change
@@ -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

/// <summary>
/// The EndPoint
/// </summary>
internal const string endpoint = "https://YOUR_ACCOUNT.openai.azure.com/";

/// <summary>
/// The Deployment name
/// </summary>
internal const string deploymentName = "deployment name";

/// <summary>
/// The Image Deployment name
/// </summary>
internal const string imageDeploymentName = "IMAGE_MODEL_NAME";

/// <summary>
/// The API key
/// </summary>
internal const string key = "API key";

/// <summary>
/// The already credential validated field
/// </summary>
private static bool isAlreadyValidated;

/// <summary>
/// The uri result field
/// </summary>
Expand Down Expand Up @@ -64,19 +40,13 @@ private async void ValidateCredential()
{
this.GetAzureOpenAIKernal();

if (isAlreadyValidated)
{
return;
}

try
{
if (Client != null)
{
await Client!.CompleteAsync("Hello, Test Check");
ChatHistory = string.Empty;
IsCredentialValid = true;
isAlreadyValidated = true;
}
else
{
Expand Down Expand Up @@ -110,16 +80,27 @@ private void GetAzureOpenAIKernal()
/// Retrieves an answer from the deployment name model using the provided user prompt.
/// </summary>
/// <param name="userPrompt">The user prompt.</param>
/// <param name="includeClassContext">Whether to include class structure context.</param>
/// <returns>The AI response.</returns>
internal async Task<string> GetAnswerFromGPT(string userPrompt)
internal async Task<string> 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();
}
Expand All @@ -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");
}
}
}

/// <summary>
/// Creates a concise context string that explains the structure of chart-related classes
/// </summary>
/// <returns>A string containing class structure information</returns>
private string GetChartClassStructureContext()
{
return @"
Chart class model reference:
ChartConfig{ChartType:enum, Title:string, Series:Collection<SeriesConfig>, XAxis/YAxis:Collection<AxisConfig>, ShowLegend:bool, SideBySidePlacement:bool}
SeriesConfig{Type:enum, Name:string, DataSource:Collection<DataModel>, Tooltip:bool}
AxisConfig{Title:string, Type:string('Numerical'|'DateTime'|'Category'|'Logarithmic'), Minimum/Maximum:double?}";
}

/// <summary>
/// Gets specific context about a particular chart class based on need
/// </summary>
/// <param name="classType">The class type to get context for ("ChartConfig", "SeriesConfig", or "AxisConfig")</param>
/// <returns>Specific context about the requested class</returns>
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<string> 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<byte[]> ConvertImageSourceToByteArray(ImageSource imageSource)
{
Stream stream = await ConvertImageSourceToStream(imageSource);

using (MemoryStream memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}

private static async Task<Stream> 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<string> 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<AIContent>
{
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
}
}
14 changes: 0 additions & 14 deletions ChartGeneratorAISample/ChartGenerator/AppShell.xaml

This file was deleted.

10 changes: 0 additions & 10 deletions ChartGeneratorAISample/ChartGenerator/AppShell.xaml.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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}">

<chart:SfCartesianChart.Title>
<Label Text="{Binding Title}" HorizontalOptions="Center" HorizontalTextAlignment="Center"/>
</chart:SfCartesianChart.Title>
<chart:SfCartesianChart.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<local:SeriesTemplates />
<local:ChartResources />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</chart:SfCartesianChart.Resources>
Expand All @@ -19,20 +21,5 @@
<chart:ChartLegend IsVisible="{Binding ShowLegend}" Placement="Bottom" ToggleSeriesVisibility="True"/>
</chart:SfCartesianChart.Legend>

<chart:SfCartesianChart.XAxes>
<chart:CategoryAxis>
<chart:CategoryAxis.Title>
<chart:ChartAxisTitle Text="{Binding XAxis.Title}"/>
</chart:CategoryAxis.Title>
</chart:CategoryAxis>
</chart:SfCartesianChart.XAxes>

<chart:SfCartesianChart.YAxes>
<chart:NumericalAxis Minimum="{Binding YAxis.Min}" Maximum="{Binding YAxis.Max}">
<chart:NumericalAxis.Title>
<chart:ChartAxisTitle Text="{Binding YAxis.Title}" />
</chart:NumericalAxis.Title>
</chart:NumericalAxis>
</chart:SfCartesianChart.YAxes>

</chart:SfCartesianChart>
Loading