1

I am making a chatbot (well I'm attempting!) and I have set up wit.ai to handle the text-to-speech of the bot. My issue is that I am pulling all of the chat into the speaker on each update which is overloading the TTS, when I really only want the line of the Bot (Dave) to go through the speaker and update with each new line. How can I isolate only the bots lines? Would love some help with this!

public class SimpleCharacter : MonoBehaviour
{
    public GameObject item;
    public Scrollbar verticalScrollbar;
    public ScrollRect scrollRect;
    public TMP_Text chatText;
    private Animator anim;
    public TTSSpeaker _speaker;


    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("Simple Character start 0 ");
        anim = GetComponentInChildren<Animator>();
        if (anim == null)
        {
            Debug.Log("Simple Character anim is null");
        }
        Debug.Log("Simple Character start 1");
    }

    public void Think (string text)
    {
        string chat = chatText.text;
        chat = chat + "/n" + text;
        chatText.text = text;
        anim.SetTrigger("Think");
    }

    public void Talk(List<Choice> choices)
    {
        
        string chat = chatText.text;
        chatText.text = "";
        Debug.Log("////////////////////// :  " + chat);
        chat = chat + "/n" + choices[0].ToString();
        Debug.Log("////////////////////// :  " + chat);
        chatText.text = chat;

        chatText.text = choices[0].ToString();
        anim.SetTrigger("Talk");    
    }    

    public void Talk (string text)
    {
        string chat = chatText.text;
        chat = chat + "/n" + text;
        chatText.text = chat;
        chatText.text = text;

        _speaker.Speak(chatText.text);
                
        }
    
}

Daves's lines are being received from this character's script

namespace OpenAI_Unity
{
    
    public class OAICharacter : OAICompletion
    {
        protected override StringBuilder GetDefaultInformation()
        {
            Debug.Log("GetDefaultInformation -  OAICharacter");
            var sb = base.GetDefaultInformation();
            sb.Replace("[Subject]", this.characterName);
            sb.Append($"\n\nHuman: Hi\n{characterName}: Hello\nHuman: ");

            return sb;
        }
        public override string Description { get => $"The following is a conversation between a Human and {characterName}.\n"; set => throw new System.NotImplementedException(); }

        public override string InjectStartText { get => "\n" + characterName + ":"; set => throw new System.NotImplementedException(); }
        [SerializeField]
        private string characterName = "Dave";

        public override string InjectRestartText { get => "\nHuman: "; set => throw new System.NotImplementedException(); }

        public override string[] StopSequences { get => new string[] { "\n", "Human:" }; set => throw new System.NotImplementedException(); }

        public override int NumOutputs { get => 1; set => throw new NotImplementedException(); }


        private void ThrowError (string value)
        {
            Debug.LogError($"Can not set OAICharacter variable to {value}! If you want to modify these please use an OAISimpleObject instead");
        }

    }
}

This is the controller script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using OpenAI_Unity;
using TMPro;

public class ChatController : MonoBehaviour
{
    public OAICharacter _oaiCharacter;
    public TMP_InputField questionInput;
    // Start is called before the first frame update
    void Start()
    {
        questionInput.onEndEdit.AddListener((string data) =>
        {
            if (!string.IsNullOrEmpty(data))
            {

                _oaiCharacter.AddToStory(data);
            }
            questionInput.text = "";
        });
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Tab))
        {
            questionInput.Select();
            questionInput.ActivateInputField();
        }
    }
}

And the completion

namespace OpenAI_Unity
{
    /// <summary>
    /// Used for objects that communicate with OpenAI Completion
    /// Abstract itself, for a Generic and fully customizable Completion object use OAIGenericCompletion
    /// </summary>
    public abstract class OAICompletion : MonoBehaviour
    {
        public abstract string Description
        {
            get; set;
        }

        public abstract string InjectStartText
        {
            get; set;
        }

        public abstract string InjectRestartText
        {
            get; set;
        }

        public abstract string[] StopSequences
        {
            get; set;
        }

        public EngineEnum engine;

        public enum EngineEnum
        {
            Ada,
            Babbage,
            Curie,
            Davinci
        }

        public enum LogLevel
        {
            None,
            Responses,
            ResponsesAndMemory
        }

        public LogLevel logLevel;

        public int Max_tokens = 16;
        [Range(0, 1)]
        public double Temperature = 0.1;
        [Range(0, 1)]
        public double Top_p = 1;
        public abstract int NumOutputs {get;set;}
        [Range(0, 1)]
        public double PresencePenalty = 1;
        [Range(0, 1)]
        public double FrequencyPenalty = 1;
        public int LogProbs = 1;

        public StringEvent QuestionReceivedEvent;
        public ChoicesEvent ResponseReceivedEvent;
        public ChoiceEvent ResponseReceivedEvent1;

        /// <summary>
        /// This can be disabled when using multiple responses, since they should not be manually added to the entire memory
        /// </summary>
        [HideInInspector]
        public bool autoAddResponseToMemory = true;


        public StringBuilder memory ;

        private void Start()
        {
            Debug.Log("OAI Completion start 0 ");
            memory = GetDefaultInformation();
            Debug.Log("Start - memory: " + memory);
            
            
            Debug.Log("OAI Completion start 1 ");
        }

        


