blob: a651e929c9bc7d8cac11a0349f9e197a892e274a [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/mahi/mahi_manager_impl.h"
#include <stdint.h>
#include <algorithm>
#include <string>
#include <vector>
#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "ash/system/mahi/mahi_panel_widget.h"
#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "ash/webui/settings/public/constants/setting.mojom.h"
#include "base/functional/callback.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/mahi/mahi_browser_delegate_ash.h"
#include "chrome/browser/manta/manta_service_factory.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/settings_window_manager_chromeos.h"
#include "chromeos/components/mahi/public/cpp/mahi_manager.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/feedback/feedback_constants.h"
#include "components/manta/features.h"
#include "components/manta/manta_service.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/views/widget/unique_widget_ptr.h"
namespace {
using chromeos::MahiResponseStatus;
using crosapi::mojom::MahiContextMenuActionType;
std::unique_ptr<manta::MahiProvider> CreateProvider() {
if (!manta::features::IsMantaServiceEnabled()) {
return nullptr;
}
Profile* profile = ProfileManager::GetActiveUserProfile();
if (!profile) {
return nullptr;
}
if (manta::MantaService* service =
manta::MantaServiceFactory::GetForProfile(profile)) {
return service->CreateMahiProvider();
}
return nullptr;
}
ash::MahiBrowserDelegateAsh* GetMahiBrowserDelgateAsh() {
auto* mahi_browser_delegate_ash = crosapi::CrosapiManager::Get()
->crosapi_ash()
->mahi_browser_delegate_ash();
CHECK(mahi_browser_delegate_ash);
return mahi_browser_delegate_ash;
}
} // namespace
namespace ash {
MahiManagerImpl::MahiManagerImpl() {
session_observation_.Observe(Shell::Get()->session_controller());
PrefService* last_active_user_pref_service =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
if (last_active_user_pref_service) {
OnActiveUserPrefServiceChanged(last_active_user_pref_service);
}
}
MahiManagerImpl::~MahiManagerImpl() {
mahi_panel_widget_.reset();
mahi_provider_.reset();
}
void MahiManagerImpl::OpenMahiPanel(int64_t display_id) {
if (!IsEnabled()) {
return;
}
mahi_panel_widget_ = MahiPanelWidget::CreatePanelWidget(display_id);
mahi_panel_widget_->Show();
}
std::u16string MahiManagerImpl::GetContentTitle() {
return current_page_info_->title;
}
gfx::ImageSkia MahiManagerImpl::GetContentIcon() {
return current_page_info_->favicon_image;
}
void MahiManagerImpl::GetSummary(MahiSummaryCallback callback) {
MaybeInitialize();
current_panel_url_ = current_page_info_->url;
GetMahiBrowserDelgateAsh()->GetContentFromClient(
current_page_info_->client_id, current_page_info_->page_id,
base::BindOnce(&MahiManagerImpl::OnGetPageContentForSummary,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void MahiManagerImpl::GetOutlines(MahiOutlinesCallback callback) {
std::vector<chromeos::MahiOutline> outlines;
for (int i = 0; i < 5; i++) {
outlines.emplace_back(
chromeos::MahiOutline(i, u"Outline " + base::NumberToString16(i)));
}
std::move(callback).Run(outlines, MahiResponseStatus::kSuccess);
}
void MahiManagerImpl::GoToOutlineContent(int outline_id) {}
void MahiManagerImpl::AnswerQuestion(const std::u16string& question,
bool current_panel_content,
MahiAnswerQuestionCallback callback) {
MaybeInitialize();
if (current_panel_content) {
mahi_provider_->QuestionAndAnswer(
base::UTF16ToUTF8(current_panel_content_->page_content),
current_panel_qa_, base::UTF16ToUTF8(question),
base::BindOnce(&MahiManagerImpl::OnMahiProviderQAResponse,
weak_ptr_factory_.GetWeakPtr(), question,
std::move(callback)));
return;
}
current_panel_url_ = current_page_info_->url;
GetMahiBrowserDelgateAsh()->GetContentFromClient(
current_page_info_->client_id, current_page_info_->page_id,
base::BindOnce(&MahiManagerImpl::OnGetPageContentForQA,
weak_ptr_factory_.GetWeakPtr(), question,
std::move(callback)));
}
void MahiManagerImpl::GetSuggestedQuestion(
MahiGetSuggestedQuestionCallback callback) {
std::move(callback).Run(u"test suggested question",
MahiResponseStatus::kSuccess);
}
void MahiManagerImpl::SetCurrentFocusedPageInfo(
crosapi::mojom::MahiPageInfoPtr info) {
// TODO(b/318565610): consider adding default icon when there is no icon
// available.
current_page_info_ = std::move(info);
const bool availability =
current_page_info_->IsDistillable.value_or(false) &&
!current_panel_url_.EqualsIgnoringRef(current_page_info_->url);
NotifyRefreshAvailability(/*available=*/availability);
}
void MahiManagerImpl::OnContextMenuClicked(
crosapi::mojom::MahiContextMenuRequestPtr context_menu_request) {
switch (context_menu_request->action_type) {
case MahiContextMenuActionType::kSummary:
case MahiContextMenuActionType::kOutline:
// TODO(b/318565610): Update the behaviour of kOutline.
OpenMahiPanel(context_menu_request->display_id);
return;
case MahiContextMenuActionType::kQA:
OpenMahiPanel(context_menu_request->display_id);
// Ask question.
// TODO(b/331837721): `MahiManagerImpl` should own an instance of
// `MahiUiController` and use it to answer question here. This
// functionality shouldn't need to be routed through the widget. We also
// need to add unit test logic for this after the refactor.
if (!context_menu_request->question) {
return;
}
if (!mahi_panel_widget_) {
return;
}
// When the user sends a question from the context menu, we treat it as
// the start of a new journey, so we set `current_panel_content` false.
static_cast<MahiPanelWidget*>(mahi_panel_widget_.get())
->SendQuestion(context_menu_request->question.value(),
/*current_panel_content=*/false);
return;
case MahiContextMenuActionType::kSettings:
chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(
ProfileManager::GetActiveUserProfile(),
chromeos::settings::mojom::kSystemPreferencesSectionPath,
chromeos::settings::mojom::Setting::kMahiOnOff);
return;
case MahiContextMenuActionType::kNone:
return;
}
}
bool MahiManagerImpl::IsEnabled() {
return IsSupportedWithCorrectFeatureKey() &&
Shell::Get()->session_controller()->GetActivePrefService()->GetBoolean(
ash::prefs::kMahiEnabled);
}
void MahiManagerImpl::NotifyRefreshAvailability(bool available) {
auto* mahi_widget = static_cast<MahiPanelWidget*>(mahi_panel_widget_.get());
if (mahi_widget) {
mahi_widget->NotifyRefreshAvailabilityChanged(available);
}
}
void MahiManagerImpl::OnActiveUserPrefServiceChanged(
PrefService* pref_service) {
CHECK(pref_service);
// Subscribes again to pref changes.
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(pref_service);
pref_change_registrar_->Add(
ash::prefs::kMahiEnabled,
base::BindRepeating(&MahiManagerImpl::OnMahiPrefChanged,
weak_ptr_factory_.GetWeakPtr()));
OnMahiPrefChanged();
}
void MahiManagerImpl::OnMahiPrefChanged() {
CHECK(pref_change_registrar_);
CHECK(pref_change_registrar_->prefs());
if (!pref_change_registrar_->prefs()->GetBoolean(ash::prefs::kMahiEnabled)) {
mahi_panel_widget_.reset();
}
}
void MahiManagerImpl::MaybeInitialize() {
if (!mahi_provider_) {
mahi_provider_ = CreateProvider();
}
CHECK(mahi_provider_);
}
void MahiManagerImpl::OnGetPageContentForSummary(
MahiSummaryCallback callback,
crosapi::mojom::MahiPageContentPtr mahi_content_ptr) {
if (!mahi_content_ptr) {
std::move(callback).Run(u"summary text",
MahiResponseStatus::kContentExtractionError);
return;
}
// Assign current panel content and clear the current panel QA
current_panel_content_ = std::move(mahi_content_ptr);
current_panel_qa_.clear();
CHECK(mahi_provider_);
mahi_provider_->Summarize(
base::UTF16ToUTF8(current_panel_content_->page_content),
base::BindOnce(&MahiManagerImpl::OnMahiProviderSummaryResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void MahiManagerImpl::OnMahiProviderSummaryResponse(
MahiSummaryCallback summary_callback,
base::Value::Dict dict,
manta::MantaStatus status) {
latest_summary_ = u"...";
if (status.status_code != manta::MantaStatusCode::kOk) {
latest_response_status_ = MahiResponseStatus::kUnknownError;
std::move(summary_callback)
.Run(u"Couldn't get summary", latest_response_status_);
return;
}
if (auto* text = dict.FindString("outputData")) {
latest_response_status_ = MahiResponseStatus::kSuccess;
latest_summary_ = base::UTF8ToUTF16(*text);
std::move(summary_callback).Run(latest_summary_, latest_response_status_);
} else {
latest_response_status_ = MahiResponseStatus::kCantFindOutputData;
std::move(summary_callback)
.Run(u"Cannot find outputdata", latest_response_status_);
}
}
void MahiManagerImpl::OnMahiProviderQAResponse(
const std::u16string& question,
MahiAnswerQuestionCallback callback,
base::Value::Dict dict,
manta::MantaStatus status) {
if (status.status_code != manta::MantaStatusCode::kOk) {
latest_response_status_ = MahiResponseStatus::kUnknownError;
current_panel_qa_.emplace_back(base::UTF16ToUTF8(question), "");
std::move(callback).Run(std::nullopt, latest_response_status_);
return;
}
if (auto* text = dict.FindString("outputData")) {
latest_response_status_ = MahiResponseStatus::kSuccess;
current_panel_qa_.emplace_back(base::UTF16ToUTF8(question), *text);
std::move(callback).Run(base::UTF8ToUTF16(*text), latest_response_status_);
} else {
latest_response_status_ = MahiResponseStatus::kCantFindOutputData;
std::move(callback).Run(std::nullopt, latest_response_status_);
}
}
void MahiManagerImpl::OnGetPageContentForQA(
const std::u16string& question,
MahiAnswerQuestionCallback callback,
crosapi::mojom::MahiPageContentPtr mahi_content_ptr) {
if (!mahi_content_ptr) {
std::move(callback).Run(std::nullopt,
MahiResponseStatus::kContentExtractionError);
return;
}
// Assign current panel content and clear the current panel QA
current_panel_content_ = std::move(mahi_content_ptr);
current_panel_qa_.clear();
mahi_provider_->QuestionAndAnswer(
base::UTF16ToUTF8(current_panel_content_->page_content),
current_panel_qa_, base::UTF16ToUTF8(question),
base::BindOnce(&MahiManagerImpl::OnMahiProviderQAResponse,
weak_ptr_factory_.GetWeakPtr(), question,
std::move(callback)));
}
void MahiManagerImpl::OpenFeedbackDialog() {
std::string description_template = base::StringPrintf(
"#Mahi user feedback:\n\n-----------\nlatest status code: %d\nlatest "
"summary: %s",
static_cast<int>(latest_response_status_),
base::UTF16ToUTF8(latest_summary_).c_str());
if (!current_panel_qa_.empty()) {
base::StringAppendF(&description_template, "\nQA history:");
for (const auto& [question, answer] : current_panel_qa_) {
base::StringAppendF(&description_template, "\nQ:%s\nA:%s\n",
question.c_str(), answer.c_str());
}
}
base::Value::Dict ai_metadata;
ai_metadata.Set(feedback::kMahiMetadataKey, "true");
// TODO(b:329166865): add mahi feedback placeholder
chrome::ShowFeedbackPage(
/*browser=*/chrome::FindBrowserWithProfile(
ProfileManager::GetActiveUserProfile()),
/*source=*/chrome::kFeedbackSourceAI, description_template,
/*description_placeholder_text=*/
base::UTF16ToUTF8(
l10n_util::GetStringUTF16(IDS_MAHI_FEEDBACK_PLACEHOLDER)),
/*category_tag=*/"mahi",
/*extra_diagnostics=*/std::string(),
/*autofill_metadata=*/base::Value::Dict(), std::move(ai_metadata));
}
} // namespace ash