This commit is contained in:
Kosta Petan 2024-11-12 07:19:41 -08:00 committed by GitHub
commit f3499e212a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
94 changed files with 13427 additions and 21 deletions

View File

@ -86,8 +86,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Extension
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.Runtime", "src\Microsoft.AutoGen\Runtime\Microsoft.AutoGen.Runtime.csproj", "{A905E29A-7110-497F-ADC5-2CE2A148FEA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AutoGen.ServiceDefaults", "src\Microsoft.AutoGen\ServiceDefaults\Microsoft.AutoGen.ServiceDefaults.csproj", "{D7E9D90B-5595-4E72-A90A-6DE20D9AB7AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AgentChat", "AgentChat", "{668726B9-77BC-45CF-B576-0F0773BF1615}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Anthropic.Samples", "samples\AutoGen.Anthropic.Samples\AutoGen.Anthropic.Samples.csproj", "{84020C4A-933A-4693-9889-1B99304A7D76}"
@ -134,6 +132,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{686480
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HelloAgentState", "samples\Hello\HelloAgentState\HelloAgentState.csproj", "{64EF61E7-00A6-4E5E-9808-62E10993A0E5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MarketingTeam", "MarketingTeam", "{ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.AgentHost", "samples\marketing-team\Marketing.AgentHost\Marketing.AgentHost.csproj", "{6C796ACE-9599-4D55-AA0D-F1615B2D8C42}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.Agents", "samples\marketing-team\Marketing.Agents\Marketing.Agents.csproj", "{7EF1F26A-85BF-4C6F-8891-331C44B5802B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.AppHost", "samples\marketing-team\Marketing.AppHost\Marketing.AppHost.csproj", "{31DC6301-E354-46AA-BA0A-BED38AED8D36}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.Backend", "samples\marketing-team\Marketing.Backend\Marketing.Backend.csproj", "{60132BCE-0E8B-4E13-AEB0-919BC795E068}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.ServiceDefaults", "samples\marketing-team\Marketing.ServiceDefaults\Marketing.ServiceDefaults.csproj", "{F7C5E0CD-6FDA-4118-B419-74377C9CFD27}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Marketing.Shared", "samples\marketing-team\Marketing.Shared\Marketing.Shared.csproj", "{06CD34BB-93BF-467D-891A-91C44F0CD423}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevTeam.ServiceDefaults", "samples\dev-team\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj", "{FA454D8D-F4CD-4B8F-97EB-AB2BE18AD3AD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -268,10 +282,6 @@ Global
{A905E29A-7110-497F-ADC5-2CE2A148FEA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A905E29A-7110-497F-ADC5-2CE2A148FEA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A905E29A-7110-497F-ADC5-2CE2A148FEA0}.Release|Any CPU.Build.0 = Release|Any CPU
{D7E9D90B-5595-4E72-A90A-6DE20D9AB7AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7E9D90B-5595-4E72-A90A-6DE20D9AB7AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7E9D90B-5595-4E72-A90A-6DE20D9AB7AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7E9D90B-5595-4E72-A90A-6DE20D9AB7AE}.Release|Any CPU.Build.0 = Release|Any CPU
{84020C4A-933A-4693-9889-1B99304A7D76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{84020C4A-933A-4693-9889-1B99304A7D76}.Debug|Any CPU.Build.0 = Debug|Any CPU
{84020C4A-933A-4693-9889-1B99304A7D76}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -348,6 +358,34 @@ Global
{64EF61E7-00A6-4E5E-9808-62E10993A0E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64EF61E7-00A6-4E5E-9808-62E10993A0E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64EF61E7-00A6-4E5E-9808-62E10993A0E5}.Release|Any CPU.Build.0 = Release|Any CPU
{6C796ACE-9599-4D55-AA0D-F1615B2D8C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C796ACE-9599-4D55-AA0D-F1615B2D8C42}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C796ACE-9599-4D55-AA0D-F1615B2D8C42}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C796ACE-9599-4D55-AA0D-F1615B2D8C42}.Release|Any CPU.Build.0 = Release|Any CPU
{7EF1F26A-85BF-4C6F-8891-331C44B5802B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7EF1F26A-85BF-4C6F-8891-331C44B5802B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EF1F26A-85BF-4C6F-8891-331C44B5802B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EF1F26A-85BF-4C6F-8891-331C44B5802B}.Release|Any CPU.Build.0 = Release|Any CPU
{31DC6301-E354-46AA-BA0A-BED38AED8D36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31DC6301-E354-46AA-BA0A-BED38AED8D36}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31DC6301-E354-46AA-BA0A-BED38AED8D36}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31DC6301-E354-46AA-BA0A-BED38AED8D36}.Release|Any CPU.Build.0 = Release|Any CPU
{60132BCE-0E8B-4E13-AEB0-919BC795E068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{60132BCE-0E8B-4E13-AEB0-919BC795E068}.Debug|Any CPU.Build.0 = Debug|Any CPU
{60132BCE-0E8B-4E13-AEB0-919BC795E068}.Release|Any CPU.ActiveCfg = Release|Any CPU
{60132BCE-0E8B-4E13-AEB0-919BC795E068}.Release|Any CPU.Build.0 = Release|Any CPU
{F7C5E0CD-6FDA-4118-B419-74377C9CFD27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7C5E0CD-6FDA-4118-B419-74377C9CFD27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7C5E0CD-6FDA-4118-B419-74377C9CFD27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7C5E0CD-6FDA-4118-B419-74377C9CFD27}.Release|Any CPU.Build.0 = Release|Any CPU
{06CD34BB-93BF-467D-891A-91C44F0CD423}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06CD34BB-93BF-467D-891A-91C44F0CD423}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06CD34BB-93BF-467D-891A-91C44F0CD423}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06CD34BB-93BF-467D-891A-91C44F0CD423}.Release|Any CPU.Build.0 = Release|Any CPU
{FA454D8D-F4CD-4B8F-97EB-AB2BE18AD3AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA454D8D-F4CD-4B8F-97EB-AB2BE18AD3AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA454D8D-F4CD-4B8F-97EB-AB2BE18AD3AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA454D8D-F4CD-4B8F-97EB-AB2BE18AD3AD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -386,7 +424,6 @@ Global
{E0C991D9-0DB8-471C-ADC9-5FB16E2A0106} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{952827D4-8D4C-4327-AE4D-E8D25811EF35} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{A905E29A-7110-497F-ADC5-2CE2A148FEA0} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{D7E9D90B-5595-4E72-A90A-6DE20D9AB7AE} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{668726B9-77BC-45CF-B576-0F0773BF1615} = {686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}
{84020C4A-933A-4693-9889-1B99304A7D76} = {668726B9-77BC-45CF-B576-0F0773BF1615}
{5777515F-4053-42F9-AF2B-95D8D0F5384A} = {668726B9-77BC-45CF-B576-0F0773BF1615}
@ -409,6 +446,14 @@ Global
{97550E87-48C6-4EBF-85E1-413ABAE9DBFD} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{CF4C92BD-28AE-4B8F-B173-601004AEC9BF} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{64EF61E7-00A6-4E5E-9808-62E10993A0E5} = {7EB336C2-7C0A-4BC8-80C6-A3173AB8DC45}
{ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470} = {686480D7-8FEC-4ED3-9C5D-CEBE1057A7ED}
{6C796ACE-9599-4D55-AA0D-F1615B2D8C42} = {ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}
{7EF1F26A-85BF-4C6F-8891-331C44B5802B} = {ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}
{31DC6301-E354-46AA-BA0A-BED38AED8D36} = {ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}
{60132BCE-0E8B-4E13-AEB0-919BC795E068} = {ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}
{F7C5E0CD-6FDA-4118-B419-74377C9CFD27} = {ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}
{06CD34BB-93BF-467D-891A-91C44F0CD423} = {ACC20E50-0E9A-47AE-B6B3-9F1AFE74E470}
{FA454D8D-F4CD-4B8F-97EB-AB2BE18AD3AD} = {05B9C173-6441-4DCA-9AC4-E897EF75F331}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}

View File

@ -18,6 +18,7 @@
<PackageVersion Include="Aspire.Hosting.Orleans" Version="8.2.1" />
<PackageVersion Include="Aspire.Hosting.Qdrant" Version="8.2.1" />
<PackageVersion Include="Aspire.Hosting.Redis" Version="8.2.0" />
<PackageVersion Include="Aspire.Hosting.Python" Version="8.2.0" />
<PackageVersion Include="Azure.AI.OpenAI" Version=" 2.1.0-beta.1" />
<PackageVersion Include="Azure.AI.Inference" Version="1.0.0-beta.1" />
<PackageVersion Include="Azure.Data.Tables" Version="12.9.1" />
@ -38,7 +39,7 @@
<PackageVersion Include="Grpc.Core" Version="2.46.6" />
<PackageVersion Include="Grpc.Net.ClientFactory" Version="2.66.0" />
<PackageVersion Include="Grpc.Tools" Version="2.67.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.65.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.66.0" />
<PackageVersion Include="Google.Protobuf" Version="3.28.2" />
<PackageVersion Include="Microsoft.AspNetCore.App" Version="8.0.4" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8" />

View File

@ -10,7 +10,8 @@
<ItemGroup>
<ProjectReference Include="../../../src/Microsoft.AutoGen/Runtime/Microsoft.AutoGen.Runtime.csproj" />
<ProjectReference Include="../../../src/Microsoft.AutoGen/ServiceDefaults/Microsoft.AutoGen.ServiceDefaults.csproj" />
<ProjectReference Include="..\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj" />
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -9,8 +9,6 @@
<ItemGroup>
<ProjectReference Include="../../../src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj" />
<ProjectReference Include="../../../src/Microsoft.AutoGen/ServiceDefaults/Microsoft.AutoGen.ServiceDefaults.csproj" />
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\SemanticKernel\Microsoft.AutoGen.Extensions.SemanticKernel.csproj" />
</ItemGroup>

View File

@ -0,0 +1,16 @@
{
"profiles": {
"DevTeam.AppHost": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17034;http://localhost:15043",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21249",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22030"
}
}
}
}

