Skip to content

Commit 4cda799

Browse files
committed
Completed adding an initial OpenAI hat detection command
1 parent 314ab9d commit 4cda799

14 files changed

+148
-331
lines changed

ConsoleChatbot/ConsoleChatbot.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
2222
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.0" />
2323
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="9.0.0" />
24+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
2425
</ItemGroup>
2526

2627
</Project>

Fritz.Chatbot/Commands/AddHatCommand.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 113 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,150 @@
1-
using Fritz.StreamLib.Core;
1+
using Azure.AI.OpenAI;
2+
using Fritz.StreamLib.Core;
23
using Fritz.StreamTools.Hubs;
34
using Microsoft.AspNetCore.SignalR;
4-
using Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction;
5+
using Microsoft.Extensions.AI;
56
using Microsoft.Extensions.Configuration;
67
using System;
8+
using System.ClientModel;
9+
using System.Collections.Generic;
10+
using System.Diagnostics;
11+
using System.IO;
712
using System.Threading.Tasks;
813

914
namespace Fritz.Chatbot.Commands
1015
{
1116
public class PredictHatCommand : IBasicCommand
1217
{
1318
public string Trigger => "hat";
14-
public string Description => "Identify which hat Fritz is wearing";
19+
public string Description => "Describe which hat Fritz is wearing";
1520
public TimeSpan? Cooldown => TimeSpan.FromSeconds(30);
1621

17-
private string _CustomVisionKey = "";
18-
private string _AzureEndpoint = "";
19-
private string _TwitchChannel = "";
20-
private Guid _AzureProjectId;
22+
private DateTimeOffset? _LastRun = DateTimeOffset.MinValue;
23+
private TimeSpan _LastRunCooldown = TimeSpan.FromMinutes(5);
24+
private HatMetadata _LastHatDetected = null!;
2125

22-
internal static string IterationName = "";
23-
private ScreenshotTrainingService _TrainHat;
26+
private ScreenshotTrainingService _ObsConnection;
2427
//private readonly HatDescriptionRepository _Repository;
2528
private readonly IHubContext<ObsHub> _HubContext;
29+
private readonly string _AzureOpenAiEndpoint;
30+
private readonly string _AzureOpenAiKey;
31+
private readonly string _AzureOpenAiModel;
2632

2733
public PredictHatCommand(IConfiguration configuration, ScreenshotTrainingService service, IHubContext<ObsHub> hubContext)
2834
{
29-
_CustomVisionKey = configuration["AzureServices:HatDetection:Key"];
30-
_AzureEndpoint = configuration["AzureServices:HatDetection:CustomVisionEndpoint"];
31-
_TwitchChannel = configuration["StreamServices:Twitch:Channel"];
32-
_AzureProjectId = Guid.Parse(configuration["AzureServices:HatDetection:ProjectId"]);
33-
_TrainHat = service;
34-
//_Repository = repository;
35+
_AzureOpenAiEndpoint = configuration["AzureOpenAiEndpoint"];
36+
_AzureOpenAiKey = configuration["AzureOpenAiKey"];
37+
_AzureOpenAiModel = configuration["AzureOpenAiModel"];
38+
_ObsConnection = service;
3539
_HubContext = hubContext;
3640
}
3741

3842
public async Task Execute(IChatService chatService, string userName, ReadOnlyMemory<char> rhs)
3943
{
4044

41-
var client = new CustomVisionPredictionClient()
45+
if (!userName.Equals("csharpfritz", StringComparison.InvariantCultureIgnoreCase)
46+
&& _LastRun.HasValue && DateTimeOffset.Now - _LastRun < _LastRunCooldown)
4247
{
43-
ApiKey = _CustomVisionKey,
44-
Endpoint = _AzureEndpoint,
45-
};
48+
// send a message about the last hat detected
49+
await chatService.SendMessageAsync($"@{userName} from my last analysis {Math.Round(DateTimeOffset.Now.Subtract(_LastRun.Value).TotalMinutes)} minutes ago I can tell you Fritz's hat is:");
50+
await chatService.SendMessageAsync(_LastHatDetected.Description);
51+
return;
52+
}
4653

47-
await _HubContext.Clients.All.SendAsync("shutter");
48-
var obsImage = await _TrainHat.GetScreenshotFromObs();
54+
var obsImage = await _ObsConnection.GetScreenshotFromObs();
4955

5056
////////////////////////////
5157

5258

59+
HatMetadata hatDetected;
60+
61+
try
62+
{
63+
hatDetected = await PredictHat(obsImage);
64+
}
65+
catch
66+
{
67+
await chatService.SendMessageAsync("There was an error detecting this hat. Please try again in 30 seconds");
68+
return;
69+
}
70+
71+
if (string.IsNullOrEmpty(hatDetected.Name))
72+
{
73+
await chatService.SendMessageAsync("There was an error detecting a hat. Please try again in 30 seconds");
74+
return;
75+
}
76+
77+
// send a message about the hat detected
78+
await chatService.SendMessageAsync($"@{userName} I can tell you Fritz's hat is: {hatDetected.Name}");
79+
await chatService.SendMessageAsync(hatDetected.Description);
80+
await chatService.SendMessageAsync(hatDetected.Conclusion);
81+
_LastHatDetected = hatDetected;
82+
_LastRun = DateTimeOffset.Now;
83+
84+
}
85+
86+
private async Task<HatMetadata> PredictHat(Stream obsImage)
87+
{
88+
89+
var client = new AzureOpenAIClient(
90+
new Uri(_AzureOpenAiEndpoint),
91+
new ApiKeyCredential(_AzureOpenAiKey))
92+
.AsChatClient(_AzureOpenAiModel);
93+
94+
var systemPrompt =
95+
"""
96+
You are an AI assistant that can detect and describe baseball-style
97+
hats from an image. The user will provide an image of someone wearing a hat
98+
and they might be also wearing a headset. Ignore the headset and focus on the hat.
99+
100+
This hat comes from a collection that includes hats that fall into one of these categories:
101+
Sports Teams, Colleges, Marvel Comics, Microsoft logos, Star Wars,
102+
Video Games, and other popular culture references.
103+
104+
Be as descriptive as possible about the hat focusing on its design, colors,
105+
logo placement, text, category, organization that the hat references, and any notable features.
106+
107+
Limit the description to 3 sentences.
108+
109+
Suggest a name for the hat based on the color, organization, or logo.
110+
111+
If the hat references a sports team or college, you should conclude with that school's slogan or cheer.
112+
113+
If the hat references a Marvel comic character, you should conclude with a comment about the hat in the tone of the references character or organization
114+
""";
115+
116+
var file = new byte[obsImage.Length];
117+
obsImage.ReadExactly(file, 0, (int)obsImage.Length);
118+
var messages = new List<ChatMessage>
119+
{
120+
new ChatMessage(ChatRole.System, systemPrompt),
121+
new ChatMessage(ChatRole.User, new AIContent[] {
122+
new ImageContent(file, "image/webp"), //
123+
new TextContent("Generate a description of the hat"), // Generate a description of the hat
124+
})
125+
};
126+
127+
var sw = Stopwatch.StartNew();
128+
var response = await client.CompleteAsync<HatMetadata>(messages, options: new ChatOptions { Temperature = 0.1f });
129+
130+
Console.WriteLine($"Elapsed: {sw.Elapsed}");
131+
Console.WriteLine($"Predicted hat: {response.Result}");
132+
return response.Result;
133+
53134
}
54135

55136
}
56137

