diff --git a/Directory.Build.props b/Directory.Build.props
index 57f06a40f636..55f8a7d31e96 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -26,6 +26,7 @@
false
false
false
+ false
@@ -256,6 +257,13 @@
$(MauiPreviousPlatforms)
$(MauiGraphicsPreviousPlatforms);net$(_MauiPreviousDotNetVersion)-macos$(MacosPreviousTargetFrameworkVersion)
+
+
+ $(MauiPlatforms)
+ $(MauiEssentialsAIPlatforms);net$(_MauiDotNetVersion)-macos$(MacosTargetFrameworkVersion)
+
+ $(MauiPreviousPlatforms)
+ $(MauiEssentialsAIPreviousPlatforms);net$(_MauiPreviousDotNetVersion)-macos$(MacosPreviousTargetFrameworkVersion)
diff --git a/Microsoft.Maui-dev.sln b/Microsoft.Maui-dev.sln
index 794ff6270d15..9af223fe1b84 100644
--- a/Microsoft.Maui-dev.sln
+++ b/Microsoft.Maui-dev.sln
@@ -246,6 +246,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maui.Controls.Sample.Embedd
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controls.ManualTests", "src\Controls\tests\ManualTests\Controls.ManualTests.csproj", "{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI", "AI", "{BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B194A14-0963-842E-BCDD-4A2CBB559451}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{81F26DD8-70CC-7F24-050C-594BCCA2AEBB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BE0B3DF2-F37C-449D-F624-39E4FE56170C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI", "src\AI\src\Essentials.AI\Essentials.AI.csproj", "{376DAEDF-D41D-4AF4-9548-9DDC9538D533}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.Sample", "src\AI\samples\Essentials.AI.Sample\Essentials.AI.Sample.csproj", "{7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.Benchmarks", "src\AI\tests\Essentials.AI.Benchmarks\Essentials.AI.Benchmarks.csproj", "{B578E852-193C-4F37-ACB1-2B9B1B32F6B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.DeviceTests", "src\AI\tests\Essentials.AI.DeviceTests\Essentials.AI.DeviceTests.csproj", "{90B0A751-82EA-4575-B22C-256EF3A4FEF1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.UnitTests", "src\AI\tests\Essentials.AI.UnitTests\Essentials.AI.UnitTests.csproj", "{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -635,6 +653,26 @@ Global
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -748,6 +786,14 @@ Global
{55905937-1399-46DB-BA38-E426801CB759} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{4ADCBA87-30DB-44F5-85E9-94A4F4132FD9} = {E1082E26-D700-4127-9329-66D673FD2D55}
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
+ {9B194A14-0963-842E-BCDD-4A2CBB559451} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {81F26DD8-70CC-7F24-050C-594BCCA2AEBB} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {BE0B3DF2-F37C-449D-F624-39E4FE56170C} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533} = {9B194A14-0963-842E-BCDD-4A2CBB559451}
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0} = {81F26DD8-70CC-7F24-050C-594BCCA2AEBB}
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/Microsoft.Maui-mac.slnf b/Microsoft.Maui-mac.slnf
index d459143e2a5a..623e1571661a 100644
--- a/Microsoft.Maui-mac.slnf
+++ b/Microsoft.Maui-mac.slnf
@@ -2,6 +2,11 @@
"solution": {
"path": "Microsoft.Maui-dev.sln",
"projects": [
+ "src\\AI\\samples\\Essentials.AI.Sample\\Essentials.AI.Sample.csproj",
+ "src\\AI\\src\\Essentials.AI\\Essentials.AI.csproj",
+ "src\\AI\\tests\\Essentials.AI.Benchmarks\\Essentials.AI.Benchmarks.csproj",
+ "src\\AI\\tests\\Essentials.AI.DeviceTests\\Essentials.AI.DeviceTests.csproj",
+ "src\\AI\\tests\\Essentials.AI.UnitTests\\Essentials.AI.UnitTests.csproj",
"src\\BlazorWebView\\samples\\MauiRazorClassLibrarySample\\MauiRazorClassLibrarySample.csproj",
"src\\BlazorWebView\\samples\\WebViewAppShared\\WebViewAppShared.csproj",
"src\\BlazorWebView\\src\\Maui\\Microsoft.AspNetCore.Components.WebView.Maui.csproj",
diff --git a/Microsoft.Maui-vscode.sln b/Microsoft.Maui-vscode.sln
index 7217d8c92cfd..3e88b3db593e 100644
--- a/Microsoft.Maui-vscode.sln
+++ b/Microsoft.Maui-vscode.sln
@@ -220,6 +220,24 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGen.UnitTests", "src\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Maui.Controls.Xaml.Benchmarks", "src\Controls\tests\Xaml.Benchmarks\Microsoft.Maui.Controls.Xaml.Benchmarks.csproj", "{9A0A5037-DB03-4C80-876C-61FCDAE4CCAD}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI", "AI", "{BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B194A14-0963-842E-BCDD-4A2CBB559451}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{81F26DD8-70CC-7F24-050C-594BCCA2AEBB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BE0B3DF2-F37C-449D-F624-39E4FE56170C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI", "src\AI\src\Essentials.AI\Essentials.AI.csproj", "{376DAEDF-D41D-4AF4-9548-9DDC9538D533}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.Sample", "src\AI\samples\Essentials.AI.Sample\Essentials.AI.Sample.csproj", "{7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.Benchmarks", "src\AI\tests\Essentials.AI.Benchmarks\Essentials.AI.Benchmarks.csproj", "{B578E852-193C-4F37-ACB1-2B9B1B32F6B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.DeviceTests", "src\AI\tests\Essentials.AI.DeviceTests\Essentials.AI.DeviceTests.csproj", "{90B0A751-82EA-4575-B22C-256EF3A4FEF1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.UnitTests", "src\AI\tests\Essentials.AI.UnitTests\Essentials.AI.UnitTests.csproj", "{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -563,6 +581,26 @@ Global
{9A0A5037-DB03-4C80-876C-61FCDAE4CCAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9A0A5037-DB03-4C80-876C-61FCDAE4CCAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9A0A5037-DB03-4C80-876C-61FCDAE4CCAD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -664,6 +702,14 @@ Global
{4ADCBA87-30DB-44F5-85E9-94A4F4132FD9} = {E1082E26-D700-4127-9329-66D673FD2D55}
{A426B2FC-F012-436B-BDD9-BEC0025DB96B} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
{9A0A5037-DB03-4C80-876C-61FCDAE4CCAD} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
+ {9B194A14-0963-842E-BCDD-4A2CBB559451} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {81F26DD8-70CC-7F24-050C-594BCCA2AEBB} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {BE0B3DF2-F37C-449D-F624-39E4FE56170C} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533} = {9B194A14-0963-842E-BCDD-4A2CBB559451}
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0} = {81F26DD8-70CC-7F24-050C-594BCCA2AEBB}
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/Microsoft.Maui-windows.slnf b/Microsoft.Maui-windows.slnf
index 708d9629a8d9..4ed4c36e2225 100644
--- a/Microsoft.Maui-windows.slnf
+++ b/Microsoft.Maui-windows.slnf
@@ -2,6 +2,11 @@
"solution": {
"path": "Microsoft.Maui-dev.sln",
"projects": [
+ "src\\AI\\samples\\Essentials.AI.Sample\\Essentials.AI.Sample.csproj",
+ "src\\AI\\src\\Essentials.AI\\Essentials.AI.csproj",
+ "src\\AI\\tests\\Essentials.AI.Benchmarks\\Essentials.AI.Benchmarks.csproj",
+ "src\\AI\\tests\\Essentials.AI.DeviceTests\\Essentials.AI.DeviceTests.csproj",
+ "src\\AI\\tests\\Essentials.AI.UnitTests\\Essentials.AI.UnitTests.csproj",
"src\\BlazorWebView\\samples\\BlazorWinFormsApp\\BlazorWinFormsApp.csproj",
"src\\BlazorWebView\\samples\\BlazorWpfApp\\BlazorWpfApp.csproj",
"src\\BlazorWebView\\samples\\MauiRazorClassLibrarySample\\MauiRazorClassLibrarySample.csproj",
diff --git a/Microsoft.Maui.sln b/Microsoft.Maui.sln
index fbbeaadc0200..37c133515923 100644
--- a/Microsoft.Maui.sln
+++ b/Microsoft.Maui.sln
@@ -251,6 +251,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Maui.Controls.Sample.Embedd
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Controls.ManualTests", "src\Controls\tests\ManualTests\Controls.ManualTests.csproj", "{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI", "AI", "{BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9B194A14-0963-842E-BCDD-4A2CBB559451}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{81F26DD8-70CC-7F24-050C-594BCCA2AEBB}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BE0B3DF2-F37C-449D-F624-39E4FE56170C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI", "src\AI\src\Essentials.AI\Essentials.AI.csproj", "{376DAEDF-D41D-4AF4-9548-9DDC9538D533}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.Sample", "src\AI\samples\Essentials.AI.Sample\Essentials.AI.Sample.csproj", "{7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.Benchmarks", "src\AI\tests\Essentials.AI.Benchmarks\Essentials.AI.Benchmarks.csproj", "{B578E852-193C-4F37-ACB1-2B9B1B32F6B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.DeviceTests", "src\AI\tests\Essentials.AI.DeviceTests\Essentials.AI.DeviceTests.csproj", "{90B0A751-82EA-4575-B22C-256EF3A4FEF1}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Essentials.AI.UnitTests", "src\AI\tests\Essentials.AI.UnitTests\Essentials.AI.UnitTests.csproj", "{BD5D52FA-CA8E-4236-979A-95B4CE4158D8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -648,6 +666,26 @@ Global
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -764,6 +802,14 @@ Global
{F1BC506B-3A9E-4779-994E-339AFB21C9B9} = {7AC28763-9C68-4BF9-A1BA-25CBFFD2D15C}
{4ADCBA87-30DB-44F5-85E9-94A4F4132FD9} = {E1082E26-D700-4127-9329-66D673FD2D55}
{E2BFD1F1-07A8-8DBE-3661-894D0FE37D9C} = {25D0D27A-C5FE-443D-8B65-D6C987F4A80E}
+ {9B194A14-0963-842E-BCDD-4A2CBB559451} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {81F26DD8-70CC-7F24-050C-594BCCA2AEBB} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {BE0B3DF2-F37C-449D-F624-39E4FE56170C} = {BA58FF10-E1F2-1F22-EED5-4647CFF8BF60}
+ {376DAEDF-D41D-4AF4-9548-9DDC9538D533} = {9B194A14-0963-842E-BCDD-4A2CBB559451}
+ {7A6CB0C2-B5A9-44BA-B774-D1E1C1E371D0} = {81F26DD8-70CC-7F24-050C-594BCCA2AEBB}
+ {B578E852-193C-4F37-ACB1-2B9B1B32F6B6} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {90B0A751-82EA-4575-B22C-256EF3A4FEF1} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
+ {BD5D52FA-CA8E-4236-979A-95B4CE4158D8} = {BE0B3DF2-F37C-449D-F624-39E4FE56170C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0B8ABEAD-D2B5-4370-A187-62B5ABE4EE50}
diff --git a/eng/Microsoft.Maui.Packages-mac.slnf b/eng/Microsoft.Maui.Packages-mac.slnf
index 5034642aaa1c..5529edb22fdb 100644
--- a/eng/Microsoft.Maui.Packages-mac.slnf
+++ b/eng/Microsoft.Maui.Packages-mac.slnf
@@ -2,6 +2,7 @@
"solution": {
"path": "..\\Microsoft.Maui.sln",
"projects": [
+ "src\\AI\\src\\Essentials.AI\\Essentials.AI.csproj",
"src\\BlazorWebView\\src\\Maui\\Microsoft.AspNetCore.Components.WebView.Maui.csproj",
"src\\Compatibility\\Core\\src\\Compatibility.csproj",
"src\\Controls\\Foldable\\src\\Controls.Foldable.csproj",
diff --git a/eng/Microsoft.Maui.Packages.slnf b/eng/Microsoft.Maui.Packages.slnf
index 45bbff7c3a3a..7c21491d97e3 100644
--- a/eng/Microsoft.Maui.Packages.slnf
+++ b/eng/Microsoft.Maui.Packages.slnf
@@ -2,6 +2,7 @@
"solution": {
"path": "..\\Microsoft.Maui.sln",
"projects": [
+ "src\\AI\\src\\Essentials.AI\\Essentials.AI.csproj",
"src\\BlazorWebView\\src\\Maui\\Microsoft.AspNetCore.Components.WebView.Maui.csproj",
"src\\BlazorWebView\\src\\WindowsForms\\Microsoft.AspNetCore.Components.WebView.WindowsForms.csproj",
"src\\BlazorWebView\\src\\Wpf\\Microsoft.AspNetCore.Components.WebView.Wpf.csproj",
diff --git a/eng/NuGetVersions.targets b/eng/NuGetVersions.targets
index 123b99d6c4ff..6063ac89b505 100644
--- a/eng/NuGetVersions.targets
+++ b/eng/NuGetVersions.targets
@@ -84,6 +84,14 @@
Update="System.Runtime.CompilerServices.Unsafe"
Version="$(SystemRuntimeCompilerServicesUnsafePackageVersion)"
/>
+
+
+
https://github.com/dotnet/templating
3f4da9ced34942d83054e647f3b1d9d7dde281e8
+
+ https://github.com/dotnet/dotnet
+ 7b29526f2107416f68578bcb9deaca74fcfcf7f0
+
+
+ https://github.com/dotnet/dotnet
+ 7b29526f2107416f68578bcb9deaca74fcfcf7f0
+
https://github.com/dotnet/dotnet
7b29526f2107416f68578bcb9deaca74fcfcf7f0
diff --git a/eng/Versions.props b/eng/Versions.props
index 5295426aefa5..a3c239e7d6b4 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -40,6 +40,8 @@
$(MicrosoftNETCoreAppRefPackageVersion)
$(MicrosoftNETCoreAppRefPackageVersion)
+ 10.0.0
+ 10.0.0
10.0.0
10.0.0
10.0.0
@@ -52,6 +54,7 @@
10.0.0
10.0.0
10.0.0
+ 10.0.0
36.1.2
35.0.105
diff --git a/src/AI/samples/Directory.Build.props b/src/AI/samples/Directory.Build.props
new file mode 100644
index 000000000000..41af9256608e
--- /dev/null
+++ b/src/AI/samples/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+ true
+ true
+ Maui
+ $(WarningsNotAsErrors);XC0022;XC0023
+ true
+
+
+
diff --git a/src/AI/samples/Directory.Build.targets b/src/AI/samples/Directory.Build.targets
new file mode 100644
index 000000000000..68be46514e92
--- /dev/null
+++ b/src/AI/samples/Directory.Build.targets
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/App.xaml b/src/AI/samples/Essentials.AI.Sample/App.xaml
new file mode 100644
index 000000000000..bcb92097e3ff
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/App.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/App.xaml.cs b/src/AI/samples/Essentials.AI.Sample/App.xaml.cs
new file mode 100644
index 000000000000..c82cafb92a64
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/App.xaml.cs
@@ -0,0 +1,14 @@
+namespace Maui.Controls.Sample;
+
+public partial class App : Application
+{
+ public App()
+ {
+ InitializeComponent();
+ }
+
+ protected override Window CreateWindow(IActivationState? activationState)
+ {
+ return new Window(new AppShell());
+ }
+}
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/AppShell.xaml b/src/AI/samples/Essentials.AI.Sample/AppShell.xaml
new file mode 100644
index 000000000000..036ee279df00
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/AppShell.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/AppShell.xaml.cs b/src/AI/samples/Essentials.AI.Sample/AppShell.xaml.cs
new file mode 100644
index 000000000000..d9d0dee6c438
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/AppShell.xaml.cs
@@ -0,0 +1,15 @@
+using Maui.Controls.Sample.Pages;
+
+namespace Maui.Controls.Sample;
+
+public partial class AppShell : Shell
+{
+ public AppShell()
+ {
+ InitializeComponent();
+
+ // Register routes for navigation
+ // Only TripPlanningPage is navigable - LandmarkTripView is a child component
+ Routing.RegisterRoute(nameof(TripPlanningPage), typeof(TripPlanningPage));
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Converters/InvertedBoolConverter.cs b/src/AI/samples/Essentials.AI.Sample/Converters/InvertedBoolConverter.cs
new file mode 100644
index 000000000000..3862bbd20460
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Converters/InvertedBoolConverter.cs
@@ -0,0 +1,16 @@
+using System.Globalization;
+
+namespace Maui.Controls.Sample.Converters;
+
+public class InvertedBoolConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value is bool boolValue && !boolValue;
+ }
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value is bool boolValue && !boolValue;
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Converters/IsNotNullConverter.cs b/src/AI/samples/Essentials.AI.Sample/Converters/IsNotNullConverter.cs
new file mode 100644
index 000000000000..d81d737ce8f8
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Converters/IsNotNullConverter.cs
@@ -0,0 +1,16 @@
+using System.Globalization;
+
+namespace Maui.Controls.Sample.Converters;
+
+public class IsNotNullConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value is not null;
+ }
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Converters/IsNotNullOrEmptyConverter.cs b/src/AI/samples/Essentials.AI.Sample/Converters/IsNotNullOrEmptyConverter.cs
new file mode 100644
index 000000000000..650da7bb383d
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Converters/IsNotNullOrEmptyConverter.cs
@@ -0,0 +1,16 @@
+using System.Globalization;
+
+namespace Maui.Controls.Sample.Converters;
+
+public class IsNotNullOrEmptyConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ return value is string str && !string.IsNullOrWhiteSpace(str);
+ }
+
+ public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Directory.Build.targets b/src/AI/samples/Essentials.AI.Sample/Directory.Build.targets
new file mode 100644
index 000000000000..fcc6017c6249
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Directory.Build.targets
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Essentials.AI.Sample.csproj b/src/AI/samples/Essentials.AI.Sample/Essentials.AI.Sample.csproj
new file mode 100644
index 000000000000..c27e64c3a63b
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Essentials.AI.Sample.csproj
@@ -0,0 +1,90 @@
+
+
+
+ $(MauiSamplePlatforms)
+ $(TargetFrameworks);$(MauiSamplePreviousPlatforms)
+ Maui.Essentials.AI.Sample
+ Microsoft.Maui.Essentials.AI.Sample
+ Exe
+ true
+ true
+ false
+ enable
+ enable
+ preview
+ SourceGen
+
+ maccatalyst-x64
+ maccatalyst-arm64
+ 2727d4aa-a3a5-484b-9447-91604761972b
+
+
+
+ Essentials AI
+ com.microsoft.maui.essentials.ai
+ 1.0
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(DefineConstants);ENABLE_OPENAI_CLIENT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))\AppData\Roaming\Microsoft\UserSecrets\$(UserSecretsId)\secrets.json
+
+
+ $([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))/.microsoft/usersecrets/$(UserSecretsId)/secrets.json
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/MauiProgram.cs b/src/AI/samples/Essentials.AI.Sample/MauiProgram.cs
new file mode 100644
index 000000000000..02290d97b2b7
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/MauiProgram.cs
@@ -0,0 +1,99 @@
+using System.Reflection;
+using Maui.Controls.Sample.Pages;
+using Maui.Controls.Sample.Services;
+using Maui.Controls.Sample.ViewModels;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+#if ENABLE_OPENAI_CLIENT
+using System.ClientModel;
+using OpenAI;
+using OpenAI.Chat;
+#endif
+
+namespace Maui.Controls.Sample;
+
+public static class MauiProgram
+{
+ public static bool UseCloudAI = true;
+
+ public static MauiApp CreateMauiApp()
+ {
+ var builder = MauiApp.CreateBuilder();
+
+ builder.Configuration
+ .AddJsonStream(GetUserSecretsStream() ?? throw new InvalidOperationException("User secrets file not found as embedded resource."));
+
+ builder.UseMauiApp();
+
+#if IOS || ANDROID || MACCATALYST
+ builder.UseMauiMaps();
+#endif
+
+ builder.ConfigureFonts(fonts =>
+ {
+ fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
+ fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
+ });
+
+ // Register AI
+ #if ENABLE_OPENAI_CLIENT
+ if (UseCloudAI)
+ {
+ var aiSection = builder.Configuration.GetSection("AI");
+ var client = new ChatClient(
+ credential: new ApiKeyCredential(aiSection["ApiKey"] ?? throw new InvalidOperationException("API Key not found in user secrets.")),
+ model: aiSection["DeploymentName"] ?? throw new InvalidOperationException("Deployment Name not found in user secrets."),
+ options: new OpenAIClientOptions()
+ {
+ Endpoint = new(aiSection["Endpoint"] ?? throw new InvalidOperationException("Endpoint not found in user secrets.")),
+ });
+ var ichatClient = client.AsIChatClient();
+
+ builder.Services.AddSingleton(provider =>
+ {
+ var lf = provider.GetRequiredService();
+ var realClient = ichatClient
+ .AsBuilder()
+ .UseLogging(lf)
+ .UseFunctionInvocation()
+ .Build();
+ return realClient;
+ });
+ }
+ #endif
+
+ // Register Pages
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
+ // Register ViewModels
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+
+ // Register Services
+ builder.Services.AddSingleton(sp => LandmarkDataService.Instance);
+ builder.Services.AddTransient();
+ builder.Services.AddTransient();
+ builder.Services.AddHttpClient();
+
+ // Configure Logging
+ builder.Services.AddLogging();
+ builder.Logging.AddDebug();
+ builder.Logging.AddConsole();
+#if DEBUG
+ builder.Logging.SetMinimumLevel(LogLevel.Debug);
+#else
+ builder.Logging.SetMinimumLevel(LogLevel.Information);
+#endif
+
+ return builder.Build();
+ }
+
+ private static Stream? GetUserSecretsStream()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var stream = assembly.GetManifestResourceStream("Maui.Essentials.AI.Sample.secrets.json");
+ return stream;
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Models/Itinerary.cs b/src/AI/samples/Essentials.AI.Sample/Models/Itinerary.cs
new file mode 100644
index 000000000000..d88be040c450
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Models/Itinerary.cs
@@ -0,0 +1,101 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace Maui.Controls.Sample.Models;
+
+[DisplayName("Itinerary")]
+[Description("A travel itinerary with days and activities.")]
+public record Itinerary
+{
+ [Description("An exciting name for the trip.")]
+ public required string Title { get; init; }
+
+ public required string DestinationName { get; init; }
+
+ public required string Description { get; init; }
+
+ [Description("An explanation of how the itinerary meets the person's special requests.")]
+ public required string Rationale { get; init; }
+
+ [Description("A list of day-by-day plans.")]
+ [Length(3, 3)]
+ public required List Days { get; init; }
+
+ public static Itinerary GetExampleTripToJapan() =>
+ new()
+ {
+ Title = "Onsen Trip to Japan",
+ DestinationName = "Mt. Fuji",
+ Description = "Sushi, hot springs, and ryokan with a toddler!",
+ Rationale =
+ """
+ You are traveling with a child, so climbing Mt. Fuji is probably not an option,
+ but there is lots to do around Kawaguchiko Lake, including Fujikyu.
+ I recommend staying in a ryokan because you love hotsprings.
+ """,
+ Days = [
+ new DayPlan
+ {
+ Title = "Sushi and Shopping Near Kawaguchiko",
+ Subtitle = "Spend your final day enjoying sushi and souvenir shopping.",
+ Destination = "Kawaguchiko Lake",
+ Activities = [
+ new Activity
+ {
+ Type = ActivityKind.FoodAndDining,
+ Title = "The Restaurant serving Sushi",
+ Description = "Visit an authentic sushi restaurant for lunch."
+ },
+ new Activity
+ {
+ Type = ActivityKind.Shopping,
+ Title = "The Plaza",
+ Description = "Enjoy souvenir shopping at various shops."
+ },
+ new Activity
+ {
+ Type = ActivityKind.Sightseeing,
+ Title = "The Beautiful Cherry Blossom Park",
+ Description = "Admire the beautiful cherry blossom trees in the park."
+ },
+ new Activity
+ {
+ Type = ActivityKind.HotelAndLodging,
+ Title = "The Hotel",
+ Description = "Spend one final evening in the hotspring before heading home."
+ }]
+ }]
+ };
+}
+
+[DisplayName("DayPlan")]
+public record DayPlan
+{
+ [Description("A unique and exciting title for this day plan.")]
+ public required string Title { get; init; }
+
+ public required string Subtitle { get; init; }
+
+ public required string Destination { get; init; }
+
+ [Length(3, 3)]
+ public required List Activities { get; init; }
+}
+
+[DisplayName("Activity")]
+public record Activity
+{
+ public required ActivityKind Type { get; init; }
+
+ public required string Title { get; init; }
+
+ public required string Description { get; init; }
+}
+
+public enum ActivityKind
+{
+ Sightseeing,
+ FoodAndDining,
+ Shopping,
+ HotelAndLodging
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Models/Landmark.cs b/src/AI/samples/Essentials.AI.Sample/Models/Landmark.cs
new file mode 100644
index 000000000000..3d7aafd3b114
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Models/Landmark.cs
@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace Maui.Controls.Sample.Models;
+
+public record Landmark
+{
+ [JsonPropertyName("id")]
+ public int Id { get; init; }
+
+ [JsonPropertyName("name")]
+ public required string Name { get; init; }
+
+ [JsonPropertyName("continent")]
+ public required string Continent { get; init; }
+
+ [JsonPropertyName("description")]
+ public required string Description { get; init; }
+
+ [JsonPropertyName("shortDescription")]
+ public required string ShortDescription { get; init; }
+
+ [JsonPropertyName("latitude")]
+ public double Latitude { get; init; }
+
+ [JsonPropertyName("longitude")]
+ public double Longitude { get; init; }
+
+ [JsonPropertyName("span")]
+ public double Span { get; init; }
+
+ [JsonPropertyName("placeID")]
+ public string? PlaceId { get; init; }
+
+ [JsonIgnore]
+ public string BackgroundImageName => $"{Id}";
+
+ [JsonIgnore]
+ public string ThumbnailImageName => $"{Id}_thumb";
+
+ [JsonIgnore]
+ public Location Location => new(Latitude, Longitude);
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Models/Weather.cs b/src/AI/samples/Essentials.AI.Sample/Models/Weather.cs
new file mode 100644
index 000000000000..a2c2c8646a07
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Models/Weather.cs
@@ -0,0 +1,86 @@
+using System.Text.Json.Serialization;
+
+namespace Maui.Controls.Sample.Models;
+
+public record WeatherForecast
+{
+ [JsonPropertyName("latitude")]
+ public double Latitude { get; init; }
+
+ [JsonPropertyName("longitude")]
+ public double Longitude { get; init; }
+
+ [JsonPropertyName("daily")]
+ public DailyWeather Daily { get; init; } = new();
+}
+
+public record DailyWeather
+{
+ [JsonPropertyName("time")]
+ public List Time { get; init; } = [];
+
+ [JsonPropertyName("temperature_2m_mean")]
+ public List TemperatureMean { get; init; } = [];
+
+ [JsonPropertyName("weather_code")]
+ public List WeatherCode { get; init; } = [];
+}
+
+public static class WeatherCodeExtensions
+{
+ public static string GetWeatherEmoji(int code)
+ {
+ return code switch
+ {
+ 0 => "☀️",
+ 1 or 2 => "🌤️",
+ 3 => "☁️",
+ 45 or 48 => "🌫️",
+ 51 or 53 or 55 or 56 or 57 => "🌧️",
+ 61 or 63 or 65 => "🌧️",
+ 66 or 67 => "🌧️",
+ 71 or 73 or 75 or 77 => "❄️",
+ 80 or 81 or 82 => "🌧️",
+ 85 or 86 => "❄️",
+ 95 => "⛈️",
+ 96 or 99 => "⛈️",
+ _ => "☁️"
+ };
+ }
+
+ public static string GetWeatherDescription(int code)
+ {
+ return code switch
+ {
+ 0 => "Clear sky",
+ 1 => "Mainly clear",
+ 2 => "Partly cloudy",
+ 3 => "Overcast",
+ 45 => "Fog",
+ 48 => "Depositing rime fog",
+ 51 => "Light drizzle",
+ 53 => "Moderate drizzle",
+ 55 => "Dense drizzle",
+ 56 => "Light freezing drizzle",
+ 57 => "Dense freezing drizzle",
+ 61 => "Slight rain",
+ 63 => "Moderate rain",
+ 65 => "Heavy rain",
+ 66 => "Light freezing rain",
+ 67 => "Heavy freezing rain",
+ 71 => "Slight snow",
+ 73 => "Moderate snow",
+ 75 => "Heavy snow",
+ 77 => "Snow grains",
+ 80 => "Slight rain showers",
+ 81 => "Moderate rain showers",
+ 82 => "Violent rain showers",
+ 85 => "Slight snow showers",
+ 86 => "Heavy snow showers",
+ 95 => "Thunderstorm",
+ 96 => "Thunderstorm with slight hail",
+ 99 => "Thunderstorm with heavy hail",
+ _ => "Unknown"
+ };
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Pages/LandmarksPage.xaml b/src/AI/samples/Essentials.AI.Sample/Pages/LandmarksPage.xaml
new file mode 100644
index 000000000000..6b5c624b5604
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Pages/LandmarksPage.xaml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Pages/LandmarksPage.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Pages/LandmarksPage.xaml.cs
new file mode 100644
index 000000000000..71fb097ad41e
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Pages/LandmarksPage.xaml.cs
@@ -0,0 +1,26 @@
+using Maui.Controls.Sample.Models;
+using Maui.Controls.Sample.ViewModels;
+
+namespace Maui.Controls.Sample.Pages;
+
+public partial class LandmarksPage : ContentPage
+{
+ public LandmarksPage(LandmarksViewModel viewModel)
+ {
+ InitializeComponent();
+
+ BindingContext = viewModel;
+
+ Loaded += async (_, _) => await viewModel.InitializeAsync();
+ }
+
+ private async void OnLandmarkTapped(object? sender, Landmark landmark)
+ {
+ var parameters = new Dictionary
+ {
+ { "Landmark", landmark }
+ };
+
+ await Shell.Current.GoToAsync(nameof(TripPlanningPage), parameters);
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Pages/TripPlanningPage.xaml b/src/AI/samples/Essentials.AI.Sample/Pages/TripPlanningPage.xaml
new file mode 100644
index 000000000000..1ce3711ec370
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Pages/TripPlanningPage.xaml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Pages/TripPlanningPage.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Pages/TripPlanningPage.xaml.cs
new file mode 100644
index 000000000000..4bbfe575d52b
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Pages/TripPlanningPage.xaml.cs
@@ -0,0 +1,20 @@
+using Maui.Controls.Sample.ViewModels;
+
+namespace Maui.Controls.Sample.Pages;
+
+public partial class TripPlanningPage : ContentPage
+{
+ public TripPlanningPage(TripPlanningViewModel viewModel)
+ {
+ InitializeComponent();
+
+ BindingContext = viewModel;
+
+ Loaded += async (_, _) => await viewModel.InitializeAsync();
+ }
+
+ private async void OnBackButtonClicked(object? sender, EventArgs e)
+ {
+ await Shell.Current.GoToAsync("..");
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Android/AndroidManifest.xml b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 000000000000..e9937ad77d51
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Android/MainActivity.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/MainActivity.cs
new file mode 100644
index 000000000000..cebbb65a1fcf
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/MainActivity.cs
@@ -0,0 +1,10 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+
+namespace Maui.Controls.Sample;
+
+[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
+public class MainActivity : MauiAppCompatActivity
+{
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Android/MainApplication.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/MainApplication.cs
new file mode 100644
index 000000000000..7ca70bdfbefe
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/MainApplication.cs
@@ -0,0 +1,15 @@
+using Android.App;
+using Android.Runtime;
+
+namespace Maui.Controls.Sample;
+
+[Application]
+public class MainApplication : MauiApplication
+{
+ public MainApplication(IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Android/Resources/values/colors.xml b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/Resources/values/colors.xml
new file mode 100644
index 000000000000..c04d7492abf8
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Android/Resources/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #512BD4
+ #2B0B98
+ #2B0B98
+
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/AppDelegate.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/AppDelegate.cs
new file mode 100644
index 000000000000..70930ecd7044
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace Maui.Controls.Sample;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Entitlements.plist b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Entitlements.plist
new file mode 100644
index 000000000000..de4adc94a9c9
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Entitlements.plist
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+ com.apple.security.network.client
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Info.plist b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 000000000000..72689771518a
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UIDeviceFamily
+
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Program.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Program.cs
new file mode 100644
index 000000000000..d96ccc991875
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/MacCatalyst/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace Maui.Controls.Sample;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/App.xaml b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/App.xaml
new file mode 100644
index 000000000000..bd70e8ea2553
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/App.xaml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/App.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/App.xaml.cs
new file mode 100644
index 000000000000..fee8c0b3dd3e
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,24 @@
+using Microsoft.UI.Xaml;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace Maui.Controls.Sample.WinUI;
+
+///
+/// Provides application-specific behavior to supplement the default Application class.
+///
+public partial class App : MauiWinUIApplication
+{
+ ///
+ /// Initializes the singleton application object. This is the first line of authored code
+ /// executed, and as such is the logical equivalent of main() or WinMain().
+ ///
+ public App()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/Package.appxmanifest b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 000000000000..b5b1cc504dbe
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+ $placeholder$
+ User Name
+ $placeholder$.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/app.manifest b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/app.manifest
new file mode 100644
index 000000000000..1a5a8fb34b88
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/AppDelegate.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/AppDelegate.cs
new file mode 100644
index 000000000000..70930ecd7044
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/AppDelegate.cs
@@ -0,0 +1,9 @@
+using Foundation;
+
+namespace Maui.Controls.Sample;
+
+[Register("AppDelegate")]
+public class AppDelegate : MauiUIApplicationDelegate
+{
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Info.plist b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Info.plist
new file mode 100644
index 000000000000..0004a4fdee5d
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Program.cs b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Program.cs
new file mode 100644
index 000000000000..d96ccc991875
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Program.cs
@@ -0,0 +1,15 @@
+using ObjCRuntime;
+using UIKit;
+
+namespace Maui.Controls.Sample;
+
+public class Program
+{
+ // This is the main entry point of the application.
+ static void Main(string[] args)
+ {
+ // if you want to use a different Application Delegate class from "AppDelegate"
+ // you can specify it here.
+ UIApplication.Main(args, null, typeof(AppDelegate));
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Resources/PrivacyInfo.xcprivacy b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Resources/PrivacyInfo.xcprivacy
new file mode 100644
index 000000000000..24ab3b4334cb
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Platforms/iOS/Resources/PrivacyInfo.xcprivacy
@@ -0,0 +1,51 @@
+
+
+
+
+
+ NSPrivacyAccessedAPITypes
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryFileTimestamp
+ NSPrivacyAccessedAPITypeReasons
+
+ C617.1
+
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategorySystemBootTime
+ NSPrivacyAccessedAPITypeReasons
+
+ 35F9.1
+
+
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryDiskSpace
+ NSPrivacyAccessedAPITypeReasons
+
+ E174.1
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Properties/launchSettings.json b/src/AI/samples/Essentials.AI.Sample/Properties/launchSettings.json
new file mode 100644
index 000000000000..4f857936f4f5
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Windows Machine": {
+ "commandName": "Project",
+ "nativeDebugging": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/AppIcon/appicon.svg b/src/AI/samples/Essentials.AI.Sample/Resources/AppIcon/appicon.svg
new file mode 100644
index 000000000000..9d63b6513a1c
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/AppIcon/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/AppIcon/appiconfg.svg b/src/AI/samples/Essentials.AI.Sample/Resources/AppIcon/appiconfg.svg
new file mode 100644
index 000000000000..21dfb25f187b
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/AppIcon/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Fonts/OpenSans-Regular.ttf b/src/AI/samples/Essentials.AI.Sample/Resources/Fonts/OpenSans-Regular.ttf
new file mode 100644
index 000000000000..63ba3efc179b
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Fonts/OpenSans-Regular.ttf differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Fonts/OpenSans-Semibold.ttf b/src/AI/samples/Essentials.AI.Sample/Resources/Fonts/OpenSans-Semibold.ttf
new file mode 100644
index 000000000000..774cb9bb310b
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Fonts/OpenSans-Semibold.ttf differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Images/dotnet_bot.png b/src/AI/samples/Essentials.AI.Sample/Resources/Images/dotnet_bot.png
new file mode 100644
index 000000000000..054167e59747
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Images/dotnet_bot.png differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/AboutAssets.txt b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/AboutAssets.txt
new file mode 100644
index 000000000000..89dc758d6e0d
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/AboutAssets.txt
@@ -0,0 +1,15 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories). Deployment of the asset to your application
+is automatically handled by the following `MauiAsset` Build Action within your `.csproj`.
+
+
+
+These files will be deployed with your package and will be accessible using Essentials:
+
+ async Task LoadMauiAsset()
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt");
+ using var reader = new StreamReader(stream);
+
+ var contents = reader.ReadToEnd();
+ }
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1001.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1001.jpg
new file mode 100644
index 000000000000..ff63af08bec4
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1001.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1002.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1002.jpg
new file mode 100644
index 000000000000..2446e92a1f9b
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1002.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1003.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1003.jpg
new file mode 100644
index 000000000000..82121c8cf678
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1003.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1004.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1004.jpg
new file mode 100644
index 000000000000..e56bc7582ad8
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1004.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1005.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1005.jpg
new file mode 100644
index 000000000000..1efa74bd53a5
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1005.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1006.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1006.jpg
new file mode 100644
index 000000000000..9100f361c084
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1006.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1007.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1007.jpg
new file mode 100644
index 000000000000..47bd2c822c8a
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1007.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1008.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1008.jpg
new file mode 100644
index 000000000000..e6b22fc2c68f
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1008.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1009.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1009.jpg
new file mode 100644
index 000000000000..2d93e94b1943
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1009.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1010.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1010.jpg
new file mode 100644
index 000000000000..ff25d95f4b47
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1010.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1011.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1011.jpg
new file mode 100644
index 000000000000..43cc8b03cd2f
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1011.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1012.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1012.jpg
new file mode 100644
index 000000000000..79e172af3c95
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1012.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1014.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1014.jpg
new file mode 100644
index 000000000000..963098d9bd41
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1014.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1015.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1015.jpg
new file mode 100644
index 000000000000..4aecc64251f4
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1015.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1016.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1016.jpg
new file mode 100644
index 000000000000..3bc356c9ac7b
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1016.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1017.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1017.jpg
new file mode 100644
index 000000000000..5077a4f43b95
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1017.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1018.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1018.jpg
new file mode 100644
index 000000000000..6755635f1805
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1018.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1019.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1019.jpg
new file mode 100644
index 000000000000..4542228ae996
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1019.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1020.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1020.jpg
new file mode 100644
index 000000000000..b2f4f5ddcc3c
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1020.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1021.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1021.jpg
new file mode 100644
index 000000000000..c9544e06b5c8
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Backgrounds/1021.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1001_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1001_thumb.jpg
new file mode 100644
index 000000000000..72d869b25e3c
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1001_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1002_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1002_thumb.jpg
new file mode 100644
index 000000000000..85a8f1f0d529
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1002_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1003_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1003_thumb.jpg
new file mode 100644
index 000000000000..403190c922bc
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1003_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1004_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1004_thumb.jpg
new file mode 100644
index 000000000000..34ca80f8a46b
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1004_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1005_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1005_thumb.jpg
new file mode 100644
index 000000000000..1368e60d20ed
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1005_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1006_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1006_thumb.jpg
new file mode 100644
index 000000000000..53a8fade6407
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1006_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1007_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1007_thumb.jpg
new file mode 100644
index 000000000000..c4f31826b935
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1007_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1008_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1008_thumb.jpg
new file mode 100644
index 000000000000..2a2847896819
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1008_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1009_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1009_thumb.jpg
new file mode 100644
index 000000000000..475d182f067f
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1009_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1010_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1010_thumb.jpg
new file mode 100644
index 000000000000..1e74432e16a8
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1010_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1011_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1011_thumb.jpg
new file mode 100644
index 000000000000..6c4d02ca5cef
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1011_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1012_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1012_thumb.jpg
new file mode 100644
index 000000000000..1c50f3d6c96a
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1012_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1014_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1014_thumb.jpg
new file mode 100644
index 000000000000..eb17bcb430a0
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1014_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1015_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1015_thumb.jpg
new file mode 100644
index 000000000000..880dea3f0666
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1015_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1016_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1016_thumb.jpg
new file mode 100644
index 000000000000..e77757bfd17b
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1016_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1017_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1017_thumb.jpg
new file mode 100644
index 000000000000..f457c251e4c3
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1017_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1018_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1018_thumb.jpg
new file mode 100644
index 000000000000..9e204f823b6e
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1018_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1019_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1019_thumb.jpg
new file mode 100644
index 000000000000..18399c92c3ae
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1019_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1020_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1020_thumb.jpg
new file mode 100644
index 000000000000..e4cc55d85877
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1020_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1021_thumb.jpg b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1021_thumb.jpg
new file mode 100644
index 000000000000..1893301c13ca
Binary files /dev/null and b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/Images/Thumbnails/1021_thumb.jpg differ
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Raw/landmarkData.json b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/landmarkData.json
new file mode 100644
index 000000000000..b582a1311a95
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/Raw/landmarkData.json
@@ -0,0 +1,222 @@
+[
+ {
+ "name": "Sahara Desert",
+ "continent": "Africa",
+ "id": 1001,
+ "placeID": "IC6C65CA81B4B2772",
+ "longitude": 10.33569,
+ "latitude": 23.90013,
+ "span": 40.0,
+ "description": "The sprawling Sahara Desert spans more than 3.5 million square miles and is the largest hot desert in the world. Covering much of northern Africa, it reaches three major bodies of water: the Atlantic Ocean in the west, the Red Sea in the east, and the Mediterranean Sea to the north. To the south is the Sahei region. The Sahara’s harsh environment is characterized by vast sand dunes, arid plains, and rocky plateaus. The tallest sand dune is located in the Erg Chebbi region of southeastern Morocco and reaches a height of roughly 330 feet. The desert plays an important role in shaping climate patterns and ecological systems.\n\nFormed over the course of millions of years and dating back to the Precambrian era, the Sahara was once a lush, verdant landscape with lakes and rivers. Over many years, wind erosion has sculpted towering cliffs, rock arches, deep canyons and many striking and picturesque landforms. Beneath a surface composed of gravel plains and sand seas lies vast underground aquifers that store ancient water, hidden remnants of a time when the Sahara was a much more hospitable place.\n\nWhile the Sahara experiences extreme temperatures and has scarce rainfall, it supports a variety of plant and animal life that have adapted to the harsh conditions. The sparse vegetation includes drought-tolerant varieties such as desert grasses such as Lovegrass that can store water in their roots and help prevent soil erosion. Other vegetation includes Acacia trees, date palms, and olive trees. Animal life has also adapted to be able to endure the extreme heat and extended periods of time with little or no water. Fennec foxes, camels, addax, and scorpions are some examples of the species found in the Sahara. Many animals have made nocturnal adaptations in order to remain hidden during the high daytime temperatures. Many migratory birds pass through the region and take advantage of seasonal water sources and oases.",
+ "shortDescription": "Covering much of northern Africa, it reaches three major bodies of water: the Atlantic Ocean in the west, the Red Sea in the east, and the Mediterranean Sea to the north."
+ },
+ {
+ "name": "Serengeti",
+ "continent": "Africa",
+ "id": 1002,
+ "placeID": "IB3A0184A4D301279",
+ "longitude": 34.88159,
+ "latitude": -2.45469,
+ "span": 10.0,
+ "description": "Located in northern Tanzania and southwestern Kenya, the Serengeti is a mix of riparian forests, acacia woodlands, and grasslands that covers nearly 12,000 square miles. With open plains that stretch seemingly forever, it creates a breathtaking backdrop for one of the most spectacular wildlife events on Earth: the awe-inspiring Great Migration.\n\nVolcano activity from the nearby Ngorongoro Highlands shaped much of the region, depositing nutrient-rich soil as a result of past eruptions. The Serengeti sits atop Precambrian rock formations and supports vast herds of herbivores with abundant grasses. The Mara and Grumeti rivers carve their way through he plains, providing crucial water supplies to sustain wildlife year-round.\n\nThe Serengeti is home to one of Africa’s highest concentrations of large mammal species, including elephants, giraffes, hyenas, lions, and zebras. Every year, more than a million wildebeest make a circular migration across the Serengeti Plains, following seasonal rains. Their grazing and trampling of grass allow new grasses to grow, while their wast helps fertilize the soil.",
+ "shortDescription": "Located in northern Tanzania and southwestern Kenya, the Serengeti is a mix of riparian forests, acacia woodlands, and grasslands that covers nearly 12,000 square miles."
+ },
+ {
+ "name": "Deadvlei",
+ "continent": "Africa",
+ "id": 1003,
+ "placeID": "IBD2966F32E73D261",
+ "longitude": 15.29429,
+ "latitude": -24.7629,
+ "span": 10.0,
+ "description": "An otherworldly landscape of contrasting bleached white claypan ground, blackened camel thorn trees, and fiery red-orange dunes, Deadvlei is located in Namibia’s Namib-Naukluft National Park. Over 900 years ago, Deadvlei was a thriving marshland, but dried up as shifting sand dunes cut off its water supply. Despite it’s remote location, Deadvlei has become a renown tourist attraction, especially for photographers, enticing visitors from around the world to witness it’s unique beauty.\n\nHome to some of highest dunes in the world — some reaching up to 1100 feet — are sculpted into graceful ever-changing shapes. The vibrant hue of the dunes is a result of the iron oxide content in the sand and shift with the sunlight.\n\nDue to extreme arid conditions, the dead camel trees don’t decompose, leaving behind ghostly reminders of a vibrant ecosystem dating back a millennia. Even with the harsh environment, some plant species such as nara melons and salsola have adapted and survive off the morning mist and rare rainfall.",
+ "shortDescription": "An otherworldly landscape of contrasting bleached white claypan ground, blackened camel thorn trees, and fiery red-orange dunes, Deadvlei is located in Namibia’s Namib-Naukluft National Park."
+ },
+ {
+ "name": "Grand Canyon",
+ "continent": "North America",
+ "id": 1004,
+ "placeID": "I55488B3D1D9B2D4B",
+ "longitude": -113.16096,
+ "latitude": 36.21904,
+ "span": 10.0,
+ "description": "When you look down from the rim of the Grand Canyon, you are looking at a visual story of Earth’s ancient past. Carved over millions of years by the Colorado River, the canyon stretches 277 miles in length, spans widths up to 18 miles, and reaches depths of over a mile (6,093 feet). The vibrant house of the canyon, ranging from deep reds and oranges to subtle shades of purple, it is an iconic natural wonder.\n\nThe geological story that the canyon tells spans nearly two billion years. Vishnu Schist at the bottom of the Inner Gorge is estimated to be 1.7 billion years old. The much younger Kaibab Limestone formation on the rim is the canyon’s top most rock layer, dating back a mere 270 million years. Between these two extremes, wind and water erosion have sculpted stunning cliffs, mesas, and buttes to create an intricate landscape of canyons within canyons. Every year, the seasonal variations in the Colorado River’s flow continue to carve the canyons deeper and deeper.\n\nJust as the geology story shifts with the altitude change, the plant and animal diversity changes dramatically. Flora ranges from desert species such as cacti and junipers to high-elevation forests of spruce fir or ponderosa pine, groundsels, yarrow, and lupine. Bighorn sheep, mule deer, and the elusive mountain lion are just some of the wildlife species that make their home in this ecological treasure.",
+ "shortDescription": "Carved over millions of years by the Colorado River, the canyon stretches 277 miles in length, spans widths up to 18 miles, and reaches depths of over a mile (6,093 feet)."
+ },
+ {
+ "name": "Niagara Falls",
+ "continent": "North America",
+ "id": 1005,
+ "placeID": "I433E22BD30C61C40",
+ "longitude": -79.07401,
+ "latitude": 43.07792,
+ "span": 4.0,
+ "description": "Niagara Falls is comprised of three separate falls, Horseshoe Falls, American Falls, and Bridal Veil Falls. It lies at the border between Canada and the United States. The millions of gallons of water that plunge over its edge originates from Lake Erie in the Great Lakes. The flow of water produces a roaring cascade, fills the air with mist, and creates vibrant rainbows. It is one of the most famous and powerful waterfalls in the world.\n\nThe Falls began to form at the end of the last Ice Age, taking more than 12,000 years to fully develop. The process continues today as the flow of water continues to erode sedimentary layers of rock to shape and reshape the landscape. Preservation work has been done to preserve the Falls, and the volume of water has reduced due to diversion for hydroelectric power, which generates nearly 4.9 million kilowatts of power.\n\nA dynamic ecosystem exists around the Falls. Fish life thrives in the Niagara River, including species such as bass and sturgeon. Gulls, bald eagles, and peregrine falcons are some of the avian wildlife that can be seen flying above the Falls, while lush vegetation grows along the riverbanks.",
+ "shortDescription": "Niagara Falls is comprised of three separate falls, Horseshoe Falls, American Falls, and Bridal Veil Falls. It lies at the border between Canada and the United States. The millions of gallons of water that plunge over its edge originates from Lake Erie in the Great Lakes. The flow of water produces a roaring cascade, fills the air with mist, and creates vibrant rainbows."
+ },
+ {
+ "name": "Joshua Tree",
+ "continent": "North America",
+ "id": 1006,
+ "placeID": "I34674B3D3B032AA2",
+ "longitude": -115.80826,
+ "latitude": 33.88752,
+ "span": 10.0,
+ "description": "Spanning more than 1,200 square miles, Joshua Tree National Park is located in Southern California not far from Palm Springs. The park spans both the Mojave and Colorado deserts and is most known for the rugged rock formations and the spiky, twisted Joshua trees. The two deserts have distinct characteristics. On the western side of the park, the higher and cooler Mojave is a high desert ecosystem while the eastern side’s Colorado is a low desert climate.\n\nAncient volcanic activity, erosion, and tectonic fault movement shaped the park’s dramatic landscape. Surreal formations of massive granite boulders, rock hills (known as inselbergs), hidden canyons, and the unique Joshua trees create an otherworldly beauty.\n\nDespite the harsh desert climates, a variety of resilient wildlife and plant life can be found. Larger mammals such as coyote, desert bighorn sheep, mountain lion often burrow or stay in caves during the hot day time hours, becoming more active at night. Ancient bristlecone pines, creosote bush, and ephemeral spring wildflowers are some of the more than 1,000 species of plant found Joshua Tree.",
+ "shortDescription": "Spanning more than 1,200 square miles, Joshua Tree National Park is located in Southern California, not far from Palm Springs."
+ },
+ {
+ "name": "Rocky Mountains",
+ "continent": "North America",
+ "id": 1007,
+ "placeID": "IBD757C9B53C92D9E",
+ "longitude": -112.99872,
+ "latitude": 47.62596,
+ "span": 16.0,
+ "description": "Stretching 3,000 miles from northwest Canada to southwest United States, the Rocky Mountains are the setting for some of North America’s most stunning scenery. With soaring peaks, jagged summits, pastoral alpine meadows, dense forests, and crystal clear lakes, the Rockies are home to a wide range of flora and fauna. The mountain habitats support a wide range wildlife including wolves, elk, moose, bighorn sheep, grizzly bears, and wolverines. The variety of plants found in the Rocky Mountains is similarly diverse including over 900 species of wildflowers and conifers such as pine, spruce, and fir. Deciduous trees, known for their brilliant colorful autumn leaves contribute to some of the most biologically diverse habitats in the Rocky Mountain National Park. All of these ecological elements and waterways provide an important habitat for North America migratory birds.\n\nThe geological history of the Rocky Mountains dates back approximately 60 to 70 million years when several tectonic plates began shifting under the North American plate. The result was a long, broad belt of mountains running along western North America. The dramatic peaks and valleys we see today were further formed by tectonic activity and erosion by glaciers. The majority of the highest peaks are found in Colorado. Numerous public parks protect much of the mountain range, providing tourists plenty of year-round activities.",
+ "shortDescription": "Stretching 3,000 miles from northwest Canada to southwest United States, the Rocky Mountains are the setting for some of North America’s most stunning scenery."
+ },
+ {
+ "name": "Monument Valley",
+ "continent": "North America",
+ "id": 1008,
+ "placeID": "IAB1F0D2360FAAD29",
+ "longitude": -110.348,
+ "latitude": 36.874,
+ "span": 10.0,
+ "description": "Presenting a spectacular display of towering sandstone buttes, vibrant red rock formations, and expansive open plains, Monument Valley is an iconic landscape of the Colorado Plateau, located near the Arizona-Utah border. The stratified buttes rise dramatically from the valley floor reaching heights up to 1,000 feet. Formed by rivers, wind, and ice, the valley is comprised largely of siltstone and sand deposits. Softer material eroded away, leaving the massive sandstone buttes that remain today.\n\nVegetation in Monument Valley is sparse but beautiful. Contributing to an interesting palette, plants like purple sage complement the red rock formations with splashes of purple flowers and white or gray leaves. Rabbitbrush brings in yellow flowers and green leaves. Mojave yucca plants have fine hairs and a wax coating to trap moisture and reflect sunlight to help it survive.\n\nAnimal life is diverse, with large mammals like the mountain lions, coyotes, and jackrabbits. Reptiles include lizards such as the long-nose leopard lizard as well as iguanas and various snakes. With watchful eyes soaring above all of this are red-tailed hawks, tree sparrows, and more.",
+ "shortDescription": "Presenting a spectacular display of towering sandstone buttes, vibrant red rock formations, and expansive open plains, Monument Valley is an iconic landscape of the Colorado Plateau, located near the Arizona-Utah border."
+ },
+ {
+ "name": "Muir Woods",
+ "continent": "North America",
+ "id": 1009,
+ "placeID": "I907589547EB05261",
+ "longitude": -122.57482,
+ "latitude": 37.8922,
+ "span": 2.0,
+ "description": "Frequently shrouded in coastal marine layer fog from the Pacific Ocean, Muir Woods National Monument is an old-growth redwood forest. Located 12 miles north of San Francisco, covering 558 acres, and containing 6 miles of gorgeous hiking trails, Muir Woods is part of the Golden Gate National Recreation Area.\n\nThe moist environment promotes a rich community of interesting plants, organized into three layers. The lowest layer is the herbaceous layer and is full of shade-loving life. The next layer is the understory, where shrubs and tress such as the California bay and tan oak grow. Providing shelter and platforms for various tree-dwelling species is the topmost layer, or canopy.\n\nWithin the multilayered, dense habitat, it’s easy for wildlife to remain unseen, making the forest sometimes appear empty. Looks, however, are deceiving. Muir Wood hosts over 50 species of birds including spotted owls and pileated woodpeckers. Mammals range in size from the small American shrew mole to the black-tailed mule deer. Most mammals are nocturnal or burrowing animals, contributing to the sense of emptiness in the forest.",
+ "shortDescription": "Located 12 miles north of San Francisco, covering 558 acres, and containing 6 miles of gorgeous hiking trails, Muir Woods is part of the Golden Gate National Recreation Area."
+ },
+ {
+ "name": "Amazon Rainforest",
+ "continent": "South America",
+ "id": 1010,
+ "placeID": "I76A1045FB9294971",
+ "longitude": -62.80802,
+ "latitude": -3.50879,
+ "span": 30.0,
+ "description": "As a member of an elite club of natural landmarks that cover more than one percent of the planet’s surface, the Amazon rainforest covers approximately 2.3 million square miles. The majority of the rainforest is in Brazil, but it spills over into other neighboring countries including Peru, Bolivia, and Columbia. While it’s common to think of a rainforest as being sparsely populated, an estimated 30 million people live in the Amazon.\n\nWith the highest diversity of plant life on Earth, the Amazon may contain as many as 80,000 plant species. An astounding 75 percent of those species are endemic to the area, not being found anywhere else, including 16,000 trees species. Other unique species include giant Amazon water lily, rubber trees, and cacao trees.\n\nHundreds of animal species are also found in the rainforest. Notably, the Amazon is one of Earth’s last refuges for jaguars, harpy eagles, and pink river dolphins. Many other large animals live in the Amazon including cougars, the black caiman, and even the Caquetá titi monkey which purrs like a cat.",
+ "shortDescription": "As a member of an elite club of natural landmarks that cover more than one percent of the planet’s surface, the Amazon rainforest covers approximately 2.3 million square miles."
+ },
+ {
+ "name": "Lençóis Maranhenses",
+ "continent": "South America",
+ "id": 1011,
+ "placeID": "I292A37DAC754D6A0",
+ "longitude": -43.03345,
+ "latitude": -2.57812,
+ "span": 10.0,
+ "description": "Lençóis Maranhenses National Park covers approximately 380,000 acres on the northeastern coast of Brazil, including 43 miles of coastline along the Atlantic Ocean. The interior of the park is composed of rolling sand dunes that reach as high as 130 feet.\n\nDespite looking like a desert environment, the area receives about 47 inches of rain per year. During the rainy seasons, the spaces between the dune peaks fill with freshwater lagoons.\n\nWith desert-like but wet locale, a unique and diverse ecosystem has evolved. Vegetation adapted to coastal and freshwater environments such as Restinga and mangrove are found here. Four endangered species of animals reside in the park: the scarlet ibis, neotropical otter, oncilla, and West Indian manatee. The wolf fish is a unique species that burrows into wet mud during the dry season.",
+ "shortDescription": "Lençóis Maranhenses National Park covers approximately 380,000 acres on the northeastern coast of Brazil, including 43 miles of coastline along the Atlantic Ocean."
+ },
+ {
+ "name": "Uyuni Salt Flat",
+ "continent": "South America",
+ "id": 1012,
+ "placeID": "ID903C9A78EB0CAAD",
+ "longitude": -67.48914,
+ "latitude": -20.13378,
+ "span": 10.0,
+ "description": "At an elevation of nearly 12,000 feet above sea level near the crest of the Andes mountain range in southwestern Bolivia, the Salar de Uyuni is the world’s largest salt flat. Extraordinarily flat, with elevation variation no more than one meter over the nearly 4,000 square mile area, the Salar was formed by several prehistoric lakes evaporating over the last 40,000 years. The resulting salt crust is several meters thick and rich in lithium.\n\nDespite its prehistoric origin, modern technology has found an important use for the Uyuni. Because salt flats are large, stable surfaces, they are ideal for satellite calibration. Uyuni is especially well suited for this task due to the lack of industry, long low rain periods, and very clear dry air. During the rainy season, the flat turns into a shallow lake with a glass-like surface and becomes the world’s largest natural mirror.\n\nThe salt flat has few forms of plant life, which includes the giant cacti, Echinopsis pasacana and Echinopsis tarijensis, which tower up to 40 feet over the Uyuni flats. Other plants found in Uyuni include pitaya (or dragon fruit), quinoa plants, and queñua bushes.",
+ "shortDescription": "At an elevation of nearly 12,000 feet above sea level near the crest of the Andes mountain range in southwestern Bolivia, the Salar de Uyuni is the world’s largest salt flat."
+ },
+ {
+ "name": "White Cliffs of Dover",
+ "continent": "Europe",
+ "id": 1014,
+ "placeID": "I77B160572D5A2EB1",
+ "longitude": 1.36351,
+ "latitude": 51.13641,
+ "span": 4.0,
+ "description": "Standing guard over the narrowest part of the English Channel, the White Cliffs of Dover present a striking façade of chalk streaked with accents of black flint. Over millions of years, skeletons of tiny algae and other sea creatures settled into the white mud under the sea. After these deposits compacted to form chalk, a major mountain building event forced the undersea masses above sea level to form the cliffs.\n\nAbove the cliffs, a chalk grassland supports many species of wildflowers, butterflies, and birds. Orchids, rock samphire, and the unusual oxtongue broomrape are found here. In spring and autumn, the rare Adonis blue butterfly can be seen in the grasslands, as can the similar looking chalk hill blue. Many migratory birds use the cliffs as their first landing point after crossing the English Channel. Peregrine falcons and the skylark are some of the birds that make their homes along the cliffs.",
+ "shortDescription": "Standing guard over the narrowest part of the English Channel, the White Cliffs of Dover present a striking façade of chalk streaked with accents of black flint."
+ },
+ {
+ "name": "Alps",
+ "continent": "Europe",
+ "id": 1015,
+ "placeID": "IE380E71D265F97C0",
+ "longitude": 10.54773,
+ "latitude": 46.77367,
+ "span": 6.0,
+ "description": "Extending nearly 750 miles from Nice, France in the west to Trieste, Italy in the east, the Alps stretch across eight different countries. The mountains formed over tens of millions of years as tectonic plates collided, forcing sedimentary rock to rise into mountain peaks. Mont Blanc is the tallest mountain in Europe, soaring over 15,700 feet. The high altitude and sheer size of the mountains have an effect on the climate in Europe.\n\nDiverse and unique flora has developed in the Alps by adapting to a high-altitude environment. The iconic Edelweiss is an alpine flower that thrives in rocky limestone. Mountain cranberry and bluets are rare and fragile plants found above treelike. Dwarf willow is a resilient plant that does well in places where snow lingers into spring.\n\nLarge mammals like the red deer, ibex, Eurasian lynx, and chamois are all found in low and high altitude regions. Many rodents such as voles and the Alpine marmot live underground and burrow in the Alps. Some reptiles including adders and vipers live near the snow line, but because they cannot tolerate the cold temperatures they hibernate underground and soak up warmth on rocky ledges.",
+ "shortDescription": "Extending nearly 750 miles from Nice, France in the west to Trieste, Italy in the east, the Alps stretch across eight different countries."
+ },
+ {
+ "name": "Mount Fuji",
+ "continent": "Asia",
+ "id": 1016,
+ "placeID": "I2CC1DF519EDD7ACD",
+ "longitude": 138.72744,
+ "latitude": 35.36072,
+ "span": 10.0,
+ "description": "When seen at a distance, Mount Fuji presents a beautiful, nearly symmetric, often snow-capped profile and is Japan’s tallest and most iconic mountain. The volcanic cone rises gracefully up to slightly more than 12,000 feet. On clear days, the mountain is visible as far away as Tokyo. Despite its seemingly peaceful distant existence, Mount Fuji is an active volcano. Its last eruption was in 1707.\n\nSimilar to other exceptionally tall mountains, Fuji-san is home to many ecological zones from its base to its summit. In the lower elevations, deciduous and coniferous trees such as the Japanese oak and cedars are common. As you climb in elevation, the climate becomes harsher and plant life transitions to alpine plants and shrubs that have adapted to colder temperatures. At the highest altitudes, a volcanic desert environment exists.\n\nMany mammals and birds are found in the forests on Mount Fuji. Black bears live there, although squirrels and fox are more likely to be seen. The Japanese serow is a rare and protected species of goat-antelope that roams secretively through dense forests. High altitude birds such as the Iwahibari and Hoshigarasu are found above 8,200 feet, while several species of warblers, flycatchers, and Ural and scops owls live in lower altitudes.",
+ "shortDescription": "When seen at a distance, Mount Fuji presents a beautiful, nearly symmetric, often snow-capped profile and is Japan’s tallest and most iconic mountain."
+ },
+ {
+ "name": "Wulingyuan",
+ "continent": "Asia",
+ "id": 1017,
+ "placeID": "I818C4BA5FE11BDD6",
+ "longitude": 110.45242,
+ "latitude": 29.35106,
+ "span": 10.0,
+ "description": "Featuring more than 3,000 sandstone pillars and peaks, Wulingyuan is a scenic and historic site in China’s Hunan Province. Often shrouded in mist, the surreal landscape of pillars surrounded by forests spans over 100 square miles. Among the picturesque lakes, rivers, and waterfalls, the site also contains 40 caves and one of the world’s highest natural bridges, named Tianqiashengkong.\n\nWith plentiful rainfall and dense forests, Wulingyuan has created a good environment for varied animal and plant life. Unique species like the clouded leopard, Chinese giant salamander, Asiatic wild dog, and Asiatic black bear live among the forests. Other animals include various monkeys — including rhesus monkeys — as well as deer, birds, and reptiles. Notable plant species include the dove tree and ginkgo.",
+ "shortDescription": "Featuring more than 3,000 sandstone pillars and peaks, Wulingyuan is a scenic and historic site in China’s Hunan Province."
+ },
+ {
+ "name": "Mount Everest",
+ "continent": "Asia",
+ "id": 1018,
+ "placeID": "IE16B9C217B9B0DC1",
+ "longitude": 86.9251,
+ "latitude": 27.98816,
+ "span": 10.0,
+ "description": "In addition to perhaps being the world’s most well-known mountain, Mount Everest is the world’s tallest above sea level. With an altitude just over 29,031 feet, the mountain attracts climbers and experienced mountaineers from all over the world. Everest’s icy peaks pierce the sky, surrounded by swirling clouds and intensely strong winds.\n\nGeologically, Mount Everest is part of the Himalayan mountain range which is formed by the Eurasian and Indian tectonic plates. Movement of these plates began around 50 million years ago and continues to push Everest even higher. Layers of metamorphic and sedimentary rock are topped by marine limestone that was once at the bottom of the ocean remind us of the Earth’s dynamic history.\n\nDespite the extreme conditions found at the high altitudes on Everest, the mountain is home to a unique high-altitude ecosystem. Mosses and lichens can survive the extreme climate along with high altitude plants such as the Himalayan juniper, dwarf rhododendrons, and the snow lotus. Animals found in the lower elevations include the Himalayan tahr, snow leopard, Himalayan black bear, and the red panda.",
+ "shortDescription": "With an altitude just over 29,031 feet, the mountain attracts climbers and experienced mountaineers from all over the world."
+ },
+ {
+ "name": "Great Barrier Reef",
+ "continent": "Australia/Oceana",
+ "id": 1019,
+ "placeID": "IF436B51611F3F9D1",
+ "longitude": 145.97842,
+ "latitude": -16.7599,
+ "span": 16.0,
+ "description": "Comprised of over 2,900 individual reefs, 900 islands, and spanning a monumental 133,000 square miles, the Great Barrier Reef is the world’s largest coral reef system. Situated in the Coral Sea just off the coast of Queensland, Australia, the Great Barrier Reef is the world’s largest single structure made by living organisms. It’s large enough to be seen from space.\n\nThe reef system supports a dizzying range of diverse life, including many species that are vulnerable or endangered, and some are endemic to the area. Dozens of cetaceans including dwarf minke whale, Indo-Pacific humpback dolphin, and the humpback whale live here. Clownfish, snapper, and coral trout are just some of the 1,500 fish species found in the waters surrounding the reef.\n\nSometimes overshadowed by the magnificent aquatic animal life, the Great Barrier Reef is home to over 2,000 species of native plants. Seagrass meadows are one of the most fundamental parts of the flora found in the reef system. Eelgrass or Turtle grass meadows thrive in the shallow coastal waters and help to maintain the biodiversity, stability, and productivity of the reef.",
+ "shortDescription": "Comprised of over 2,900 individual reefs, 900 islands, and spanning a monumental 133,000 square miles, the Great Barrier Reef is the world’s largest coral reef system."
+ },
+ {
+ "name": "Maui",
+ "continent": "Australia/Oceana",
+ "id": 1020,
+ "placeID": "I8F3C2D4A1E5B6789",
+ "longitude": -156.33170,
+ "latitude": 20.79838,
+ "span": 8.0,
+ "description": "The second-largest island in Hawaii, Maui offers a stunning tapestry of volcanic landscapes, lush rainforests, pristine beaches, and dramatic coastal cliffs. Known as the \"Valley Isle,\" Maui is dominated by two massive volcanoes: the dormant Haleakalā in the east and the older, eroded West Maui Mountains. Haleakalā National Park features a massive volcanic crater that reaches over 10,000 feet in elevation, offering breathtaking sunrise views and unique high-altitude ecosystems.\n\nThe island's diverse geography creates distinct climate zones, from arid leeward coasts to verdant windward rainforests receiving over 400 inches of annual rainfall. The famous Road to Hana winds through tropical paradise, passing countless waterfalls, bamboo forests, and dramatic ocean vistas. Maui's volcanic soil supports rich agriculture, including sugarcane, pineapple, coffee, and exotic tropical fruits.\n\nMarine life thrives in Maui's warm Pacific waters. Humpback whales migrate to the shallow channels between the Hawaiian islands from December to May, making Maui one of the world's premier whale-watching destinations. Hawaiian green sea turtles, spinner dolphins, and vibrant coral reefs attract snorkelers and divers year-round. The island's beaches range from golden sand to unique red and black volcanic shores. Native Hawaiian plants such as the silversword, which grows only on Haleakalā's slopes, and endemic bird species like the nēnē (Hawaiian goose) highlight the island's ecological significance and ongoing conservation efforts.",
+ "shortDescription": "The second-largest island in Hawaii, Maui offers a stunning tapestry of volcanic landscapes, lush rainforests, pristine beaches, and dramatic coastal cliffs."
+ },
+ {
+ "name": "South Shetland Islands",
+ "continent": "Antarctica",
+ "id": 1021,
+ "placeID": "I1AAF5FE1DF954A59",
+ "longitude": -58.70703,
+ "latitude": -61.79436,
+ "span": 20.0,
+ "description": "The South Shetland Islands consist of 11 major islands and several minor ones, making up more than 1,400 square miles of land area. The Antarctic island chain is located in the Drake Passage. Almost all of the land area is permanently covered by glaciers. Active geothermal vents and hot springs provide balance the two contrasting extremes of fire and ice.\n\nVarious plants have adapted to the harsh conditions, including two flowering plants — the Antarctic hair grass and Antarctic pearlwort. Other examples of adapted plants include mosses, liverworts, lichens, and fungi. Notable penguin species include Adélie, Chinstrap, and Gentoo. Crabeater, Leopard, Weddell, and fur seals are some of the island’s other inhabitants.",
+ "shortDescription": "The South Shetland Islands consist of 11 major islands and several minor ones, making up more than 1,400 square miles of land area."
+ }
+]
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Splash/splash.svg b/src/AI/samples/Essentials.AI.Sample/Resources/Splash/splash.svg
new file mode 100644
index 000000000000..21dfb25f187b
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/Splash/splash.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Styles/Colors.xaml b/src/AI/samples/Essentials.AI.Sample/Resources/Styles/Colors.xaml
new file mode 100644
index 000000000000..149ca54b5291
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/Styles/Colors.xaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+ #512BD4
+ #ac99ea
+ #242424
+ #DFD8F7
+ #9880e5
+ #2B0B98
+
+ White
+ Black
+ #D600AA
+ #190649
+ #1f1f1f
+
+ #E1E1E1
+ #C8C8C8
+ #ACACAC
+ #919191
+ #6E6E6E
+ #404040
+ #2D2D2D
+ #212121
+ #141414
+
+ #FFF3CD
+ #856404
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AI/samples/Essentials.AI.Sample/Resources/Styles/Styles.xaml b/src/AI/samples/Essentials.AI.Sample/Resources/Styles/Styles.xaml
new file mode 100644
index 000000000000..5fef12ae8109
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Resources/Styles/Styles.xaml
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/BufferedChatClient.cs b/src/AI/samples/Essentials.AI.Sample/Services/BufferedChatClient.cs
new file mode 100644
index 000000000000..f5500a014bf7
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/BufferedChatClient.cs
@@ -0,0 +1,130 @@
+using System.Runtime.CompilerServices;
+using System.Text;
+using Microsoft.Extensions.AI;
+
+namespace Maui.Controls.Sample.Services;
+
+///
+/// A delegating chat client that buffers streaming text content to reduce the frequency of UI updates.
+///
+///
+///
+/// This client wraps an existing and throttles streaming responses by
+/// accumulating text content until a minimum buffer size and time delay have been met. This helps
+/// maintain smooth UI rendering and scrolling when displaying streaming AI responses.
+///
+///
+/// Non-text content such as function calls are passed through immediately without buffering, and
+/// any buffered text is flushed before such content is yielded.
+///
+///
+public class BufferedChatClient(IChatClient innerClient, int minBufferSize = 100, TimeSpan? bufferDelay = null)
+ : DelegatingChatClient(innerClient)
+{
+ private readonly int _minBufferSize = minBufferSize;
+ private readonly TimeSpan _bufferDelay = bufferDelay ?? TimeSpan.FromMilliseconds(250);
+
+ ///
+ /// Gets streaming chat response updates with buffering applied to text content.
+ ///
+ /// The chat messages to send to the model.
+ /// Optional chat options to configure the request.
+ /// A cancellation token to cancel the operation.
+ ///
+ /// An asynchronous enumerable of instances where text content
+ /// is buffered and yielded only when both the minimum buffer size and time delay conditions are met.
+ /// Non-text content is passed through immediately.
+ ///
+ ///
+ ///
+ /// The buffering behavior follows these rules:
+ ///
+ ///
+ /// - Text content is accumulated until at least the configured minimum buffer size
+ /// has been buffered AND the configured buffer delay has elapsed.
+ /// - When non-text content (such as function calls) is encountered, any buffered text is
+ /// flushed immediately, then the non-text content is yielded.
+ /// - At the end of the stream, any remaining buffered text is flushed.
+ ///
+ ///
+ public override async IAsyncEnumerable GetStreamingResponseAsync(
+ IEnumerable messages,
+ ChatOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var textBuffer = new StringBuilder();
+ var lastYieldTicks = Environment.TickCount64;
+ ChatResponseUpdate? lastUpdate = null;
+
+ await foreach (var update in InnerClient.GetStreamingResponseAsync(messages, options, cancellationToken))
+ {
+ var currentYieldTicks = Environment.TickCount64;
+ var hasNonTextContent = false;
+
+ // Check if this update has non-text content (function calls, etc.)
+ foreach (var item in update.Contents)
+ {
+ if (item is not TextContent)
+ {
+ hasNonTextContent = true;
+ break;
+ }
+ }
+
+ // If we have non-text content, flush buffer and yield immediately
+ if (hasNonTextContent)
+ {
+ if (textBuffer.Length > 0 && lastUpdate is not null)
+ {
+ yield return CreateBufferedUpdate(lastUpdate, textBuffer.ToString());
+ textBuffer.Clear();
+ lastYieldTicks = currentYieldTicks;
+ }
+
+ yield return update;
+ lastUpdate = null;
+ continue;
+ }
+
+ // Buffer text content
+ foreach (var item in update.Contents)
+ {
+ if (item is TextContent textContent)
+ {
+ textBuffer.Append(textContent.Text);
+ }
+ }
+
+ lastUpdate = update;
+
+ var shouldFlush =
+ textBuffer.Length >= _minBufferSize &&
+ TimeSpan.FromMilliseconds(currentYieldTicks - lastYieldTicks) >= _bufferDelay;
+
+ if (shouldFlush)
+ {
+ yield return CreateBufferedUpdate(update, textBuffer.ToString());
+ textBuffer.Clear();
+ lastYieldTicks = currentYieldTicks;
+ lastUpdate = null;
+ }
+ }
+
+ // Flush any remaining buffered text
+ if (textBuffer.Length > 0 && lastUpdate is not null)
+ {
+ yield return CreateBufferedUpdate(lastUpdate, textBuffer.ToString());
+ }
+ }
+
+ private static ChatResponseUpdate CreateBufferedUpdate(ChatResponseUpdate original, string newText) =>
+ new()
+ {
+ CreatedAt = original.CreatedAt,
+ FinishReason = original.FinishReason,
+ Role = original.Role,
+ Contents = [new TextContent(newText)],
+ RawRepresentation = original.RawRepresentation,
+ AdditionalProperties = original.AdditionalProperties
+ };
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/ItineraryService.cs b/src/AI/samples/Essentials.AI.Sample/Services/ItineraryService.cs
new file mode 100644
index 000000000000..7a7b9e4be770
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/ItineraryService.cs
@@ -0,0 +1,106 @@
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Maui.Controls.Sample.Models;
+using Maui.Controls.Sample.Services.Tools;
+using Microsoft.Extensions.AI;
+
+namespace Maui.Controls.Sample.Services;
+
+#pragma warning disable CS9113 // Parameter is unread.
+public class ItineraryService(IChatClient chatClient, LandmarkDataService landmarkService)
+#pragma warning restore CS9113 // Parameter is unread.
+{
+ public record ItineraryStreamUpdate(
+ ToolLookup? ToolLookup = null,
+ ToolLookup? ToolLookupResult = null,
+ Itinerary? PartialItinerary = null);
+
+ public async IAsyncEnumerable StreamItineraryAsync(
+ Landmark landmark,
+ int dayCount,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() },
+ };
+
+ var findPointsOfInterestTool = new FindPointsOfInterestTool(landmark);
+ var findPointsOfInterestFunction = AIFunctionFactory.Create(findPointsOfInterestTool.Call);
+
+ string[] systemInstructions = [
+ "Your job is to create an itinerary for the person.",
+ "Each day needs an activity, hotel and restaurant.",
+ $"""
+ Always use the findPointsOfInterest tool to find businesses and activities in {landmark.Name}, especially hotels and restaurants.
+
+ The point of interest categories may include:
+ """,
+ string.Join(", ", Enum.GetNames()),
+ $"Here is a description of {landmark.Name} for your reference when considering what activities to generate:",
+ landmark.Description
+ ];
+
+ string[] userPrompt = [
+ $"Generate a {dayCount}-day itinerary to {landmark.Name}.",
+ "Give it a fun title and description.",
+ "Here is an example, but don't copy it:",
+ JsonSerializer.Serialize(Itinerary.GetExampleTripToJapan(), jsonOptions)
+ ];
+
+ var messages = new List
+ {
+ new(ChatRole.System, [.. systemInstructions.Select(s => new TextContent(s))]),
+ new(ChatRole.User, [.. userPrompt.Select(s => new TextContent(s))])
+ };
+
+ var options = new ChatOptions
+ {
+ Tools = [findPointsOfInterestFunction],
+ ResponseFormat = ChatResponseFormat.ForJsonSchema(jsonOptions)
+ };
+
+ var deserializer = new StreamingJsonDeserializer(jsonOptions);
+
+ var bufferedClient = new BufferedChatClient(chatClient, minBufferSize: 100, bufferDelay: TimeSpan.FromMilliseconds(250));
+ await foreach (var update in bufferedClient.GetStreamingResponseAsync(messages, options, cancellationToken))
+ {
+ // Detect tool calls from the streaming update
+ foreach (var item in update.Contents)
+ {
+ if (item is FunctionCallContent functionCall)
+ {
+ var toolLookup = new ToolLookup
+ {
+ Id = functionCall.CallId,
+ Arguments = functionCall.Arguments
+ };
+
+ yield return new ItineraryStreamUpdate { ToolLookup = toolLookup };
+ }
+ else if (item is FunctionResultContent functionResult)
+ {
+ var toolLookup = new ToolLookup
+ {
+ Id = functionResult.CallId,
+ Result = functionResult.Result
+ };
+
+ yield return new ItineraryStreamUpdate { ToolLookupResult = toolLookup };
+ }
+ else if (item is TextContent textContent)
+ {
+ var partialItinerary = deserializer.ProcessChunk(textContent.Text);
+ if (partialItinerary is not null)
+ {
+ yield return new ItineraryStreamUpdate { PartialItinerary = partialItinerary };
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/LandmarkDataService.cs b/src/AI/samples/Essentials.AI.Sample/Services/LandmarkDataService.cs
new file mode 100644
index 000000000000..1a8248b40f31
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/LandmarkDataService.cs
@@ -0,0 +1,57 @@
+using System.Text.Json;
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.Services;
+
+public class LandmarkDataService
+{
+ private static readonly Lazy _instance = new(() => new LandmarkDataService());
+ public static LandmarkDataService Instance => _instance.Value;
+
+ private List? _landmarks;
+ private Dictionary>? _landmarksByContinent;
+ private Dictionary? _landmarksById;
+ private Landmark? _featuredLandmark;
+
+ public IReadOnlyList Landmarks => _landmarks ?? [];
+ public IReadOnlyDictionary> LandmarksByContinent => _landmarksByContinent ?? new Dictionary>();
+ public Landmark? FeaturedLandmark => _featuredLandmark;
+
+ private LandmarkDataService()
+ {
+ LoadLandmarksAsync().ConfigureAwait(false);
+ }
+
+ public async Task LoadLandmarksAsync()
+ {
+ try
+ {
+ using var stream = await FileSystem.OpenAppPackageFileAsync("landmarkData.json");
+ using var reader = new StreamReader(stream);
+ var json = await reader.ReadToEndAsync();
+
+ _landmarks = JsonSerializer.Deserialize>(json, new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ }) ?? [];
+
+ _landmarksByContinent = _landmarks
+ .GroupBy(l => l.Continent)
+ .ToDictionary(g => g.Key, g => g.ToList());
+
+ _landmarksById = _landmarks.ToDictionary(l => l.Id);
+
+ _featuredLandmark = _landmarksById.GetValueOrDefault(1020);
+ }
+ catch (Exception)
+ {
+ _landmarks = [];
+ _landmarksByContinent = new Dictionary>();
+ _landmarksById = new Dictionary();
+ }
+ }
+
+ public Landmark? GetLandmarkById(int id) => _landmarksById?.GetValueOrDefault(id);
+
+ public IEnumerable GetLandmarkNames() => _landmarks?.Select(l => l.Name) ?? [];
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/StreamingJsonDeserializer.cs b/src/AI/samples/Essentials.AI.Sample/Services/StreamingJsonDeserializer.cs
new file mode 100644
index 000000000000..22678567023f
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/StreamingJsonDeserializer.cs
@@ -0,0 +1,633 @@
+using System.Buffers;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
+
+namespace Maui.Controls.Sample.Services;
+
+///
+/// Zero-allocation streaming JSON deserializer using Utf8JsonWriter with ReadOnlySpan<byte> overloads.
+/// Eliminates string allocations by using the writer's native UTF-8 span APIs.
+///
+internal sealed class StreamingJsonDeserializer
+ where T : class
+{
+ private readonly ArrayBufferWriter _byteBuffer = new(initialCapacity: 4096);
+ private readonly ArrayBufferWriter _reconstructionBuffer = new(initialCapacity: 4096);
+ private readonly Stack _bracketStack = new();
+
+ private readonly JsonSerializerOptions _options;
+ private readonly bool _skipDeserialization;
+ private T? _lastGoodModel;
+
+ ///
+ /// Initializes a new instance of the StreamingJsonDeserializer.
+ ///
+ /// Custom JSON serialization options, or null to use defaults.
+ /// If true, reconstructs JSON without deserializing to the target type (useful for validation).
+ public StreamingJsonDeserializer(JsonSerializerOptions? options = null, bool skipDeserialization = false)
+ {
+ _skipDeserialization = skipDeserialization;
+ options ??= new JsonSerializerOptions();
+ _options = new JsonSerializerOptions(options)
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ AllowTrailingCommas = true,
+ ReadCommentHandling = JsonCommentHandling.Skip,
+
+ // Make all properties optional to handle incomplete streaming JSON
+ TypeInfoResolver = new DefaultJsonTypeInfoResolver
+ {
+ Modifiers =
+ {
+ static typeInfo =>
+ {
+ if (typeInfo.Kind != JsonTypeInfoKind.Object)
+ return;
+
+ foreach (var propertyInfo in typeInfo.Properties)
+ {
+ propertyInfo.IsRequired = false;
+ }
+ }
+ }
+ }
+ };
+ }
+
+ ///
+ /// Gets the current accumulated JSON as UTF-8 bytes (zero-allocation view).
+ ///
+ public ReadOnlySpan PartialJsonUtf8 => _byteBuffer.WrittenSpan;
+
+ ///
+ /// Gets the current accumulated JSON as a string (allocates for string conversion).
+ ///
+ public string PartialJson => Encoding.UTF8.GetString(_byteBuffer.WrittenSpan);
+
+ ///
+ /// Gets the last successfully deserialized model, or null if none have succeeded yet.
+ ///
+ public T? LastGoodModel => _lastGoodModel;
+
+ ///
+ /// Processes a new chunk of JSON text from a streaming source.
+ /// Accumulates the chunk into the internal buffer and attempts to deserialize.
+ ///
+ /// The incoming JSON text fragment to process.
+ /// The most recently successfully deserialized model, or null if none have succeeded yet.
+ public T? ProcessChunk(string chunk)
+ {
+ if (string.IsNullOrEmpty(chunk))
+ return _lastGoodModel;
+
+ // Convert the incoming chunk to UTF-8 bytes and append to the buffer
+ var byteCount = Encoding.UTF8.GetByteCount(chunk);
+ var span = _byteBuffer.GetSpan(byteCount);
+ Encoding.UTF8.GetBytes(chunk, span);
+ _byteBuffer.Advance(byteCount);
+
+ // Attempt to deserialize the accumulated JSON so far
+ var parsed = TryDeserializeIncremental();
+ if (parsed != null)
+ {
+ _lastGoodModel = parsed;
+ }
+
+ return _lastGoodModel;
+ }
+
+ ///
+ /// Resets the deserializer state, clearing all accumulated data and cached models.
+ /// Call this to start processing a new stream from scratch.
+ ///
+ public void Reset()
+ {
+ _byteBuffer.Clear();
+ _reconstructionBuffer.Clear();
+ _bracketStack.Clear();
+ _lastGoodModel = default;
+ }
+
+ ///
+ /// Attempts to reconstruct and deserialize the accumulated JSON buffer.
+ /// Handles incomplete JSON by closing unclosed structures and extracting partial string values.
+ ///
+ /// A deserialized model instance if successful, otherwise null.
+ private T? TryDeserializeIncremental()
+ {
+ var bytes = _byteBuffer.WrittenSpan;
+ if (bytes.IsEmpty)
+ return null;
+
+ try
+ {
+ // Reconstruct valid JSON from potentially incomplete input
+ var reconstructedBytes = ReconstructValidJsonWithUtf8Writer(bytes);
+
+ if (reconstructedBytes.Length > 0)
+ {
+ if (_skipDeserialization)
+ return null;
+
+ try
+ {
+ return JsonSerializer.Deserialize(reconstructedBytes, _options);
+ }
+ catch (JsonException ex)
+ {
+ Debug.WriteLine($"Deserialization failed: {ex.Message}");
+
+ // Deserialization failed - JSON may still be too incomplete
+ return null;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Reconstruction failed: {ex.Message}");
+
+ // Reconstruction failed
+ }
+
+ return null;
+ }
+
+ ///
+ /// Reconstructs valid, complete JSON from potentially incomplete streaming JSON input.
+ /// Uses Utf8JsonReader to parse tokens and Utf8JsonWriter to rebuild valid JSON structure.
+ /// Handles incomplete values by completing partial strings and closing unclosed brackets/braces.
+ ///
+ /// The potentially incomplete UTF-8 JSON bytes to reconstruct.
+ /// A span containing complete, valid UTF-8 JSON bytes.
+ private ReadOnlySpan ReconstructValidJsonWithUtf8Writer(ReadOnlySpan incompleteUtf8Json)
+ {
+ _reconstructionBuffer.Clear();
+ _bracketStack.Clear();
+
+ // Use non-final block mode to allow incomplete JSON
+ var reader = new Utf8JsonReader(incompleteUtf8Json, isFinalBlock: false, state: default);
+ var writer = new Utf8JsonWriter(_reconstructionBuffer);
+
+ // Track pending property name for key-value pairs
+ ReadOnlySpan pendingPropertyNameBytes = default;
+
+ try
+ {
+ while (reader.Read())
+ {
+ switch (reader.TokenType)
+ {
+ case JsonTokenType.StartObject:
+ WritePendingPropertyName(writer, ref pendingPropertyNameBytes);
+ writer.WriteStartObject();
+ // Track that this object needs closing
+ _bracketStack.Push((byte)'}');
+ break;
+
+ case JsonTokenType.EndObject:
+ // Only close if we have a matching open brace
+ if (_bracketStack.Count > 0 && _bracketStack.Peek() == (byte)'}')
+ {
+ _bracketStack.Pop();
+ writer.WriteEndObject();
+ }
+ break;
+
+ case JsonTokenType.StartArray:
+ WritePendingPropertyName(writer, ref pendingPropertyNameBytes);
+ writer.WriteStartArray();
+ // Track that this array needs closing
+ _bracketStack.Push((byte)']');
+ break;
+
+ case JsonTokenType.EndArray:
+ // Only close if we have a matching open bracket
+ if (_bracketStack.Count > 0 && _bracketStack.Peek() == (byte)']')
+ {
+ _bracketStack.Pop();
+ writer.WriteEndArray();
+ }
+ break;
+
+ case JsonTokenType.PropertyName:
+ // Store property name bytes to write later when we get the value
+ // Must copy to array since the span becomes invalid after reader advances
+ pendingPropertyNameBytes = reader.HasValueSequence
+ ? reader.ValueSequence.ToArray()
+ : reader.ValueSpan.ToArray();
+ break;
+
+ case JsonTokenType.String:
+ WriteStringValue(writer, ref reader, ref pendingPropertyNameBytes);
+ break;
+
+ case JsonTokenType.Number:
+ WriteNumberValue(writer, ref reader, ref pendingPropertyNameBytes);
+ break;
+
+ case JsonTokenType.True:
+ WriteBooleanValue(writer, true, ref pendingPropertyNameBytes);
+ break;
+
+ case JsonTokenType.False:
+ WriteBooleanValue(writer, false, ref pendingPropertyNameBytes);
+ break;
+
+ case JsonTokenType.Null:
+ WriteNullValue(writer, ref pendingPropertyNameBytes);
+ break;
+ }
+ }
+ }
+ catch
+ {
+ // Reader exhausted - expected for incomplete JSON
+ }
+
+ // If we have a property name without a value, try to extract the partial value
+ if (!pendingPropertyNameBytes.IsEmpty)
+ {
+ TryWritePartialValue(writer, incompleteUtf8Json, pendingPropertyNameBytes);
+ }
+
+ // Close any unclosed JSON structures (objects/arrays) to make the JSON valid
+ while (_bracketStack.Count > 0)
+ {
+ var bracket = _bracketStack.Pop();
+ if (bracket == (byte)'}')
+ writer.WriteEndObject();
+ else if (bracket == (byte)']')
+ writer.WriteEndArray();
+ }
+
+ writer.Flush();
+ return _reconstructionBuffer.WrittenSpan;
+ }
+
+ ///
+ /// Writes a pending property name to the JSON writer if one exists, then clears it.
+ /// Used before writing values or nested structures.
+ ///
+ private static void WritePendingPropertyName(Utf8JsonWriter writer, ref ReadOnlySpan pendingPropertyNameBytes)
+ {
+ if (!pendingPropertyNameBytes.IsEmpty)
+ {
+ // Use span-based overload for zero-allocation property name writing
+ writer.WritePropertyName(pendingPropertyNameBytes);
+ pendingPropertyNameBytes = default;
+ }
+ }
+
+ ///
+ /// Writes a string value from the reader to the writer.
+ /// Handles both property values (with pending property name) and array elements (without).
+ ///
+ private static void WriteStringValue(Utf8JsonWriter writer, ref Utf8JsonReader reader, ref ReadOnlySpan pendingPropertyNameBytes)
+ {
+ ReadOnlySpan stringBytes = GetStringBytes(ref reader);
+
+ if (!pendingPropertyNameBytes.IsEmpty)
+ {
+ // Write as property: "propertyName": "value"
+ writer.WriteString(pendingPropertyNameBytes, stringBytes);
+ pendingPropertyNameBytes = default;
+ }
+ else
+ {
+ // Write as standalone value (array element or root value)
+ writer.WriteStringValue(stringBytes);
+ }
+ }
+
+ ///
+ /// Extracts string bytes from the JSON reader, handling escape sequences if present.
+ ///
+ /// UTF-8 bytes representing the unescaped string value.
+ private static ReadOnlySpan GetStringBytes(ref Utf8JsonReader reader)
+ {
+ if (reader.ValueIsEscaped)
+ {
+ // String contains escape sequences - need to unescape them
+ var unescapedBuffer = new ArrayBufferWriter();
+ var span = unescapedBuffer.GetSpan(reader.HasValueSequence ? (int)reader.ValueSequence.Length : reader.ValueSpan.Length);
+ int written = reader.CopyString(span);
+ unescapedBuffer.Advance(written);
+ return unescapedBuffer.WrittenSpan;
+ }
+
+ // No escaping - return raw bytes (handle both contiguous and segmented buffers)
+ return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+ }
+
+ ///
+ /// Writes a numeric value from the reader to the writer.
+ /// Handles both property values and array elements, choosing the most appropriate numeric type (long, double, or decimal).
+ ///
+ private static void WriteNumberValue(Utf8JsonWriter writer, ref Utf8JsonReader reader, ref ReadOnlySpan pendingPropertyNameBytes)
+ {
+ if (!pendingPropertyNameBytes.IsEmpty)
+ {
+ // Write as property: "propertyName": 123
+ // Try integer first, then floating point, finally decimal for precision
+ if (reader.TryGetInt64(out var longValue))
+ writer.WriteNumber(pendingPropertyNameBytes, longValue);
+ else if (reader.TryGetDouble(out var doubleValue))
+ writer.WriteNumber(pendingPropertyNameBytes, doubleValue);
+ else
+ writer.WriteNumber(pendingPropertyNameBytes, reader.GetDecimal());
+
+ pendingPropertyNameBytes = default;
+ }
+ else
+ {
+ // Write as standalone value
+ if (reader.TryGetInt64(out var longValue))
+ writer.WriteNumberValue(longValue);
+ else if (reader.TryGetDouble(out var doubleValue))
+ writer.WriteNumberValue(doubleValue);
+ else
+ writer.WriteNumberValue(reader.GetDecimal());
+ }
+ }
+
+ ///
+ /// Writes a boolean value to the writer.
+ /// Handles both property values and array elements.
+ ///
+ private static void WriteBooleanValue(Utf8JsonWriter writer, bool value, ref ReadOnlySpan pendingPropertyNameBytes)
+ {
+ if (!pendingPropertyNameBytes.IsEmpty)
+ {
+ // Write as property: "propertyName": true/false
+ writer.WriteBoolean(pendingPropertyNameBytes, value);
+ pendingPropertyNameBytes = default;
+ }
+ else
+ {
+ // Write as standalone value
+ writer.WriteBooleanValue(value);
+ }
+ }
+
+ ///
+ /// Writes a null value to the writer.
+ /// Handles both property values and array elements.
+ ///
+ private static void WriteNullValue(Utf8JsonWriter writer, ref ReadOnlySpan pendingPropertyNameBytes)
+ {
+ if (!pendingPropertyNameBytes.IsEmpty)
+ {
+ // Write as property: "propertyName": null
+ writer.WriteNull(pendingPropertyNameBytes);
+ pendingPropertyNameBytes = default;
+ }
+ else
+ {
+ // Write as standalone value
+ writer.WriteNullValue();
+ }
+ }
+
+ ///
+ /// Attempts to extract and write a partially received value for a pending property.
+ /// This handles cases where streaming JSON cuts off mid-value (strings, numbers, booleans, or null).
+ ///
+ /// The JSON writer to write the completed property to.
+ /// The complete accumulated JSON buffer.
+ /// The UTF-8 bytes of the property name.
+ private static void TryWritePartialValue(Utf8JsonWriter writer, ReadOnlySpan utf8Json, ReadOnlySpan propertyNameBytes)
+ {
+ // Build search pattern for property name: "propertyName":
+ Span pattern = stackalloc byte[propertyNameBytes.Length + 3];
+ pattern[0] = (byte)'"';
+ propertyNameBytes.CopyTo(pattern[1..^2]);
+ pattern[^2] = (byte)'"';
+ pattern[^1] = (byte)':';
+
+ // Find the last occurrence of the property:value pattern
+ var index = utf8Json.LastIndexOf(pattern);
+ if (index < 0)
+ return;
+
+ // Get the value portion after the colon
+ var valueStartIndex = index + pattern.Length;
+ if (valueStartIndex >= utf8Json.Length)
+ return;
+
+ var partialValueBytes = utf8Json[valueStartIndex..];
+
+ // Skip any whitespace after the colon
+ int i = 0;
+ while (i < partialValueBytes.Length && (partialValueBytes[i] == (byte)' ' || partialValueBytes[i] == (byte)'\t' || partialValueBytes[i] == (byte)'\n' || partialValueBytes[i] == (byte)'\r'))
+ i++;
+
+ if (i >= partialValueBytes.Length)
+ return;
+
+ partialValueBytes = partialValueBytes[i..];
+
+ // Determine the value type by the first character
+ var firstChar = partialValueBytes[0];
+
+ if (firstChar == (byte)'"')
+ {
+ // String value - extract everything after the opening quote
+ var stringValueBytes = partialValueBytes[1..];
+ var unescapedBytes = UnescapeJsonStringBytes(stringValueBytes);
+ writer.WriteString(propertyNameBytes, unescapedBytes.WrittenSpan);
+ }
+ else if (firstChar == (byte)'-' || (firstChar >= (byte)'0' && firstChar <= (byte)'9'))
+ {
+ // Number value - extract digits, decimal point, exponent
+ var numberEnd = 0;
+ while (numberEnd < partialValueBytes.Length && IsNumberChar(partialValueBytes[numberEnd]))
+ numberEnd++;
+
+ if (numberEnd > 0)
+ {
+ var numberBytes = partialValueBytes[..numberEnd];
+ // Try to parse as a number using a temporary reader
+ try
+ {
+ var numberReader = new Utf8JsonReader(numberBytes, isFinalBlock: true, state: default);
+ if (numberReader.Read() && numberReader.TokenType == JsonTokenType.Number)
+ {
+ if (numberReader.TryGetInt64(out var longValue))
+ writer.WriteNumber(propertyNameBytes, longValue);
+ else if (numberReader.TryGetDouble(out var doubleValue))
+ writer.WriteNumber(propertyNameBytes, doubleValue);
+ else
+ writer.WriteNumber(propertyNameBytes, numberReader.GetDecimal());
+ }
+ }
+ catch
+ {
+ // Invalid number format - skip
+ }
+ }
+ }
+ else if (partialValueBytes.Length >= 4 && partialValueBytes[..4].SequenceEqual("true"u8))
+ {
+ writer.WriteBoolean(propertyNameBytes, true);
+ }
+ else if (partialValueBytes.Length >= 5 && partialValueBytes[..5].SequenceEqual("false"u8))
+ {
+ writer.WriteBoolean(propertyNameBytes, false);
+ }
+ else if (partialValueBytes.Length >= 4 && partialValueBytes[..4].SequenceEqual("null"u8))
+ {
+ writer.WriteNull(propertyNameBytes);
+ }
+ // else: incomplete literal (e.g., "tru", "fal", "nul") - skip for now
+ }
+
+ ///
+ /// Checks if a byte is a valid character in a JSON number (digit, decimal point, sign, or exponent).
+ ///
+ private static bool IsNumberChar(byte b)
+ {
+ return b >= (byte)'0' && b <= (byte)'9'
+ || b == (byte)'.'
+ || b == (byte)'-'
+ || b == (byte)'+'
+ || b == (byte)'e'
+ || b == (byte)'E';
+ }
+
+ ///
+ /// Unescapes JSON string escape sequences (like \n, \t, \uXXXX) in UTF-8 bytes.
+ /// Handles standard escape sequences and Unicode escape sequences.
+ ///
+ /// The UTF-8 bytes containing escape sequences.
+ /// A buffer containing the unescaped UTF-8 bytes.
+ private static ArrayBufferWriter UnescapeJsonStringBytes(ReadOnlySpan escapedBytes)
+ {
+ // Handle empty input
+ if (escapedBytes.IsEmpty)
+ return new ArrayBufferWriter();
+
+ // Remove trailing backslash if present (incomplete escape sequence)
+ if (escapedBytes[^1] == (byte)'\\')
+ escapedBytes = escapedBytes[..^1];
+
+ // Handle case where only a backslash was present
+ if (escapedBytes.IsEmpty)
+ return new ArrayBufferWriter();
+
+ var buffer = new ArrayBufferWriter(escapedBytes.Length);
+
+ for (int i = 0; i < escapedBytes.Length; i++)
+ {
+ if (escapedBytes[i] == (byte)'\\' && i + 1 < escapedBytes.Length)
+ {
+ // Process escape sequences
+ switch (escapedBytes[i + 1])
+ {
+ case (byte)'"':
+ case (byte)'\\':
+ case (byte)'/':
+ // Simple character escapes - write the escaped character directly
+ buffer.GetSpan(1)[0] = escapedBytes[i + 1];
+ buffer.Advance(1);
+ i++; // Skip the escape sequence
+ break;
+ case (byte)'b':
+ buffer.GetSpan(1)[0] = (byte)'\b';
+ buffer.Advance(1);
+ i++;
+ break;
+ case (byte)'f':
+ buffer.GetSpan(1)[0] = (byte)'\f';
+ buffer.Advance(1);
+ i++;
+ break;
+ case (byte)'n':
+ buffer.GetSpan(1)[0] = (byte)'\n';
+ buffer.Advance(1);
+ i++;
+ break;
+ case (byte)'r':
+ buffer.GetSpan(1)[0] = (byte)'\r';
+ buffer.Advance(1);
+ i++;
+ break;
+ case (byte)'t':
+ buffer.GetSpan(1)[0] = (byte)'\t';
+ buffer.Advance(1);
+ i++;
+ break;
+ case (byte)'u' when i + 5 < escapedBytes.Length:
+ // Unicode escape sequence: \uXXXX (4 hex digits)
+ if (TryParseHexToChar(escapedBytes.Slice(i + 2, 4), out char unicodeChar))
+ {
+ // Convert the Unicode character to UTF-8 bytes directly into the buffer
+ var unichars = new ReadOnlySpan(in unicodeChar);
+ var bufferBytes = buffer.GetSpan(4);
+ var bytesWritten = Encoding.UTF8.GetBytes(unichars, bufferBytes);
+ buffer.Advance(bytesWritten);
+ i += 5; // Skip \uXXXX
+ }
+ else
+ {
+ // Invalid hex - write the backslash as-is
+ buffer.GetSpan(1)[0] = escapedBytes[i];
+ buffer.Advance(1);
+ }
+ break;
+ default:
+ // Unknown escape - write the backslash as-is
+ buffer.GetSpan(1)[0] = escapedBytes[i];
+ buffer.Advance(1);
+ break;
+ }
+ }
+ else
+ {
+ // Regular character - copy as-is
+ buffer.GetSpan(1)[0] = escapedBytes[i];
+ buffer.Advance(1);
+ }
+ }
+
+ return buffer;
+ }
+
+ ///
+ /// Parses a 4-character hexadecimal sequence (like "00A9" for ©) into a Unicode character.
+ /// Used for handling \uXXXX escape sequences in JSON strings.
+ ///
+ /// UTF-8 bytes representing 4 hexadecimal digits.
+ /// The resulting Unicode character if parsing succeeded.
+ /// True if parsing succeeded, false if the hex sequence is invalid.
+ private static bool TryParseHexToChar(ReadOnlySpan hexBytes, out char result)
+ {
+ result = '\0';
+ if (hexBytes.Length != 4)
+ return false;
+
+ int value = 0;
+ foreach (byte b in hexBytes)
+ {
+ // Convert ASCII hex digit to numeric value (0-15)
+ int hexDigit = b switch
+ {
+ >= (byte)'0' and <= (byte)'9' => b - '0',
+ >= (byte)'A' and <= (byte)'F' => b - 'A' + 10,
+ >= (byte)'a' and <= (byte)'f' => b - 'a' + 10,
+ _ => -1
+ };
+
+ if (hexDigit < 0)
+ return false;
+
+ // Shift previous value left by 4 bits (one hex digit) and OR in the new digit
+ value = (value << 4) | hexDigit;
+ }
+
+ result = (char)value;
+ return true;
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/TaggingService.cs b/src/AI/samples/Essentials.AI.Sample/Services/TaggingService.cs
new file mode 100644
index 000000000000..09924002530a
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/TaggingService.cs
@@ -0,0 +1,47 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+
+namespace Maui.Controls.Sample.Services;
+
+public class TaggingService(IChatClient chatClient)
+{
+ public async Task> GenerateTagsAsync(string text, CancellationToken cancellationToken = default)
+ {
+ var systemPrompt =
+ """
+ Your job is to extract the most relevant tags from the input text.
+ """;
+
+ var userPrompt =
+ $"""
+ Extract relevant tags from this text:
+
+ {text}
+ """;
+
+ var messages = new List
+ {
+ new(ChatRole.System, systemPrompt),
+ new(ChatRole.User, userPrompt)
+ };
+
+ var response = await chatClient.GetResponseAsync(messages, cancellationToken: cancellationToken);
+ var jsonText = response.ToString();
+
+ var jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true
+ };
+
+ return JsonSerializer.Deserialize(jsonText, jsonOptions)?.Tags ?? [];
+ }
+
+ public class TaggingResponse
+ {
+ [Description("Most important topics in the input text.")]
+ [Length(5, 5)]
+ public List Tags { get; set; } = [];
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/Tools/FindPointsOfInterestTool.cs b/src/AI/samples/Essentials.AI.Sample/Services/Tools/FindPointsOfInterestTool.cs
new file mode 100644
index 000000000000..4e3a46972c7f
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/Tools/FindPointsOfInterestTool.cs
@@ -0,0 +1,44 @@
+using System.ComponentModel;
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.Services.Tools;
+
+public class FindPointsOfInterestTool(Landmark landmark)
+{
+ public enum Category
+ {
+ Cafe,
+ Campground,
+ Hotel,
+ Marina,
+ Museum,
+ NationalMonument,
+ Restaurant,
+ }
+
+ [DisplayName("findPointsOfInterest")]
+ [Description("Finds points of interest for a landmark.")]
+ public string Call(
+ [Description("This is the type of destination to look up for.")]
+ Category pointOfInterest,
+ [Description("The natural language query of what to search for.")]
+ string naturalLanguageQuery)
+ {
+ var suggestions = GetSuggestions(pointOfInterest);
+
+ return $"There are these {pointOfInterest} in {landmark.Name}: {string.Join(", ", suggestions)}";
+ }
+
+ private static string[] GetSuggestions(Category category) =>
+ category switch
+ {
+ Category.Cafe => ["Cafe 1", "Cafe 2", "Cafe 3"],
+ Category.Campground => ["Campground 1", "Campground 2", "Campground 3"],
+ Category.Hotel => ["Hotel 1", "Hotel 2", "Hotel 3"],
+ Category.Marina => ["Marina 1", "Marina 2", "Marina 3"],
+ Category.Museum => ["Museum 1", "Museum 2", "Museum 3"],
+ Category.NationalMonument => ["The National Rock 1", "The National Rock 2", "The National Rock 3"],
+ Category.Restaurant => ["Restaurant 1", "Restaurant 2", "Restaurant 3"],
+ _ => []
+ };
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/Tools/ToolLookup.cs b/src/AI/samples/Essentials.AI.Sample/Services/Tools/ToolLookup.cs
new file mode 100644
index 000000000000..11ec348468ba
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/Tools/ToolLookup.cs
@@ -0,0 +1,10 @@
+namespace Maui.Controls.Sample.Services.Tools;
+
+public class ToolLookup
+{
+ public required string Id { get; init; }
+
+ public IDictionary? Arguments { get; set; }
+
+ public object? Result { get; set; }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Services/WeatherService.cs b/src/AI/samples/Essentials.AI.Sample/Services/WeatherService.cs
new file mode 100644
index 000000000000..4dfde72f2ed5
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Services/WeatherService.cs
@@ -0,0 +1,42 @@
+using System.Text.Json;
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.Services;
+
+public class WeatherService(HttpClient httpClient)
+{
+ public async Task GetWeatherForecastAsync(double latitude, double longitude, DateOnly date)
+ {
+ try
+ {
+ var url = $"https://api.open-meteo.com/v1/forecast?latitude={latitude:F4}&longitude={longitude:F4}&daily=temperature_2m_mean,weather_code&timezone=auto";
+
+ var response = await httpClient.GetStringAsync(url);
+ var forecast = JsonSerializer.Deserialize(response);
+
+ if (forecast?.Daily == null || forecast.Daily.Time.Count == 0)
+ {
+ return "☁️ Weather unavailable";
+ }
+
+ // Find the index for the requested date
+ var dateString = date.ToString("yyyy-MM-dd");
+ var index = forecast.Daily.Time.IndexOf(dateString);
+
+ if (index < 0 || index >= forecast.Daily.TemperatureMean.Count)
+ {
+ return "☁️ Weather unavailable";
+ }
+
+ var temp = forecast.Daily.TemperatureMean[index];
+ var weatherCode = forecast.Daily.WeatherCode[index];
+ var emoji = WeatherCodeExtensions.GetWeatherEmoji(weatherCode);
+
+ return $"{emoji} {temp:F0}°C";
+ }
+ catch
+ {
+ return "☁️ Weather unavailable";
+ }
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/ViewModels/DayPlanViewModel.cs b/src/AI/samples/Essentials.AI.Sample/ViewModels/DayPlanViewModel.cs
new file mode 100644
index 000000000000..2dd30a462679
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/ViewModels/DayPlanViewModel.cs
@@ -0,0 +1,22 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.ViewModels;
+
+public partial class DayPlanViewModel(DayPlan dayPlan, Landmark landmark, DateOnly date) : ObservableObject
+{
+ public string Title => dayPlan.Title;
+
+ public string Subtitle => dayPlan.Subtitle;
+
+ public string Destination => dayPlan.Destination;
+
+ public List Activities => dayPlan.Activities;
+
+ public Landmark Landmark { get; } = landmark;
+
+ public DateOnly Date { get; } = date;
+
+ [ObservableProperty]
+ public partial string WeatherForecast { get; set; } = "☁️ Weather unavailable";
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/ViewModels/ItineraryViewModel.cs b/src/AI/samples/Essentials.AI.Sample/ViewModels/ItineraryViewModel.cs
new file mode 100644
index 000000000000..2c7aa8816ba9
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/ViewModels/ItineraryViewModel.cs
@@ -0,0 +1,30 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.ViewModels;
+
+public partial class ItineraryViewModel(Itinerary itinerary, Landmark landmark) : ObservableObject
+{
+ public string Title => itinerary.Title;
+
+ public string Description => itinerary.Description;
+
+ public string Rationale => itinerary.Rationale;
+
+ public ObservableCollection Days =>
+ field ??= CreateDays();
+
+ private ObservableCollection CreateDays()
+ {
+ var startDate = DateOnly.FromDateTime(DateTime.Today);
+
+ var list = new ObservableCollection();
+ for (int i = 0; i < itinerary.Days.Count; i++)
+ {
+ list.Add(new DayPlanViewModel(itinerary.Days[i], landmark, startDate.AddDays(i)));
+ }
+
+ return list;
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/ViewModels/LandmarksViewModel.cs b/src/AI/samples/Essentials.AI.Sample/ViewModels/LandmarksViewModel.cs
new file mode 100644
index 000000000000..97ff7ec1f55c
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/ViewModels/LandmarksViewModel.cs
@@ -0,0 +1,51 @@
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Maui.Controls.Sample.Models;
+using Maui.Controls.Sample.Services;
+
+namespace Maui.Controls.Sample.ViewModels;
+
+public record ContinentGroup(string Name, List Landmarks);
+
+public partial class LandmarksViewModel(LandmarkDataService dataService) : ObservableObject
+{
+ [ObservableProperty]
+ public partial Landmark? FeaturedLandmark { get; private set; }
+
+ [ObservableProperty]
+ public partial bool IsLoading { get; set; }
+
+ public ObservableCollection ContinentGroups => field ??= [];
+
+ public async Task InitializeAsync()
+ {
+ if (IsLoading || ContinentGroups.Count > 0)
+ return;
+
+ await LoadLandmarksAsync();
+ }
+
+ private async Task LoadLandmarksAsync()
+ {
+ IsLoading = true;
+ try
+ {
+ await dataService.LoadLandmarksAsync();
+
+ FeaturedLandmark = dataService.FeaturedLandmark;
+
+ ContinentGroups.Clear();
+ foreach (var continent in dataService.LandmarksByContinent.Keys.OrderBy(c => c))
+ {
+ if (dataService.LandmarksByContinent.TryGetValue(continent, out var landmarks))
+ {
+ ContinentGroups.Add(new ContinentGroup(continent, landmarks));
+ }
+ }
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/ViewModels/TripPlanningViewModel.cs b/src/AI/samples/Essentials.AI.Sample/ViewModels/TripPlanningViewModel.cs
new file mode 100644
index 000000000000..a2b805d290b6
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/ViewModels/TripPlanningViewModel.cs
@@ -0,0 +1,156 @@
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Windows.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
+using Maui.Controls.Sample.Models;
+using Maui.Controls.Sample.Services;
+
+namespace Maui.Controls.Sample.ViewModels;
+
+[QueryProperty(nameof(Landmark), "Landmark")]
+public partial class TripPlanningViewModel(ItineraryService itineraryService, TaggingService taggingService, WeatherService weatherService, IDispatcher dispatcher) : ObservableObject
+{
+ public enum TripPlanningState
+ {
+ Initial, // Show landmark description and generate button
+ Generating, // Show planning view with tool lookups
+ Complete, // Show full itinerary
+ Error // Show error message
+ }
+
+ [ObservableProperty]
+ public partial Landmark Landmark { get; set; }
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(IsGeneratingState))]
+ [NotifyPropertyChangedFor(nameof(HasItinerary))]
+ public partial ItineraryViewModel? Itinerary { get; set; }
+
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(IsInitialState))]
+ [NotifyPropertyChangedFor(nameof(IsGeneratingState))]
+ [NotifyPropertyChangedFor(nameof(HasItinerary))]
+ [NotifyPropertyChangedFor(nameof(IsErrorState))]
+ [NotifyPropertyChangedFor(nameof(IsNotErrorState))]
+ public partial TripPlanningState CurrentState { get; set; } = TripPlanningState.Initial;
+
+ [ObservableProperty]
+ public partial string? ErrorMessage { get; set; }
+
+ public bool IsInitialState => CurrentState == TripPlanningState.Initial;
+ public bool IsGeneratingState => CurrentState == TripPlanningState.Generating && Itinerary is null;
+ public bool HasItinerary => CurrentState == TripPlanningState.Complete || Itinerary is not null;
+ public bool IsErrorState => CurrentState == TripPlanningState.Error;
+ public bool IsNotErrorState => CurrentState != TripPlanningState.Error;
+
+ public ObservableCollection GeneratedTags => field ??= [];
+
+ public ObservableCollection ToolLookupHistory => field ??= [];
+
+ public ICommand GenerateItineraryCommand =>
+ field ??= new Command(async () => await RequestItineraryAsync(), () => Landmark is not null && CurrentState == TripPlanningState.Initial);
+
+ public async Task InitializeAsync()
+ {
+ if (Landmark is null || GeneratedTags.Count > 0)
+ return;
+
+ // Generate tags for the landmark description
+ await GenerateTagsAsync();
+ }
+
+ private async Task GenerateTagsAsync()
+ {
+ try
+ {
+ var tags = await taggingService.GenerateTagsAsync(Landmark.Description);
+ GeneratedTags.Clear();
+ foreach (var tag in tags)
+ {
+ GeneratedTags.Add(tag);
+ await Task.Delay(100); // Simulate slight delay for better UX
+ }
+ }
+ catch (Exception ex)
+ {
+ // Silently fail tag generation - it's not critical
+ Debug.WriteLine($"Tag generation failed: {ex.Message}");
+ }
+ }
+
+ private async Task RequestItineraryAsync()
+ {
+ CurrentState = TripPlanningState.Generating;
+ ErrorMessage = string.Empty;
+ Itinerary = null;
+ ToolLookupHistory.Clear();
+
+ try
+ {
+ // Build the itinerary
+ await Task.Run(BuildItineraryAsync);
+
+ // Fetch weather for each day
+ if (Itinerary is not null)
+ {
+ foreach (var dayVm in Itinerary.Days)
+ {
+ dayVm.WeatherForecast = await weatherService.GetWeatherForecastAsync(
+ Landmark.Latitude,
+ Landmark.Longitude,
+ dayVm.Date);
+ }
+ }
+
+ CurrentState = TripPlanningState.Complete;
+ }
+ catch (Exception ex)
+ {
+ CurrentState = TripPlanningState.Error;
+ ErrorMessage = ex.Message;
+ }
+ }
+
+ private async Task BuildItineraryAsync()
+ {
+ Itinerary? latestItinerary = null;
+
+ var lookups = new Dictionary();
+
+ // Generate itinerary with streaming updates
+ await foreach (var update in itineraryService.StreamItineraryAsync(Landmark, 3))
+ {
+ // Handle tool lookups
+ if (update.ToolLookup is not null)
+ {
+ dispatcher.Dispatch(() =>
+ {
+ var text = update.ToolLookup.Arguments?["pointOfInterest"]?.ToString() ?? "Unknown";
+ lookups[update.ToolLookup.Id] = text;
+ ToolLookupHistory.Add(text);
+ });
+ }
+
+ // Handle tool lookup results
+ if (update.ToolLookupResult is not null)
+ {
+ dispatcher.Dispatch(() =>
+ {
+ if (lookups.TryGetValue(update.ToolLookupResult.Id, out var text))
+ {
+ ToolLookupHistory.Remove(text);
+ }
+
+ ToolLookupHistory.Add(update.ToolLookupResult.Result?.ToString() ?? "Unknown Result");
+ });
+ }
+
+ // Handle partial itinerary updates
+ if (update.PartialItinerary is not null)
+ {
+ latestItinerary = update.PartialItinerary;
+ Itinerary = new ItineraryViewModel(latestItinerary, Landmark);
+ }
+ }
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ActivityListView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ActivityListView.xaml
new file mode 100644
index 000000000000..8145ad89bf7b
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ActivityListView.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ActivityListView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ActivityListView.xaml.cs
new file mode 100644
index 000000000000..ac3c77009c08
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ActivityListView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class ActivityListView : ContentView
+{
+ public ActivityListView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/DayView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/DayView.xaml
new file mode 100644
index 000000000000..0db8be14dd40
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/DayView.xaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/DayView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/DayView.xaml.cs
new file mode 100644
index 000000000000..bdf96c0a8bfe
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/DayView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class DayView : ContentView
+{
+ public DayView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryPlanningView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryPlanningView.xaml
new file mode 100644
index 000000000000..095d5034126c
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryPlanningView.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryPlanningView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryPlanningView.xaml.cs
new file mode 100644
index 000000000000..0828ac21e7d1
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryPlanningView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class ItineraryPlanningView : ContentView
+{
+ public ItineraryPlanningView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryView.xaml
new file mode 100644
index 000000000000..018b349ceb33
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryView.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryView.xaml.cs
new file mode 100644
index 000000000000..fa3f15bcfc16
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/ItineraryView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class ItineraryView : ContentView
+{
+ public ItineraryView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDescriptionView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDescriptionView.xaml
new file mode 100644
index 000000000000..7edd9d2d88ee
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDescriptionView.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDescriptionView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDescriptionView.xaml.cs
new file mode 100644
index 000000000000..917e1d9baa63
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDescriptionView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class LandmarkDescriptionView : ContentView
+{
+ public LandmarkDescriptionView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDetailMapView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDetailMapView.xaml
new file mode 100644
index 000000000000..6e394f37b6fb
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDetailMapView.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDetailMapView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDetailMapView.xaml.cs
new file mode 100644
index 000000000000..661bf2c41eac
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkDetailMapView.xaml.cs
@@ -0,0 +1,30 @@
+using Maui.Controls.Sample.Models;
+using Microsoft.Maui.Controls.Maps;
+using Microsoft.Maui.Maps;
+
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class LandmarkDetailMapView : ContentView
+{
+ public LandmarkDetailMapView()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnBindingContextChanged()
+ {
+ base.OnBindingContextChanged();
+
+ if (BindingContext is Landmark landmark)
+ {
+ // Set ItemsSource to a single-item collection for the pin
+ map.ItemsSource = new[] { landmark };
+
+ // Move map to show the landmark
+ var center = new Location(landmark.Latitude, landmark.Longitude);
+ var span = new MapSpan(center, landmark.Span, landmark.Span);
+
+ map.MoveToRegion(span);
+ }
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkTripView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkTripView.xaml
new file mode 100644
index 000000000000..14f457410bab
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkTripView.xaml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkTripView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkTripView.xaml.cs
new file mode 100644
index 000000000000..d998030011d1
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/LandmarkTripView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class LandmarkTripView : ContentView
+{
+ public LandmarkTripView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/MessageView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/MessageView.xaml
new file mode 100644
index 000000000000..6d8fbab6cd09
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/MessageView.xaml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/MessageView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/MessageView.xaml.cs
new file mode 100644
index 000000000000..aa962edb2396
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Itinerary/MessageView.xaml.cs
@@ -0,0 +1,9 @@
+namespace Maui.Controls.Sample.Views.Itinerary;
+
+public partial class MessageView : ContentView
+{
+ public MessageView()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkFeaturedItemView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkFeaturedItemView.xaml
new file mode 100644
index 000000000000..f479e5ac76ea
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkFeaturedItemView.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkFeaturedItemView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkFeaturedItemView.xaml.cs
new file mode 100644
index 000000000000..4b0188b1e7b9
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkFeaturedItemView.xaml.cs
@@ -0,0 +1,30 @@
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.Views.Landmarks;
+
+public partial class LandmarkFeaturedItemView : ContentView
+{
+ public static readonly BindableProperty LandmarkProperty =
+ BindableProperty.Create(nameof(Landmark), typeof(Landmark), typeof(LandmarkFeaturedItemView), null);
+
+ public Landmark? Landmark
+ {
+ get => (Landmark?)GetValue(LandmarkProperty);
+ set => SetValue(LandmarkProperty, value);
+ }
+
+ public event EventHandler? LandmarkTapped;
+
+ public LandmarkFeaturedItemView()
+ {
+ InitializeComponent();
+ }
+
+ private void OnFeaturedLandmarkTapped(object? sender, TappedEventArgs e)
+ {
+ if (Landmark is not null)
+ {
+ LandmarkTapped?.Invoke(this, Landmark);
+ }
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkHorizontalListView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkHorizontalListView.xaml
new file mode 100644
index 000000000000..dc27987a293e
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkHorizontalListView.xaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkHorizontalListView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkHorizontalListView.xaml.cs
new file mode 100644
index 000000000000..2b7bd2af4d8a
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkHorizontalListView.xaml.cs
@@ -0,0 +1,28 @@
+using System.Collections;
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.Views.Landmarks;
+
+public partial class LandmarkHorizontalListView : ContentView
+{
+ public static readonly BindableProperty LandmarksProperty =
+ BindableProperty.Create(nameof(Landmarks), typeof(IEnumerable), typeof(LandmarkHorizontalListView), null);
+
+ public IEnumerable? Landmarks
+ {
+ get => (IEnumerable?)GetValue(LandmarksProperty);
+ set => SetValue(LandmarksProperty, value);
+ }
+
+ public event EventHandler? LandmarkTapped;
+
+ public LandmarkHorizontalListView()
+ {
+ InitializeComponent();
+ }
+
+ private void OnLandmarkItemTapped(object? sender, Landmark landmark)
+ {
+ LandmarkTapped?.Invoke(this, landmark);
+ }
+}
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkListItemView.xaml b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkListItemView.xaml
new file mode 100644
index 000000000000..9b2f86a964ef
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkListItemView.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkListItemView.xaml.cs b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkListItemView.xaml.cs
new file mode 100644
index 000000000000..99c03ef43e31
--- /dev/null
+++ b/src/AI/samples/Essentials.AI.Sample/Views/Landmarks/LandmarkListItemView.xaml.cs
@@ -0,0 +1,30 @@
+using Maui.Controls.Sample.Models;
+
+namespace Maui.Controls.Sample.Views.Landmarks;
+
+public partial class LandmarkListItemView : ContentView
+{
+ public static readonly BindableProperty LandmarkProperty =
+ BindableProperty.Create(nameof(Landmark), typeof(Landmark), typeof(LandmarkListItemView), null);
+
+ public Landmark? Landmark
+ {
+ get => (Landmark?)GetValue(LandmarkProperty);
+ set => SetValue(LandmarkProperty, value);
+ }
+
+ public event EventHandler? LandmarkTapped;
+
+ public LandmarkListItemView()
+ {
+ InitializeComponent();
+ }
+
+ private void OnLandmarkTapped(object? sender, TappedEventArgs e)
+ {
+ if (Landmark is not null)
+ {
+ LandmarkTapped?.Invoke(this, Landmark);
+ }
+ }
+}
diff --git a/src/AI/src/Essentials.AI/Essentials.AI.csproj b/src/AI/src/Essentials.AI/Essentials.AI.csproj
new file mode 100644
index 000000000000..54511ca0cff6
--- /dev/null
+++ b/src/AI/src/Essentials.AI/Essentials.AI.csproj
@@ -0,0 +1,33 @@
+
+
+ netstandard2.1;netstandard2.0;$(_MauiDotNetTfm);$(MauiEssentialsAIPlatforms)
+ $(TargetFrameworks);$(_MauiPreviousDotNetTfm);$(MauiEssentialsAIPreviousPlatforms)
+ Microsoft.Maui.Essentials.AI
+ Microsoft.Maui.Essentials.AI
+ true
+ true
+ true
+ enable
+ enable
+
+
+
+ true
+ False
+ Microsoft.Maui.Essentials.AI
+ $(DefaultPackageTags);essentials;ai
+ .NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating native mobile and desktop apps with C# and XAML. This package contains a collection of cross-platform APIs for working with device AI and local models.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-android/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-android/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-android/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-android/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-android/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-android/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-ios/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-ios/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-ios/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-ios/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-ios/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-ios/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-maccatalyst/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-macos/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-macos/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-macos/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-macos/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-macos/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-macos/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-windows/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-windows/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-windows/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net-windows/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net-windows/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/net/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/net/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/net/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/netstandard/PublicAPI.Shipped.txt b/src/AI/src/Essentials.AI/PublicAPI/netstandard/PublicAPI.Shipped.txt
new file mode 100644
index 000000000000..7dc5c58110bf
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/netstandard/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/src/Essentials.AI/PublicAPI/netstandard/PublicAPI.Unshipped.txt b/src/AI/src/Essentials.AI/PublicAPI/netstandard/PublicAPI.Unshipped.txt
new file mode 100644
index 000000000000..ab058de62d44
--- /dev/null
+++ b/src/AI/src/Essentials.AI/PublicAPI/netstandard/PublicAPI.Unshipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/AI/tests/Essentials.AI.Benchmarks/Essentials.AI.Benchmarks.csproj b/src/AI/tests/Essentials.AI.Benchmarks/Essentials.AI.Benchmarks.csproj
new file mode 100644
index 000000000000..6ca19dbafe79
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.Benchmarks/Essentials.AI.Benchmarks.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ Release
+ net10.0
+ Essentials.AI.Benchmarks
+ Microsoft.Maui.Essentials.AI.Benchmarks
+ false
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/AI/tests/Essentials.AI.Benchmarks/Models.cs b/src/AI/tests/Essentials.AI.Benchmarks/Models.cs
new file mode 100644
index 000000000000..2fde6e72e222
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.Benchmarks/Models.cs
@@ -0,0 +1,38 @@
+using System.Collections.Generic;
+
+namespace Maui.Controls.Sample.Models;
+
+public class TravelItinerary
+{
+ public string? Title { get; set; }
+ public string? Destination { get; set; }
+ public string? Description { get; set; }
+ public List? Days { get; set; }
+}
+
+public class DayItinerary
+{
+ public int Day { get; set; }
+ public string? Date { get; set; }
+ public string? Summary { get; set; }
+ public List? Activities { get; set; }
+}
+
+public class Activity
+{
+ public string? Time { get; set; }
+ public string? Title { get; set; }
+ public string? Description { get; set; }
+ public ActivityType Type { get; set; }
+}
+
+public enum ActivityType
+{
+ Breakfast,
+ Sightseeing,
+ Lunch,
+ Adventure,
+ Dinner,
+ Cultural,
+ Leisure
+}
diff --git a/src/AI/tests/Essentials.AI.Benchmarks/Program.cs b/src/AI/tests/Essentials.AI.Benchmarks/Program.cs
new file mode 100644
index 000000000000..bac0be2bcdb8
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.Benchmarks/Program.cs
@@ -0,0 +1,11 @@
+using BenchmarkDotNet.Running;
+
+namespace Microsoft.Maui.Essentials.AI.Benchmarks;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
+ }
+}
diff --git a/src/AI/tests/Essentials.AI.Benchmarks/StreamingJsonDeserializerBenchmark.cs b/src/AI/tests/Essentials.AI.Benchmarks/StreamingJsonDeserializerBenchmark.cs
new file mode 100644
index 000000000000..fe705f6f54cf
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.Benchmarks/StreamingJsonDeserializerBenchmark.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+using System.Text.Json;
+using BenchmarkDotNet.Attributes;
+using Maui.Controls.Sample.Models;
+using Maui.Controls.Sample.Services;
+
+namespace Microsoft.Maui.Essentials.AI.Benchmarks;
+
+[MemoryDiagnoser]
+[GcServer(true)]
+[SimpleJob(iterationCount: 20, warmupCount: 5)]
+public class StreamingJsonDeserializerBenchmark
+{
+ private string[]? _chunks;
+ private JsonSerializerOptions? _options;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ // Read the test data stream
+ var streamPath = Path.Combine(AppContext.BaseDirectory, "maui-itinerary-1.txt");
+ var streamContent = File.ReadAllText(streamPath);
+ _chunks = streamContent.Split('\n', StringSplitOptions.RemoveEmptyEntries);
+
+ _options = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ AllowTrailingCommas = true
+ };
+ }
+
+ [Benchmark(Description = "StreamingJsonDeserializer (skipDeserialization=true)")]
+ public void NewUtf8JsonWriterSkipDeserializationApproach()
+ {
+ var deserializer = new StreamingJsonDeserializer(_options, skipDeserialization: true);
+
+ TravelItinerary? lastModel = null;
+ foreach (var chunk in _chunks!)
+ {
+ var model = deserializer.ProcessChunk(chunk);
+ if (model != null)
+ lastModel = model;
+ }
+ }
+
+ [Benchmark(Description = "StreamingJsonDeserializer (skipDeserialization=false)")]
+ public void OldStringBuilderSkipDeserializationApproach()
+ {
+ var deserializer = new StreamingJsonDeserializer(_options, skipDeserialization: false);
+
+ TravelItinerary? lastModel = null;
+ foreach (var chunk in _chunks!)
+ {
+ var model = deserializer.ProcessChunk(chunk);
+ if (model != null)
+ lastModel = model;
+ }
+ }
+}
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Directory.Build.targets b/src/AI/tests/Essentials.AI.DeviceTests/Directory.Build.targets
new file mode 100644
index 000000000000..ee0f29e9005c
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Directory.Build.targets
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Essentials.AI.DeviceTests.csproj b/src/AI/tests/Essentials.AI.DeviceTests/Essentials.AI.DeviceTests.csproj
new file mode 100644
index 000000000000..c73e12921329
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Essentials.AI.DeviceTests.csproj
@@ -0,0 +1,55 @@
+
+
+
+ $(MauiDeviceTestsPlatforms)
+ Exe
+ true
+ Microsoft.Maui.Essentials.AI.DeviceTests
+ Microsoft.Maui.Essentials.AI.DeviceTests
+
+ maccatalyst-x64
+ maccatalyst-arm64
+ android-arm64;android-x64
+ true
+ enable
+ enable
+
+
+
+ AI Tests
+ com.microsoft.maui.ai.devicetests
+ 1
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/MauiProgram.cs b/src/AI/tests/Essentials.AI.DeviceTests/MauiProgram.cs
new file mode 100644
index 000000000000..f7ca7b6ccd5e
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/MauiProgram.cs
@@ -0,0 +1,27 @@
+using Microsoft.Maui.Hosting;
+using Microsoft.Maui.TestUtils.DeviceTests.Runners;
+
+namespace Microsoft.Maui.Essentials.AI.DeviceTests;
+
+public static class MauiProgram
+{
+ public static MauiApp CreateMauiApp()
+ {
+ var appBuilder = MauiApp.CreateBuilder();
+ appBuilder
+ .ConfigureTests(new TestOptions
+ {
+ Assemblies =
+ {
+ typeof(MauiProgram).Assembly
+ },
+ })
+ .UseHeadlessRunner(new HeadlessRunnerOptions
+ {
+ RequiresUIContext = true,
+ })
+ .UseVisualRunner();
+
+ return appBuilder.Build();
+ }
+}
\ No newline at end of file
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Android/AndroidManifest.xml b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Android/AndroidManifest.xml
new file mode 100644
index 000000000000..a4f8550d786e
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Android/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/MacCatalyst/Info.plist b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/MacCatalyst/Info.plist
new file mode 100644
index 000000000000..c96dd0a22544
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/MacCatalyst/Info.plist
@@ -0,0 +1,30 @@
+
+
+
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/App.xaml b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/App.xaml
new file mode 100644
index 000000000000..4a1877bd2b52
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/App.xaml
@@ -0,0 +1,5 @@
+
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/App.xaml.cs b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/App.xaml.cs
new file mode 100644
index 000000000000..4fcf629b9a40
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/App.xaml.cs
@@ -0,0 +1,10 @@
+using Microsoft.Maui.Hosting;
+
+namespace Microsoft.Maui.Essentials.AI.DeviceTests.WinUI;
+
+public partial class App : MauiWinUIApplication
+{
+ public App() => InitializeComponent();
+
+ protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
+}
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/Package.appxmanifest b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/Package.appxmanifest
new file mode 100644
index 000000000000..afa2dcd56da0
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/Package.appxmanifest
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+ $placeholder$
+ Microsoft
+ $placeholder$.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/app.manifest b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/app.manifest
new file mode 100644
index 000000000000..f9ef30446a98
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/Windows/app.manifest
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+ true/PM
+ PerMonitorV2, PerMonitor
+
+
+
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Platforms/iOS/Info.plist b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/iOS/Info.plist
new file mode 100644
index 000000000000..0004a4fdee5d
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Platforms/iOS/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ LSRequiresIPhoneOS
+
+ UIDeviceFamily
+
+ 1
+ 2
+
+ UIRequiredDeviceCapabilities
+
+ arm64
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ XSAppIconAssets
+ Assets.xcassets/appicon.appiconset
+
+
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Resources/Raw/dotnet_bot.png b/src/AI/tests/Essentials.AI.DeviceTests/Resources/Raw/dotnet_bot.png
new file mode 100644
index 000000000000..1096e673e93e
Binary files /dev/null and b/src/AI/tests/Essentials.AI.DeviceTests/Resources/Raw/dotnet_bot.png differ
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Resources/appicon.svg b/src/AI/tests/Essentials.AI.DeviceTests/Resources/appicon.svg
new file mode 100644
index 000000000000..9d63b6513a1c
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Resources/appicon.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Resources/appiconfg.svg b/src/AI/tests/Essentials.AI.DeviceTests/Resources/appiconfg.svg
new file mode 100644
index 000000000000..21dfb25f187b
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Resources/appiconfg.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/src/AI/tests/Essentials.AI.DeviceTests/Tests/SampleTests.cs b/src/AI/tests/Essentials.AI.DeviceTests/Tests/SampleTests.cs
new file mode 100644
index 000000000000..3dc52862a77b
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.DeviceTests/Tests/SampleTests.cs
@@ -0,0 +1,12 @@
+using Xunit;
+
+namespace Microsoft.Maui.Essentials.AI.DeviceTests;
+
+public partial class SampleTests
+{
+ [Fact]
+ public void Passes()
+ {
+ Assert.True(true);
+ }
+}
diff --git a/src/AI/tests/Essentials.AI.UnitTests/Essentials.AI.UnitTests.csproj b/src/AI/tests/Essentials.AI.UnitTests/Essentials.AI.UnitTests.csproj
new file mode 100644
index 000000000000..2dfe6fe3d0ba
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.UnitTests/Essentials.AI.UnitTests.csproj
@@ -0,0 +1,36 @@
+
+
+
+ $(_MauiDotNetTfm)
+ Microsoft.Maui.Essentials.AI.UnitTests
+ Microsoft.Maui.Essentials.AI.UnitTests
+ false
+ true
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/src/AI/tests/Essentials.AI.UnitTests/Tests/SampleTests.cs b/src/AI/tests/Essentials.AI.UnitTests/Tests/SampleTests.cs
new file mode 100644
index 000000000000..7650c2ba322e
--- /dev/null
+++ b/src/AI/tests/Essentials.AI.UnitTests/Tests/SampleTests.cs
@@ -0,0 +1,12 @@
+using Xunit;
+
+namespace Microsoft.Maui.Essentials.AI.UnitTests;
+
+public partial class SampleTests
+{
+ [Fact]
+ public void Passes()
+ {
+ Assert.True(true);
+ }
+}
diff --git a/src/Controls/src/Core/VisualStateManager.cs b/src/Controls/src/Core/VisualStateManager.cs
index 314501ccbea2..32eb27ba0ae3 100644
--- a/src/Controls/src/Core/VisualStateManager.cs
+++ b/src/Controls/src/Core/VisualStateManager.cs
@@ -82,10 +82,6 @@ public static bool GoToState(VisualElement visualElement, string name)
var specificity = vsgSpecificity.CopyStyle(1, 0, 0, 0);
- // Debug output for issue #27202
- System.Diagnostics.Debug.WriteLine($"[VSM] GoToState({name}) - VSG specificity: {vsgSpecificity.StyleInfo}");
- System.Diagnostics.Debug.WriteLine($"[VSM] VSM setter specificity will be used");
-
foreach (VisualStateGroup group in groups)
{
if (group.CurrentState?.Name == name)
diff --git a/src/PublicAPI.targets b/src/PublicAPI.targets
index 23a4d9be6df1..a05edf1bfc46 100644
--- a/src/PublicAPI.targets
+++ b/src/PublicAPI.targets
@@ -8,7 +8,7 @@
Validate
-
+ Generate
Validate