Initial Commit
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
[Serializable]
|
||||
public class ConfidenceRange
|
||||
{
|
||||
public float minConfidence;
|
||||
public float maxConfidence;
|
||||
public UnityEvent onWithinConfidenceRange = new UnityEvent();
|
||||
public UnityEvent onOutsideConfidenceRange = new UnityEvent();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a9a869d94ca2214fb1f2f5c3aeef455
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using Meta.WitAi.Attributes;
|
||||
using Meta.WitAi.Json;
|
||||
using Meta.WitAi.Utilities;
|
||||
using UnityEngine;
|
||||
using Utilities;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers an event when no intents were recognized in an utterance.
|
||||
/// </summary>
|
||||
[AddComponentMenu("Wit.ai/Response Matchers/Out Of Domain")]
|
||||
public class OutOfScopeUtteranceHandler : WitResponseHandler
|
||||
{
|
||||
[Tooltip("If set to a value greater than zero, any intent that returns with a confidence lower than this value will be treated as out of domain/scope.")]
|
||||
[Range(0, 1f)]
|
||||
[SerializeField] private float confidenceThreshold = 0.0f;
|
||||
[Space(WitRuntimeStyles.HeaderPaddingTop)]
|
||||
[TooltipBox("Triggered when a activation on the associated AppVoiceExperience does not return any intents.")]
|
||||
[SerializeField] private StringEvent onOutOfDomain = new StringEvent();
|
||||
|
||||
protected override string OnValidateResponse(WitResponseNode response, bool isEarlyResponse)
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
return "Response is null";
|
||||
}
|
||||
if (response["intents"].Count > 0)
|
||||
{
|
||||
if (response.GetFirstIntent()["confidence"].AsFloat < confidenceThreshold)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return "Intents found";
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
protected override void OnResponseInvalid(WitResponseNode response, string error) {}
|
||||
protected override void OnResponseSuccess(WitResponseNode response)
|
||||
{
|
||||
onOutOfDomain?.Invoke(response.GetTranscription());
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 960c102c70216114fa5c3800d4172d67
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Json;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
[AddComponentMenu("Wit.ai/Response Matchers/Simple Intent Handler")]
|
||||
public class SimpleIntentHandler : WitIntentMatcher
|
||||
{
|
||||
[SerializeField] private UnityEvent onIntentTriggered = new UnityEvent();
|
||||
|
||||
[Tooltip("Confidence ranges are executed in order. If checked, all confidence values will be checked instead of stopping on the first one that matches.")]
|
||||
[SerializeField] public bool allowConfidenceOverlap;
|
||||
#if UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5
|
||||
[NonReorderable]
|
||||
#endif
|
||||
[SerializeField] public ConfidenceRange[] confidenceRanges;
|
||||
|
||||
public UnityEvent OnIntentTriggered => onIntentTriggered;
|
||||
|
||||
protected override void OnResponseSuccess(WitResponseNode response)
|
||||
{
|
||||
onIntentTriggered.Invoke();
|
||||
UpdateRanges(response);
|
||||
}
|
||||
protected override void OnResponseInvalid(WitResponseNode response, string error)
|
||||
{
|
||||
UpdateRanges(response);
|
||||
}
|
||||
|
||||
private void UpdateRanges(WitResponseNode response)
|
||||
{
|
||||
// Find intents if possible
|
||||
var intents = response?.GetIntents();
|
||||
if (intents == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate intents
|
||||
foreach (var intentData in intents)
|
||||
{
|
||||
if (string.Equals(intent, intentData.name, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
// Found intent
|
||||
RefreshConfidenceRange(intentData.confidence, confidenceRanges, allowConfidenceOverlap);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not matched
|
||||
RefreshConfidenceRange(0, confidenceRanges, allowConfidenceOverlap);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d2d3ff93ff48bd40ab5bca3cf4e6d2c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Json;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
[AddComponentMenu("Wit.ai/Response Matchers/Simple String Entity Handler")]
|
||||
public class SimpleStringEntityHandler : WitIntentMatcher
|
||||
{
|
||||
[SerializeField] public string entity;
|
||||
[SerializeField] public string format;
|
||||
[SerializeField] private StringEntityMatchEvent onIntentEntityTriggered
|
||||
= new StringEntityMatchEvent();
|
||||
|
||||
public StringEntityMatchEvent OnIntentEntityTriggered => onIntentEntityTriggered;
|
||||
|
||||
protected override string OnValidateResponse(WitResponseNode response, bool isEarlyResponse)
|
||||
{
|
||||
// Return base
|
||||
string result = base.OnValidateResponse(response, isEarlyResponse);
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
// Not found
|
||||
var entityValue = response.GetFirstEntityValue(entity);
|
||||
if (string.IsNullOrEmpty(entityValue))
|
||||
{
|
||||
return $"Missing required entity: {entity}";
|
||||
}
|
||||
// Fail
|
||||
return string.Empty;
|
||||
}
|
||||
protected override void OnResponseInvalid(WitResponseNode response, string error) { }
|
||||
protected override void OnResponseSuccess(WitResponseNode response)
|
||||
{
|
||||
var entityValue = response.GetFirstEntityValue(entity);
|
||||
if (!string.IsNullOrEmpty(format))
|
||||
{
|
||||
onIntentEntityTriggered.Invoke(format.Replace("{value}", entityValue));
|
||||
}
|
||||
else
|
||||
{
|
||||
onIntentEntityTriggered.Invoke(entityValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class StringEntityMatchEvent : UnityEvent<string> {}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77dceeee73817ec4c844564714e05c77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.Conduit;
|
||||
using Meta.WitAi.Json;
|
||||
using Meta.WitAi.Data.Intents;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
// Abstract class to share confidence handling
|
||||
public abstract class WitIntentMatcher : WitResponseHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Intent name to be matched
|
||||
/// </summary>
|
||||
[Header("Intent Settings")]
|
||||
[SerializeField] public string intent;
|
||||
|
||||
/// <summary>
|
||||
/// Confidence threshold
|
||||
/// </summary>
|
||||
[FormerlySerializedAs("confidence")]
|
||||
[Range(0, 1f), SerializeField] public float confidenceThreshold = .6f;
|
||||
|
||||
// Handle simple intent validation
|
||||
protected override string OnValidateResponse(WitResponseNode response, bool isEarlyResponse)
|
||||
{
|
||||
// No response
|
||||
if (response == null)
|
||||
{
|
||||
return "No response";
|
||||
}
|
||||
// Check against all intents
|
||||
WitIntentData[] intents = response.GetIntents();
|
||||
if (intents == null || intents.Length == 0)
|
||||
{
|
||||
return "No intents found";
|
||||
}
|
||||
// Find intent
|
||||
WitIntentData found = null;
|
||||
foreach (var intentData in intents)
|
||||
{
|
||||
if (string.Equals(intent, intentData.name, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
found = intentData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found == null)
|
||||
{
|
||||
return $"Missing required intent '{intent}'";
|
||||
}
|
||||
// Check confidence
|
||||
if (found.confidence < confidenceThreshold)
|
||||
{
|
||||
return $"Required intent '{intent}' confidence too low: {found.confidence:0.000}\nRequired: {confidenceThreshold:0.000}";
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
Manifest.WitResponseMatcherIntents.Add(intent);
|
||||
base.OnEnable();
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
Manifest.WitResponseMatcherIntents.Remove(intent);
|
||||
base.OnDisable();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9dca9f31f9df1bd4d9cda88e81bf3082
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Meta.WitAi.Data;
|
||||
using Meta.WitAi.Json;
|
||||
using Meta.WitAi.Requests;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
[Serializable]
|
||||
public class WitResponseEvent : UnityEvent<WitResponseNode> {}
|
||||
[Serializable]
|
||||
public class WitResponseErrorEvent : UnityEvent<WitResponseNode, string> {}
|
||||
|
||||
public abstract class WitResponseHandler : MonoBehaviour
|
||||
{
|
||||
[FormerlySerializedAs("wit")]
|
||||
[SerializeField] public VoiceService Voice;
|
||||
[SerializeField] public bool ValidateEarly = false;
|
||||
|
||||
// Whether validated early
|
||||
private bool _validated = false;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (!Voice) Voice = FindObjectOfType<VoiceService>();
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (!Voice) Voice = FindObjectOfType<VoiceService>();
|
||||
if (!Voice)
|
||||
{
|
||||
VLog.E($"VoiceService not found in scene.\nDisabling {GetType().Name} on {gameObject.name}");
|
||||
enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Voice.VoiceEvents.OnSend.AddListener(OnRequestSend);
|
||||
Voice.VoiceEvents.OnValidatePartialResponse.AddListener(HandleValidateEarlyResponse);
|
||||
Voice.VoiceEvents.OnResponse.AddListener(HandleFinalResponse);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
if (Voice)
|
||||
{
|
||||
Voice.VoiceEvents.OnSend.RemoveListener(OnRequestSend);
|
||||
Voice.VoiceEvents.OnValidatePartialResponse.RemoveListener(HandleValidateEarlyResponse);
|
||||
Voice.VoiceEvents.OnResponse.RemoveListener(HandleFinalResponse);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnRequestSend(VoiceServiceRequest request)
|
||||
{
|
||||
_validated = false;
|
||||
}
|
||||
|
||||
protected virtual void HandleValidateEarlyResponse(VoiceSession session)
|
||||
{
|
||||
if (ValidateEarly && !_validated)
|
||||
{
|
||||
string invalidReason = OnValidateResponse(session.response, true);
|
||||
if (string.IsNullOrEmpty(invalidReason))
|
||||
{
|
||||
_validated = true;
|
||||
OnResponseSuccess(session.response);
|
||||
session.validResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected virtual void HandleFinalResponse(WitResponseNode response)
|
||||
{
|
||||
// Not yet handled
|
||||
if (!_validated)
|
||||
{
|
||||
string invalidReason = OnValidateResponse(response, false);
|
||||
if (!string.IsNullOrEmpty(invalidReason))
|
||||
{
|
||||
OnResponseInvalid(response, invalidReason);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnResponseSuccess(response);
|
||||
}
|
||||
_validated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle validation
|
||||
protected abstract string OnValidateResponse(WitResponseNode response, bool isEarlyResponse);
|
||||
// Handle invalid
|
||||
protected abstract void OnResponseInvalid(WitResponseNode response, string error);
|
||||
// Handle valid
|
||||
protected abstract void OnResponseSuccess(WitResponseNode response);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// For tests
|
||||
public void HandleResponse(WitResponseNode response) => HandleFinalResponse(response);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Refresh confidence range
|
||||
/// </summary>
|
||||
/// <param name="confidence"></param>
|
||||
/// <param name="confidenceRanges"></param>
|
||||
/// <param name="allowConfidenceOverlap"></param>
|
||||
/// <returns></returns>
|
||||
public static bool RefreshConfidenceRange(float confidence, ConfidenceRange[] confidenceRanges, bool allowConfidenceOverlap)
|
||||
{
|
||||
// Whether found
|
||||
bool foundIn = false;
|
||||
bool foundOut = false;
|
||||
|
||||
// Iterate confidences
|
||||
for (int i = 0; null != confidenceRanges && i < confidenceRanges.Length; i++)
|
||||
{
|
||||
var range = confidenceRanges[i];
|
||||
|
||||
// Within Range
|
||||
if (confidence >= range.minConfidence &&
|
||||
confidence <= range.maxConfidence)
|
||||
{
|
||||
// Ignore overlap
|
||||
if (!allowConfidenceOverlap && foundIn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Within delegate
|
||||
range.onWithinConfidenceRange?.Invoke();
|
||||
foundIn = true;
|
||||
}
|
||||
// Out of Range
|
||||
else
|
||||
{
|
||||
// Ignore overlap
|
||||
if (!allowConfidenceOverlap && foundOut)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Outside delegate
|
||||
range.onOutsideConfidenceRange?.Invoke();
|
||||
foundOut = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return if found within
|
||||
return foundIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 097381abbd7a3364f80ac4460551a2cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Meta.WitAi.Attributes;
|
||||
using Meta.WitAi.Data;
|
||||
using Meta.WitAi.Json;
|
||||
using Meta.WitAi.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
[AddComponentMenu("Wit.ai/Response Matchers/Response Matcher")]
|
||||
public class WitResponseMatcher : WitIntentMatcher
|
||||
{
|
||||
[FormerlySerializedAs("valuePaths")]
|
||||
[Header("Value Matching")]
|
||||
#if UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5
|
||||
[NonReorderable]
|
||||
#endif
|
||||
[SerializeField] public ValuePathMatcher[] valueMatchers;
|
||||
|
||||
[Header("Output")]
|
||||
#if UNITY_2021_3_2 || UNITY_2021_3_3 || UNITY_2021_3_4 || UNITY_2021_3_5
|
||||
[NonReorderable]
|
||||
#endif
|
||||
[SerializeField] private FormattedValueEvents[] formattedValueEvents;
|
||||
[SerializeField] private MultiValueEvent onMultiValueEvent = new MultiValueEvent();
|
||||
|
||||
[TooltipBox("Triggered if the matching conditions did not match. The parameter will be the transcription that was received. This will only trigger if there were values for intents or entities, but those values didn't match this matcher.")]
|
||||
[SerializeField] private StringEvent onDidNotMatch = new StringEvent();
|
||||
|
||||
[TooltipBox("Triggered if a request was checked and no intents were found. This will still trigger if entities match and only applies to intents. The parameter will be the transcription.")]
|
||||
[SerializeField] private StringEvent onOutOfDomain = new StringEvent();
|
||||
|
||||
private static Regex valueRegex = new Regex(Regex.Escape("{value}"), RegexOptions.Compiled);
|
||||
|
||||
// Handle validation
|
||||
protected override string OnValidateResponse(WitResponseNode response, bool isEarlyResponse)
|
||||
{
|
||||
// Return base
|
||||
string result = base.OnValidateResponse(response, isEarlyResponse);
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
// Only check value matches on early
|
||||
if (isEarlyResponse && !ValueMatches(response))
|
||||
{
|
||||
return "No value matches";
|
||||
}
|
||||
// Success
|
||||
return string.Empty;
|
||||
}
|
||||
// Ignore for mismatched intent
|
||||
protected override void OnResponseInvalid(WitResponseNode response, string error)
|
||||
{
|
||||
if (response.GetIntents().Length > 0 || response.EntityCount() > 0)
|
||||
{
|
||||
onDidNotMatch?.Invoke(response.GetTranscription());
|
||||
}
|
||||
|
||||
if (response.GetIntents().Length == 0)
|
||||
{
|
||||
onOutOfDomain?.Invoke(response.GetTranscription());
|
||||
}
|
||||
}
|
||||
// Handle valid callback
|
||||
protected override void OnResponseSuccess(WitResponseNode response)
|
||||
{
|
||||
// Check value matches
|
||||
if (ValueMatches(response))
|
||||
{
|
||||
for (int j = 0; j < formattedValueEvents.Length; j++)
|
||||
{
|
||||
var formatEvent = formattedValueEvents[j];
|
||||
var result = formatEvent.format;
|
||||
for (int i = 0; i < valueMatchers.Length; i++)
|
||||
{
|
||||
var reference = valueMatchers[i].Reference;
|
||||
var value = reference.GetStringValue(response);
|
||||
if (!string.IsNullOrEmpty(formatEvent.format))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
result = valueRegex.Replace(result, value, 1);
|
||||
result = result.Replace("{" + i + "}", value);
|
||||
}
|
||||
else if (result.Contains("{" + i + "}"))
|
||||
{
|
||||
result = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
{
|
||||
formatEvent.onFormattedValueEvent?.Invoke(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
onDidNotMatch?.Invoke(response.GetTranscription());
|
||||
}
|
||||
|
||||
// Get all values & perform multi value event
|
||||
List<string> values = new List<string>();
|
||||
foreach (var matcher in valueMatchers)
|
||||
{
|
||||
// Add value
|
||||
var value = matcher.Reference.GetStringValue(response);
|
||||
values.Add(value);
|
||||
|
||||
// Refresh confidence
|
||||
if (matcher.ConfidenceReference != null)
|
||||
{
|
||||
float confidenceValue = ValueMatches(response, matcher)
|
||||
? matcher.ConfidenceReference.GetFloatValue(response)
|
||||
: 0f;
|
||||
RefreshConfidenceRange(confidenceValue, matcher.confidenceRanges, matcher.allowConfidenceOverlap);
|
||||
}
|
||||
}
|
||||
onMultiValueEvent.Invoke(values.ToArray());
|
||||
}
|
||||
|
||||
private bool ValueMatches(WitResponseNode response)
|
||||
{
|
||||
bool matches = true;
|
||||
for (int i = 0; i < valueMatchers.Length && matches; i++)
|
||||
{
|
||||
matches &= ValueMatches(response, valueMatchers[i]);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private bool ValueMatches(WitResponseNode response, ValuePathMatcher matcher)
|
||||
{
|
||||
var value = matcher.Reference.GetStringValue(response);
|
||||
bool result = !matcher.contentRequired || !string.IsNullOrEmpty(value);
|
||||
switch (matcher.matchMethod)
|
||||
{
|
||||
case MatchMethod.RegularExpression:
|
||||
result &= Regex.Match(value, matcher.matchValue).Success;
|
||||
break;
|
||||
case MatchMethod.Text:
|
||||
result &= value == matcher.matchValue;
|
||||
break;
|
||||
case MatchMethod.IntegerComparison:
|
||||
result &= CompareInt(value, matcher);
|
||||
break;
|
||||
case MatchMethod.FloatComparison:
|
||||
result &= CompareFloat(value, matcher);
|
||||
break;
|
||||
case MatchMethod.DoubleComparison:
|
||||
result &= CompareDouble(value, matcher);
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool CompareDouble(string value, ValuePathMatcher matcher)
|
||||
{
|
||||
|
||||
// This one is freeform based on the input so we will retrun false if it is not parsable
|
||||
if (!double.TryParse(value, out double dValue)) return false;
|
||||
|
||||
// We will throw an exception if match value is not a numeric value. This is a developer
|
||||
// error.
|
||||
double dMatchValue = double.Parse(matcher.matchValue);
|
||||
|
||||
switch (matcher.comparisonMethod)
|
||||
{
|
||||
case ComparisonMethod.Equals:
|
||||
return Math.Abs(dValue - dMatchValue) < matcher.floatingPointComparisonTolerance;
|
||||
case ComparisonMethod.NotEquals:
|
||||
return Math.Abs(dValue - dMatchValue) > matcher.floatingPointComparisonTolerance;
|
||||
case ComparisonMethod.Greater:
|
||||
return dValue > dMatchValue;
|
||||
case ComparisonMethod.Less:
|
||||
return dValue < dMatchValue;
|
||||
case ComparisonMethod.GreaterThanOrEqualTo:
|
||||
return dValue >= dMatchValue;
|
||||
case ComparisonMethod.LessThanOrEqualTo:
|
||||
return dValue <= dMatchValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CompareFloat(string value, ValuePathMatcher matcher)
|
||||
{
|
||||
|
||||
// This one is freeform based on the input so we will retrun false if it is not parsable
|
||||
if (!float.TryParse(value, out float dValue)) return false;
|
||||
|
||||
// We will throw an exception if match value is not a numeric value. This is a developer
|
||||
// error.
|
||||
float dMatchValue = float.Parse(matcher.matchValue);
|
||||
|
||||
switch (matcher.comparisonMethod)
|
||||
{
|
||||
case ComparisonMethod.Equals:
|
||||
return Math.Abs(dValue - dMatchValue) <
|
||||
matcher.floatingPointComparisonTolerance;
|
||||
case ComparisonMethod.NotEquals:
|
||||
return Math.Abs(dValue - dMatchValue) >
|
||||
matcher.floatingPointComparisonTolerance;
|
||||
case ComparisonMethod.Greater:
|
||||
return dValue > dMatchValue;
|
||||
case ComparisonMethod.Less:
|
||||
return dValue < dMatchValue;
|
||||
case ComparisonMethod.GreaterThanOrEqualTo:
|
||||
return dValue >= dMatchValue;
|
||||
case ComparisonMethod.LessThanOrEqualTo:
|
||||
return dValue <= dMatchValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CompareInt(string value, ValuePathMatcher matcher)
|
||||
{
|
||||
|
||||
// This one is freeform based on the input so we will retrun false if it is not parsable
|
||||
if (!int.TryParse(value, out int dValue)) return false;
|
||||
|
||||
// We will throw an exception if match value is not a numeric value. This is a developer
|
||||
// error.
|
||||
int dMatchValue = int.Parse(matcher.matchValue);
|
||||
|
||||
switch (matcher.comparisonMethod)
|
||||
{
|
||||
case ComparisonMethod.Equals:
|
||||
return dValue == dMatchValue;
|
||||
case ComparisonMethod.NotEquals:
|
||||
return dValue != dMatchValue;
|
||||
case ComparisonMethod.Greater:
|
||||
return dValue > dMatchValue;
|
||||
case ComparisonMethod.Less:
|
||||
return dValue < dMatchValue;
|
||||
case ComparisonMethod.GreaterThanOrEqualTo:
|
||||
return dValue >= dMatchValue;
|
||||
case ComparisonMethod.LessThanOrEqualTo:
|
||||
return dValue <= dMatchValue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class MultiValueEvent : UnityEvent<string[]>
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ValueEvent : UnityEvent<string>
|
||||
{ }
|
||||
|
||||
[Serializable]
|
||||
public class FormattedValueEvents
|
||||
{
|
||||
[Tooltip("Modify the string output, values can be inserted with {value} or {0}, {1}, {2}")]
|
||||
public string format;
|
||||
public ValueEvent onFormattedValueEvent = new ValueEvent();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ValuePathMatcher
|
||||
{
|
||||
[Tooltip("The path to a value within a WitResponseNode")]
|
||||
public string path;
|
||||
[Tooltip("A reference to a wit value object")]
|
||||
public WitValue witValueReference;
|
||||
[Tooltip("Does this path need to have text in the value to be considered a match")]
|
||||
public bool contentRequired = true;
|
||||
[Tooltip("If set the match value will be treated as a regular expression.")]
|
||||
public MatchMethod matchMethod;
|
||||
[Tooltip("The operator used to compare the value with the match value. Ex: response.value > matchValue")]
|
||||
public ComparisonMethod comparisonMethod;
|
||||
[Tooltip("Value used to compare with the result when Match Required is set")]
|
||||
public string matchValue;
|
||||
|
||||
[Tooltip("The variance allowed when comparing two floating point values for equality")]
|
||||
public double floatingPointComparisonTolerance = .0001f;
|
||||
|
||||
[Tooltip("Confidence ranges are executed in order. If checked, all confidence values will be checked instead of stopping on the first one that matches.")]
|
||||
[SerializeField] public bool allowConfidenceOverlap;
|
||||
[Tooltip("The confidence levels to handle for this value.\nNOTE: The selected node must have a confidence sibling node.")]
|
||||
public ConfidenceRange[] confidenceRanges;
|
||||
|
||||
private WitResponseReference pathReference;
|
||||
private WitResponseReference confidencePathReference;
|
||||
|
||||
public WitResponseReference ConfidenceReference
|
||||
{
|
||||
get
|
||||
{
|
||||
if (null != confidencePathReference) return confidencePathReference;
|
||||
|
||||
var confidencePath = Reference?.path;
|
||||
if (!string.IsNullOrEmpty(confidencePath))
|
||||
{
|
||||
confidencePath = confidencePath.Substring(0, confidencePath.LastIndexOf("."));
|
||||
confidencePath += ".confidence";
|
||||
confidencePathReference = WitResultUtilities.GetWitResponseReference(confidencePath);
|
||||
}
|
||||
|
||||
return confidencePathReference;
|
||||
}
|
||||
}
|
||||
public WitResponseReference Reference
|
||||
{
|
||||
get
|
||||
{
|
||||
if (witValueReference) return witValueReference.Reference;
|
||||
|
||||
if (null == pathReference || pathReference.path != path)
|
||||
{
|
||||
pathReference = WitResultUtilities.GetWitResponseReference(path);
|
||||
}
|
||||
|
||||
return pathReference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ComparisonMethod
|
||||
{
|
||||
Equals,
|
||||
NotEquals,
|
||||
Greater,
|
||||
GreaterThanOrEqualTo,
|
||||
Less,
|
||||
LessThanOrEqualTo
|
||||
}
|
||||
|
||||
public enum MatchMethod
|
||||
{
|
||||
None,
|
||||
Text,
|
||||
RegularExpression,
|
||||
IntegerComparison,
|
||||
FloatComparison,
|
||||
DoubleComparison
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 591c3d6f017c11b4faa41506d75635b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
using System.Text.RegularExpressions;
|
||||
using Meta.WitAi.Json;
|
||||
using Meta.WitAi.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Meta.WitAi.CallbackHandlers
|
||||
{
|
||||
[AddComponentMenu("Wit.ai/Response Matchers/Utterance Matcher")]
|
||||
public class WitUtteranceMatcher : WitResponseHandler
|
||||
{
|
||||
[SerializeField] private string searchText;
|
||||
[SerializeField] private bool exactMatch = true;
|
||||
[SerializeField] private bool useRegex;
|
||||
|
||||
[SerializeField] private StringEvent onUtteranceMatched = new StringEvent();
|
||||
|
||||
private Regex regex;
|
||||
|
||||
protected override string OnValidateResponse(WitResponseNode response, bool isEarlyResponse)
|
||||
{
|
||||
var text = response["text"].Value;
|
||||
if (!IsMatch(text))
|
||||
{
|
||||
return "Required utterance does not match";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
protected override void OnResponseInvalid(WitResponseNode response, string error){}
|
||||
protected override void OnResponseSuccess(WitResponseNode response)
|
||||
{
|
||||
var text = response["text"].Value;
|
||||
onUtteranceMatched?.Invoke(text);
|
||||
}
|
||||
|
||||
private bool IsMatch(string text)
|
||||
{
|
||||
if (useRegex)
|
||||
{
|
||||
if (null == regex)
|
||||
{
|
||||
regex = new Regex(searchText, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
}
|
||||
|
||||
var match = regex.Match(text);
|
||||
if (match.Success)
|
||||
{
|
||||
if (exactMatch && match.Value == text)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (exactMatch && text.ToLower() == searchText.ToLower())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (text.ToLower().Contains(searchText.ToLower()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0a93535f06eabe47bf93b4b504873c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user