57-
internal record HatData(string Name, string Description);
138+
internal record HatMetadata(
139+
string Color,
140+
string Name,
141+
bool HasLogo,
142+
string? LogoShape,
143+
string Text,
144+
string Description,
145+
string Category,
146+
string LogoDescription,
147+
string Organization,
148+
string Conclusion);
58149

59150
}

Fritz.Chatbot/Commands/ResetHatAiCommand.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using Fritz.StreamLib.Core;
22
using System;
3-
using System.Collections.Generic;
4-
using System.Text;
53
using System.Threading.Tasks;
64

75
namespace Fritz.Chatbot.Commands
@@ -18,7 +16,7 @@ public async Task Execute(IChatService chatService, string userName, bool isMode
1816

1917
if (!(isModerator || isBroadcaster)) return;
2018

21-
PredictHatCommand.IterationName = string.Empty;
19+
//PredictHatCommand.IterationName = string.Empty;
2220
await chatService.SendMessageAsync("Reset the AI iteration and will detect the latest for Hat identification next time !hat is called");
2321

2422
}

Fritz.Chatbot/Commands/TrainHatCommand.cs

Lines changed: 0 additions & 45 deletions
This file was deleted.

Fritz.Chatbot/Fritz.Chatbot.csproj

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@
99
</ItemGroup>
1010

