7
7
using OpenAI . Threads ;
8
8
using System ;
9
9
using System . Collections . Generic ;
10
- using System . Diagnostics ;
11
10
using System . Linq ;
12
11
using System . Threading ;
13
12
using System . Threading . Tasks ;
@@ -104,12 +103,8 @@ private async void Awake()
104
103
{
105
104
Tool . GetOrCreateTool ( openAI . ImagesEndPoint , nameof ( ImagesEndpoint . GenerateImageAsync ) )
106
105
} ) ,
107
- destroyCancellationToken ) ;
108
-
109
- thread = await openAI . ThreadsEndpoint . CreateThreadAsync (
110
- new CreateThreadRequest ( assistant ) ,
111
- destroyCancellationToken ) ;
112
-
106
+ destroyCancellationToken ) ;
107
+ thread = await openAI . ThreadsEndpoint . CreateThreadAsync ( new ( assistant ) , destroyCancellationToken ) ;
113
108
inputField . onSubmit . AddListener ( SubmitChat ) ;
114
109
submitButton . onClick . AddListener ( SubmitChat ) ;
115
110
recordButton . onClick . AddListener ( ToggleRecording ) ;
@@ -123,6 +118,8 @@ private async void Awake()
123
118
{
124
119
switch ( e )
125
120
{
121
+ case TaskCanceledException :
122
+ case OperationCanceledException :
126
123
case ObjectDisposedException :
127
124
// ignored
128
125
break ;
@@ -158,7 +155,15 @@ private async void Awake()
158
155
}
159
156
catch ( Exception e )
160
157
{
161
- Debug . LogError ( e ) ;
158
+ switch ( e )
159
+ {
160
+ case TaskCanceledException :
161
+ case OperationCanceledException :
162
+ break ;
163
+ default :
164
+ Debug . LogError ( e ) ;
165
+ break ;
166
+ }
162
167
}
163
168
}
164
169
}
@@ -182,6 +187,7 @@ private async void SubmitChat()
182
187
inputField . ReleaseSelection ( ) ;
183
188
inputField . interactable = false ;
184
189
submitButton . interactable = false ;
190
+ recordButton . interactable = false ;
185
191
var userMessage = new Message ( inputField . text ) ;
186
192
var userMessageContent = AddNewTextMessageContent ( Role . User ) ;
187
193
userMessageContent . text = $ "User: { inputField . text } ";
@@ -192,6 +198,57 @@ private async void SubmitChat()
192
198
try
193
199
{
194
200
await thread . CreateMessageAsync ( userMessage , destroyCancellationToken ) ;
201
+
202
+ async Task StreamEventHandler ( IServerSentEvent @event )
203
+ {
204
+ try
205
+ {
206
+ switch ( @event )
207
+ {
208
+ case MessageResponse message :
209
+ switch ( message . Status )
210
+ {
211
+ case MessageStatus . InProgress :
212
+ if ( message . Role == Role . Assistant )
213
+ {
214
+ assistantMessageContent . text += message . PrintContent ( ) ;
215
+ scrollView . verticalNormalizedPosition = 0f ;
216
+ }
217
+ break ;
218
+ case MessageStatus . Completed :
219
+ if ( message . Role == Role . Assistant )
220
+ {
221
+ await GenerateSpeechAsync ( message . PrintContent ( ) , destroyCancellationToken ) ;
222
+ scrollView . verticalNormalizedPosition = 0f ;
223
+ }
224
+ break ;
225
+ }
226
+ break ;
227
+ case RunResponse run :
228
+ if ( run . Status == RunStatus . RequiresAction )
229
+ {
230
+ await ProcessToolCalls ( run ) ;
231
+ }
232
+
233
+ break ;
234
+ case Error errorResponse :
235
+ throw errorResponse . Exception ?? new Exception ( errorResponse . Message ) ;
236
+ }
237
+ }
238
+ catch ( Exception e )
239
+ {
240
+ switch ( e )
241
+ {
242
+ case TaskCanceledException :
243
+ case OperationCanceledException :
244
+ break ;
245
+ default :
246
+ Debug . LogError ( e ) ;
247
+ break ;
248
+ }
249
+ }
250
+ }
251
+
195
252
var run = await thread . CreateRunAsync ( assistant , StreamEventHandler , destroyCancellationToken ) ;
196
253
await run . WaitForStatusChangeAsync ( timeout : 60 , cancellationToken : destroyCancellationToken ) ;
197
254
}
@@ -212,55 +269,14 @@ private async void SubmitChat()
212
269
if ( destroyCancellationToken is { IsCancellationRequested : false } )
213
270
{
214
271
inputField . interactable = true ;
215
- EventSystem . current . SetSelectedGameObject ( inputField . gameObject ) ;
216
272
submitButton . interactable = true ;
273
+ recordButton . interactable = true ;
274
+ EventSystem . current . SetSelectedGameObject ( inputField . gameObject ) ;
217
275
}
218
276
219
277
isChatPending = false ;
220
278
}
221
279
222
- async Task StreamEventHandler ( IServerSentEvent @event )
223
- {
224
- try
225
- {
226
- switch ( @event )
227
- {
228
- case MessageResponse message :
229
- switch ( message . Status )
230
- {
231
- case MessageStatus . InProgress :
232
- if ( message . Role == Role . Assistant )
233
- {
234
- assistantMessageContent . text += message . PrintContent ( ) ;
235
- scrollView . verticalNormalizedPosition = 0f ;
236
- }
237
- break ;
238
- case MessageStatus . Completed :
239
- if ( message . Role == Role . Assistant )
240
- {
241
- await GenerateSpeechAsync ( message . PrintContent ( ) , destroyCancellationToken ) ;
242
- }
243
- break ;
244
- }
245
- break ;
246
- case RunResponse run :
247
- switch ( run . Status )
248
- {
249
- case RunStatus . RequiresAction :
250
- await ProcessToolCalls ( run ) ;
251
- break ;
252
- }
253
- break ;
254
- case Error errorResponse :
255
- throw errorResponse . Exception ?? new Exception ( errorResponse . Message ) ;
256
- }
257
- }
258
- catch ( Exception e )
259
- {
260
- Debug . LogError ( e ) ;
261
- }
262
- }
263
-
264
280
async Task ProcessToolCalls ( RunResponse run )
265
281
{
266
282
Debug . Log ( nameof ( ProcessToolCalls ) ) ;
@@ -315,20 +331,17 @@ private async Task GenerateSpeechAsync(string text, CancellationToken cancellati
315
331
#pragma warning disable CS0612 // Type or member is obsolete
316
332
var request = new SpeechRequest ( text , Model . TTS_1 , voice , SpeechResponseFormat . PCM ) ;
317
333
#pragma warning restore CS0612 // Type or member is obsolete
318
- var stopwatch = Stopwatch . StartNew ( ) ;
319
334
var speechClip = await openAI . AudioEndpoint . GetSpeechAsync ( request , partialClip =>
320
335
{
321
336
streamAudioSource . BufferCallback ( partialClip . AudioSamples ) ;
322
337
} , cancellationToken ) ;
323
- var playbackTime = speechClip . AudioClip . length - ( float ) stopwatch . Elapsed . TotalSeconds + 0.1f ;
338
+ await Awaiters . DelayAsync ( TimeSpan . FromSeconds ( speechClip . Length ) , cancellationToken ) . ConfigureAwait ( true ) ;
339
+ ( ( AudioSource ) streamAudioSource ) . clip = speechClip . AudioClip ;
324
340
325
341
if ( enableDebug )
326
342
{
327
343
Debug . Log ( speechClip . CachePath ) ;
328
344
}
329
-
330
- await Awaiters . DelayAsync ( TimeSpan . FromSeconds ( playbackTime ) , cancellationToken ) . ConfigureAwait ( true ) ;
331
- ( ( AudioSource ) streamAudioSource ) . clip = speechClip . AudioClip ;
332
345
}
333
346
finally
334
347
{
@@ -391,7 +404,9 @@ private async void ProcessRecording(Tuple<string, AudioClip> recording)
391
404
392
405
try
393
406
{
407
+ inputField . interactable = false ;
394
408
recordButton . interactable = false ;
409
+ submitButton . interactable = false ;
395
410
var request = new AudioTranscriptionRequest ( clip , temperature : 0.1f , language : "en" ) ;
396
411
var userInput = await openAI . AudioEndpoint . CreateTranscriptionTextAsync ( request , destroyCancellationToken ) ;
397
412
@@ -405,12 +420,22 @@ private async void ProcessRecording(Tuple<string, AudioClip> recording)
405
420
}
406
421
catch ( Exception e )
407
422
{
408
- Debug . LogError ( e ) ;
409
- inputField . interactable = true ;
410
- }
411
- finally
412
- {
413
- recordButton . interactable = true ;
423
+ if ( ! isChatPending )
424
+ {
425
+ inputField . interactable = true ;
426
+ recordButton . interactable = true ;
427
+ submitButton . interactable = true ;
428
+ }
429
+
430
+ switch ( e )
431
+ {
432
+ case TaskCanceledException :
433
+ case OperationCanceledException :
434
+ break ;
435
+ default :
436
+ Debug . LogError ( e ) ;
437
+ break ;
438
+ }
414
439
}
415
440
}
416
441
}
0 commit comments