diff --git a/src/main/java/io/spokestack/spokestack/Spokestack.java b/src/main/java/io/spokestack/spokestack/Spokestack.java index 96a9245..01c68bb 100644 --- a/src/main/java/io/spokestack/spokestack/Spokestack.java +++ b/src/main/java/io/spokestack/spokestack/Spokestack.java @@ -2,6 +2,8 @@ import android.content.Context; import io.spokestack.spokestack.dialogue.DialogueManager; +import io.spokestack.spokestack.dialogue.FinalizedPrompt; +import io.spokestack.spokestack.dialogue.Prompt; import io.spokestack.spokestack.nlu.NLUManager; import io.spokestack.spokestack.nlu.NLUResult; import io.spokestack.spokestack.nlu.tensorflow.parsers.DigitsParser; @@ -146,6 +148,12 @@ private Spokestack(Builder builder) throws Exception { .addTTSListener(this) .build(); } + + if (builder.dialogueBuilder.hasPolicy() + || builder.speechConfig.containsKey("dialogue-policy-file") + || builder.speechConfig.containsKey("dialogue-policy-class")) { + this.dialogueManager = builder.dialogueBuilder.build(); + } } /** @@ -349,6 +357,55 @@ public void stopPlayback() { } } + // dialogue + + /** + * @return The dialogue manager currently in use. + */ + public DialogueManager getDialogueManager() { + return dialogueManager; + } + + /** + * Finalize a prompt, interpolating template strings using the current + * conversation data store. + * + *
+ * This method can only be used if a dialogue manager is active. + *
+ * + * @param prompt The prompt to be finalized. + * @return The finalized prompt. + * + * @see DialogueManager#finalizePrompt(Prompt) + * @see io.spokestack.spokestack.dialogue.ConversationData + */ + public FinalizedPrompt finalizePrompt(Prompt prompt) { + if (this.dialogueManager != null) { + return dialogueManager.finalizePrompt(prompt); + } + return null; + } + + /** + * Stores an object in the conversation data store for use in interpolating + * system prompts. + * + *+ * This method can only be used if a dialogue manager is active. + *
+ * + * @param key The name of the object to store. + * @param value The object to store. + * + * @see io.spokestack.spokestack.dialogue.ConversationData#set(String, Object) + */ + public void putConversationData(String key, Object value) { + if (this.dialogueManager != null) { + dialogueManager.getDataStore().set(key, value); + } + } + // listeners /** @@ -948,7 +1005,9 @@ public Spokestack build() throws Exception { + "TTSManager.Builder.setAndroidContext()"); } } - if (!this.speechConfig.containsKey("dialogue-policy-file") + + if (!this.dialogueBuilder.hasPolicy() + && !this.speechConfig.containsKey("dialogue-policy-file") && !this.speechConfig.containsKey("dialogue-policy-class")) { this.useDialogue = false; } diff --git a/src/main/java/io/spokestack/spokestack/dialogue/DialogueManager.java b/src/main/java/io/spokestack/spokestack/dialogue/DialogueManager.java index a7fac4b..31da97d 100644 --- a/src/main/java/io/spokestack/spokestack/dialogue/DialogueManager.java +++ b/src/main/java/io/spokestack/spokestack/dialogue/DialogueManager.java @@ -133,6 +133,17 @@ public ConversationData getDataStore() { return this.dataStore; } + /** + * Finalize a prompt, interpolating template strings using the current + * conversation data store. + * + * @param prompt The prompt to be finalized. + * @return The finalized prompt. + */ + public FinalizedPrompt finalizePrompt(Prompt prompt) { + return prompt.finalizePrompt(this.dataStore); + } + /** * Dump the dialogue policy's current state to the currently registered data * store. This can be used in conjunction with {@link #load(String) load()} @@ -271,6 +282,15 @@ public Builder withDialoguePolicy(String policyClass) { return this; } + /** + * @return whether this builder has a dialogue policy enabled via + * class or JSON file. + */ + public boolean hasPolicy() { + return this.config.containsKey("dialogue-policy-file") + || this.config.containsKey("dialogye-policy-class"); + } + /** * Specify the data store to use for conversation data. * diff --git a/src/main/java/io/spokestack/spokestack/dialogue/FinalizedPrompt.java b/src/main/java/io/spokestack/spokestack/dialogue/FinalizedPrompt.java new file mode 100644 index 0000000..db3562d --- /dev/null +++ b/src/main/java/io/spokestack/spokestack/dialogue/FinalizedPrompt.java @@ -0,0 +1,169 @@ +package io.spokestack.spokestack.dialogue; + +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.List; + +/** + * A finalized prompt contains the same fields as a {@link Prompt}, but instead + * of template placeholders, its contents are fully interpolated strings ready + * to be displayed to the user or synthesized by TTS. + */ +public final class FinalizedPrompt { + private final String id; + private final String text; + private final String voice; + private final Proposal proposal; + private final FinalizedPrompt[] reprompts; + private final boolean endsConversation; + + private FinalizedPrompt(Builder builder) { + this.id = builder.id; + this.text = builder.text; + if (builder.voice == null) { + this.voice = builder.text; + } else { + this.voice = builder.voice; + } + this.proposal = builder.proposal; + this.reprompts = builder.reprompts; + this.endsConversation = builder.endsConversation; + } + + /** + * @return The prompt's ID. + */ + public String getId() { + return id; + } + + /** + * Get a version of the prompt formatted for TTS synthesis. + * + * @return A version of the prompt formatted for TTS synthesis. + */ + public String getVoice() { + return voice; + } + + /** + * Get a version of the prompt formatted for print. + * + * @return A version of the prompt formatted for print. + */ + public String getText() { + return text; + } + + /** + * @return this prompt's proposal. + */ + public Proposal getProposal() { + return proposal; + } + + /** + * @return any reprompts associated with this prompt. + */ + public FinalizedPrompt[] getReprompts() { + return reprompts; + } + + /** + * @return {@code true} if the conversation should end after the current + * prompt is delivered; {@code false} otherwise. + */ + public boolean endsConversation() { + return endsConversation; + } + + @Override + public String toString() { + return "Prompt{" + + "id='" + id + '\'' + + ", text='" + text + '\'' + + ", voice='" + voice + '\'' + + ", proposal=" + proposal + + ", reprompts=" + Arrays.toString(reprompts) + + ", endsConversation=" + endsConversation + + '}'; + } + + /** + * Prompt builder API. + */ + public static final class Builder { + + private final String id; + private final String text; + private String voice; + private Proposal proposal; + private FinalizedPrompt[] reprompts; + private boolean endsConversation; + + /** + * Create a new prompt builder with the minimal set of required data. + * + * @param promptId The prompt's ID. + * @param textReply A reply template formatted for print. + */ + public Builder(@NonNull String promptId, @NonNull String textReply) { + this.id = promptId; + this.text = textReply; + this.reprompts = new FinalizedPrompt[0]; + } + + /** + * Signals that the prompt to be built should end the conversation with + * the user. + * + * @return the updated builder + */ + public Builder endsConversation() { + this.endsConversation = true; + return this; + } + + /** + * Add a reply template formatted for TTS synthesis to the current + * prompt. + * + * @param voiceReply The voice prompt to be added. + * @return the updated builder + */ + public Builder withVoice(@NonNull String voiceReply) { + this.voice = voiceReply; + return this; + } + + /** + * Add a proposal to the current prompt. + * + * @param prop The proposal to be added. + * @return the updated builder + */ + public Builder withProposal(@NonNull Proposal prop) { + this.proposal = prop; + return this; + } + + /** + * Specify reprompts for the current prompt. + * + * @param prompts The reprompts to attach. + * @return the updated builder + */ + public Builder withReprompts(@NonNull List+ * Formats values for both display and synthesis using {@link + * String#valueOf(Object)}. + *
*/ public class InMemoryConversationData implements ConversationData { diff --git a/src/main/java/io/spokestack/spokestack/dialogue/Prompt.java b/src/main/java/io/spokestack/spokestack/dialogue/Prompt.java index 44eed2a..c415ed4 100644 --- a/src/main/java/io/spokestack/spokestack/dialogue/Prompt.java +++ b/src/main/java/io/spokestack/spokestack/dialogue/Prompt.java @@ -2,6 +2,7 @@ import androidx.annotation.NonNull; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; @@ -127,6 +128,32 @@ public boolean endsConversation() { return endsConversation; } + /** + * Finalize this prompt, filling in all placeholders with data from the + * conversation's data store. + * + * @param dataStore The current state of the conversation data to use for + * filling placeholders in prompts. + * @return A finalized version of this prompt ready for display/synthesis. + */ + public FinalizedPrompt finalizePrompt(ConversationData dataStore) { + List