1111
<ItemGroup>
12+
<PackageReference Include="Azure.AI.OpenAI" Version="2.1.0" />
1213
<PackageReference Include="Markdig" Version="0.39.1" />
1314
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
1415
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
1516
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
16-
<PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.CustomVision.Prediction" Version="1.0.0" />
17-
<PackageReference Include="Microsoft.Azure.CognitiveServices.Vision.CustomVision.Training" Version="1.0.0" />
17+
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="9.0.1-preview.1.24570.5" />
1818
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.0" />
1919
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.0" />
2020
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.0" />
@@ -23,8 +23,12 @@
2323
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
2424
<PackageReference Include="Microsoft.Rest.ClientRuntime" Version="2.3.24" />
2525
<PackageReference Include="NetCoreAudio" Version="2.0.0" />
26+
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
2627
<PackageReference Include="System.Net.Http" Version="4.3.4" />
2728
<PackageReference Include="System.Text.Encodings.Web" Version="9.0.0" />
29+
<PackageReference Include="Microsoft.Extensions.AI" Version="9.0.1-preview.1.24570.5" />
30+
<PackageReference Include="Microsoft.Extensions.AI.AzureAIInference" Version="9.0.1-preview.1.24570.5" />
31+
<PackageReference Include="OpenAI" Version="2.1.0" />
2832
</ItemGroup>
2933

3034
<ItemGroup>

Fritz.Chatbot/ObsHub.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
using System.Diagnostics;
66
using System.IO;
77
using System.Text;
8-
using System.Threading.Channels;
98
using System.Threading.Tasks;
109

1110
namespace Fritz.StreamTools.Hubs
1211
{
1312

14-
public class ObsHub : Hub<ITakeScreenshots> {
13+
public class ObsHub : Hub<ITakeScreenshots>
14+
{
1515

1616
public static int ConnectedCount = 0;
1717
public static List<string> _ConnectionIds = new List<string>();
@@ -34,7 +34,8 @@ public override async Task OnDisconnectedAsync(Exception exception)
3434
await base.OnDisconnectedAsync(exception);
3535
}
3636

37-
public async Task PostScreenshot(IAsyncEnumerable<string> stream) {
37+
public async Task PostScreenshot(IAsyncEnumerable<string> stream)
38+
{
3839

3940
var sb = new StringBuilder();
4041
await foreach (var item in stream)
@@ -43,7 +44,7 @@ public async Task PostScreenshot(IAsyncEnumerable<string> stream) {
4344
}
4445

4546
Debug.WriteLine(sb.Length);
46-
var cleanString = sb.ToString().Replace("data:image/png;base64,", "");
47+
var cleanString = sb.ToString().Replace("data:image/webp;base64,", "");
4748

4849
var bytes = Convert.FromBase64String(cleanString);
4950

@@ -58,21 +59,24 @@ public interface IServerTakeScreenshot
5859
Task TakeScreenshot();
5960
}
6061

61-
public class ScreenshotReceivedEventArgs : EventArgs {
62+
public class ScreenshotReceivedEventArgs : EventArgs
63+
{
6264

6365
public Stream Screenshot { get; set; }
6466

6567
}
6668

67-
public class ScreenshotSink {
69+
public class ScreenshotSink
70+
{
6871

6972
public static readonly ScreenshotSink Instance = new ScreenshotSink();
7073

7174
private ScreenshotSink() { }
7275

7376
public event EventHandler<ScreenshotReceivedEventArgs> ScreenshotReceived;
7477

75-
public void OnScreenshotReceived(byte[] imageData) {
78+
public void OnScreenshotReceived(byte[] imageData)
79+
{
7680

7781
var args = new ScreenshotReceivedEventArgs() { Screenshot = new MemoryStream(imageData) };
7882
ScreenshotReceived?.Invoke(null, args);

0 commit comments

Comments
 (0)