View File

@ -29,7 +29,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../src/Microsoft.AutoGen/ServiceDefaults/Microsoft.AutoGen.ServiceDefaults.csproj" />
<ProjectReference Include="..\DevTeam.Shared\DevTeam.Shared.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\SemanticKernel\Microsoft.AutoGen.Extensions.SemanticKernel.csproj" />
</ItemGroup>

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Extensions.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace Microsoft.Extensions.Hosting;
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
// This project should be referenced by each service project in your solution.
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.AddServiceDiscovery();
});
// Uncomment the following to restrict the allowed schemes for service discovery.
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
// {
// options.AllowedSchemes = ["https"];
// });
return builder;
}
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("Microsoft.Orleans");
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("Microsoft.Orleans.Application")
.AddSource("AutoGen.Agent");
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
//{
// builder.Services.AddOpenTelemetry()
// .UseAzureMonitor();
//}
return builder;
}
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder;
}
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (app.Environment.IsDevelopment())
{
// All health checks must pass for app to be considered ready to accept traffic after starting
app.MapHealthChecks("/health");
// Only health checks tagged with the "live" tag must pass for app to be considered alive
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}

View File

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="../../../src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj" />
<ProjectReference Include="..\DevTeam.ServiceDefaults\DevTeam.ServiceDefaults.csproj" />
</ItemGroup>
<PropertyGroup>

View File