        public void Brainwash(bool resetToDefault = true)
        {
            memory = resetToDefault ? GetDefaultInformation() : new StringBuilder();
        }

        /// <summary>
        /// D
        /// </summary>
        /// <returns></returns>
        protected virtual StringBuilder GetDefaultInformation()
        {
            Debug.Log("GetDefaultInformation -  OAICompletion");
            StringBuilder sb = new StringBuilder();
            sb.Append(Description);

            foreach (OAIBehavior behavior in GetComponents<OAIBehavior>())
            {
                string behaviorText = behavior.GetAsText();
                sb.Append(behaviorText);
                sb.Append(" ");
            }            

            return sb;
        }

        public async void AddToStory(string value)
        {
            
            //QuestionReceivedEvent?.Invoke(value);

            
            //Character should remember what they said before, since every time we send a request it requires the full 'story' to OpenAI
            memory.Append(value).Append(InjectStartText);
            QuestionReceivedEvent?.Invoke(memory.ToString());
            if (logLevel == LogLevel.ResponsesAndMemory)
            {
                Debug.Log(memory);
            }

            if (!OAIEngine.Instance)
            {
                Debug.LogError("No OAIEngine object found in scene. Make sure there's a GameObject with an OAIEngine Component in your scene");
                return;
            }    

            //We allow the engine to change per request (= per character and per statement)
            OAIEngine.Instance.Api.UsingEngine = GetEngine(engine);

            if (NumOutputs < 1)
            {
                Debug.LogWarning($"NumOutputs was set to {NumOutputs}. You should have at least 1 output!");
                NumOutputs = 1;
            } else if (autoAddResponseToMemory && NumOutputs > 1)
            {
                Debug.Log("Multiple or no outputs are requested while autoAddResponseToMemory is still true. You should set this to false and manually call 'AddResponseToMemory' after selecting your prefered response.");
            }
            Debug.Log("Stop Seq: " + StopSequences[0] + " _ " + StopSequences[1]);
            var c = new CompletionRequest(memory.ToString(), Max_tokens, Temperature, Top_p, NumOutputs, PresencePenalty, FrequencyPenalty, LogProbs, StopSequences);
            var results = await OAIEngine.Instance.Api.Completions.CreateCompletionsAsync(c);

            //ResponseReceivedEvent?.Invoke(results.Completions);

            //We make it easy by auto-adding responses to the memory
            if (autoAddResponseToMemory)
            {
                var r = results.Completions[0].Text;
                AddResponseToMemory(r);
                if (logLevel == LogLevel.Responses || logLevel == LogLevel.ResponsesAndMemory)
                {
                    Debug.Log(r);
                }                
            }
        }

        public void AddResponseToMemory (string value)
        {
            memory.Append(value).Append(InjectRestartText);
            ResponseReceivedEvent1?.Invoke(memory.ToString());
            Debug.Log("Memory: " + memory);
        }

        private Engine GetEngine(EngineEnum e)
        {
            switch (e)
            {
                case EngineEnum.Ada:
                    return Engine.Ada;
                case EngineEnum.Babbage:
                    return Engine.Babbage;
                case EngineEnum.Curie:
                    return Engine.Curie;
                case EngineEnum.Davinci:
                    return Engine.Davinci;
            }
            return Engine.Default;
        }

    }

}
Mutaman89
  • 11
  • 2
  • 1
    Posting pictures of error messages and code is bad form. Please put text as text and use the SO's code syntax. – Voidsay Jan 29 '23 at 11:43
  • Apologies Voidsay! first time posting. I have updated it! – Mutaman89 Jan 29 '23 at 14:31
  • I think instead of directly working via the text field you should rather simply store the last line into a new class field -> after a new update is received set that class field to the newly added line, add it also to your text field and let only that last line be read .. then reset the field to `null` -> doesn't read again until there is actually a new line "received" – derHugo Jan 30 '23 at 07:05
  • How/where exactly are `Talk`, `Think` etc called from? – derHugo Jan 30 '23 at 07:05
  • And why do you always assign `chatText.text` twice, once with a concatenated `chatText.text = chat;` and then you directly overwrite it with e.g. `chatText.text = text;` .. that looks quite redundant – derHugo Jan 30 '23 at 07:09
  • Hey @derHugo, Talk/think come from the animator. I can see what you mean with the double assigning, it needs a clean up. I've become a little fried working on the project tbh – Mutaman89 Jan 30 '23 at 19:57
  • To come back to your question... nothing in your given code/explanation gives us any hint how you/the code could possibly decide/know that a certain line comes from `Dave` .. in other words define us what a `Dave` is ;) – derHugo Jan 31 '23 at 08:01
  • Sorry, @derHugo I'm such a muppet! ok I have updated and added the character and the completion scripts for Daves line references – Mutaman89 Jan 31 '23 at 08:46

1 Answers1

0

You should work with the text string that is passed to the function instead of the entire chat text. Assuming that all spoken dialog starts with "Dave: " you can easily detect this substring in the text, remove it and pass it to your text to speech.

using System;

string botName = "Dave: ";

public void Talk(string text){
  chatText.text += "\n" + text;

  if(text.StartsWith(botName)){
    string spokenText = text.Remove(0, botName.Length)
    _speaker.Speak(spokenText);
  }
}
Voidsay
  • 1,462
  • 2
  • 3
  • 15