blob: a8d02f76bc8e95161737dd9d7fc3213691062007 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "peridot/bin/suggestion_engine/query_processor.h"
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include "peridot/bin/suggestion_engine/suggestion_engine_helper.h"
#include "peridot/lib/fidl/json_xdr.h"
namespace modular {
namespace {
constexpr char kQueryContextKey[] = "/suggestion_engine/current_query";
} // namespace
QueryProcessor::QueryProcessor(fuchsia::media::AudioPtr audio,
std::shared_ptr<SuggestionDebugImpl> debug)
: debug_(debug),
media_player_(std::move(audio), debug),
has_audio_response_(false) {
media_player_.SetSpeechStatusCallback(
[this](fuchsia::modular::SpeechStatus status) {
NotifySpeechListeners(status);
});
}
QueryProcessor::~QueryProcessor() = default;
void QueryProcessor::Initialize(
fidl::InterfaceHandle<fuchsia::modular::ContextWriter> context_writer) {
context_writer_.Bind(std::move(context_writer));
}
void QueryProcessor::ExecuteQuery(
fuchsia::modular::UserInput input, int count,
fidl::InterfaceHandle<fuchsia::modular::QueryListener> listener) {
// TODO(jwnichols): I'm not sure this is correct or should be here
NotifySpeechListeners(fuchsia::modular::SpeechStatus::PROCESSING);
// Process:
// 1. Close out and clean up any existing query process
// 2. Update the context engine with the new query
// 3. Set up the ask variables in suggestion engine
// 4. Get suggestions from each of the QueryHandlers
// 5. Filter and Rank the suggestions as received
// 6. Send "done" to SuggestionListener
// Step 1
CleanUpPreviousQuery();
// Step 2
std::string query = input.text;
if (!query.empty() && context_writer_.is_bound()) {
// Update context engine
std::string formattedQuery;
XdrFilterType<std::string> filter_list[] = {
XdrFilter<std::string>,
nullptr,
};
XdrWrite(&formattedQuery, &query, filter_list);
context_writer_->WriteEntityTopic(kQueryContextKey, formattedQuery);
// Update suggestion engine debug interface
debug_->OnAskStart(query, &suggestions_);
}
// Steps 3 - 6
activity_ = debug_->GetIdleWaiter()->RegisterOngoingActivity();
active_query_ = std::make_unique<QueryRunner>(std::move(listener),
std::move(input), count);
active_query_->SetResponseCallback(
[this, input](const std::string& handler_url,
fuchsia::modular::QueryResponse response) {
OnQueryResponse(input, handler_url, std::move(response));
});
active_query_->SetEndRequestCallback(
[this, input] { OnQueryEndRequest(input); });
active_query_->Run(query_handlers_);
}
void QueryProcessor::RegisterFeedbackListener(
fidl::InterfaceHandle<fuchsia::modular::FeedbackListener> speech_listener) {
speech_listeners_.AddInterfacePtr(speech_listener.Bind());
}
void QueryProcessor::RegisterQueryHandler(
fidl::StringPtr url, fidl::InterfaceHandle<fuchsia::modular::QueryHandler>
query_handler_handle) {
auto query_handler = query_handler_handle.Bind();
query_handlers_.emplace_back(std::move(query_handler), url);
fuchsia::modular::QueryHandlerPtr& handler = query_handlers_.back().handler;
handler.set_error_handler([this, ptr = handler.get()] {
auto it = std::find_if(query_handlers_.begin(), query_handlers_.end(),
[ptr](const QueryHandlerRecord& record) {
return record.handler.get() == ptr;
});
FXL_DCHECK(it != query_handlers_.end());
auto to_erase = query_handlers_.end() - 1;
std::iter_swap(it, to_erase);
query_handlers_.erase(to_erase);
});
}
void QueryProcessor::SetFilters(
std::vector<std::unique_ptr<SuggestionActiveFilter>>&& active_filters,
std::vector<std::unique_ptr<SuggestionPassiveFilter>>&& passive_filters) {
suggestions_.SetActiveFilters(std::move(active_filters));
suggestions_.SetPassiveFilters(std::move(passive_filters));
}
void QueryProcessor::SetRanker(std::unique_ptr<Ranker> ranker) {
suggestions_.SetRanker(std::move(ranker));
}
RankedSuggestion* QueryProcessor::GetSuggestion(
const std::string& suggestion_uuid) const {
return suggestions_.GetSuggestion(suggestion_uuid);
}
void QueryProcessor::CleanUpPreviousQuery() {
has_audio_response_ = false;
active_query_.reset();
suggestions_.RemoveAllSuggestions();
}
void QueryProcessor::AddProposal(const std::string& source_url,
fuchsia::modular::Proposal proposal) {
suggestions_.RemoveProposal(source_url, proposal.id);
SuggestionPrototype* suggestion =
CreateSuggestionPrototype(&query_prototypes_, source_url,
"" /* Emtpy story_id */, std::move(proposal));
suggestions_.AddSuggestion(suggestion);
}
void QueryProcessor::NotifySpeechListeners(
fuchsia::modular::SpeechStatus status) {
for (auto& speech_listener : speech_listeners_.ptrs()) {
(*speech_listener)
->OnStatusChanged(fuchsia::modular::SpeechStatus::PROCESSING);
}
}
void QueryProcessor::OnQueryResponse(fuchsia::modular::UserInput input,
const std::string& handler_url,
fuchsia::modular::QueryResponse response) {
// TODO(rosswang): defer selection of "I don't know" responses
if (!has_audio_response_ && response.audio_response) {
has_audio_response_ = true;
// TODO(rosswang): Wait for other potential voice responses so that we
// choose the best one. We don't have criteria for "best" yet, and we only
// have one agent (Kronk) with voice responses now, so play immediately.
// TODO(rosswang): allow falling back on natural language text response
// without a spoken response
fidl::StringPtr text_response =
std::move(response.natural_language_response);
if (!text_response) {
text_response = "";
}
for (auto& listener : speech_listeners_.ptrs()) {
(*listener)->OnTextResponse(text_response);
}
media_player_.PlayAudioResponse(std::move(response.audio_response));
}
// Ranking currently happens as each set of proposals are added.
for (size_t i = 0; i < response.proposals->size(); ++i) {
AddProposal(handler_url, std::move(response.proposals->at(i)));
}
suggestions_.Refresh(input);
// Update the fuchsia::modular::QueryListener with new results
NotifyOfResults();
// Update the suggestion engine debug interface
debug_->OnAskStart(input.text, &suggestions_);
}
void QueryProcessor::OnQueryEndRequest(fuchsia::modular::UserInput input) {
debug_->OnAskStart(input.text, &suggestions_);
if (!has_audio_response_) {
// there was no media response for this query, so idle immediately
NotifySpeechListeners(fuchsia::modular::SpeechStatus::IDLE);
}
activity_ = nullptr;
}
void QueryProcessor::NotifyOfResults() {
const auto& suggestion_vector = suggestions_.Get();
fidl::VectorPtr<fuchsia::modular::Suggestion> window;
for (size_t i = 0;
i < active_query_->max_results() && i < suggestion_vector.size(); i++) {
window.push_back(CreateSuggestion(*suggestion_vector[i]));
}
if (window) {
active_query_->listener()->OnQueryResults(std::move(window));
}
}
} // namespace modular