@ -0,0 +1,38 @@
/.infra/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../../src/Microsoft.AutoGen/Runtime/Microsoft.AutoGen.Runtime.csproj" />
<ProjectReference Include="..\Marketing.Shared\Marketing.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@Marketing.AgentHost_HostAddress = http://localhost:5136
GET {{Marketing.AgentHost_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Program.cs
using Microsoft.AutoGen.Runtime;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.AddAgentService();
var app = builder.Build();
app.MapDefaultEndpoints();
app.MapAgentService();
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:59666",
"sslPort": 44374
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5136",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7069;http://localhost:5136",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Auditor.cs
using Marketing.Shared;
using Microsoft.AutoGen.Abstractions;
using Microsoft.AutoGen.Agents;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
namespace Marketing.Agents;
[TopicSubscription("default")]
public class Auditor(IAgentContext context, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<Auditor> logger)
: SKAiAgent<AuditorState>(context, memory, kernel, typeRegistry),
IHandle<AuditText>
{
public async Task Handle(AuditText item)
{
logger.LogInformation($"[{nameof(Auditor)}] Event {nameof(AuditText)}. Text: {{Text}}", item.Text);
var context = new KernelArguments { ["input"] = AppendChatHistory(item.Text) };
var auditorAnswer = await CallFunction(AuditorPrompts.AuditText, context);
if (auditorAnswer.Contains("NOTFORME", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
await SendAuditorAlertEvent(auditorAnswer, item.UserId);
}
private async Task SendAuditorAlertEvent(string auditorAlertMessage, string userId)
{
var auditorAlert = new AuditorAlert
{
AuditorAlertMessage = auditorAlertMessage,
UserId = userId
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(auditorAlert);
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// AuditorPrompts.cs
namespace Marketing.Agents;
public static class AuditorPrompts
{
public const string AuditText = """
You are an Auditor in a Marketing team
Audit the text bello and make sure we do not give discounts larger than 10%
If the text talks about a larger than 10% discount, reply with a message to the user saying that the discount is too large, and by company policy we are not allowed.
If the message says who wrote it, add that information in the response as well
In any other case, reply with NOTFORME
---
Input: {{$input}}
---
""";
}

View File

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// CommunityManager.cs
using Marketing.Shared;
using Microsoft.AutoGen.Abstractions;
using Microsoft.AutoGen.Agents;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
namespace Marketing.Agents;
[TopicSubscription("default")]
public class CommunityManager(IAgentContext context, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<CommunityManager> logger)
: SKAiAgent<CommunityManagerState>(context, memory, kernel, typeRegistry),
IHandle<UserConnected>,
IHandle<UserChatInput>,
IHandle<ArticleCreated>
{
public async Task Handle(ArticleCreated item)
{
logger.LogInformation($"Article created: {item.Article}");
_state.Data.Article = item.Article;
await HandleGeneration(item.UserId, item.UserMessage);
}
public async Task Handle(UserChatInput item)
{
logger.LogInformation($"UserChatInput: {item.UserMessage}");
if (_state.Data.Article == null) { return; }
await HandleGeneration(item.UserId, item.UserMessage);
}
private async Task HandleGeneration(string userId, string userMessage)
{
var input = _state.Data.Article + "| USER REQUEST: " + userMessage;
var context = new KernelArguments { ["input"] = AppendChatHistory(input) };
var socialMediaPost = await CallFunction(CommunityManagerPrompts.WritePost, context);
if (socialMediaPost.Contains("NOTFORME", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
_state.Data.WrittenSocialMediaPost = socialMediaPost;
await SendDesignedCreatedEvent(socialMediaPost, userId);
}
public async Task Handle(UserConnected item)
{
//The user reconnected, let's send the last message if we have one
var lastMessage = _state.History.LastOrDefault()?.Message;
if (string.IsNullOrWhiteSpace(lastMessage))
{
return;
}
await SendDesignedCreatedEvent(lastMessage, item.UserId);
}
private async Task SendDesignedCreatedEvent(string socialMediaPost, string userId)
{
var socialMediaPostCreatedEvent = new SocialMediaPostCreated
{
SocialMediaPost = socialMediaPost,
UserId = userId
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(socialMediaPostCreatedEvent);
}
// This is just an example on how you can synchronously call an specific agent
public Task<string> GetArticle()
{
return Task.FromResult(_state.Data.WrittenSocialMediaPost);
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// CommunityManagerPrompts.cs
namespace Marketing.Agents;
public static class CommunityManagerPrompts
{
public const string WritePost = """
You are a Marketing community manager writer.
If the request from the user is to write a post to promote a new product, or if it is specifically talking to you (community manager)
then you should write a post based on the user request
Your writings are going to be posted on Tweeter. So be informal, friendly and add some hashtags and emojis.
Remember, the tweet cannot be longer than 280 characters.
If the request was not intedend for you. reply with <NOTFORME>"
---
Input: {{$input}}
---
""";
}

View File

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// GraphicDesigner.cs
using Marketing.Shared;
using Microsoft.AutoGen.Abstractions;
using Microsoft.AutoGen.Agents;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel.TextToImage;
namespace Marketing.Agents;
[TopicSubscription("default")]
public class GraphicDesigner(IAgentContext context, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<GraphicDesigner> logger)
: SKAiAgent<GraphicDesignerState>(context, memory, kernel, typeRegistry),
IHandle<UserConnected>,
IHandle<ArticleCreated>
{
public async Task Handle(UserConnected item)
{
var lastMessage = _state.History.LastOrDefault()?.Message;
if (string.IsNullOrWhiteSpace(lastMessage))
{
return;
}
await SendDesignedCreatedEvent(lastMessage, item.UserId);
}
public async Task Handle(ArticleCreated item)
{
//For demo purposes, we do not recreate images if they already exist
if (!string.IsNullOrEmpty(_state.Data.ImageUrl))
{
return;
}
logger.LogInformation($"[{nameof(GraphicDesigner)}] Event {nameof(ArticleCreated)}.");
var dallEService = _kernel.GetRequiredService<ITextToImageService>();
var imageUri = await dallEService.GenerateImageAsync(item.Article, 1024, 1024);
_state.Data.ImageUrl = imageUri;
await SendDesignedCreatedEvent(imageUri, item.UserId);
}
private async Task SendDesignedCreatedEvent(string imageUri, string userId)
{
var graphicDesignEvent = new GraphicDesignCreated
{
ImageUri = imageUri,
UserId = userId
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(graphicDesignEvent);
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// GraphicDesignerPrompts.cs
namespace Marketing.Agents;
public static class GraphicDesignerPrompts
{
public const string GenerateImage = """
You are a Marketing community manager graphic designer.
Bellow is a campaign that you need to create a image for.
Create an image of maximum 500x500 pixels that could be use in social medias as a marketing image.
Only answer if the user is asking for an image to be created or if the user is asking for a new campaign
Input: {{$input}}
""";
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Net.Client" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Marketing.ServiceDefaults\Marketing.ServiceDefaults.csproj" />
<ProjectReference Include="..\Marketing.Shared\Marketing.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@Marketing.Agents_HostAddress = http://localhost:5019
GET {{Marketing.Agents_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Program.cs
using Marketing.Agents;
using Marketing.Shared;
using Microsoft.AutoGen.Agents;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.ConfigureSemanticKernel();
builder.AddAgentWorker(builder.Configuration["AGENT_HOST"]!)
.AddAgent<Writer>("writer")
.AddAgent<GraphicDesigner>("graphic-designer")
.AddAgent<Auditor>("auditor")
.AddAgent<CommunityManager>("community-manager");
var app = builder.Build();
app.MapDefaultEndpoints();
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:2285",
"sslPort": 44301
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5019",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7150;http://localhost:5019",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Writer.cs
using Marketing.Shared;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel;
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Abstractions;
namespace Marketing.Agents;
[TopicSubscription("default")]
public class Writer(IAgentContext context, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ILogger<Writer> logger)
: SKAiAgent<WriterState>(context, memory, kernel, typeRegistry),
IHandle<UserConnected>,
IHandle<UserChatInput>,
IHandle<AuditorAlert>
{
public async Task Handle(UserConnected item)
{
logger.LogInformation($"User Connected: {item.UserId}");
string? lastMessage = _state.History.LastOrDefault()?.Message;
if (string.IsNullOrWhiteSpace(lastMessage))
{
return;
}
await SendArticleCreatedEvent(lastMessage, item.UserId);
}
public async Task Handle(UserChatInput item)
{
logger.LogInformation($"UserChatInput: {item.UserMessage}");
var context = new KernelArguments { ["input"] = AppendChatHistory(item.UserMessage) };
var newArticle = await CallFunction(WriterPrompts.Write, context);
if (newArticle.Contains("NOTFORME", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
// TODO: Implement
// var agentState = _state.Data.ToAgentState(this.AgentId, "Etag");
// await Store("WrittenArticle", newArticle);
await SendArticleCreatedEvent(newArticle, item.UserId);
}
public async Task Handle(AuditorAlert item)
{
logger.LogInformation($"Auditor feedback: {item.AuditorAlertMessage}");
var context = new KernelArguments { ["input"] = AppendChatHistory(item.AuditorAlertMessage) };
var newArticle = await CallFunction(WriterPrompts.Adjust, context);
if (newArticle.Contains("NOTFORME", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
await SendArticleCreatedEvent(newArticle, item.UserId);
}
private async Task SendArticleCreatedEvent(string article, string userId)
{
var articleCreatedEvent = new ArticleCreated
{
Article = article,
UserId = userId
}.ToCloudEvent(this.AgentId.Key);
var auditTextEvent = new AuditText
{
Text = "Article written by the Writer: " + article,
UserId = userId
}.ToCloudEvent(this.AgentId.Key);
await PublishEvent(articleCreatedEvent);
await PublishEvent(auditTextEvent);
}
//protected override Task<RpcResponse> HandleRequest(RpcRequest request) => request.Method switch
//{
// "GetArticle" => Task.FromResult(new RpcResponse
// {
// Payload = new Payload
// {
// DataContentType = "text/plain",
// Data = ByteString.CopyFromUtf8(_state.Data.WrittenArticle),
// DataType = "text"
// }
// }),
// _ => Task.FromResult(new RpcResponse { Error = $"Unknown method, '{request.Method}'." }),
//};
public Task<string> GetArticle()
{
return Task.FromResult(_state.Data.WrittenArticle);
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// WriterPrompts.cs
namespace Marketing.Agents;
public static class WriterPrompts
{
public const string Write = """
This is a multi agent app. You are a Marketing Campaign writer Agent.
If the request is not for you, answer with <NOTFORME>.
If the request is about writing or modifying an existing campaign, then you should write a campaign based on the user request.
Write up to three paragraphs to promote the product the user is asking for.
Bellow are a series of inputs from the user that you can use.
If the input talks about twitter or images, dismiss it and return <NOTFORME>
Input: {{$input}}
""";
public const string Adjust = """
This is a multi agent app. You are a Marketing Campaign writer Agent.
If the request is not for you, answer with <NOTFORME>.
If the request is about writing or modifying an existing campaign, then you should write a campaign based on the user request.
The campaign is not compliant with the company policy, and you need to adjust it. This is the message from the automatic auditor agent regarding what is wrong with the original campaign
---
Input: {{$input}}
---
Return only the new campaign text but adjusted to the auditor request
""";
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>f0492827-f837-4305-9a7c-29e61abd4788</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" />
<PackageReference Include="Aspire.Hosting.Azure.ApplicationInsights" />
<PackageReference Include="Aspire.Hosting.Azure.CognitiveServices" />
<PackageReference Include="Aspire.Hosting.NodeJs" />
<PackageReference Include="Aspire.Hosting.Orleans" />
<PackageReference Include="Aspire.Hosting.Qdrant" />
<PackageReference Include="Aspire.Hosting.Redis" />
<PackageReference Include="Aspire.Hosting.Python" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Marketing.Agents\Marketing.Agents.csproj" />
<ProjectReference Include="..\Marketing.AgentHost\Marketing.AgentHost.csproj" />
<ProjectReference Include="..\Marketing.Backend\Marketing.Backend.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Program.cs
var builder = DistributedApplication.CreateBuilder(args);
builder.AddAzureProvisioning();
var orleans = builder.AddOrleans("orleans")
.WithDevelopmentClustering();
var agentHost = builder.AddProject<Projects.Marketing_AgentHost>("agenthost")
.WithReference(orleans);
var agentHostHttps = agentHost.GetEndpoint("https");
var backend = builder.AddProject<Projects.Marketing_Backend>("backend")
.WithEnvironment("AGENT_HOST", $"{agentHostHttps.Property(EndpointProperty.Url)}")
.WithEnvironment("OpenAI__Key", builder.Configuration["OpenAI:Key"])
.WithEnvironment("OpenAI__Endpoint", builder.Configuration["OpenAI:Endpoint"]);
builder.AddProject<Projects.Marketing_Agents>("marketing-agents")
.WithEnvironment("AGENT_HOST", $"{agentHostHttps.Property(EndpointProperty.Url)}")
.WithEnvironment("OpenAI__Key", builder.Configuration["OpenAI:Key"])
.WithEnvironment("OpenAI__Endpoint", builder.Configuration["OpenAI:Endpoint"]);
//var ep = agentHost.GetEndpoint("http");
//builder.AddPythonProject("python-worker", "../../../../../python/", "./packages/autogen-core/samples/marketing-team/worker.py")
// .WithEnvironment("AGENT_HOST", $"{agentHostHttps.Property(EndpointProperty.Host)}:{agentHostHttps.Property(EndpointProperty.Port)}");
builder.AddNpmApp("frontend", "../Marketing.Frontend", "dev")
.WithReference(backend)
.WithEnvironment("NEXT_PUBLIC_BACKEND_URI", backend.GetEndpoint("http"))
.WithHttpEndpoint(env: "PORT")
.WithExternalHttpEndpoints()
.PublishAsDockerFile();
builder.Build().Run();

View File

@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17034;http://localhost:15043",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21249",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22030"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15043",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19105",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20096"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SignalRAgent.cs
using Marketing.Shared;
using Microsoft.SemanticKernel.Memory;
using Microsoft.SemanticKernel;
using Marketing.Backend.Hubs;
using Google.Protobuf.WellKnownTypes;
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Abstractions;
namespace Marketing.Backend.Agents;
[TopicSubscription("default")]
public class SignalRAgent(IAgentContext context, Kernel kernel, ISemanticTextMemory memory, [FromKeyedServices("EventTypes")] EventTypes typeRegistry, ISignalRService signalRClient)
: SKAiAgent<Empty>(context, memory, kernel, typeRegistry),
IHandle<ArticleCreated>,
IHandle<GraphicDesignCreated>,
IHandle<SocialMediaPostCreated>,
IHandle<AuditorAlert>
{
public async Task Handle(SocialMediaPostCreated item)
{
ArgumentNullException.ThrowIfNull(item);
await signalRClient.SendMessageToSpecificClient(item.UserId, item.SocialMediaPost, Hubs.AgentTypes.CommunityManager);
}
public async Task Handle(ArticleCreated item)
{
ArgumentNullException.ThrowIfNull(item);
await signalRClient.SendMessageToSpecificClient(item.UserId, item.Article, Hubs.AgentTypes.Chat);
}
public async Task Handle(GraphicDesignCreated item)
{
ArgumentNullException.ThrowIfNull(item);
await signalRClient.SendMessageToSpecificClient(item.UserId, item.ImageUri, Hubs.AgentTypes.GraphicDesigner);
}
public async Task Handle(AuditorAlert item)
{
ArgumentNullException.ThrowIfNull(item);
await signalRClient.SendMessageToSpecificClient(item.UserId, item.AuditorAlertMessage, Hubs.AgentTypes.Auditor);
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ArticlesController.cs
using Marketing.Shared;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Runtime;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace Marketing.Backend.Controllers;
[Route("api/[controller]")]
[ApiController]
public class Articles(AgentWorker client) : ControllerBase
{
private readonly AgentWorker _client = client;
//// GET api/<Post>/5
//[HttpGet("{id}")]
//public async Task<string> Get(string id)
//{
// var response = await _client.(new AgentId("writer", id), "GetArticle", []);
// return response.Payload.Data.ToStringUtf8();
//}
// PUT api/<Post>/5
[HttpPut("{UserId}")]
public async Task<string> Put(string userId, [FromBody] string userMessage)
{
ArgumentNullException.ThrowIfNull(userId);
var evt = new UserChatInput
{
UserId = userId,
UserMessage = userMessage,
};
await _client.PublishEventAsync(evt.ToCloudEvent(userId));
return $"Task {userId} accepted";
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// AgentTypes.cs
namespace Marketing.Backend.Hubs;
public enum AgentTypes
{
Chat,
CommunityManager,
GraphicDesigner,
Auditor,
Accountant,
Librarian
}

View File

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ArticleHub.cs
using Marketing.Shared;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Runtime;
namespace Marketing.Backend.Hubs;
public class ArticleHub(AgentWorker client) : Hub<IArticleHub>
{
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
SignalRConnectionsDB.ConnectionIdByUser.TryRemove(Context.ConnectionId, out _);
await base.OnDisconnectedAsync(exception);
}
/// <summary>
/// This method is called when a new message from the client arrives.
/// </summary>
/// <param name="frontEndMessage"></param>
/// <returns></returns>
public async Task ProcessMessage(FrontEndMessage frontEndMessage)
{
ArgumentNullException.ThrowIfNull(frontEndMessage);
ArgumentNullException.ThrowIfNull(client);
var evt = new UserChatInput { UserId = frontEndMessage.UserId, UserMessage = frontEndMessage.Message };
await client.PublishEventAsync(evt.ToCloudEvent(frontEndMessage.UserId));
}
public async Task ConnectToAgent(string userId)
{
ArgumentNullException.ThrowIfNull(userId);
ArgumentNullException.ThrowIfNull(client);
var frontEndMessage = new FrontEndMessage()
{
UserId = userId,
Message = "Connected to agents",
Agent = AgentTypes.Chat.ToString()
};
SignalRConnectionsDB.ConnectionIdByUser.AddOrUpdate(userId, Context.ConnectionId, (key, oldValue) => Context.ConnectionId);
// Notify the agents that a new user got connected.
var data = new Dictionary<string, string>
{
["UserId"] = frontEndMessage.UserId,
["userMessage"] = frontEndMessage.Message,
};
var evt = new UserConnected { UserId = userId };
await client.PublishEventAsync(evt.ToCloudEvent(userId));
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// FrontEndMessage.cs
namespace Marketing.Backend.Hubs;
public class FrontEndMessage
{
public required string UserId { get; set; }
public required string Message { get; set; }
public required string Agent { get; set; }
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// IArticleHub.cs
namespace Marketing.Backend.Hubs;
public interface IArticleHub
{
public Task ConnectToAgent(string UserId);
public Task ChatMessage(FrontEndMessage frontEndMessage);
public Task SendMessageToSpecificClient(string userId, string message);
}

View File

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ISignalRService.cs
namespace Marketing.Backend.Hubs;
public interface ISignalRService
{
Task SendMessageToSpecificClient(string userId, string message, AgentTypes agentType);
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SignalRConnectionsDB.cs
using System.Collections.Concurrent;
namespace Marketing.Backend.Hubs;
public static class SignalRConnectionsDB
{
public static ConcurrentDictionary<string, string> ConnectionIdByUser { get; } = new ConcurrentDictionary<string, string>();
public static ConcurrentDictionary<string, string> AllConnections { get; } = new ConcurrentDictionary<string, string>();
}

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SignalRService.cs
using Microsoft.AspNetCore.SignalR;
namespace Marketing.Backend.Hubs;
public class SignalRService(IHubContext<ArticleHub> hubContext) : ISignalRService
{
public async Task SendMessageToSpecificClient(string userId, string message, AgentTypes agentType)
{
var connectionId = SignalRConnectionsDB.ConnectionIdByUser[userId];
var frontEndMessage = new FrontEndMessage()
{
UserId = userId,
Message = message,
Agent = agentType.ToString()
};
await hubContext.Clients.Client(connectionId).SendAsync("ReceiveMessage", frontEndMessage);
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Octokit.Webhooks.AspNetCore" />
<PackageReference Include="Octokit" />
<PackageReference Include="Microsoft.SemanticKernel" />
<PackageReference Include="Microsoft.SemanticKernel.Connectors.Qdrant" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Microsoft.Extensions.Azure" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Azure.ResourceManager.ContainerInstance" />
<PackageReference Include="Azure.Storage.Files.Shares" />
<PackageReference Include="Azure.Data.Tables" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Extensions\CloudEvents\Microsoft.AutoGen.Extensions.CloudEvents.csproj" />
<ProjectReference Include="..\Marketing.Shared\Marketing.Shared.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@Marketing.Backend_HostAddress = http://localhost:5019
GET {{Marketing.Backend_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Program.cs
using Marketing.Backend.Agents;
using Marketing.Shared;
using Marketing.Backend.Hubs;
using Microsoft.AutoGen.Agents;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.ConfigureSemanticKernel();
builder.Services.AddHttpClient();
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
builder.Services.AddSignalR();
builder.AddAgentWorker(builder.Configuration["AGENT_HOST"]!)
.AddAgent<SignalRAgent>("signalr-hub");
builder.Services.AddSingleton<AgentWorker>();
builder.Services.AddSingleton<ISignalRService, SignalRService>();
// Allow any CORS origin if in DEV
const string AllowDebugOriginPolicy = "AllowDebugOrigin";
if (builder.Environment.IsDevelopment())
{
builder.Services.AddCors(options =>
{
options.AddPolicy(AllowDebugOriginPolicy, builder =>
{
builder
.WithOrigins("*") // client url
.AllowAnyHeader()
.AllowAnyMethod();
});
});
}
var app = builder.Build();
app.MapDefaultEndpoints();
app.UseRouting();
app.UseCors(AllowDebugOriginPolicy);
app.MapControllers();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
app.MapHub<ArticleHub>("/articlehub");
app.Run();

View File

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:51446",
"sslPort": 44363
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5020",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7224;http://localhost:5020",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@ -0,0 +1,2 @@
# Front end needs the environment variables at build time
!.env

View File

@ -0,0 +1 @@
NEXT_PUBLIC_BACKEND_URI=<AZURE_BACKEND_URI>

View File

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

View File

@ -0,0 +1,40 @@
.env
/.infra/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -0,0 +1,2 @@
legacy-peer-deps=true
strict-peer-dependencies=false

View File

@ -0,0 +1,31 @@
FROM refinedev/node:18 AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
else echo "Lockfile not found." && exit 1; \
fi
FROM base AS builder
ENV NEXT_TELEMETRY_DEBUG=1
COPY --from=deps /app/refine/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
ENV NODE_ENV=production
COPY --from=builder /app/refine/public ./public
RUN mkdir .next
RUN chown refine:nodejs .next
COPY --from=builder --chown=refine:nodejs /app/refine/.next/standalone ./
COPY --from=builder --chown=refine:nodejs /app/refine/.next/static ./.next/static
USER refine
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

View File

@ -0,0 +1,11 @@
## TODO: describe the frontend app
## How I started
```
npm -i
```
## How to run it
```shell
npm run dev
```

View File

@ -0,0 +1,23 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
images: {
domains: ['dalleprodsec.blob.core.windows.net', 'oaidalleapiprodscus.blob.core.windows.net'],
remotePatterns: [
{
protocol: 'https',
hostname: 'dalleprodsec.blob.core.windows.net',
port: '**',
pathname: '**',
},
{
protocol: 'https',
hostname: 'oaidalleapiprodscus.blob.core.windows.net',
port: '**',
pathname: '**',
},
],
},
};
export default nextConfig;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
{
"name": "Learning",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"dev": "cross-env NODE_OPTIONS=--max_old_space_size=4096 refine dev",
"build": "refine build",
"start": "refine start",
"lint": "eslint '**/*.{js,jsx,ts,tsx}'",
"refine": "refine"
},
"dependencies": {
"@emotion/react": "^11.8.2",
"@emotion/styled": "^11.8.1",
"@fontsource/roboto": "^4.5.8",
"@microsoft/signalr": "^8.0.0",
"@mui/icons-material": "^5.8.3",
"@mui/lab": "^5.0.0-alpha.85",
"@mui/material": "^5.8.6",
"@mui/x-data-grid": "^6.6.0",
"@refinedev/cli": "^2.16.21",
"@refinedev/core": "^4.47.1",
"@refinedev/devtools": "^1.1.32",
"@refinedev/kbar": "^1.3.6",
"@refinedev/mui": "^5.14.4",
"@refinedev/nextjs-router": "^6.0.0",
"@refinedev/react-hook-form": "^4.8.14",
"@refinedev/simple-rest": "^5.0.1",
"js-cookie": "^3.0.5",
"next": "^14.2.5",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-flippy": "^1.1.0",
"uuidv4": "^6.2.13"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"@types/node": "^18.0.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/parser": "5.48.0",
"cross-env": "^7.0.3",
"eslint": "^8",
"eslint-config-next": "14.1.0",
"typescript": "^4.7.4"
},
"refine": {
"projectId": "Z0Y3hf-6BZFyJ-VWX5Qq"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f6c1e8aa044af0cf16af9840f4b8c34dbefc37994febfd286cd9704d5584989a
size 2787

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3e3bd4af4498ca6dcd037e2c9a165dad307dde631380a3408fc13039ba33617b
size 5195

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79970b593cf2a95c7e2aac828272982a19c7f149bead9b8d8256cfdc46b12860
size 66564

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e9c6f416afca867f3501df6ee02d32bf245c02d46688155232ba0faa6ecec585
size 15771

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c0d515e8f60f037366e52e8f1d82e28a676808a9c30c20b2dc93b9b0554bffa7
size 84596

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2447ccc6fecf13857e32a06659f75637d39d560dc3e0b9673ff855a8bd6ac4ba
size 28777

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7253f301aa0fd63fad4935c51eba121f766a630a9f47b25d24cd7b281e3ca943
size 14950

View File

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a4c797fb932b0334d675d56e8c3bcfb967a3a3aa9da88affa7daf302992021f9
size 2490

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@ -0,0 +1,57 @@
import { DevtoolsProvider } from "@providers/devtools";
import { Refine } from "@refinedev/core";
import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar";
import { RefineSnackbarProvider, notificationProvider } from "@refinedev/mui";
import routerProvider from "@refinedev/nextjs-router";
import { Metadata } from "next";
import { cookies } from "next/headers";
import React, { Suspense } from "react";
export const metadata: Metadata = {
title: "AgNext Marketing Sample",
description: "AgNext Marketing Sample",
icons: {
icon: "/favicon.ico",
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body>
<Suspense>
<RefineKbarProvider>
<RefineSnackbarProvider>
<DevtoolsProvider>
<Refine
routerProvider={routerProvider}
notificationProvider={notificationProvider}
resources={[
{
name: "marketing-app",
list: "/marketing",
}
]}
options={{
syncWithLocation: true,
warnWhenUnsavedChanges: true,
useNewQueryKeys: true,
projectId: "Z0Y3hf-6BZFyJ-VWX5Qq",
}}
>
{children}
<RefineKbar />
</Refine>
</DevtoolsProvider>
</RefineSnackbarProvider>
</RefineKbarProvider>
</Suspense>
</body>
</html>
);
}

View File

@ -0,0 +1,158 @@
"use client";
import * as React from 'react';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import Public from '@mui/icons-material/Public';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import HandshakeTwoToneIcon from '@mui/icons-material/HandshakeTwoTone';
import WorkspacePremiumTwoToneIcon from '@mui/icons-material/WorkspacePremiumTwoTone';
import { styled } from '@mui/material/styles';
import LightbulbIcon from '@mui/icons-material/Lightbulb';
import { Button, Container, Grid, TextField } from '@mui/material';
const data = [
{ icon: <HandshakeTwoToneIcon />, label: 'Bank vs Mrs Peters - Settled - chf 1.5M - 1 year' },
{ icon: <WorkspacePremiumTwoToneIcon />, label: 'Bank vs Mr Pertussi - Won - chf0 - 4 years' },
{ icon: <Public />, label: 'Bank vs Governnent - Public Case - chf 3.7M - 10 years' },
];
type Sender = 'user' | 'CommunityManager' | 'GraphicDesigner' | 'Writer' | 'Auditor';
const senderColors: Record<Sender, string> = {
'user': '#d1e7dd',
'CommunityManager': '#d4e2d4',
'GraphicDesigner': '#f0e8e8',
'Writer': '#add8e6',
'Auditor': '#ff7f7f',
};
const FireNav = styled(List)<{ component?: React.ElementType }>({
'& .MuiListItemButton-root': {
paddingLeft: 24,
paddingRight: 24,
},
'& .MuiListItemIcon-root': {
minWidth: 0,
marginRight: 16,
},
'& .MuiSvgIcon-root': {
fontSize: 20,
},
});
type Message = {
sender: string;
text: any;
};
type ChatProps = {
messages: Message[];
setMessages: (messages: Message[]) => void;
sendMessage: (message: string, agent: string) => void;
};
export default function Chat({ messages, setMessages, sendMessage }: ChatProps) {
const [open, setOpen] = React.useState(true);
const [message, setMessage] = React.useState<string>('');
const handleSend = (message:string) => {
setMessages([...messages, { sender: 'user', text: message }]);
sendMessage(message, "chat");
};
return (
<FireNav component="nav" disablePadding>
<ListItemButton
alignItems="flex-start"
onClick={() => setOpen(!open)}
sx={{
px: 3,
pt: 2.5,
pb: open ? 0 : 2.5,
'&:hover, &:focus': { '& #arrowdownicon': { opacity: open ? 1 : 0 } },
}}
>
<ListItemIcon sx={{ my: 0, opacity: 1, class: "menuicon" }}>
<LightbulbIcon />
</ListItemIcon>
<ListItemText
primary="Chat"
primaryTypographyProps={{
fontSize: 15,
fontWeight: 'medium',
lineHeight: '20px',
mb: '2px',
}}
secondary="What would you like the campaing to be about?"
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
lineHeight: '16px',
color: open ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)',
}}
sx={{ my: 0 }}
/>
<KeyboardArrowDown
id="arrowdownicon"
sx={{
mr: -1,
opacity: 0,
transform: open ? 'rotate(-180deg)' : 'rotate(0)',
transition: '0.2s',
}}
/>
</ListItemButton>
{open && (
<div style={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 50px)' }}>
<Container maxWidth={false} style={{ overflowY: 'auto', flex: '1 0 auto', maxHeight: 'calc(100vh - 150px)'}}>
<div style={{ margin: '0 auto', fontFamily: "sans-serif" }}>
{messages.map((message, index) => (
<div key={index} style={{
margin: '10px',
padding: '10px',
borderRadius: '10px',
backgroundColor: senderColors[message.sender as Sender] || '#d4e2d4',
alignSelf: message.sender === 'user' ? 'flex-end' : 'flex-start',
maxWidth: '80%',
wordWrap: 'break-word'
}}>
<strong>{message.sender}:</strong> {message.text}
</div>
))}
</div>
</Container>
<Container maxWidth={false} style={{ height: '150px' }}>
<Grid container spacing={1} alignItems="flex-end">
<Grid item xs={11}>
<TextField
value={message}
onChange={(e) => setMessage(e.target.value)}
fullWidth
inputProps={{ style: { height: 'auto' } }}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSend((e.target as HTMLInputElement).value);
setMessage('');
}
}}
/>
</Grid>
<Grid item xs={1}>
<Button style={{ height: '100%' }} onClick={() => {
handleSend(message);
setMessage('');
}}>
Send
</Button>
</Grid>
</Grid>
</Container>
</div>
)}
</FireNav>
);
}

View File

@ -0,0 +1,131 @@
"use client";
import * as React from 'react';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import { styled } from '@mui/material/styles';
import AppShortcut from '@mui/icons-material/AttachMoney';
import LoopIcon from '@mui/icons-material/Loop';
import { Card, CardContent, Typography } from '@mui/material';
import Image from 'next/image';
const FireNav = styled(List)<{ component?: React.ElementType }>({
'& .MuiListItemButton-root': {
paddingLeft: 24,
paddingRight: 24,
},
'& .MuiListItemIcon-root': {
minWidth: 0,
marginRight: 16,
},
'& .MuiSvgIcon-root': {
fontSize: 20,
},
});
type CommunityManagerProps = {
article: string;
open: boolean;
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
imgUrl: string;
};
export default function CommunityManager({ article, open, setOpen, imgUrl }: CommunityManagerProps) {
console.log(`[CommunityManager] Rendering. Url: '${imgUrl}'`);
return (
<Box sx={{ display: 'flex' }}>
<FireNav component="nav" disablePadding>
<Box
sx={{
//bgcolor: open ? 'rgba(71, 98, 130, 0.2)' : null,
//pb: open ? 2 : 0,
}}
>
<ListItemButton
alignItems="flex-start"
onClick={() => setOpen(!open)}
sx={{
px: 3,
pt: 2.5,
pb: open ? 0 : 2.5,
'&:hover, &:focus': { '& #arrowdownicon': { opacity: open ? 1 : 0 } },
}}
>
<ListItemIcon sx={{ my: 0, opacity: 1, class: "menuicon" }}>
<AppShortcut />
</ListItemIcon>
<ListItemText
primary="Social Media posts"
primaryTypographyProps={{
fontSize: 15,
fontWeight: 'medium',
lineHeight: '20px',
mb: '2px',
}}
secondary="Posts in social media"
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
lineHeight: '16px',
color: open ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)',
}}
sx={{ my: 0 }}
/>
<KeyboardArrowDown
id="arrowdownicon"
sx={{
mr: -1,
opacity: 0,
transform: open ? 'rotate(-180deg)' : 'rotate(0)',
transition: '0.2s',
}}
/>
</ListItemButton>
{open && (
article === '' || article === null ? (
<Box>
<LoopIcon
sx={{
animation: "spin 2s linear infinite",
"@keyframes spin": {
"0%": {
transform: "rotate(360deg)",
},
"100%": {
transform: "rotate(0deg)",
},
},
}}
/>
</Box>
) : (
<Card>
<CardContent>
<Typography variant="h5" component="div">
Social media posts on X
</Typography>
<p>{article}</p>
{imgUrl && (
<div style={{ width: '100%', height: '500px', position: 'relative' }}>
<Image
layout='fill'
objectFit='cover'
src={imgUrl}
alt="Graphic designer is working on an image ..."
/>
</div>
)}
</CardContent>
</Card>
)
)}
</Box>
</FireNav>
</Box>
);
}

View File

@ -0,0 +1,111 @@
"use client";
import * as React from 'react';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import Public from '@mui/icons-material/Public';
import HandshakeTwoToneIcon from '@mui/icons-material/HandshakeTwoTone';
import WorkspacePremiumTwoToneIcon from '@mui/icons-material/WorkspacePremiumTwoTone';
import GavelIcon from '@mui/icons-material/Gavel';
import { styled } from '@mui/material/styles';
import { green, pink } from '@mui/material/colors';
import AttachMoneyIcon from '@mui/icons-material/AttachMoney';
const data = [
{ icon: <HandshakeTwoToneIcon sx={{ color: green[500] }} />, label: 'IPA discount form 9-9 - eur 15k' },
{ icon: <GavelIcon sx={{ color: green[500] }} />, label: '2x1 brewery birthday - eur 250k' },
{ icon: <HandshakeTwoToneIcon sx={{ color: green[500] }} />, label: 'Summer day 1 - CHF 180k' },
{ icon: <HandshakeTwoToneIcon sx={{ color: pink[500] }} />, label: 'Worldcup promo 1.5M' },
];
const FireNav = styled(List)<{ component?: React.ElementType }>({
'& .MuiListItemButton-root': {
paddingLeft: 24,
paddingRight: 24,
},
'& .MuiListItemIcon-root': {
minWidth: 0,
marginRight: 16,
},
'& .MuiSvgIcon-root': {
fontSize: 20,
},
});
export default function CostList() {
const [open, setOpen] = React.useState(false);
console.log(`[Marketing] Rendering.`);
return (
<Box sx={{ display: 'flex' }}>
<FireNav component="nav" disablePadding>
<Box
sx={{
//bgcolor: open ? 'rgba(71, 98, 130, 0.2)' : null,
//pb: open ? 2 : 0,
}}
>
<ListItemButton
alignItems="flex-start"
onClick={() => setOpen(!open)}
sx={{
px: 3,
pt: 2.5,
pb: open ? 0 : 2.5,
'&:hover, &:focus': { '& #arrowdownicon': { opacity: open ? 1 : 0 } },
}}
>
<ListItemIcon sx={{ my: 0, opacity: 1, class: "menuicon"}}>
<AttachMoneyIcon/>
</ListItemIcon>
<ListItemText
primary="Economy of similar cases"
primaryTypographyProps={{
fontSize: 15,
fontWeight: 'medium',
lineHeight: '20px',
mb: '2px',
}}
secondary="Cost of similar cases in the past"
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
lineHeight: '16px',
color: open ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)',
}}
sx={{ my: 0 }}
/>
<KeyboardArrowDown
id="arrowdownicon"
sx={{
mr: -1,
opacity: 0,
transform: open ? 'rotate(-180deg)' : 'rotate(0)',
transition: '0.2s',
}}
/>
</ListItemButton>
{open &&
data.map((item) => (
<ListItemButton
key={item.label}
sx={{ py: 0, minHeight: 32, color: 'rgba(255,255,255,.8)' }}
>
<ListItemIcon sx={{ color: 'inherit' }}>
{item.icon}
</ListItemIcon>
<ListItemText
primary={item.label}
primaryTypographyProps={{ fontSize: 14, fontWeight: 'medium' }}
/>
</ListItemButton>
))}
</Box>
</FireNav>
</Box>
);
}

View File

@ -0,0 +1,170 @@
"use client";
import * as React from 'react';
import Box from '@mui/material/Box';
import List from '@mui/material/List';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemIcon from '@mui/material/ListItemIcon';
import ListItemText from '@mui/material/ListItemText';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import Public from '@mui/icons-material/Public';
import HandshakeTwoToneIcon from '@mui/icons-material/HandshakeTwoTone';
import WorkspacePremiumTwoToneIcon from '@mui/icons-material/WorkspacePremiumTwoTone';
import { styled } from '@mui/material/styles';
import FolderIcon from '@mui/icons-material/Folder';
import ExpandLess from '@mui/icons-material/ExpandLess';
import ExpandMore from '@mui/icons-material/ExpandMore';
import Collapse from '@mui/material/Collapse';
import FilePresentIcon from '@mui/icons-material/FilePresent';
const data = [
{ icon: <HandshakeTwoToneIcon />, label: 'Internal guidance for marketing campaigns' },
{ icon: <WorkspacePremiumTwoToneIcon />, label: 'Belgium law on marketing of alcohol' },
{ icon: <Public />, label: 'something else' },
];
const FireNav = styled(List)<{ component?: React.ElementType }>({
'& .MuiListItemButton-root': {
paddingLeft: 24,
paddingRight: 24,
},
'& .MuiListItemIcon-root': {
minWidth: 0,
marginRight: 16,
},
'& .MuiSvgIcon-root': {
fontSize: 20,
},
});
export default function RelevantDocumentList() {
const [open, setOpen] = React.useState(false);
console.log(`[Marketing] Rendering.`);
const [courtCasesOpen, setCourtCasesOpen] = React.useState(true);
const courtCasesOpenHandleClick = () => {
setCourtCasesOpen(!courtCasesOpen);
};
const [lawOpen, setLawOpen] = React.useState(true);
const LawOpenHandleClick = () => {
setLawOpen(!lawOpen);
};
return (
<Box sx={{ display: 'flex' }}>
<FireNav component="nav" disablePadding>
<Box
sx={{
bgcolor: open ? 'rgba(71, 98, 130, 0.2)' : null,
pb: open ? 2 : 0,
}}
>
<ListItemButton
alignItems="flex-start"
onClick={() => setOpen(!open)}
sx={{
px: 3,
pt: 2.5,
pb: open ? 0 : 2.5,
'&:hover, &:focus': { '& #arrowdownicon': { opacity: open ? 1 : 0 } },
}}
>
<ListItemIcon sx={{ my: 0, opacity: 1, class: "menuicon" }}>
<FilePresentIcon />
</ListItemIcon>
<ListItemText
primary="Relevant files"
primaryTypographyProps={{
fontSize: 15,
fontWeight: 'medium',
lineHeight: '20px',
mb: '2px',
}}
secondary="Files that might be relevant to your case."
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
lineHeight: '16px',
color: open ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)',
}}
sx={{ my: 0 }}
/>
<KeyboardArrowDown
id="arrowdownicon"
sx={{
mr: -1,
opacity: 0,
transform: open ? 'rotate(-180deg)' : 'rotate(0)',
transition: '0.2s',
}}
/>
</ListItemButton>
{open && (
<Box>
<List
sx={{ bgcolor: 'background.paper', textAlign: 'left' }}
component="nav"
aria-labelledby="nested-list-subheader"
>
{/* Court cases */}
<List>
<ListItemButton onClick={courtCasesOpenHandleClick}>
<ListItemIcon>
<FolderIcon />
</ListItemIcon>
<ListItemText primary="Internal docs" />
{courtCasesOpen ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={courtCasesOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItemButton>
<ListItemIcon sx={{ pl: 4 }}>
<img src="/static/icons/docs.png" height={20} width={20} />
</ListItemIcon>
<ListItemText primary="Marketing campaings general guidelines" />
</ListItemButton>
<ListItemButton>
<ListItemIcon sx={{ pl: 4 }}>
<img src="/static/icons/pdf.png" height={20} width={20} />
</ListItemIcon>
<ListItemText primary="Marketing regulations in Belgium" />
</ListItemButton>
</List>
</Collapse>
</List>
{/* Laws */}
<List>
<ListItemButton onClick={LawOpenHandleClick}>
<ListItemIcon>
<FolderIcon />
</ListItemIcon>
<ListItemText primary="Public " />
{lawOpen ? <ExpandLess /> : <ExpandMore />}
</ListItemButton>
<Collapse in={lawOpen} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItemButton>
<ListItemIcon sx={{ pl: 4 }}>
<img src="/static/icons/edge.png" height={20} width={20} />
</ListItemIcon>
<ListItemText primary="Worldwide discount" />
</ListItemButton>
<ListItemButton>
<ListItemIcon sx={{ pl: 4 }}>
<img src="/static/icons/edge.png" height={20} width={20} />
</ListItemIcon>
<ListItemText primary="Color week - T-Shitrs 2022" />
</ListItemButton>
</List>
</Collapse>
</List>
</List>
</Box>
)}
</Box>
</FireNav>
</Box>
);
}

View File

@ -0,0 +1,214 @@
"use client";
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import React, { useRef, useState } from 'react';
import { styled, ThemeProvider, createTheme } from '@mui/material/styles';
import Paper from '@mui/material/Paper';
import Stack from '@mui/material/Stack';
import Divider from '@mui/material/Divider';
import StakeholderList from './stakeholders/stakeholders';
import CostList from './costs/cost';
import RelevantDocumentList from './docs/docs';
import Chat from './chat/chat';
import CommunityManager from './community-manager/community-manager';
import { Container, Grid } from '@mui/material';
import { HubConnectionBuilder, HubConnection, LogLevel } from '@microsoft/signalr';
import { v4 as uuidv4 } from 'uuid';
type SignalRMessage = {
userId: string;
message: string;
agent: string;
};
export default function Marketing() {
const Item = styled(Paper)(({ theme }) => ({
backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
...theme.typography.body2,
padding: theme.spacing(0),
textAlign: 'center',
color: theme.palette.text.secondary,
}));
// Add this style
const Background = styled('div')({
backgroundImage: `url(/static/background1.webp)`,
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
height: '100vh',
});
const [userId, setUserId] = React.useState<string>(uuidv4());
const [connection, setConnection] = React.useState<HubConnection>();
const [messages, setMessages] = React.useState<{ sender: string; text: any; }[]>([]);
//Community manager state
const [ article, setArticle ] = useState<string>('');
const [ imgUrl, setImgUrl ] = useState<string>('');
const [ communityManagerOpen, setCommunityManagerOpen ] = useState<boolean>(false);
const createSignalRConnection = async (userId: string) => {
try {
console.log(`[MainPage] Reading environment variables [${process.env.NEXT_PUBLIC_BACKEND_URI}]`);
var uri = process.env.NEXT_PUBLIC_BACKEND_URI
? process.env.NEXT_PUBLIC_BACKEND_URI
: 'http://localhost:5244';
uri = new URL('articlehub', uri).href;
console.log(`[MainPage] Connecting to [${uri}]`);
// initi the connection
const connection = new HubConnectionBuilder()
.withUrl(uri, {withCredentials: false})
.configureLogging(LogLevel.Information)
.build();
//setup handler
connection.on('ReceiveMessage', (message: SignalRMessage) => {
console.log(`[MainPage][${message.userId}] Received message from ${message.agent}: ${message.message}`);
if (message.agent === 'Chat') {
const newMessage = { sender: 'Writer', text: message.message };
setMessages(prevMessages => [...prevMessages, newMessage]);
}
if (message.agent === 'Auditor') {
const newMessage = { sender: 'Auditor', text: message.message };
setMessages(prevMessages => [...prevMessages, newMessage]);
}
if (message.agent === 'CommunityManager') {
setArticle(message.message);
const newMessage = { sender: message.agent, text: 'Community Manager: ' + message.message };
setMessages(prevMessages => [...prevMessages, newMessage]);
}
if (message.agent === 'GraphicDesigner') {
setImgUrl(message.message);
const newMessage = { sender: message.agent, text: 'Graphic Designer: Check the image I created!'};
setMessages(prevMessages => [...prevMessages, newMessage]);
}
});
connection.onclose(async () => {
console.log(`[MainPage] Connection closed.`);
try {
await connection.start();
console.log(`Connection ID: ${connection.connectionId}`);
await connection.invoke('ConnectToAgent', userId);
console.log(`[MainPage] Connection re-established.`);
} catch (error) {
console.error(error);
}
});
await connection.start();
console.log(`Connection ID: ${connection.connectionId}`);
await connection.invoke('ConnectToAgent', userId);
setConnection(connection);
console.log(`[MainPage] Connection established.`);
} catch (error) {
console.error(error);
}
};
const setMessagesInUI = async (messages: { sender: string; text: any; }[]) => {
await setMessages(messages);
}
const sendMessage = async (message: string, agent: string) => {
if (connection) {
const frontEndMessage:SignalRMessage = {
userId: userId,
message: message,
agent: agent
};
console.log(`[MainPage][${{agent}}] Sending message: ${message}`);
await connection.invoke('ProcessMessage', frontEndMessage);
console.log(`[MainPage][${{agent}}] message sent`);
} else {
console.error(`[MainPage] Connection not established.`);
}
}
React.useEffect(() => {
createSignalRConnection(userId);
}, []);
const defaultTheme = createTheme({
typography: {
fontFamily: 'Helvetica, Arial, sans-serif',
},
});
const rightPannelTheme = createTheme({
typography: {
fontFamily: 'Helvetica, Arial, sans-serif',
},
components: {
MuiListItemButton: {
defaultProps: {
disableTouchRipple: true,
},
},
},
palette: {
mode: 'dark',
// primary: { main: 'rgb(102, 157, 246)' },
// background: { paper: 'rgb(5, 30, 52)' },
primary: { main: '#006BD6' },
background: { paper: 'grey' },
},
});
console.log(`[Marketing] Rendering.`);
return (
<ThemeProvider theme={defaultTheme}>
<Background>
<Container maxWidth="xl" disableGutters >
<Grid container spacing={3}>
<Grid item xs={6}>
<Paper elevation={0} style={{ border: '1px solid #000' }}>
<Chat messages={messages} setMessages={setMessagesInUI} sendMessage={sendMessage}/>
</Paper>
</Grid>
<Grid item xs={6}>
<Stack spacing={0}>
<ThemeProvider theme={rightPannelTheme}>
<Item>
<Paper elevation={0}>
<StakeholderList />
</Paper>
<Divider />
</Item>
<Item>
<Paper elevation={0}>
<CostList />
</Paper>
<Divider />
</Item>
<Item>
<Paper elevation={0}>
<RelevantDocumentList />
</Paper>
<Divider />
</Item>
<Item>
<Paper elevation={0}>
<CommunityManager article={article} open={communityManagerOpen} setOpen={setCommunityManagerOpen} imgUrl={imgUrl}/>
</Paper>
<Divider />
</Item>
</ThemeProvider>
</Stack>
</Grid>
</Grid>
</Container>
</Background>
</ThemeProvider>
);
}

View File

@ -0,0 +1,218 @@
"use client";
import React, { useRef, useState } from 'react';
import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';
import { styled } from '@mui/material/styles';
import { Avatar, Box, Container } from '@mui/material';
import Badge, { BadgeProps } from '@mui/material/Badge';
import { Typography } from '@mui/material';
import List from '@mui/material/List';
import ListItemAvatar from '@mui/material/ListItemAvatar';
import ListItemButton from '@mui/material/ListItemButton';
import ListItemText from '@mui/material/ListItemText';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import PersonIcon from '@mui/icons-material/Person';
import ListItemIcon from '@mui/material/ListItemIcon';
import { Title } from '@refinedev/mui';
const GreenStyledBadge = styled(Badge)(({ theme }) => ({
'& .MuiBadge-badge': {
backgroundColor: '#44b700',
color: '#44b700',
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
'&::after': {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: '50%',
animation: 'ripple 1.2s infinite ease-in-out',
border: '1px solid currentColor',
content: '""',
},
},
'@keyframes ripple': {
'0%': {
transform: 'scale(.8)',
opacity: 1,
},
'100%': {
transform: 'scale(2.4)',
opacity: 0,
},
},
}));
const RedStyledBadge = styled(Badge)(({ theme }) => ({
'& .MuiBadge-badge': {
backgroundColor: 'red',
color: '#44b700',
boxShadow: `0 0 0 2px ${theme.palette.background.paper}`,
'&::after': {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
borderRadius: '50%',
animation: 'ripple 1.2s infinite ease-in-out',
border: '1px solid currentColor',
content: '""',
},
},
'@keyframes ripple': {
'0%': {
transform: 'scale(.8)',
opacity: 1,
},
'100%': {
transform: 'scale(2.4)',
opacity: 0,
},
},
}));
export default function StakeholderList() {
const [open, setOpen] = React.useState(false);
console.log(`[Marketing] Rendering.`);
return (
<Box sx={{ display: 'flex' }}>
<List sx={{ width: '100%', maxWidth: 360, bgcolor: 'background.paper' }}>
<ListItemButton
alignItems="flex-start"
onClick={() => setOpen(!open)}
sx={{
px: 3,
pt: 2.5,
pb: open ? 0 : 2.5,
'&:hover, &:focus': { '& #arrowdownicon': { opacity: open ? 1 : 0 } },
}}
>
<ListItemIcon sx={{ my: 0, opacity: 1, class: "menuicon" }}>
<PersonIcon />
</ListItemIcon>
<ListItemText
primary="Auditor"
primaryTypographyProps={{
fontSize: 15,
fontWeight: 'medium',
lineHeight: '20px',
mb: '2px',
}}
secondary="Auditing rules"
secondaryTypographyProps={{
noWrap: true,
fontSize: 12,
lineHeight: '16px',
color: open ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0.5)',
}}
sx={{ my: 0 }}
/>
<KeyboardArrowDown
id="arrowdownicon"
sx={{
mr: -1,
opacity: 0,
transform: open ? 'rotate(-180deg)' : 'rotate(0)',
transition: '0.2s',
}}
/>
</ListItemButton>
{open && (
<Box>
<ListItemButton alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Language check" src="/static/check.png" />
</ListItemAvatar>
<ListItemText
primary="Language check"
/>
</ListItemButton>
<ListItemButton alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Financial check" src="/static/check.png" />
</ListItemAvatar>
<ListItemText
primary="Financial check"
/>
</ListItemButton>
<ListItemButton alignItems="flex-start">
<ListItemAvatar>
<Avatar alt="Auto approval" src="/static/check.png" />
</ListItemAvatar>
<ListItemText
primary="Auto approval"
/>
</ListItemButton>
</Box>
)}
{open && (
<p>Questions? These are relevant stakeholders for you:</p>
)}
{open && (
<Box>
<ListItemButton alignItems="flex-start">
<ListItemAvatar>
<RedStyledBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
>
<Avatar alt="Lawrence Law" src="/static/face2.jpg" />
</RedStyledBadge>
</ListItemAvatar>
<ListItemText
primary="Lina Maria"
secondary={
<React.Fragment>
<Typography
sx={{ display: 'inline' }}
component="span"
variant="body2"
color="text.primary"
>
General Attorney
</Typography>
</React.Fragment>
}
/>
</ListItemButton>
<ListItemButton alignItems="flex-start">
<ListItemAvatar>
<GreenStyledBadge
overlap="circular"
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
variant="dot"
>
<Avatar alt="Remy Sharp" src="/static/face.jpg" />
</GreenStyledBadge>
</ListItemAvatar>
<ListItemText
primary="Lawrence Gevaert"
secondary={
<React.Fragment>
<Typography
sx={{ display: 'inline' }}
component="span"
variant="body2"
color="text.primary"
>
Marketing Manager
</Typography>
</React.Fragment>
}
/>
</ListItemButton>
</Box>
)}
</List>
</Box>
);
}

View File

@ -0,0 +1,15 @@
"use client";
import { Authenticated } from "@refinedev/core";
import { ErrorComponent } from "@refinedev/mui";
import { Suspense } from "react";
export default function NotFound() {
return (
<Suspense>
<Authenticated key="not-found">
<ErrorComponent />
</Authenticated>
</Suspense>
);
}

View File

@ -0,0 +1,12 @@
"use client";
import { Suspense } from "react";
import { NavigateToResource } from "@refinedev/nextjs-router";
export default function IndexPage() {
return (
<Suspense>
<NavigateToResource />
</Suspense>
);
}

View File

@ -0,0 +1,16 @@
"use client";
import {
DevtoolsPanel,
DevtoolsProvider as DevtoolsProviderBase,
} from "@refinedev/devtools";
import React from "react";
export const DevtoolsProvider = (props: React.PropsWithChildren) => {
return (
<DevtoolsProviderBase>
{props.children}
<DevtoolsPanel />
</DevtoolsProviderBase>
);
};

View File

@ -0,0 +1,44 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
],
"paths": {
"@*": [
"./src/*"
],
"@pages/*": [
"./pages/*"
]
},
"incremental": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

View File

@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Extensions.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Logging;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Trace;
namespace Microsoft.Extensions.Hosting;
// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry.
// This project should be referenced by each service project in your solution.
// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults
public static class Extensions
{
public static IHostApplicationBuilder AddServiceDefaults(this IHostApplicationBuilder builder)
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
builder.Services.ConfigureHttpClientDefaults(http =>
{
// Turn on resilience by default
http.AddStandardResilienceHandler();
// Turn on service discovery by default
http.AddServiceDiscovery();
});
// Uncomment the following to restrict the allowed schemes for service discovery.
// builder.Services.Configure<ServiceDiscoveryOptions>(options =>
// {
// options.AllowedSchemes = ["https"];
// });
return builder;
}
public static IHostApplicationBuilder ConfigureOpenTelemetry(this IHostApplicationBuilder builder)
{
builder.Logging.AddOpenTelemetry(logging =>
{
logging.IncludeFormattedMessage = true;
logging.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("Microsoft.Orleans");
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
//.AddGrpcClientInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("Microsoft.Orleans.Application")
.AddSource("AutoGen.Agent");
});
builder.AddOpenTelemetryExporters();
return builder;
}
private static IHostApplicationBuilder AddOpenTelemetryExporters(this IHostApplicationBuilder builder)
{
var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
if (useOtlpExporter)
{
builder.Services.AddOpenTelemetry().UseOtlpExporter();
}
// Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package)
//if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]))
//{
// builder.Services.AddOpenTelemetry()
// .UseAzureMonitor();
//}
return builder;
}
public static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicationBuilder builder)
{
builder.Services.AddHealthChecks()
// Add a default liveness check to ensure app is responsive
.AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
return builder;
}
public static WebApplication MapDefaultEndpoints(this WebApplication app)
{
// Adding health checks endpoints to applications in non-development environments has security implications.
// See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
if (app.Environment.IsDevelopment())
{
// All health checks must pass for app to be considered ready to accept traffic after starting
app.MapHealthChecks("/health");
// Only health checks tagged with the "live" tag must pass for app to be considered alive
app.MapHealthChecks("/alive", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live")
});
}
return app;
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<PackageReference Include="Microsoft.Extensions.ServiceDiscovery" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Microsoft.SemanticKernel" />
<PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="..\Protos\messages.proto" Link="Protos\messages.proto" />
<Protobuf Include="..\Protos\states.proto" Link="Protos\states.proto" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj" />
<ProjectReference Include="..\Marketing.ServiceDefaults\Marketing.ServiceDefaults.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Consts.cs
namespace Marketing.Shared.Options;
public static class Consts
{
public const string OrleansNamespace = "default";
}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// OpenAIOptions.cs
using System.ComponentModel.DataAnnotations;
namespace Marketing.Shared.Options;
public class OpenAIOptions
{
// Embeddings
[Required]
public required string EmbeddingsEndpoint { get; set; }
[Required]
public required string EmbeddingsApiKey { get; set; }
[Required]
public required string EmbeddingsDeploymentOrModelId { get; set; }
// Chat
[Required]
public required string ChatEndpoint { get; set; }
[Required]
public required string ChatApiKey { get; set; }
[Required]
public required string ChatDeploymentOrModelId { get; set; }
// TextToImage
[Required]
public required string ImageEndpoint { get; set; }
[Required]
public required string ImageApiKey { get; set; }
// When using OpenAI, this is not required.
public required string ImageDeploymentOrModelId { get; set; }
}

View File

@ -0,0 +1,102 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SemanticKernelHostingExtensions.cs
using System.ClientModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Marketing.Shared.Options;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using Microsoft.SemanticKernel.Memory;
using OpenAI;
namespace Marketing.Shared;
public static class SemanticKernelHostingExtensions
{
public static IHostApplicationBuilder ConfigureSemanticKernel(this IHostApplicationBuilder builder)
{
builder.Services.AddTransient(CreateKernel);
builder.Services.AddTransient(CreateMemory);
builder.Services.Configure<OpenAIOptions>(o =>
{
o.EmbeddingsEndpoint = o.ImageEndpoint = o.ChatEndpoint = builder.Configuration["OpenAI:Endpoint"] ?? throw new InvalidOperationException("Ensure that OpenAI:Endpoint is set in configuration");
o.EmbeddingsApiKey = o.ImageApiKey = o.ChatApiKey = builder.Configuration["OpenAI:Key"]!;
o.EmbeddingsDeploymentOrModelId = "text-embedding-ada-002";
o.ImageDeploymentOrModelId = "dall-e-3";
o.ChatDeploymentOrModelId = "gpt-4o";
});
builder.Services.Configure<AzureOpenAIClientOptions>(o =>
{
o.NetworkTimeout = TimeSpan.FromMinutes(3);
});
builder.Services.Configure<JsonSerializerOptions>(options =>
{
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});
return builder;
}
public static ISemanticTextMemory CreateMemory(IServiceProvider provider)
{
var openAiConfig = provider.GetRequiredService<IOptions<OpenAIOptions>>().Value;
var loggerFactory = provider.GetRequiredService<ILoggerFactory>();
var memoryBuilder = new MemoryBuilder();
#pragma warning disable SKEXP0050 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
return memoryBuilder.WithLoggerFactory(loggerFactory)
.WithMemoryStore(new VolatileMemoryStore())
.WithOpenAITextEmbeddingGeneration(openAiConfig.EmbeddingsDeploymentOrModelId, openAiConfig.EmbeddingsEndpoint, openAiConfig.EmbeddingsApiKey)
.Build();
#pragma warning restore SKEXP0050 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
}
public static Kernel CreateKernel(IServiceProvider provider)
{
OpenAIOptions openAiConfig = provider.GetRequiredService<IOptions<OpenAIOptions>>().Value;
var builder = Kernel.CreateBuilder();
// Chat
if (openAiConfig.ChatEndpoint.Contains(".azure", StringComparison.OrdinalIgnoreCase))
{
var openAIClient = new AzureOpenAIClient(new Uri(openAiConfig.ChatEndpoint), new ApiKeyCredential(openAiConfig.ChatApiKey));
builder.Services.AddAzureOpenAIChatCompletion(openAiConfig.ChatDeploymentOrModelId, openAIClient);
}
else
{
var openAIClient = new OpenAIClient(openAiConfig.ChatApiKey);
builder.Services.AddOpenAIChatCompletion(openAiConfig.ChatDeploymentOrModelId, openAIClient);
}
// Text to Image
if (openAiConfig.ImageEndpoint.Contains(".azure", StringComparison.OrdinalIgnoreCase))
{
ArgumentException.ThrowIfNullOrEmpty(openAiConfig.ImageDeploymentOrModelId);
var openAIClient = new AzureOpenAIClient(new Uri(openAiConfig.ImageEndpoint), new ApiKeyCredential(openAiConfig.ImageApiKey));
builder.Services.AddAzureOpenAITextToImage(openAiConfig.ImageDeploymentOrModelId, openAIClient);
}
else
{
builder.Services.AddOpenAITextToImage(openAiConfig.ImageApiKey, modelId: openAiConfig.ImageDeploymentOrModelId);
}
// Embeddings
if (openAiConfig.EmbeddingsEndpoint.Contains(".azure", StringComparison.OrdinalIgnoreCase))
{
var openAIClient = new AzureOpenAIClient(new Uri(openAiConfig.EmbeddingsEndpoint), new ApiKeyCredential(openAiConfig.EmbeddingsApiKey));
builder.Services.AddAzureOpenAITextEmbeddingGeneration(openAiConfig.EmbeddingsDeploymentOrModelId, openAIClient);
}
else
{
var openAIClient = new OpenAIClient(openAiConfig.EmbeddingsApiKey);
builder.Services.AddOpenAITextEmbeddingGeneration(openAiConfig.EmbeddingsDeploymentOrModelId, openAIClient);
}
return builder.Build();
}
}

View File

@ -0,0 +1,35 @@
syntax = "proto3";
package marketing;
option csharp_namespace = "Marketing.Shared";
message UserChatInput {
string user_id = 1;
string user_message = 2;
}
message ArticleCreated {
string user_id = 1;
string article = 2;
string user_message = 3;
}
message UserConnected {
string user_id = 1;
}
message GraphicDesignCreated {
string user_id = 1;
string image_uri = 2;
}
message SocialMediaPostCreated {
string user_id = 1;
string social_media_post = 2;
}
message AuditText {
string user_id = 1;
string text = 2;
}
message AuditorAlert {
string user_id = 1;
string auditor_alert_message = 2;
}

View File

@ -0,0 +1,23 @@
syntax = "proto3";
package marketing;
option csharp_namespace = "Marketing.Shared";
message CommunityManagerState {
string written_social_media_post = 1;
string article = 2;
}
message WriterState {
string written_article = 1;
}
message GraphicDesignerState {
string image_url = 1;
}
message AuditorState {
string article = 1;
}

View File

@ -0,0 +1,27 @@
## How to run this
- Install [.NET SDK 8.0.302 ](https://dotnet.microsoft.com/en-us/download/dotnet)
- Install [Nodejs](https://nodejs.org/en/download/package-manager)
- Clone the repo (if you haven't already)
```bash
git clone https://github.com/microsoft/agnext.git
```
+ Switch to the branch
```bash
git checkout kostapetan/marketing-sample
```
+ Install node packages for the frontend
```bash
cd ./dotnet/samples/marketing-team/Marketing.Frontend
npm install
```
+ Add OpenAI Key and Endpoint
```bash
cd ..\Marketing.AppHost\
dotnet user-secrets set "OpenAI:Key" "your_key"
dotnet user-secrets set "OpenAI:Endpoint" "https://your_endpoint.openai.azure.com/"
```
+ Run the application
```bash
dotnet run
```

View File

@ -0,0 +1,8 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
name: marketing-team
services:
app:
language: dotnet
project: .\Marketing.AppHost\Marketing.AppHost.csproj
host: containerapp

View File

@ -6,7 +6,6 @@ using Google.Protobuf;
using Microsoft.AspNetCore.Builder;
using Microsoft.AutoGen.Runtime;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Microsoft.AutoGen.Agents;
@ -25,13 +24,13 @@ public static class AgentsApp
}
builder.AddAgentWorker()
.AddAgents(agentTypes);
builder.AddServiceDefaults();
// builder.AddServiceDefaults();
var app = builder.Build();
if (local)
{
app.MapAgentService();
}
app.MapDefaultEndpoints();
// app.MapDefaultEndpoints();
Host = app;
await app.StartAsync().ConfigureAwait(false);
return Host;

View File

@ -2,7 +2,6 @@
// Host.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;
namespace Microsoft.AutoGen.Runtime;
@ -11,7 +10,7 @@ public static class Host
public static async Task<WebApplication> StartAsync(bool local = false)
{
var builder = WebApplication.CreateBuilder();
builder.AddServiceDefaults();
//builder.AddServiceDefaults();
if (local)
{
builder.AddLocalAgentService();
@ -22,7 +21,7 @@ public static class Host
}
var app = builder.Build();
app.MapAgentService();
app.MapDefaultEndpoints();
// app.MapDefaultEndpoints();
await app.StartAsync().ConfigureAwait(false);
return app;
}

View File

@ -14,7 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\Abstractions\Microsoft.AutoGen.Abstractions.csproj" />
<ProjectReference Include="..\ServiceDefaults\Microsoft.AutoGen.ServiceDefaults.csproj" />
</ItemGroup>
<ItemGroup>
@ -28,7 +27,7 @@
<PackageReference Include="Microsoft.Orleans.Reminders.Cosmos" />
<PackageReference Include="Microsoft.Orleans.Streaming.EventHubs" />
<PackageReference Include="Microsoft.Orleans.Reminders" />
<PackageReference Include="OrleansDashboard"/>
<PackageReference Include="OrleansDashboard" />
</ItemGroup>