1
1
using System . Diagnostics ;
2
+ using System . Net . Http . Headers ;
2
3
using Microsoft . Extensions . Options ;
3
4
using Newtonsoft . Json ;
4
5
using OpenAI ;
5
6
using OpenAI . Chat ;
6
7
using Slack_GPT_Socket . Settings ;
7
8
using Slack_GPT_Socket . Utilities . LiteDB ;
9
+ using SlackNet . Blocks ;
10
+ using SlackNet . Events ;
8
11
9
12
namespace Slack_GPT_Socket . GptApi ;
10
13
@@ -15,8 +18,10 @@ public class GptClient
15
18
{
16
19
private readonly OpenAIClient _api ;
17
20
private readonly ILogger _log ;
21
+ private readonly IOptions < ApiSettings > _settings ;
18
22
private readonly GptDefaults _gptDefaults ;
19
23
private readonly GptClientResolver _resolver ;
24
+ private readonly IHttpClientFactory _httpClientFactory ;
20
25
21
26
/// <summary>
22
27
/// Initializes a new instance of the <see cref="GptClient" /> class.
@@ -25,37 +30,133 @@ public class GptClient
25
30
/// <param name="log">The logger instance.</param>
26
31
/// <param name="settings">The API settings.</param>
27
32
public GptClient (
28
- GptCustomCommands customCommands ,
33
+ GptCustomCommands customCommands ,
29
34
IUserCommandDb userCommandDb ,
30
- ILogger < GptClient > log ,
35
+ ILogger < GptClient > log ,
31
36
IOptions < GptDefaults > gptDefaults ,
32
- IOptions < ApiSettings > settings )
37
+ IOptions < ApiSettings > settings ,
38
+ IHttpClientFactory httpClientFactory )
33
39
{
34
- var httpClient = new HttpClient
35
- {
36
- Timeout = TimeSpan . FromMinutes ( 10 )
37
- } ;
40
+ _httpClientFactory = httpClientFactory ;
38
41
_api = new OpenAIClient ( settings . Value . OpenAIKey ) ;
39
42
_log = log ;
43
+ _settings = settings ;
40
44
_gptDefaults = gptDefaults . Value ;
41
45
_resolver = new GptClientResolver ( customCommands , _gptDefaults , userCommandDb ) ;
42
46
}
43
47
44
48
/// <summary>
45
49
/// Generates a response based on the given chat prompts.
46
50
/// </summary>
51
+ /// <param name="slackEvent">Input slack event</param>
47
52
/// <param name="chatPrompts">The list of chat prompts.</param>
48
53
/// <param name="userId">The user identifier.</param>
49
54
/// <returns>A task representing the asynchronous operation, with a result of the generated response.</returns>
50
- public async Task < GptResponse > GeneratePrompt ( List < WritableMessage > chatPrompts , string userId )
55
+ public async Task < GptResponse > GeneratePrompt ( MessageEventBase slackEvent , List < WritableMessage > chatPrompts ,
56
+ string userId )
51
57
{
52
58
// get the last prompt
53
59
var userPrompt = chatPrompts . Last ( chatPrompt => chatPrompt . Role == Role . User ) ;
54
60
var prompt = GptRequest . Default ( _gptDefaults ) ;
55
61
prompt . UserId = userId ;
56
62
prompt . Prompt = userPrompt . Content ;
57
63
58
- var chatRequest = _resolver . ParseRequest ( chatPrompts , prompt ) ;
64
+ // TODO: Refactor me!!!
65
+
66
+ var files = new List < ChatMessageContentPart > ( ) ;
67
+ foreach ( var file in slackEvent . Files )
68
+ {
69
+ var fileUrl = file . UrlPrivateDownload ?? file . UrlPrivate ;
70
+ if ( string . IsNullOrEmpty ( fileUrl ) )
71
+ {
72
+ return new GptResponse
73
+ {
74
+ Error = "Requested file to process with this request, but it doesn't have a download URL"
75
+ } ;
76
+ }
77
+
78
+ var httpClient = _httpClientFactory . CreateClient ( ) ;
79
+ // configure httpClient to allow images and other files
80
+ httpClient . DefaultRequestHeaders . Accept . Add ( new MediaTypeWithQualityHeaderValue ( file . Mimetype ) ) ;
81
+ httpClient . DefaultRequestHeaders . Authorization = new AuthenticationHeaderValue ( "Bearer" , _settings . Value . SlackBotToken ) ;
82
+ var fileRequest = await httpClient . GetAsync ( fileUrl ) ;
83
+ if ( ! fileRequest . IsSuccessStatusCode )
84
+ {
85
+ return new GptResponse
86
+ {
87
+ Error = "Requested file to process with this request, but it couldn't be downloaded successfully"
88
+ } ;
89
+ }
90
+ var fileContent = await fileRequest . Content . ReadAsStreamAsync ( ) ;
91
+ var headers = fileRequest . Content . Headers ;
92
+
93
+ // check if headers contain the mimetype
94
+ if ( ! headers . TryGetValues ( "Content-Type" , out var contentTypes ) )
95
+ {
96
+ return new GptResponse
97
+ {
98
+ Error = "Requested file to process with this request, but it doesn't have a mimetype"
99
+ } ;
100
+ }
101
+ var contentType = contentTypes . FirstOrDefault ( ) ;
102
+ if ( contentType == null )
103
+ {
104
+ return new GptResponse
105
+ {
106
+ Error = "Requested file to process with this request, but it doesn't have a mimetype"
107
+ } ;
108
+ }
109
+ // check if the mimetype is equal to the file mimetype
110
+ if ( contentType != file . Mimetype )
111
+ {
112
+ return new GptResponse
113
+ {
114
+ Error = "Requested file to process with this request, but the mimetype doesn't match the file mimetype " +
115
+ $ "expected { file . Mimetype } but got { contentType } "
116
+ } ;
117
+ }
118
+
119
+ using var memoryStream = new MemoryStream ( ) ;
120
+ await fileContent . CopyToAsync ( memoryStream ) ;
121
+ memoryStream . Position = 0 ;
122
+
123
+ var chatPart = ChatMessageContentPart . CreateImagePart (
124
+ await BinaryData . FromStreamAsync ( memoryStream ) , file . Mimetype ) ;
125
+ files . Add ( chatPart ) ;
126
+ }
127
+
128
+ // TODO: Refactor me!!!
129
+
130
+ if ( slackEvent . Blocks != null )
131
+ {
132
+ foreach ( var block in slackEvent . Blocks )
133
+ {
134
+ if ( block is not RichTextBlock rtb ) continue ;
135
+ foreach ( var element in rtb . Elements )
136
+ {
137
+ if ( element is not RichTextSection rts ) continue ;
138
+ foreach ( var innerElement in rts . Elements )
139
+ {
140
+ if ( innerElement is not RichTextLink rtl ) continue ;
141
+
142
+ var uri = new Uri ( rtl . Url ) ;
143
+ if ( uri . Scheme == "http" || uri . Scheme == "https" )
144
+ {
145
+ var httpClient = _httpClientFactory . CreateClient ( ) ;
146
+ var response = await httpClient . GetAsync ( uri ) ;
147
+ if ( response . IsSuccessStatusCode &&
148
+ response . Content . Headers . ContentType ! . MediaType ! . StartsWith ( "image" ) )
149
+ {
150
+ var chatPart = ChatMessageContentPart . CreateImagePart ( uri ) ;
151
+ files . Add ( chatPart ) ;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ var chatRequest = _resolver . ParseRequest ( chatPrompts , prompt , files ) ;
59
160
60
161
try
61
162
{
@@ -65,6 +166,8 @@ public async Task<GptResponse> GeneratePrompt(List<WritableMessage> chatPrompts,
65
166
var chatCompletion = result . Value ;
66
167
_log . LogInformation ( "GPT response: {Response}" , JsonConvert . SerializeObject ( chatCompletion ) ) ;
67
168
169
+
170
+
68
171
return new GptResponse
69
172
{
70
173
Message = chatCompletion . Content . Last ( ) . Text ,
0 commit comments