blob: 4293adbb511af6021b843b702dfc068caa7a1abd [file] [log] [blame]
// Copyright 2025 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/ui/lens/lens_composebox_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/lens/lens_composebox_handler.h"
#include "chrome/browser/ui/lens/lens_overlay_query_controller.h"
#include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h"
#include "chrome/browser/ui/lens/lens_search_contextualization_controller.h"
#include "chrome/browser/ui/lens/lens_search_controller.h"
#include "chrome/browser/ui/lens/lens_search_feature_flag_utils.h"
#include "chrome/browser/ui/lens/lens_session_metrics_logger.h"
#include "components/lens/lens_features.h"
#include "components/lens/lens_overlay_mime_type.h"
#include "components/tabs/public/tab_interface.h"
#include "third_party/lens_server_proto/aim_communication.pb.h"
namespace {
lens::LensOverlayVisualInputType LensMimeTypeToVisualInputType(
lens::MimeType mime_type) {
switch (mime_type) {
case lens::MimeType::kPdf:
return lens::LensOverlayVisualInputType::VISUAL_INPUT_TYPE_PDF;
case lens::MimeType::kAnnotatedPageContent:
return lens::LensOverlayVisualInputType::VISUAL_INPUT_TYPE_WEBPAGE;
default:
return lens::LensOverlayVisualInputType::VISUAL_INPUT_TYPE_UNKNOWN;
}
}
} // namespace
namespace lens {
LensComposeboxController::LensComposeboxController(
LensSearchController* lens_search_controller,
Profile* profile)
: lens_search_controller_(lens_search_controller), profile_(profile) {}
LensComposeboxController::~LensComposeboxController() = default;
void LensComposeboxController::BindComposebox(
mojo::PendingReceiver<composebox::mojom::PageHandler> pending_handler,
mojo::PendingRemote<composebox::mojom::Page> pending_page,
mojo::PendingRemote<searchbox::mojom::Page> pending_searchbox_page,
mojo::PendingReceiver<searchbox::mojom::PageHandler>
pending_searchbox_handler) {
composebox_handler_.reset();
composebox_handler_ = std::make_unique<LensComposeboxHandler>(
this, profile_, lens_search_controller_->GetTabInterface()->GetContents(),
std::move(pending_handler), std::move(pending_page),
std::move(pending_searchbox_handler));
// TODO(crbug.com/435288212): Move searchbox mojom to use factory pattern.
composebox_handler_->SetPage(std::move(pending_searchbox_page));
// Record that the composebox was shown. The composebox handler is always
// bound, so check if the composebox is actually enabled before logging as
// shown.
if (lens::IsAimM3Enabled(profile_) &&
lens::features::GetAimSearchboxEnabled()) {
GetSessionMetricsLogger()->OnAimComposeboxShown();
}
}
void LensComposeboxController::IssueComposeboxQuery(
const std::string& query_text) {
if (!lens::IsAimM3Enabled(profile_)) {
return;
}
// Can only issue a query if the remote UI supports the DEFAULT feature.
if (remote_ui_capabilities_.empty() ||
!remote_ui_capabilities_.contains(lens::FeatureCapability::DEFAULT)) {
return;
}
// TODO(crbug.com/436318377): Reupload page content if needed.
lens::ClientToAimMessage submit_query_message =
BuildSubmitQueryMessage(query_text);
// Convert Proto to bytes to send over the API channel.
const size_t size = submit_query_message.ByteSizeLong();
std::vector<uint8_t> serialized_message(size);
submit_query_message.SerializeToArray(&serialized_message[0], size);
// Send the message to the remote UI.
lens_search_controller_->lens_overlay_side_panel_coordinator()
->SendClientMessageToAim(serialized_message);
// Record that a query was issued.
GetSessionMetricsLogger()->OnAimQueryIssued();
}
void LensComposeboxController::OnFocusChanged(bool focused) {
// Ignore if the user left focus.
if (!focused) {
return;
}
// Record that the composebox was focused.
GetSessionMetricsLogger()->OnAimComposeboxFocused();
// Ignore if recontextualization on focus is disabled.
if (!lens::features::GetShouldComposeboxContextualizeOnFocus()) {
return;
}
// If the composebox becomes focused, the user is showing intent to issue a
// new query. Upload the new page content for contextualization. The content
// is updated asynchronously, but this class does not need to wait for the
// update to complete, so a callback is not needed.
lens_search_controller_->lens_search_contextualization_controller()
->TryUpdatePageContextualization(base::DoNothing());
}
void LensComposeboxController::CloseUI() {
composebox_handler_.reset();
}
void LensComposeboxController::OnAimMessage(
const std::vector<uint8_t>& message) {
// Ignore the message if the searchbox is disabled.
if (!lens::IsAimM3Enabled(profile_)) {
return;
}
// Try and parse the message as an AimToClientMessage. Since it is the only
// message type we expect, if parsing fails, we can assume it is a malformed
// message and ignore it.
lens::AimToClientMessage aim_to_client_message;
if (!aim_to_client_message.ParseFromArray(message.data(), message.size())) {
return;
}
if (aim_to_client_message.has_handshake_response()) {
remote_ui_capabilities_.clear();
// Store the remote UI's capabilities. This should only be done once.
for (int capability_int :
aim_to_client_message.handshake_response().capabilities()) {
remote_ui_capabilities_.insert(
static_cast<lens::FeatureCapability>(capability_int));
}
lens_search_controller_->lens_overlay_side_panel_coordinator()
->AimHandshakeReceived();
GetSessionMetricsLogger()->OnAimHandshakeCompleted();
}
}
lens::LensSessionMetricsLogger*
LensComposeboxController::GetSessionMetricsLogger() {
return lens_search_controller_->lens_session_metrics_logger();
}
lens::ClientToAimMessage LensComposeboxController::BuildSubmitQueryMessage(
const std::string& query_text) {
lens::ClientToAimMessage client_to_aim_message;
lens::SubmitQuery* submit_query_message =
client_to_aim_message.mutable_submit_query();
// Set the query text and source.
submit_query_message->mutable_payload()->set_query_text(query_text);
submit_query_message->mutable_payload()->set_query_text_source(
lens::QueryPayload::QUERY_TEXT_SOURCE_KEYBOARD_INPUT);
// Populate the Lens related data from the active query flow.
lens::LensImageQueryData* lens_image_query_data =
submit_query_message->mutable_payload()->add_lens_image_query_data();
LensOverlayQueryController* query_controller =
lens_search_controller_->lens_overlay_query_controller();
LensSearchContextualizationController* contextualization_controller =
lens_search_controller_->lens_search_contextualization_controller();
lens_image_query_data->set_search_session_id(
query_controller->search_session_id());
lens_image_query_data->mutable_request_id()->CopyFrom(
*query_controller->GetNextRequestId(
lens::RequestIdUpdateMode::kSearchUrl));
lens_image_query_data->set_visual_input_type(LensMimeTypeToVisualInputType(
contextualization_controller->primary_content_type()));
return client_to_aim_message;
}
} // namespace lens