// Start chatting loop public async Task StartChatAsync(string userId, Dictionary <string, object> payloads = null) { // Get cancellation token var token = GetChatToken(); // Get user var user = await UserStore.GetUserAsync(userId); if (user == null || string.IsNullOrEmpty(user.Id)) { Debug.LogError($"Error occured in getting user: {userId}"); await OnErrorAsync(null, null, token); return; } // Get context var context = await ContextStore.GetContextAsync(user.Id); if (context == null) { Debug.LogError($"Error occured in getting context: {user.Id}"); await OnErrorAsync(null, null, token); return; } // Prompt try { await OnPromptAsync(user, context, token); } catch (TaskCanceledException) { return; } catch (Exception ex) { Debug.LogError($"Error occured in speaking prompt: {ex.Message}\n{ex.StackTrace}"); // Restart idling animation and reset face expression _ = ModelController.StartIdlingAsync(); _ = ModelController.SetDefaultFace(); return; } // Chat loop. Exit when session ends, canceled or error occures while (true) { if (token.IsCancellationRequested) { return; } // Get request (microphone / camera / QR code, etc) var request = await RequestProviders[context.Topic.RequiredRequestType].GetRequestAsync(user, context, token); try { if (!request.IsSet()) { break; } if (token.IsCancellationRequested) { return; } // Extract intent var intentResponse = await IntentExtractor.ExtractIntentAsync(request, context, token); if (string.IsNullOrEmpty(request.Intent) && string.IsNullOrEmpty(context.Topic.Name)) { await OnNoIntentAsync(request, context, token); break; } else { Debug.Log($"Intent:{request.Intent}({request.IntentPriority.ToString()})"); if (request.Entities.Count > 0) { var entitiesString = "Entities:"; foreach (var kv in request.Entities) { var v = kv.Value != null?kv.Value.ToString() : "null"; entitiesString += $"\n - {kv.Key}: {v}"; } Debug.Log(entitiesString); } } if (token.IsCancellationRequested) { return; } // Start show response var intentResponseTask = IntentExtractor.ShowResponseAsync(intentResponse, request, context, token); if (token.IsCancellationRequested) { return; } // Get dialog to process intent / topic var dialogProcessor = DialogRouter.Route(request, context, token); if (token.IsCancellationRequested) { return; } // Process dialog var dialogResponse = await dialogProcessor.ProcessAsync(request, context, token); if (token.IsCancellationRequested) { return; } // Wait for intentTask before show response of dialog if (intentResponseTask != null) { await intentResponseTask; } if (token.IsCancellationRequested) { return; } // Show response of dialog await dialogProcessor.ShowResponseAsync(dialogResponse, request, context, token); if (token.IsCancellationRequested) { return; } // Post process if (context.Topic.ContinueTopic) { // Save user await UserStore.SaveUserAsync(user); // Save context await ContextStore.SaveContextAsync(context); // Update properties for next context.IsNew = false; context.Topic.IsNew = false; context.Topic.ContinueTopic = false; } else { // Clear context data and topic then exit context.Clear(); await ContextStore.SaveContextAsync(context); break; } } catch (TaskCanceledException) { await ContextStore.DeleteContextAsync(user.Id); return; } catch (Exception ex) { Debug.LogError($"Error occured in processing chat: {ex.Message}\n{ex.StackTrace}"); if (!token.IsCancellationRequested) { // Stop running animation and voice then get new token to say error token = GetChatToken(); await OnErrorAsync(request, context, token); await ContextStore.DeleteContextAsync(user.Id); } break; } finally { if (!token.IsCancellationRequested) { // Restart idling animation and reset face expression _ = ModelController.StartIdlingAsync(); _ = ModelController.SetDefaultFace(); } } } }
// Start chatting loop public async Task StartChatAsync(string userId, bool skipPrompt = false, Request preRequest = null, Dictionary <string, object> payloads = null) { // Get cancellation token var token = GetChatToken(); // Get user var user = await UserStore.GetUserAsync(userId); if (user == null || string.IsNullOrEmpty(user.Id)) { Debug.LogError($"Error occured in getting user: {userId}"); await OnErrorAsync(null, null, token); return; } // Get context var context = await ContextStore.GetContextAsync(user.Id); if (context == null) { Debug.LogError($"Error occured in getting context: {user.Id}"); await OnErrorAsync(null, null, token); return; } // Request Request request = null; try { IsChatting = true; // Prompt if (!skipPrompt) { await OnPromptAsync(preRequest, user, context, token); } // Chat loop. Exit when session ends, canceled or error occures while (true) { if (token.IsCancellationRequested) { return; } var requestProvider = preRequest == null || preRequest.Type == RequestType.None ? RequestProviders[context.Topic.RequiredRequestType] : RequestProviders[preRequest.Type]; // Get or update request (microphone / camera / QR code, etc) request = await requestProvider.GetRequestAsync(user, context, token, preRequest); if (!request.IsSet() || request.IsCanceled) { // Clear context when request is not set or canceled context.Clear(); await ContextStore.SaveContextAsync(context); return; } if (token.IsCancellationRequested) { return; } // Extract intent if (preRequest == null || string.IsNullOrEmpty(preRequest.Intent)) { await DialogRouter.ExtractIntentAsync(request, context, token); } if (string.IsNullOrEmpty(request.Intent) && string.IsNullOrEmpty(context.Topic.Name)) { // Just exit loop without clearing context when NoIntent await OnNoIntentAsync(request, context, token); return; } else { Debug.Log($"Intent:{request.Intent}({request.IntentPriority.ToString()})"); if (request.Entities.Count > 0) { var entitiesString = "Entities:"; foreach (var kv in request.Entities) { var v = kv.Value != null?kv.Value.ToString() : "null"; entitiesString += $"\n - {kv.Key}: {v}"; } Debug.Log(entitiesString); } } if (token.IsCancellationRequested) { return; } // Get dialog to process intent / topic var dialogProcessor = DialogRouter.Route(request, context, token); if (token.IsCancellationRequested) { return; } // PreProcess var preProcessResponse = await dialogProcessor.PreProcessAsync(request, context, token); // Start showing waiting animation var waitingAnimationTask = dialogProcessor.ShowWaitingAnimationAsync(preProcessResponse, request, context, token); // Process dialog var dialogResponse = await dialogProcessor.ProcessAsync(request, context, token); if (token.IsCancellationRequested) { return; } // Wait for waiting animation before show response of dialog // TODO: Enable to cancel waitingAnimation instead of await when ProcessAsync ends. await waitingAnimationTask; if (token.IsCancellationRequested) { return; } // Show response of dialog await dialogProcessor.ShowResponseAsync(dialogResponse, request, context, token); if (token.IsCancellationRequested) { return; } // Post process if (context.Topic.ContinueTopic) { // Clear pre-request preRequest = null; // Save user await UserStore.SaveUserAsync(user); // Save context await ContextStore.SaveContextAsync(context); // Update properties for next context.IsNew = false; context.Topic.IsNew = false; context.Topic.ContinueTopic = false; } else { // Clear context data and topic when topic doesn't continue context.Clear(); await ContextStore.SaveContextAsync(context); break; } } } catch (Exception ex) { await ContextStore.DeleteContextAsync(user.Id); if (!token.IsCancellationRequested) { IsError = true; Debug.LogError($"Error occured in processing chat: {ex.Message}\n{ex.StackTrace}"); // Stop running animation and voice then get new token to say error token = GetChatToken(); await OnErrorAsync(request, context, token); } } finally { IsError = false; IsChatting = false; if (!token.IsCancellationRequested) { // NOTE: Cancel is triggered not only when just canceled but when invoked another chat session // Restart idling animation and reset face expression _ = ModelController?.StartIdlingAsync(); _ = ModelController?.SetDefaultFace(); } else { // Clear context when canceled context.Clear(); await ContextStore.SaveContextAsync(context); } } }