diff --git a/browser/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc index a37795802975..14cc051a9870 100644 --- a/browser/brave_content_browser_client.cc +++ b/browser/brave_content_browser_client.cc @@ -222,6 +222,7 @@ using extensions::ChromeContentBrowserClientExtensionsPart; #if !BUILDFLAG(IS_ANDROID) #include "brave/browser/new_tab/new_tab_shows_navigation_throttle.h" #include "brave/browser/ui/geolocation/brave_geolocation_permission_tab_helper.h" +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.h" #include "brave/browser/ui/webui/brave_news_internals/brave_news_internals_ui.h" #include "brave/browser/ui/webui/brave_rewards/rewards_page_top_ui.h" #include "brave/browser/ui/webui/brave_rewards/rewards_panel_ui.h" @@ -233,6 +234,7 @@ using extensions::ChromeContentBrowserClientExtensionsPart; #include "brave/browser/ui/webui/brave_wallet/wallet_panel_ui.h" #include "brave/browser/ui/webui/new_tab_page/brave_new_tab_ui.h" #include "brave/browser/ui/webui/private_new_tab_page/brave_private_new_tab_ui.h" +#include "brave/components/brave_custom_notes/common/brave_custom_notes.mojom.h" #include "brave/components/brave_new_tab_ui/brave_new_tab_page.mojom.h" #include "brave/components/brave_news/common/brave_news.mojom.h" #include "brave/components/brave_news/common/features.h" @@ -805,6 +807,8 @@ void BraveContentBrowserClient::RegisterBrowserInterfaceBindersForFrame( brave_wallet::mojom::PanelHandlerFactory, WalletPanelUI>(map); content::RegisterWebUIControllerInterfaceBinder< brave_private_new_tab::mojom::PageHandler, BravePrivateNewTabUI>(map); + content::RegisterWebUIControllerInterfaceBinder< + brave_custom_notes::mojom::NotesPageHandler, BraveCustomNotesUI>(map); content::RegisterWebUIControllerInterfaceBinder< brave_shields::mojom::PanelHandlerFactory, ShieldsPanelUI>(map); if (base::FeatureList::IsEnabled( diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index 038d9bbf8c78..5040977b1711 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -147,6 +147,7 @@ void RegisterProfilePrefsForMigration( // Added 10/2022 registry->RegisterIntegerPref(kDefaultBrowserLaunchingCount, 0); + registry->RegisterListPref("custom_notes"); #endif brave_wallet::RegisterProfilePrefsForMigration(registry); diff --git a/browser/sources.gni b/browser/sources.gni index 7eb5d58e8855..f85ec69eef39 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -154,6 +154,8 @@ brave_chrome_browser_deps = [ "//brave/components/brave_perf_predictor/browser", "//brave/components/brave_private_new_tab_ui/common", "//brave/components/brave_private_new_tab_ui/common:mojom", + "//brave/components/brave_custom_notes/common", + "//brave/components/brave_custom_notes/common:mojom", "//brave/components/brave_referrals/browser", "//brave/components/brave_rewards/common", "//brave/components/brave_rewards/common:features", diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index ca2e002131cf..259237dd87e5 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -285,6 +285,12 @@ source_set("ui") { "webui/private_new_tab_page/brave_private_new_tab_page_handler.h", "webui/private_new_tab_page/brave_private_new_tab_ui.cc", "webui/private_new_tab_page/brave_private_new_tab_ui.h", + "webui/brave_custom_notes/brave_custom_notes_handler.cc", + "webui/brave_custom_notes/brave_custom_notes_handler.h", + "webui/brave_custom_notes/brave_custom_notes_ui.cc", + "webui/brave_custom_notes/brave_custom_notes_ui.h", + "webui/brave_custom_notes/brave_custom_notes_api_handler.cc", + "webui/brave_custom_notes/brave_custom_notes_api_handler.h", "webui/settings/brave_adblock_handler.cc", "webui/settings/brave_adblock_handler.h", "webui/settings/brave_appearance_handler.cc", @@ -1053,6 +1059,9 @@ source_set("ui") { "//brave/components/brave_private_new_tab_ui/common", "//brave/components/brave_private_new_tab_ui/common:mojom", "//brave/components/brave_private_new_tab_ui/resources/page:generated_resources", + "//brave/components/brave_custom_notes/common", + "//brave/components/brave_custom_notes/common:mojom", + "//brave/components/brave_custom_notes/resources/page:generated_resources", "//brave/components/brave_rewards/browser", "//brave/components/brave_rewards/resources/rewards_panel:brave_rewards_panel_generated", "//brave/components/brave_rewards/resources/tip_panel:tip_panel_generated", diff --git a/browser/ui/views/toolbar/brave_toolbar_view.cc b/browser/ui/views/toolbar/brave_toolbar_view.cc index d82212f0b98a..a4f1a30ecf0a 100644 --- a/browser/ui/views/toolbar/brave_toolbar_view.cc +++ b/browser/ui/views/toolbar/brave_toolbar_view.cc @@ -8,8 +8,10 @@ #include #include #include +#include #include "base/functional/bind.h" +#include "base/values.h" #include "brave/app/brave_command_ids.h" #include "brave/browser/brave_wallet/brave_wallet_context_utils.h" #include "brave/browser/ui/tabs/brave_tab_prefs.h" @@ -40,6 +42,10 @@ #include "ui/base/metadata/metadata_impl_macros.h" #include "ui/base/window_open_disposition_utils.h" #include "ui/events/event.h" +#include "ui/views/controls/button/label_button.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/scroll_view.h" +#include "ui/views/layout/box_layout.h" #include "ui/views/window/hit_test_utils.h" #if BUILDFLAG(ENABLE_AI_CHAT) @@ -63,12 +69,13 @@ constexpr int kLocationBarMaxWidth = 1080; double GetLocationBarMarginHPercent(int toolbar_width) { double location_bar_margin_h_pc = 0.07; - if (toolbar_width < 700) + if (toolbar_width < 700) { location_bar_margin_h_pc = 0; - else if (toolbar_width < 850) + } else if (toolbar_width < 850) { location_bar_margin_h_pc = 0.03; - else if (toolbar_width < 1000) + } else if (toolbar_width < 1000) { location_bar_margin_h_pc = 0.05; + } return location_bar_margin_h_pc; } @@ -136,6 +143,9 @@ BraveToolbarView::~BraveToolbarView() = default; void BraveToolbarView::Init() { ToolbarView::Init(); + pref_service_ = browser_->profile()->GetPrefs(); + LoadNotesFromLocalStorage(); + // This will allow us to move this window by dragging toolbar. // See brave_non_client_hit_test_helper.h views::SetHitTestComponent(this, HTCAPTION); @@ -242,8 +252,29 @@ void BraveToolbarView::Init() { ui::EF_MIDDLE_MOUSE_BUTTON); wallet_->UpdateImageAndText(); + side_panel_ = container_view->AddChildViewAt( + std::make_unique(browser()), + *container_view->GetIndexOf(GetAppMenuButton()) - 1); + + wallet_ = container_view->AddChildViewAt( + std::make_unique(GetAppMenuButton(), profile), + *container_view->GetIndexOf(GetAppMenuButton()) - 1); + wallet_->SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON | + ui::EF_MIDDLE_MOUSE_BUTTON); + wallet_->UpdateImageAndText(); + UpdateWalletButtonVisibility(); + custom_button_ = container_view->AddChildViewAt( + std::make_unique( + base::BindRepeating(&BraveToolbarView::ShowCustomPopup, + base::Unretained(this)), + u"Custom Button"), + *container_view->GetIndexOf(GetAppMenuButton()) - 1); + + custom_button_->SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON); + custom_button_->SetAccessibleName(u"Open Popup"); + custom_button_->SetVisible(true); #if BUILDFLAG(ENABLE_AI_CHAT) // Don't check policy status since we're going to // setup a watcher for policy pref. @@ -318,8 +349,9 @@ void BraveToolbarView::OnEditBookmarksEnabledChanged() { } void BraveToolbarView::OnShowBookmarksButtonChanged() { - if (!bookmark_) + if (!bookmark_) { return; + } UpdateBookmarkVisibility(); } @@ -334,13 +366,16 @@ void BraveToolbarView::OnLocationBarIsWideChanged() { void BraveToolbarView::OnThemeChanged() { ToolbarView::OnThemeChanged(); - if (!brave_initialized_) + if (!brave_initialized_) { return; + } - if (display_mode_ == DisplayMode::NORMAL && bookmark_) + if (display_mode_ == DisplayMode::NORMAL && bookmark_) { bookmark_->UpdateImageAndText(); - if (display_mode_ == DisplayMode::NORMAL && wallet_) + } + if (display_mode_ == DisplayMode::NORMAL && wallet_) { wallet_->UpdateImageAndText(); + } } views::View* BraveToolbarView::GetAnchorView( @@ -359,10 +394,12 @@ void BraveToolbarView::OnProfileWasRemoved(const base::FilePath& profile_path, void BraveToolbarView::LoadImages() { ToolbarView::LoadImages(); - if (bookmark_) + if (bookmark_) { bookmark_->UpdateImageAndText(); - if (wallet_) + } + if (wallet_) { wallet_->UpdateImageAndText(); + } } void BraveToolbarView::Update(content::WebContents* tab) { @@ -383,8 +420,9 @@ void BraveToolbarView::Update(content::WebContents* tab) { } void BraveToolbarView::UpdateBookmarkVisibility() { - if (!bookmark_) + if (!bookmark_) { return; + } DCHECK_EQ(DisplayMode::NORMAL, display_mode_); bookmark_->SetVisible(browser_defaults::bookmarks_enabled && @@ -420,8 +458,9 @@ void BraveToolbarView::ShowBookmarkBubble(const GURL& url, // or the location bar if there is no bookmark button // (i.e. in non-normal display mode). views::View* anchor_view = location_bar_; - if (bookmark_ && bookmark_->GetVisible()) + if (bookmark_ && bookmark_->GetVisible()) { anchor_view = bookmark_; + } std::unique_ptr delegate; delegate = @@ -450,8 +489,9 @@ void BraveToolbarView::ViewHierarchyChanged( void BraveToolbarView::Layout(PassKey) { LayoutSuperclass(this); - if (!brave_initialized_) + if (!brave_initialized_) { return; + } // ToolbarView::Layout() handles below modes. So just return. if (display_mode_ == DisplayMode::CUSTOM_TAB || @@ -491,6 +531,98 @@ void BraveToolbarView::ResetButtonBounds() { } } +void BraveToolbarView::OnAddNote(const std::u16string& new_note) { + if (!new_note.empty()) { + notes_.push_back(base::UTF16ToUTF8(new_note)); + SaveNotesToLocalStorage(); + LOG(INFO) << "Added new note: " << base::UTF16ToUTF8(new_note); + RefreshNotesView(); + } +} + +void BraveToolbarView::OnDeleteAllNotes() { + if (!notes_.empty()) { + LOG(INFO) << "Deleting all " << notes_.size() << " notes"; + notes_.clear(); + SaveNotesToLocalStorage(); + RefreshNotesView(); + } +} + +// Save notes to preferences or a file (acting as local storage) +void BraveToolbarView::SaveNotesToLocalStorage() { + base::Value::List note_list; + for (const auto& note : notes_) { + note_list.Append(base::Value(note)); + } + + if (pref_service_) { + pref_service_->Set("custom_notes", base::Value(std::move(note_list))); + pref_service_->CommitPendingWrite(); + LOG(INFO) << "Saved " << notes_.size() << " notes to local storage"; + } else { + LOG(ERROR) << "PrefService is not initialized."; + } +} + +void BraveToolbarView::DeleteNote(size_t index) { + if (index < notes_.size()) { + notes_.erase(notes_.begin() + index); + SaveNotesToLocalStorage(); + RefreshNotesView(); + } +} + +void BraveToolbarView::RefreshNotesView() { + LoadNotesFromLocalStorage(); + LOG(INFO) << "Refreshing notes view with " << notes_.size() << " notes"; + if (custom_dialog_) { + views::View* old_notes_view = + custom_dialog_->GetContentsView()->GetViewByID(kNotesViewID); + if (old_notes_view) { + views::View* new_notes_view = BuildNotesView(); + new_notes_view->SetID(kNotesViewID); + custom_dialog_->GetContentsView()->RemoveChildView(old_notes_view); + custom_dialog_->GetContentsView()->AddChildView(new_notes_view); + custom_dialog_->GetContentsView()->InvalidateLayout(); + LOG(INFO) << "Notes view refreshed in custom dialog"; + } else { + LOG(ERROR) << "Could not find old notes view to refresh"; + } + } else { + LOG(ERROR) << "Custom dialog is null during refresh"; + } +} + +void BraveToolbarView::LoadNotesFromLocalStorage() { + notes_.clear(); + if (pref_service_) { + const base::Value* note_list_value = + pref_service_->GetUserPrefValue("custom_notes"); + if (note_list_value && note_list_value->is_list()) { + const base::Value::List& note_list = note_list_value->GetList(); + for (const auto& note_value : note_list) { + if (note_value.is_string()) { + notes_.push_back(note_value.GetString()); + } + } + } + LOG(INFO) << "Loaded " << notes_.size() << " notes from local storage"; + } else { + LOG(ERROR) << "PrefService is not initialized."; + } +} + +void BraveToolbarView::OnNoteAdded(const std::u16string& new_note) { + notes_.push_back(base::UTF16ToUTF8(new_note)); + SaveNotesToLocalStorage(); // Save the updated notes list +} + +// Handling text input changes +void BraveToolbarView::ContentsChanged(views::Textfield* sender, + const std::u16string& new_contents) { + // note_input_ = new_contents; // Update note content as it is typed +} #if BUILDFLAG(ENABLE_AI_CHAT) void BraveToolbarView::UpdateAIChatButtonVisibility() { bool should_show = ai_chat::IsAllowedForContext(browser()->profile()) && @@ -521,5 +653,128 @@ void BraveToolbarView::UpdateWalletButtonVisibility() { wallet_->SetVisible(false); } +views::View* BraveToolbarView::BuildNotesView() { + auto container = std::make_unique(); + container->SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kVertical)); + + auto scroll_view = std::make_unique(); + auto notes_container = std::make_unique(); + notes_container->SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kVertical)); + + LOG(INFO) << "Building notes view with " << notes_.size() << " notes"; + + for (size_t i = 0; i < notes_.size(); ++i) { + auto note_view = std::make_unique(); + note_view->SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kHorizontal)); + + auto note_label = + std::make_unique(base::UTF8ToUTF16(notes_[i])); + note_view->AddChildView(std::move(note_label)); + + auto delete_button = std::make_unique( + base::BindRepeating(&BraveToolbarView::DeleteNote, + base::Unretained(this), i), + u"Delete"); + note_view->AddChildView(std::move(delete_button)); + + notes_container->AddChildView(std::move(note_view)); + LOG(INFO) << "Added note to view: " << notes_[i]; + } + + scroll_view->SetContents(std::move(notes_container)); + container->AddChildView(std::move(scroll_view)); + + return container.release(); +} + +void BraveToolbarView::ShowCustomPopup() { + LoadNotesFromLocalStorage(); + LOG(INFO) << "Showing custom popup with " << notes_.size() << " notes"; + + class CustomDialogDelegate : public views::DialogDelegateView { + public: + CustomDialogDelegate(BraveToolbarView* toolbar_view) + : toolbar_view_(toolbar_view) { + SetTitle(u"Custom Note Popup"); + SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kVertical)); + + auto text_field = std::make_unique(); + text_field_ = text_field.get(); + AddChildView(std::move(text_field)); + text_field_->SetPlaceholderText(u"Enter your note here"); + + auto add_note_button = std::make_unique( + base::BindRepeating(&CustomDialogDelegate::OnAddNote, + base::Unretained(this)), + u"Add Note"); + AddChildView(std::move(add_note_button)); + + AddChildView(std::make_unique( + base::BindRepeating(&BraveToolbarView::OnDeleteAllNotes, + base::Unretained(toolbar_view_)), + u"Delete All Notes")); + + notes_container_ = AddChildView(std::make_unique()); + notes_container_->SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kVertical)); + notes_container_->SetID(kNotesViewID); + + RefreshNotesView(); + + LOG(INFO) << "CustomDialogDelegate initialized with " + << toolbar_view_->notes_.size() << " notes"; + } + + bool ShouldShowCloseButton() const override { return true; } + + private: + void OnAddNote() { + if (text_field_ && toolbar_view_) { + std::u16string note_text = text_field_->GetText(); + toolbar_view_->OnAddNote(note_text); + text_field_->SetText(u""); // Clear the text field + RefreshNotesView(); + } + } + + void RefreshNotesView() { + notes_container_->RemoveAllChildViews(); + for (size_t i = 0; i < toolbar_view_->notes_.size(); ++i) { + auto note_view = std::make_unique(); + note_view->SetLayoutManager(std::make_unique( + views::BoxLayout::Orientation::kHorizontal)); + + auto note_label = std::make_unique( + base::UTF8ToUTF16(toolbar_view_->notes_[i])); + note_view->AddChildView(std::move(note_label)); + + auto delete_button = std::make_unique( + base::BindRepeating(&BraveToolbarView::DeleteNote, + base::Unretained(toolbar_view_), i), + u"Delete"); + note_view->AddChildView(std::move(delete_button)); + + notes_container_->AddChildView(std::move(note_view)); + } + notes_container_->InvalidateLayout(); + notes_container_->SchedulePaint(); + } + + raw_ptr toolbar_view_; + raw_ptr text_field_; + raw_ptr notes_container_; + }; + + custom_dialog_ = new CustomDialogDelegate(this); + views::Widget::CreateWindowWithContext( + custom_dialog_, browser_view_->GetWidget()->GetNativeWindow(), + gfx::Rect(300, 300)) + ->Show(); +} + BEGIN_METADATA(BraveToolbarView) END_METADATA diff --git a/browser/ui/views/toolbar/brave_toolbar_view.h b/browser/ui/views/toolbar/brave_toolbar_view.h index 497f0fcede65..42222038dbea 100644 --- a/browser/ui/views/toolbar/brave_toolbar_view.h +++ b/browser/ui/views/toolbar/brave_toolbar_view.h @@ -6,6 +6,8 @@ #ifndef BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_TOOLBAR_VIEW_H_ #define BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_TOOLBAR_VIEW_H_ +#include + #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "brave/components/ai_chat/core/common/buildflags/buildflags.h" @@ -13,7 +15,10 @@ #include "chrome/browser/profiles/profile_attributes_storage.h" #include "chrome/browser/ui/views/toolbar/toolbar_view.h" #include "components/prefs/pref_member.h" +#include "components/prefs/pref_service.h" #include "ui/base/metadata/metadata_header_macros.h" +#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/controls/textfield/textfield_controller.h" #if BUILDFLAG(ENABLE_AI_CHAT) class AIChatButton; @@ -31,6 +36,15 @@ class BraveToolbarView : public ToolbarView, public ProfileAttributesStorage::Observer { METADATA_HEADER(BraveToolbarView, ToolbarView) public: + void OnAddNote(const std::u16string& new_note); + + // Method to handle deleting all notes + void OnDeleteAllNotes(); + void DeleteNote(size_t index); + void RefreshNotesView(); + void ShowCustomPopup(); + void LoadNotesFromLocalStorage(); + explicit BraveToolbarView(Browser* browser, BrowserView* browser_view); ~BraveToolbarView() override; @@ -60,6 +74,8 @@ class BraveToolbarView : public ToolbarView, void ShowBookmarkBubble(const GURL& url, bool already_bookmarked) override; void ViewHierarchyChanged( const views::ViewHierarchyChangedDetails& details) override; + // void ContentsChanged(views::Textfield* sender, const std::u16string& + // new_contents) override; private: FRIEND_TEST_ALL_PREFIXES(BraveToolbarViewTest, ToolbarDividerNotShownTest); @@ -67,7 +83,18 @@ class BraveToolbarView : public ToolbarView, void LoadImages() override; void ResetLocationBarBounds(); void ResetButtonBounds(); + void SaveNotesToLocalStorage(); void UpdateBookmarkVisibility(); + void OnNoteAdded(const std::u16string& new_note); + void ContentsChanged(); + views::View* BuildNotesView(); + std::string note_input_; + std::vector notes_; + raw_ptr text_field_ = nullptr; + raw_ptr pref_service_ = nullptr; + raw_ptr custom_button_ = nullptr; + static constexpr int kNotesViewID = 1001; // Choose a unique ID + raw_ptr custom_dialog_ = nullptr; // ToolbarButtonProvider: views::View* GetAnchorView(std::optional type) override; @@ -76,6 +103,8 @@ class BraveToolbarView : public ToolbarView, void OnProfileAdded(const base::FilePath& profile_path) override; void OnProfileWasRemoved(const base::FilePath& profile_path, const std::u16string& profile_name) override; + void ContentsChanged(views::Textfield* sender, + const std::u16string& new_contents); #if BUILDFLAG(ENABLE_AI_CHAT) void UpdateAIChatButtonVisibility(); diff --git a/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.cc b/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.cc new file mode 100644 index 000000000000..890361a30400 --- /dev/null +++ b/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.cc @@ -0,0 +1,167 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * you can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.h" + +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "url/gurl.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/values.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "base/logging.h" + +// Traffic annotation for the network requests +constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation = net::DefineNetworkTrafficAnnotation( + "brave_custom_notes_api", + R"( + semantics { + sender: "Brave Custom Notes API" + description: "Handles summarizing and rephrasing notes." + trigger: "User interacts with note features to summarize or rephrase content." + data: "User-provided note content." + destination: OTHER + } + policy { + cookies_allowed: NO + setting: "No user settings needed." + policy_exception_justification: "Not required." + })"); + +// API Endpoints +const char kSummarizeAPIEndpoint[] = "https://ping.openai.azure.com/openai/deployments/ai-summariser-gpt-35-turbo/chat/completions?api-version=2024-02-15-preview"; +const char kRephraseAPIEndpoint[] = "https://ping.openai.azure.com/openai/deployments/ai-summariser-gpt-35-turbo/chat/completions?api-version=2024-02-15-preview"; +const char kAPIKey[] = "b487c4dc0bc1490e801cb6220cf04039"; + +// Constructor implementation +BraveCustomNotesAPIHandler::BraveCustomNotesAPIHandler( + scoped_refptr url_loader_factory) + : url_loader_factory_(std::move(url_loader_factory)) {} + +// Destructor implementation +BraveCustomNotesAPIHandler::~BraveCustomNotesAPIHandler() = default; + +namespace { + +// Create network request loader +std::unique_ptr CreateLoader( + const GURL& api_url, + const std::string& content, + const std::string& mode) { + + LOG(INFO) << "Creating loader for URL: " << api_url.spec(); + LOG(INFO) << "Request content to send: " << content; + + // Create the resource request + auto resource_request = std::make_unique(); + resource_request->url = api_url; + resource_request->method = "POST"; + resource_request->headers.SetHeader("Content-Type", "application/json"); + + // Add API key header (as provided in the CURL command) + resource_request->headers.SetHeader("api-key", kAPIKey); + + // Create the URL loader + auto loader = network::SimpleURLLoader::Create( + std::move(resource_request), + kTrafficAnnotation); + + // Set timeout duration + constexpr base::TimeDelta kRequestTimeout = base::Seconds(60); + loader->SetTimeoutDuration(kRequestTimeout); + + // Building JSON body for the request + base::Value::Dict system_message; + if (mode == "summarize") { + system_message.Set("role", "system"); + system_message.Set("content", "You are a Ping AI summariser whose job is to create a summary of the text in a webpage. You help the user by creating bullet points which make it easier to get a gist of the webpage he is viewing. You also add emojis in the bullet points to make it more engaging. You create short to the point summaries for the user. Each point must not be more than a short sentence."); + } else if (mode == "rephrase") { + system_message.Set("role", "system"); + system_message.Set("content", "You are an AI designed to rephrase text. Please rephrase the provided content to make it clearer and more concise."); + } + + base::Value::Dict user_message; + user_message.Set("role", "user"); + user_message.Set("content", content); + + base::Value::List messages; + messages.Append(std::move(system_message)); + messages.Append(std::move(user_message)); + + base::Value::Dict request_dict; + request_dict.Set("messages", std::move(messages)); + request_dict.Set("max_tokens", 800); + request_dict.Set("temperature", 0.5); + request_dict.Set("frequency_penalty", 0); + request_dict.Set("presence_penalty", 0); + request_dict.Set("top_p", 0.95); + + std::string request_body; + base::JSONWriter::Write(request_dict, &request_body); + + LOG(INFO) << "JSON request body: " << request_body; + + // Attach the JSON body to the loader + loader->AttachStringForUpload(request_body, "application/json"); + + // Set retry options in case of network errors + loader->SetRetryOptions( + 3, // max retries + network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE); + + return loader; +} + + +} // namespace + + +void BraveCustomNotesAPIHandler::CallSummarizeAPI( + const std::string& content, + base::OnceCallback callback) { + auto loader = CreateLoader(GURL(kSummarizeAPIEndpoint), content, "summarize"); + + loader->DownloadToString( + url_loader_factory_.get(), + base::BindOnce( + [](std::unique_ptr loader, + base::OnceCallback callback, + std::unique_ptr response_body) { + if (response_body && !response_body->empty()) { + LOG(INFO) << "Summarize API response: " << *response_body; + std::move(callback).Run(*response_body); + } else { + LOG(ERROR) << "Failed to get response from Summarize API."; + std::move(callback).Run(""); + } + }, + std::move(loader), std::move(callback)), + 1024 * 1024); // Max response body size +} + +void BraveCustomNotesAPIHandler::CallRephraseAPI( + const std::string& content, + base::OnceCallback callback) { + auto loader = CreateLoader(GURL(kRephraseAPIEndpoint), content, "rephrase"); + + loader->DownloadToString( + url_loader_factory_.get(), + base::BindOnce( + [](std::unique_ptr loader, + base::OnceCallback callback, + std::unique_ptr response_body) { + if (response_body && !response_body->empty()) { + LOG(INFO) << "Rephrase API response: " << *response_body; + std::move(callback).Run(*response_body); + } else { + LOG(ERROR) << "Failed to get response from Rephrase API."; + std::move(callback).Run(""); + } + }, + std::move(loader), std::move(callback)), + 1024 * 1024); // Max response body size +} + diff --git a/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.h b/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.h new file mode 100644 index 000000000000..e4ab786453a0 --- /dev/null +++ b/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_API_HANDLER_H_ +#define BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_API_HANDLER_H_ + +#include +#include +#include + +#include "base/memory/weak_ptr.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" + +class BraveCustomNotesAPIHandler { + public: + // Constructor that initializes the URL loader factory + explicit BraveCustomNotesAPIHandler( + scoped_refptr url_loader_factory); + + // Destructor + ~BraveCustomNotesAPIHandler(); + + // Delete copy constructor and assignment operator to avoid object copying + BraveCustomNotesAPIHandler(const BraveCustomNotesAPIHandler&) = delete; + BraveCustomNotesAPIHandler& operator=(const BraveCustomNotesAPIHandler&) = delete; + + void CallSummarizeAPI( + const std::string& content, + base::OnceCallback callback); + +void CallRephraseAPI( + const std::string& content, + base::OnceCallback callback); + + private: + // Private method to handle the success response from Summarize API + void OnSummarizeAPIResponse(network::SimpleURLLoader* loader, + std::string* summary, + std::unique_ptr response_body); + + // Private method to handle the success response from Rephrase API + void OnRephraseAPIResponse(network::SimpleURLLoader* loader, + std::string* rephrased_content, + std::unique_ptr response_body); + + // URL loader factory to handle network requests + scoped_refptr url_loader_factory_; + + // A vector to hold all active URL loaders to manage their lifetimes + std::vector> loaders_; + + // Weak pointer factory to safely manage asynchronous callbacks + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +#endif // BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_API_HANDLER_H_ diff --git a/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.cc b/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.cc new file mode 100644 index 000000000000..b7a9476d8435 --- /dev/null +++ b/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.cc @@ -0,0 +1,320 @@ +/* Copyright(c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * you can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.h" +#include "base/json/json_reader.h" +#include "base/values.h" + +#include +#include +#include +#include +#include + +#include "base/functional/bind.h" +#include "base/strings/utf_string_conversions.h" +#include "brave/components/search_engines/brave_prepopulated_engines.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_finder.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "content/public/browser/page_navigator.h" +#include "content/public/browser/web_contents.h" + +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_api_handler.h" + +namespace { +const char kCustomNotesKey[] = "custom_notes"; +} // namespace + +// Helper class for handling API Callbacks +class BraveCustomNotesPageHandler::APICallbackHelper { + public: + explicit APICallbackHelper(base::WeakPtr handler, + int32_t note_id) + : handler_(handler), + note_id_(note_id) {} + + private: + base::WeakPtr handler_; + int32_t note_id_; +}; + +// Constructor for initialising dependencies and loading notes from preferences +BraveCustomNotesPageHandler::BraveCustomNotesPageHandler( + Profile* profile, + content::WebContents* web_contents, + mojo::PendingReceiver receiver, + scoped_refptr url_loader_factory) + : profile_(profile), + web_contents_(web_contents), + receiver_(this, std::move(receiver)), + api_handler_(std::make_unique(url_loader_factory)), + weak_ptr_factory_(this) { + LoadNotesFromPrefs(); // Loads existing notes at startup time +} + +BraveCustomNotesPageHandler::~BraveCustomNotesPageHandler() = default; + +// Setting up the communication with the client page +void BraveCustomNotesPageHandler::SetClientPage( + mojo::PendingRemote page) { + page_.Bind(std::move(page)); +} + +void BraveCustomNotesPageHandler::CreateNote(const std::string& title, + const std::string& content, + CreateNoteCallback callback) { + auto new_note = brave_custom_notes::mojom::Note::New(); + new_note->id = notes_.empty() ? 1 : notes_.back()->id + 1; + new_note->title = title; + new_note->content = content; + + notes_.push_back(std::move(new_note)); + SaveNotesToPrefs(); + + // Notifies client of success + std::move(callback).Run(true); + + if (page_) { + UpdatePageWithNotes(); + } +} + +void BraveCustomNotesPageHandler::AddNote(const std::string& content, + AddNoteCallback callback) { + auto new_note = brave_custom_notes::mojom::Note::New(); + new_note->id = notes_.empty() ? 1 : notes_.back()->id + 1; + new_note->title = "New Note"; + new_note->content = content; + + notes_.push_back(std::move(new_note)); + SaveNotesToPrefs(); + + std::move(callback).Run(true); + + if (page_) { + UpdatePageWithNotes(); + } +} + +void BraveCustomNotesPageHandler::EditNote(int32_t note_id, + const std::string& new_title, + const std::string& new_content, + EditNoteCallback callback) { + auto it = std::find_if(notes_.begin(), notes_.end(), + [note_id](const auto& note) { return note->id == note_id; }); + + if (it != notes_.end()) { + (*it)->title = new_title; + (*it)->content = new_content; + SaveNotesToPrefs(); + std::move(callback).Run(true); + + if (page_) { + UpdatePageWithNotes(); + } + } else { + std::move(callback).Run(false); + } +} + +std::optional BraveCustomNotesPageHandler::ExtractContentFromJson( + const std::string& json_response) { + // Parse the JSON response + std::optional parsed_json = base::JSONReader::Read(json_response); + if (!parsed_json || !parsed_json->is_dict()) { + LOG(ERROR) << "Failed to parse JSON or response is not a dictionary."; + return std::nullopt; + } + + const base::Value::Dict& json_dict = parsed_json->GetDict(); + const base::Value::List* choices = json_dict.FindList("choices"); + + if (!choices || choices->empty()) { + LOG(ERROR) << "No choices field found or it is empty."; + return std::nullopt; + } + + const base::Value::Dict* first_choice = (*choices)[0].GetIfDict(); + if (!first_choice) { + LOG(ERROR) << "First choice is not a dictionary."; + return std::nullopt; + } + + const base::Value::Dict* message = first_choice->FindDict("message"); + if (!message) { + LOG(ERROR) << "No message field found in the first choice."; + return std::nullopt; + } + + const std::string* content = message->FindString("content"); + if (!content) { + LOG(ERROR) << "No content field found in the message."; + return std::nullopt; + } + + return *content; +} + +void BraveCustomNotesPageHandler::SummarizeNoteContent( + int32_t note_id, SummarizeNoteContentCallback callback) { + auto it = std::find_if(notes_.begin(), notes_.end(), + [note_id](const auto& note) { return note->id == note_id; }); + + if (it != notes_.end()) { + LOG(INFO) << "Starting async summarize for note ID: " << note_id; + + api_handler_->CallSummarizeAPI( + (*it)->content, + base::BindOnce( + [](int32_t note_id, + SummarizeNoteContentCallback callback, + BraveCustomNotesPageHandler* handler, + const std::string& json_response) { + LOG(INFO) << "Raw JSON response for Summarizer: " << json_response; + + std::optional content = handler->ExtractContentFromJson(json_response); + + if (content) { + std::move(callback).Run(true, *content); + } else { + LOG(WARNING) << "Summarization failed for note ID: " << note_id; + std::move(callback).Run(false, "Failed to extract summary"); + } + }, + note_id, std::move(callback), this)); + } else { + LOG(WARNING) << "Note ID not found for summarize: " << note_id; + std::move(callback).Run(false, "Note not found"); + } +} + +void BraveCustomNotesPageHandler::RephraseNoteContent( + int32_t note_id, RephraseNoteContentCallback callback) { + auto it = std::find_if(notes_.begin(), notes_.end(), + [note_id](const auto& note) { return note->id == note_id; }); + + if (it != notes_.end()) { + LOG(INFO) << "Starting async rephrase for note ID: " << note_id; + + api_handler_->CallRephraseAPI( + (*it)->content, + base::BindOnce( + [](int32_t note_id, + RephraseNoteContentCallback callback, + BraveCustomNotesPageHandler* handler, + const std::string& json_response) { + LOG(INFO) << "Raw JSON response for Rephraser: " << json_response; + + std::optional content = handler->ExtractContentFromJson(json_response); + + if (content) { + std::move(callback).Run(true, *content); + } else { + LOG(WARNING) << "Rephrasing failed for note ID: " << note_id; + std::move(callback).Run(false, "Failed to extract rephrased content"); + } + }, + note_id, std::move(callback), this)); + } else { + LOG(WARNING) << "Note ID not found for rephrase: " << note_id; + std::move(callback).Run(false, "Note not found"); + } +} + +void BraveCustomNotesPageHandler::DeleteNote(int32_t note_id, + DeleteNoteCallback callback) { + auto it = std::remove_if( + notes_.begin(), notes_.end(), + [note_id](const auto& note) { return note->id == note_id; }); + + if (it != notes_.end()) { + notes_.erase(it, notes_.end()); + SaveNotesToPrefs(); + std::move(callback).Run(true); + + if (page_) { + UpdatePageWithNotes(); + } + } else { + std::move(callback).Run(false); + } +} + +void BraveCustomNotesPageHandler::GetNoteContent( + int32_t note_id, + GetNoteContentCallback callback) { + auto it = std::find_if(notes_.begin(), notes_.end(), + [note_id](const auto& note) { return note->id == note_id; }); + + if (it != notes_.end()) { + std::move(callback).Run(true, (*it)->content); + } else { + std::move(callback).Run(false, std::string()); + } +} + +void BraveCustomNotesPageHandler::GetAllNotes(GetAllNotesCallback callback) { + std::vector notes_copy; + notes_copy.reserve(notes_.size()); + for (const auto& note : notes_) { + notes_copy.push_back(note.Clone()); + } + std::move(callback).Run(std::move(notes_copy)); +} + +void BraveCustomNotesPageHandler::LoadNotesFromPrefs() { + notes_.clear(); + const base::Value::List& stored_notes = + profile_->GetPrefs()->GetList(kCustomNotesKey); + + for (const auto& note_value : stored_notes) { + if (!note_value.is_dict()) { + continue; + } + + const auto& dict = note_value.GetDict(); + auto note = brave_custom_notes::mojom::Note::New(); + + note->id = dict.FindInt("id").value_or(0); + const std::string* title = dict.FindString("title"); + note->title = title ? *title : ""; + const std::string* content = dict.FindString("content"); + note->content = content ? *content : ""; + + if (note->id > 0) { // Only add valid notes + notes_.push_back(std::move(note)); + } + } +} + +void BraveCustomNotesPageHandler::SaveNotesToPrefs() { + ScopedListPrefUpdate update(profile_->GetPrefs(), kCustomNotesKey); + base::Value::List& prefs_notes = update.Get(); + prefs_notes.clear(); + + for (const auto& note : notes_) { + base::Value::Dict note_dict; + note_dict.Set("id", note->id); + note_dict.Set("title", note->title); + note_dict.Set("content", note->content); + prefs_notes.Append(std::move(note_dict)); + } +} + +void BraveCustomNotesPageHandler::UpdatePageWithNotes() { + if (!page_) { + return; + } + + std::vector notes_copy; + notes_copy.reserve(notes_.size()); + for (const auto& note : notes_) { + notes_copy.push_back(note.Clone()); + } + page_->OnNotesUpdated(std::move(notes_copy)); +} \ No newline at end of file diff --git a/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.h b/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.h new file mode 100644 index 000000000000..2314880bbffa --- /dev/null +++ b/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.h @@ -0,0 +1,88 @@ +// Copyright(c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_HANDLER_H_ +#define BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_HANDLER_H_ + +#include +#include +#include +#include + +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "brave/components/brave_custom_notes/common/brave_custom_notes.mojom.h" + +class Profile; +class BraveCustomNotesAPIHandler; + +namespace content { +class WebContents; +} + +class BraveCustomNotesPageHandler + : public brave_custom_notes::mojom::NotesPageHandler { + public: + BraveCustomNotesPageHandler( + Profile* profile, + content::WebContents* web_contents, + mojo::PendingReceiver receiver, + scoped_refptr url_loader_factory); + + BraveCustomNotesPageHandler(const BraveCustomNotesPageHandler&) = delete; + BraveCustomNotesPageHandler& operator=(const BraveCustomNotesPageHandler&) = delete; + + ~BraveCustomNotesPageHandler() override; + + // brave_custom_notes::mojom::NotesPageHandler: + void SetClientPage( + mojo::PendingRemote page) override; + void CreateNote(const std::string& title, + const std::string& content, + CreateNoteCallback callback) override; + void AddNote(const std::string& content, AddNoteCallback callback) override; + void EditNote(int32_t note_id, + const std::string& new_title, + const std::string& new_content, + EditNoteCallback callback) override; + void DeleteNote(int32_t note_id, DeleteNoteCallback callback) override; + void GetNoteContent(int32_t note_id, + GetNoteContentCallback callback) override; + void GetAllNotes(GetAllNotesCallback callback) override; + void SummarizeNoteContent(int32_t note_id, + SummarizeNoteContentCallback callback) override; + void RephraseNoteContent(int32_t note_id, + RephraseNoteContentCallback callback) override; + std::optional ExtractContentFromJson(const std::string& json_response); + + private: + class APICallbackHelper; + + void LoadNotesFromPrefs(); + void SaveNotesToPrefs(); + void UpdatePageWithNotes(); + + raw_ptr profile_; + raw_ptr web_contents_; + mojo::Receiver receiver_; + mojo::Remote page_; + std::unique_ptr api_handler_; + + std::vector notes_; + + std::map pending_summarize_callbacks_; + std::map> pending_summaries_; + + std::map pending_rephrase_callbacks_; + std::map> pending_rephrased_; + + base::WeakPtrFactory weak_ptr_factory_; +}; + +#endif // BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_HANDLER_H_ \ No newline at end of file diff --git a/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.cc b/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.cc new file mode 100644 index 000000000000..0eeb0f22cb44 --- /dev/null +++ b/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.h" + +#include + +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_handler.h" +#include "brave/browser/ui/webui/brave_webui_source.h" +#include "brave/components/brave_custom_notes/common/constants.h" +#include "brave/components/brave_custom_notes/resources/page/grit/brave_custom_notes_generated_map.h" +#include "brave/components/l10n/common/localization_util.h" +#include "chrome/browser/profiles/profile.h" +#include "components/grit/brave_components_resources.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "components/strings/grit/components_strings.h" +#include "content/public/browser/web_ui_data_source.h" + +BraveCustomNotesUI::BraveCustomNotesUI(content::WebUI* web_ui, + const std::string& name) + : ui::MojoWebUIController(web_ui, false) { + Profile* profile = Profile::FromWebUI(web_ui); + content::WebUIDataSource* source = CreateAndAddWebUIDataSource( + web_ui, name, kBraveCustomNotesGenerated, kBraveCustomNotesGeneratedSize, + IDR_BRAVE_CUSTOM_NOTES_HTML); + + for (const auto& str : brave_custom_notes::kLocalizedStrings) { + std::u16string l10n_str = + brave_l10n::GetLocalizedResourceUTF16String(str.id); + source->AddString(str.name, l10n_str); + } + + source->AddBoolean("isWindowTor", profile->IsTor()); + AddBackgroundColorToSource(source, web_ui->GetWebContents()); +} + +BraveCustomNotesUI::~BraveCustomNotesUI() = default; + +void BraveCustomNotesUI::BindInterface( + mojo::PendingReceiver receiver) { + custom_notes_handler_ = std::make_unique( + Profile::FromWebUI(web_ui()), + web_ui()->GetWebContents(), + std::move(receiver), // Pass receiver first + Profile::FromWebUI(web_ui())->GetURLLoaderFactory()); // URL loader factory last +} + +WEB_UI_CONTROLLER_TYPE_IMPL(BraveCustomNotesUI) \ No newline at end of file diff --git a/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.h b/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.h new file mode 100644 index 000000000000..002fd7d0684f --- /dev/null +++ b/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.h @@ -0,0 +1,36 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_UI_H_ +#define BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_UI_H_ + +#include +#include + +#include "brave/components/brave_custom_notes/common/brave_custom_notes.mojom.h" +#include "content/public/browser/web_ui_controller.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "ui/webui/mojo_web_ui_controller.h" + +class BraveCustomNotesPageHandler; // Forward declaration + +class BraveCustomNotesUI : public ui::MojoWebUIController { + public: + BraveCustomNotesUI(content::WebUI* web_ui, const std::string& name); + ~BraveCustomNotesUI() override; + + BraveCustomNotesUI(const BraveCustomNotesUI&) = delete; + BraveCustomNotesUI& operator=(const BraveCustomNotesUI&) = delete; + + void BindInterface( + mojo::PendingReceiver receiver); + + private: + std::unique_ptr custom_notes_handler_; + + WEB_UI_CONTROLLER_TYPE_DECL(); +}; + +#endif // BRAVE_BROWSER_UI_WEBUI_BRAVE_CUSTOM_NOTES_BRAVE_CUSTOM_NOTES_UI_H_ \ No newline at end of file diff --git a/browser/ui/webui/brave_web_ui_controller_factory.cc b/browser/ui/webui/brave_web_ui_controller_factory.cc index dd7e8c39c815..c9a4bf463a96 100644 --- a/browser/ui/webui/brave_web_ui_controller_factory.cc +++ b/browser/ui/webui/brave_web_ui_controller_factory.cc @@ -13,6 +13,9 @@ #include "base/no_destructor.h" #include "brave/browser/brave_rewards/rewards_util.h" #include "brave/browser/ethereum_remote_client/buildflags/buildflags.h" +#include "brave/browser/ui/webui/brave_adblock_internals_ui.h" +#include "brave/browser/ui/webui/brave_adblock_ui.h" +#include "brave/browser/ui/webui/brave_custom_notes/brave_custom_notes_ui.h" #include "brave/browser/ui/webui/brave_rewards/rewards_page_ui.h" #include "brave/browser/ui/webui/brave_rewards/rewards_web_ui_utils.h" #include "brave/browser/ui/webui/brave_rewards_internals_ui.h" @@ -27,6 +30,7 @@ #include "brave/components/playlist/common/buildflags/buildflags.h" #include "brave/components/skus/common/features.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave_web_ui_controller_factory.h" #include "build/build_config.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/url_constants.h" @@ -92,6 +96,10 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) { web_ui->GetWebContents()->GetBrowserContext()); if (host == kSkusInternalsHost) { return new SkusInternalsUI(web_ui, url.host()); + } else if (host == kBraveCustomNotesHost) { + return new BraveCustomNotesUI(web_ui, url.host()); + } else if (host == kSkusInternalsHost) { + return new SkusInternalsUI(web_ui, url.host()); #if !BUILDFLAG(IS_ANDROID) } else if (host == kWalletPageHost && brave_wallet::IsAllowedForContext(profile)) { @@ -201,13 +209,53 @@ WebUIFactoryFunction GetWebUIFactoryFunction(WebUI* web_ui, ai_rewriter::features::IsAIRewriterEnabled()) || #endif url.host_piece() == kRewardsPageHost || - url.host_piece() == kRewardsInternalsHost) { + url.host_piece() == kRewardsInternalsHost || + url.host_piece() == kBraveCustomNotesHost) { return &NewWebUI; } return nullptr; } +// bool ChromeWebUIControllerFactory::IsWebUIAllowedToMakeNetworkRequests( +// const url::Origin& url) { +// std::string host = url.host_piece(); +// if (host == kBraveCustomNotesHost) { +// return true; +// } +// return ChromeWebUIControllerFactory::IsWebUIAllowedToMakeNetworkRequests(url); +// } + + +// bool ShouldBlockRewardsWebUI(content::BrowserContext* browser_context, +// const GURL& url) { +// if (url.host_piece() != kRewardsPageHost && +// #if !BUILDFLAG(IS_ANDROID) +// url.host_piece() != kBraveRewardsPanelHost && +// url.host_piece() != kBraveTipPanelHost && +// #endif // !BUILDFLAG(IS_ANDROID) +// url.host_piece() != kRewardsInternalsHost) { +// return false; +// } + +// Profile* profile = Profile::FromBrowserContext(browser_context); +// if (profile) { +// if (!brave_rewards::IsSupportedForProfile( +// profile, url.host_piece() == kRewardsPageHost +// ? brave_rewards::IsSupportedOptions::kSkipRegionCheck +// : brave_rewards::IsSupportedOptions::kNone)) { +// return true; +// } +// #if BUILDFLAG(IS_ANDROID) +// auto* prefs = profile->GetPrefs(); +// if (prefs && prefs->GetBoolean(kSafetynetCheckFailed)) { +// return true; +// } +// #endif // BUILDFLAG(IS_ANDROID) +// } +// return false; +// } + #if BUILDFLAG(IS_ANDROID) bool ShouldBlockWalletWebUI(content::BrowserContext* browser_context, const GURL& url) { diff --git a/browser/ui/webui/private_new_tab_page/brave_private_new_tab_page_handler.cc b/browser/ui/webui/private_new_tab_page/brave_private_new_tab_page_handler.cc index 17bb3b7b2520..333b59c2eac5 100644 --- a/browser/ui/webui/private_new_tab_page/brave_private_new_tab_page_handler.cc +++ b/browser/ui/webui/private_new_tab_page/brave_private_new_tab_page_handler.cc @@ -39,15 +39,17 @@ BravePrivateNewTabPageHandler::BravePrivateNewTabPageHandler( receiver_(this, std::move(receiver)) { #if BUILDFLAG(ENABLE_TOR) tor_launcher_factory_ = TorLauncherFactory::GetInstance(); - if (tor_launcher_factory_) + if (tor_launcher_factory_) { tor_launcher_factory_->AddObserver(this); + } #endif } BravePrivateNewTabPageHandler::~BravePrivateNewTabPageHandler() { #if BUILDFLAG(ENABLE_TOR) - if (tor_launcher_factory_) + if (tor_launcher_factory_) { tor_launcher_factory_->RemoveObserver(this); + } #endif } @@ -83,11 +85,13 @@ void BravePrivateNewTabPageHandler::GetIsTorConnected( GetIsTorConnectedCallback callback) { bool is_connected = false; #if BUILDFLAG(ENABLE_TOR) - if (tor_launcher_factory_) + if (tor_launcher_factory_) { is_connected = tor_launcher_factory_->IsTorConnected(); + } #endif - else + else { is_connected = false; + } std::move(callback).Run(is_connected); } @@ -134,8 +138,9 @@ void BravePrivateNewTabPageHandler::GoToBraveSupport() { web_contents = browser->tab_strip_model()->GetActiveWebContents(); } - if (!web_contents) + if (!web_contents) { web_contents = web_contents_; + } web_contents->OpenURL( content::OpenURLParams(GURL("https://ping-browser.com/faqs-and-help"), @@ -170,8 +175,9 @@ void BravePrivateNewTabPageHandler::OnTorInitializing( } void BravePrivateNewTabPageHandler::OnTorCircuitTimer(ConnectionStatus status) { - if (!page_) + if (!page_) { return; + } if (status == ConnectionStatus::kConnectionSlow) { // First time shot of stuck_timer_ means that 'connection is slow' we diff --git a/chromium_src/chrome/common/webui_url_constants.cc b/chromium_src/chrome/common/webui_url_constants.cc index b54d8ae46e11..da853f0af490 100644 --- a/chromium_src/chrome/common/webui_url_constants.cc +++ b/chromium_src/chrome/common/webui_url_constants.cc @@ -9,9 +9,8 @@ #define kChromeUIAttributionInternalsHost \ kChromeUIAttributionInternalsHost, kAdblockHost, kAdblockInternalsHost, \ kRewardsPageHost, kRewardsInternalsHost, kWelcomeHost, kWalletPageHost, \ - kTorInternalsHost, kSkusInternalsHost + kTorInternalsHost, kSkusInternalsHost, kBraveCustomNotesHost #include "src/chrome/common/webui_url_constants.cc" #undef kChromeUIAttributionInternalsHost - diff --git a/components/brave_custom_notes/common/BUILD.gn b/components/brave_custom_notes/common/BUILD.gn new file mode 100644 index 000000000000..2cfa57c045c3 --- /dev/null +++ b/components/brave_custom_notes/common/BUILD.gn @@ -0,0 +1,18 @@ +import("//mojo/public/tools/bindings/mojom.gni") + +static_library("common") { + sources = [ + "constants.h", + ] + + deps = [ + "//base", + "//brave/components/resources:strings", + "//ui/base", + ] +} + +mojom("mojom") { + sources = [ "brave_custom_notes.mojom" ] + public_deps = [ "//mojo/public/mojom/base" ] +} diff --git a/components/brave_custom_notes/common/brave_custom_notes.mojom b/components/brave_custom_notes/common/brave_custom_notes.mojom new file mode 100644 index 000000000000..c1ebb34e157f --- /dev/null +++ b/components/brave_custom_notes/common/brave_custom_notes.mojom @@ -0,0 +1,35 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at http://mozilla.org/MPL/2.0/. + +module brave_custom_notes.mojom; + +// Browser-side handler for requests from the WebUI page related to custom notes. +interface NotesPageHandler { + SetClientPage(pending_remote page); + CreateNote(string title, string content) => (int32 note_id); + EditNote(int32 note_id, string new_title, string new_content) => (bool success); + DeleteNote(int32 note_id) => (bool success); + GetNoteContent(int32 note_id) => (bool success, string content); + GetAllNotes() => (array notes); + AddNote(string content) => (bool success); + SummarizeNoteContent(int32 note_id) => (bool success, string summary); + RephraseNoteContent(int32 note_id) => (bool success, string rephrased_content); +}; + +// Struct that defines the properties of a note. +struct Note { + int32 id; + string title; + string content; + string timestamp; +}; + +// WebUI page that receives updates about the note-taking process. +interface CustomNotesPage { + OnNoteCreated(int32 note_id, string title); + OnNoteEdited(int32 note_id, string new_title); + OnNoteDeleted(int32 note_id); + OnNotesUpdated(array notes); +}; diff --git a/components/brave_custom_notes/common/constants.h b/components/brave_custom_notes/common/constants.h new file mode 100644 index 000000000000..fcbdc9c0f21d --- /dev/null +++ b/components/brave_custom_notes/common/constants.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_CUSTOM_NOTES_H_ +#define BRAVE_COMPONENTS_BRAVE_CUSTOM_NOTES_H_ + +#include "components/grit/brave_components_strings.h" +#include "ui/base/webui/web_ui_util.h" + +namespace brave_custom_notes { + +inline constexpr webui::LocalizedString kLocalizedStrings[] = { + {"notesHeaderTitle", IDS_BRAVE_CUSTOM_NOTES_HEADER_TITLE}, + {"notesHeaderDesc", IDS_BRAVE_CUSTOM_NOTES_HEADER_DESCRIPTION}, + {"createNoteButton", IDS_BRAVE_CUSTOM_NOTES_CREATE_NOTE_BUTTON}, + {"editNoteButton", IDS_BRAVE_CUSTOM_NOTES_EDIT_NOTE_BUTTON}, + {"deleteNoteButton", IDS_BRAVE_CUSTOM_NOTES_DELETE_NOTE_BUTTON}, + {"notePlaceholderTitle", IDS_BRAVE_CUSTOM_NOTES_PLACEHOLDER_TITLE}, + {"notePlaceholderContent", IDS_BRAVE_CUSTOM_NOTES_PLACEHOLDER_CONTENT}, + {"noteSavedMessage", IDS_BRAVE_CUSTOM_NOTES_NOTE_SAVED_MESSAGE}, + {"noteDeletedMessage", IDS_BRAVE_CUSTOM_NOTES_NOTE_DELETED_MESSAGE}, + {"notesListTitle", IDS_BRAVE_CUSTOM_NOTES_LIST_TITLE}, + {"noNotesMessage", IDS_BRAVE_CUSTOM_NOTES_NO_NOTES_MESSAGE}, + {"notesSearchPlaceholder", IDS_BRAVE_CUSTOM_NOTES_SEARCH_PLACEHOLDER}, +}; + +} // namespace brave_custom_notes + +#endif // BRAVE_COMPONENTS_BRAVE_CUSTOM_NOTES_H_ diff --git a/components/brave_custom_notes/resources/page/BUILD.gn b/components/brave_custom_notes/resources/page/BUILD.gn new file mode 100644 index 000000000000..58b9e176ac64 --- /dev/null +++ b/components/brave_custom_notes/resources/page/BUILD.gn @@ -0,0 +1,17 @@ +import("//brave/components/common/typescript.gni") + +transpile_web_ui("brave_custom_notes") { + entry_points = [[ + "custom_notes", + rebase_path("./custom_notes.tsx"), + ]] + resource_name = "brave_custom_notes" + deps = [ "//brave/components/brave_custom_notes/common:mojom_js" ] +} + +pack_web_resources("generated_resources") { + resource_name = "brave_custom_notes" + output_dir = + "$root_gen_dir/brave/components/brave_custom_notes/resources/page/" + deps = [ ":brave_custom_notes" ] +} \ No newline at end of file diff --git a/components/brave_custom_notes/resources/page/api/brave_custom_notes_handler.ts b/components/brave_custom_notes/resources/page/api/brave_custom_notes_handler.ts new file mode 100644 index 000000000000..e4cbe3a4a4a8 --- /dev/null +++ b/components/brave_custom_notes/resources/page/api/brave_custom_notes_handler.ts @@ -0,0 +1,38 @@ +/* Copyright (c) 2022 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ +// @ts-nocheck + import * as BraveCustomNotes from 'gen/brave/components/brave_custom_notes/common/brave_custom_notes.mojom.m.js' + + // Provide access to all the generated types + export * from 'gen/brave/components/brave_custom_notes/common/brave_custom_notes.mojom.m.js' + + interface API { + pageHandler: BraveCustomNotes.PageHandlerRemote + callbackRouter: BraveCustomNotes.CustomNotesPageCallbackRouter + } + + let apiInstance: API + + class CustomNotesAPI implements API { + pageHandler: BraveCustomNotes.PageHandlerRemote + callbackRouter: BraveCustomNotes.CustomNotesPageCallbackRouter + + constructor () { + // Initialize page handler to communicate with backend + this.pageHandler = BraveCustomNotes.NotesPageHandler.getRemote() + // Initialize callback router to handle responses from backend + this.callbackRouter = new BraveCustomNotes.CustomNotesPageCallbackRouter() + // Set the callback router in the page handler to link it with the frontend + this.pageHandler.setClientPage(this.callbackRouter.$.bindNewPipeAndPassRemote()) + } + } + + export default function getCustomNotesHandlerInstance () { + if (!apiInstance) { + apiInstance = new CustomNotesAPI() + } + return apiInstance + } + \ No newline at end of file diff --git a/components/brave_custom_notes/resources/page/container.tsx b/components/brave_custom_notes/resources/page/container.tsx new file mode 100644 index 000000000000..121d268abd38 --- /dev/null +++ b/components/brave_custom_notes/resources/page/container.tsx @@ -0,0 +1,326 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import * as React from 'react'; +import { useState, useEffect } from 'react'; +import styled from 'styled-components'; +import getCustomNotesHandlerInstance from './api/brave_custom_notes_handler'; + +// Styled Components +const NotesContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + max-width: 600px; + margin: 0 auto; + background-color: #f0f4f8; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + `; + +const Title = styled.h1` + font-size: 24px; + color: #333; + margin-bottom: 20px; + `; + +const NoteInput = styled.textarea` + width: 100%; + padding: 10px; + font-size: 16px; + border: 2px solid #ccc; + border-radius: 5px; + margin-bottom: 15px; + resize: none; + min-height: 80px; + `; + +const Button = styled.button` + background-color: #007bff; + color: white; + border: none; + padding: 10px 20px; + font-size: 16px; + border-radius: 5px; + cursor: pointer; + margin-right: 10px; + &:hover { + background-color: #0056b3; + } + `; + +const NotesList = styled.ul` + list-style-type: none; + padding: 0; + margin: 20px 0; + width: 100%; + `; + +const NoteItem = styled.li` + background-color: #fff; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + `; + +// Note interface +interface Note { + id: number; + title: string; + content: string; + summary?: string; + timestamp: number; +} + +const AZURE_OPENAI_API_ENDPOINT = 'https://ping.openai.azure.com/openai/deployments/ai-summariser-gpt-35-turbo/chat/completions?api-version=2024-02-15-preview'; +const AZURE_OPENAI_API_KEY = 'b487c4dc0bc1490e801cb6220cf04039'; + +const Notes: React.FC = () => { + const [note, setNote] = useState(''); + const [notesList, setNotesList] = useState([]); + const [editingNote, setEditingNote] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + // Fetch mojom instance + const customNotesAPI = getCustomNotesHandlerInstance(); + + // Load existing notes on component mount + useEffect(() => { + loadNotes(); + }, []); + + const loadNotes = () => { + customNotesAPI.pageHandler.getAllNotes() + .then((response: { notes: Note[] }) => { + setNotesList(response.notes || []); + }) + .catch((err: Error) => { + console.error(err); + alert('Failed to load notes. Please try again.'); + }); + }; + + const handleAddNote = () => { + if (note.trim()) { + setIsLoading(true); + customNotesAPI.pageHandler.addNote(note) + .then(() => { + loadNotes(); + setNote(''); + }) + .catch((err: Error) => { + console.error(err); + alert('Failed to add note. Please try again.'); + }) + .finally(() => { + setIsLoading(false); + }); + } + }; + + const handleEditNote = (noteToEdit: Note) => { + setEditingNote(noteToEdit); + setNote(noteToEdit.content); + }; + + const handleUpdateNote = () => { + if (editingNote && note.trim()) { + setIsLoading(true); + customNotesAPI.pageHandler.editNote(editingNote.id, editingNote.title, note) + .then(() => { + loadNotes(); + setNote(''); + setEditingNote(null); + }) + .catch((err: Error) => { + console.error(err); + alert('Failed to update note. Please try again.'); + }) + .finally(() => { + setIsLoading(false); + }); + } + }; + + const handleDeleteNote = (noteId: number) => { + setIsLoading(true); + customNotesAPI.pageHandler.deleteNote(noteId) + .then(() => { + loadNotes(); + }) + .catch((err: Error) => { + console.error(err); + alert('Failed to delete note. Please try again.'); + }) + .finally(() => { + setIsLoading(false); + }); + }; + + // Summarize the selected note + const handleSummarizeNote = (noteId: number) => { + setIsLoading(true); + customNotesAPI.pageHandler.summarizeNoteContent(noteId) + .then((response: { success: boolean, summary: string }) => { + if (response.success) { + setNotesList(prevNotes => + prevNotes.map(note => + note.id === noteId + ? { + ...note, + summary: response.summary, // Store summary in note object + content: note.content // Preserve original content + } + : note + ) + ); + } else { + alert('Failed to summarize note. Please try again.'); + } + }) + .catch((err: Error) => { + console.error(err); + alert('Failed to summarize note. Please try again.'); + }) + .finally(() => { + setIsLoading(false); + }); + }; + + const handleRephraseNote = async () => { + if (!note.trim()) { + alert('Please enter some text to rephrase.'); + return; + } + + setIsLoading(true); + + try { + const response = await fetch( + AZURE_OPENAI_API_ENDPOINT, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'api-key': AZURE_OPENAI_API_KEY + }, + body: JSON.stringify({ + messages: [ + { + role: 'system', + content: 'You are a helpful assistant that rephrases text while maintaining its original meaning.' + }, + { + role: 'user', + content: `Rephrase the following text: ${note}` + } + ], + max_tokens: 500, + temperature: 0.7, + top_p: 0.95, + frequency_penalty: 0, + presence_penalty: 0, + stream: false + }) + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + const rephrasedContent = data.choices[0].message.content.trim(); + if (editingNote) { + setNotesList(prevNotes => + prevNotes.map(note => + note.id === editingNote.id + ? { + ...note, + content: rephrasedContent + } + : note + ) + ); + } + setNote(rephrasedContent); + } catch (error) { + console.error('Rephrasing failed:', error); + alert('Failed to rephrase note. Please try again.'); + } finally { + setIsLoading(false); + } + }; + + return ( + + My Notes + setNote(e.target.value)} + placeholder={editingNote ? "Edit note..." : "Write a new note..."} + /> +
+ + {editingNote && ( + + )} + +
+ + {notesList.length === 0 ? ( +

No notes added yet!

+ ) : ( + + {notesList.map((note) => ( + +

{note.title}

+

{note.content}

+ {new Date(note.timestamp).toLocaleString()} + {note.summary && ( +
+

Summary:

+

{note.summary}

+
+ )} +
+ + + +
+
+ ))} +
+ )} +
+ ); +}; + +export default Notes; \ No newline at end of file diff --git a/components/brave_custom_notes/resources/page/custom_notes.html b/components/brave_custom_notes/resources/page/custom_notes.html new file mode 100644 index 000000000000..1e194a395dd5 --- /dev/null +++ b/components/brave_custom_notes/resources/page/custom_notes.html @@ -0,0 +1,28 @@ + + + + + + Brave Custom Notes + + + + + + + + + + + + +
+ + diff --git a/components/brave_custom_notes/resources/page/custom_notes.tsx b/components/brave_custom_notes/resources/page/custom_notes.tsx new file mode 100644 index 000000000000..e0179c9f9566 --- /dev/null +++ b/components/brave_custom_notes/resources/page/custom_notes.tsx @@ -0,0 +1,28 @@ +// Copyright (c) 2022 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// you can obtain one at https://mozilla.org/MPL/2.0/. + +import * as React from 'react' +import { createRoot } from 'react-dom/client' +import { initLocale } from 'brave-ui' + +import { loadTimeData } from '../../../common/loadTimeData' +import BraveCoreThemeProvider from '../../../common/BraveCoreThemeProvider' +import Notes from './container' + +function App() { + return ( + + + + ) +} + +function initialize() { + initLocale(loadTimeData.data_) + const root = createRoot(document.getElementById('mountPoint')!) + root.render() +} + +document.addEventListener('DOMContentLoaded', initialize) diff --git a/components/brave_custom_notes/resources/page/tsconfig.json b/components/brave_custom_notes/resources/page/tsconfig.json new file mode 100644 index 000000000000..9d2e1f99cb58 --- /dev/null +++ b/components/brave_custom_notes/resources/page/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../../tsconfig", + "include": [ + "**/*.ts", + "**/*.tsx", + "**/*.d.ts", + "../definitions/*.d.ts" + ] +} \ No newline at end of file diff --git a/components/constants/webui_url_constants.h b/components/constants/webui_url_constants.h index 7f8993a77a77..b5b2ce018dd1 100644 --- a/components/constants/webui_url_constants.h +++ b/components/constants/webui_url_constants.h @@ -80,6 +80,8 @@ inline constexpr char kSpeedreaderPanelHost[] = "brave-speedreader.top-chrome"; inline constexpr char kShortcutsURL[] = "chrome://settings/system/shortcuts"; inline constexpr char kChatUIURL[] = "chrome-untrusted://chat/"; inline constexpr char kChatUIHost[] = "chat"; +inline constexpr char kBraveCustomNotesHost[] = "custom-notes"; +inline constexpr char16_t kBraveCustomNotesURL[] = u"chrome://custom-notes/"; inline constexpr char kRewriterUIURL[] = "chrome://rewriter/"; inline constexpr char kRewriterUIHost[] = "rewriter"; diff --git a/components/resources/BUILD.gn b/components/resources/BUILD.gn index 452e53e516c1..618fd227d3f8 100644 --- a/components/resources/BUILD.gn +++ b/components/resources/BUILD.gn @@ -82,6 +82,7 @@ repack("resources") { "//brave/components/brave_news/browser/resources:generated_resources", "//brave/components/brave_news/browser/resources:generated_resources", "//brave/components/brave_private_new_tab_ui/resources/page:generated_resources", + "//brave/components/brave_custom_notes/resources/page:generated_resources", "//brave/components/brave_shields/resources/cookie_list_opt_in:cookie_list_opt_in_generated", "//brave/components/brave_shields/resources/panel:brave_shields_panel_generated", "//brave/components/brave_welcome_ui:generated_resources", @@ -93,6 +94,7 @@ repack("resources") { "$root_gen_dir/brave/components/brave_new_tab/resources/brave_new_tab_generated.pak", "$root_gen_dir/brave/components/brave_news/browser/resources/brave_news_internals_generated.pak", "$root_gen_dir/brave/components/brave_private_new_tab/resources/page/brave_private_new_tab_generated.pak", + "$root_gen_dir/brave/components/brave_custom_notes/resources/page/brave_custom_notes_generated.pak", "$root_gen_dir/brave/components/brave_shields/resources/cookie_list_opt_in/cookie_list_opt_in_generated.pak", "$root_gen_dir/brave/components/brave_shields/resources/panel/brave_shields_panel_generated.pak", "$root_gen_dir/brave/components/brave_welcome/resources/brave_welcome_generated.pak", diff --git a/components/resources/brave_components_resources.grd b/components/resources/brave_components_resources.grd index 0c749cd8d9b1..d2ebe24e6a06 100644 --- a/components/resources/brave_components_resources.grd +++ b/components/resources/brave_components_resources.grd @@ -8,6 +8,7 @@ + diff --git a/components/resources/brave_components_strings.grd b/components/resources/brave_components_strings.grd index 5a0936c6c1b3..ee1f0f0f862f 100644 --- a/components/resources/brave_components_strings.grd +++ b/components/resources/brave_components_strings.grd @@ -425,6 +425,23 @@ Search the web privately + + + Custom Notes + Manage your notes easily. + Add a new note + Edit note + Delete note + Title of your note + Content of your note + Note saved successfully! + Note deleted successfully! + Your Notes + No notes available. Start by adding a new note! + Search notes... + + + Additional Filters Warning: Turning on too many filters will degrade performance diff --git a/components/webui/webui_resources.cc b/components/webui/webui_resources.cc index 1366d86580ea..4ed59fbfe3cb 100644 --- a/components/webui/webui_resources.cc +++ b/components/webui/webui_resources.cc @@ -362,6 +362,31 @@ base::span GetWebUILocalizedStrings( // Private Tab - Private Window - Tor Box {"boxTorText2", IDS_BRAVE_PRIVATE_NEW_TAB_BOX_TOR_TEXT_2}, {"boxTorButton", IDS_BRAVE_PRIVATE_NEW_TAB_BOX_TOR_BUTTON}, + + // Custom Notes - Header + {"headerTitle", IDS_BRAVE_CUSTOM_NOTES_HEADER_TITLE}, + {"headerText", IDS_BRAVE_CUSTOM_NOTES_HEADER_DESCRIPTION}, + + // Custom Notes - Actions + {"createNoteButton", IDS_BRAVE_CUSTOM_NOTES_CREATE_NOTE_BUTTON}, + {"editNoteButton", IDS_BRAVE_CUSTOM_NOTES_EDIT_NOTE_BUTTON}, + {"deleteNoteButton", IDS_BRAVE_CUSTOM_NOTES_DELETE_NOTE_BUTTON}, + + // Custom Notes - Placeholders + {"notePlaceholderTitle", + IDS_BRAVE_CUSTOM_NOTES_PLACEHOLDER_TITLE}, + {"notePlaceholderContent", + IDS_BRAVE_CUSTOM_NOTES_PLACEHOLDER_CONTENT}, + + // Custom Notes - Messages + {"noteSavedMessage", IDS_BRAVE_CUSTOM_NOTES_NOTE_SAVED_MESSAGE}, + {"noteDeletedMessage", + IDS_BRAVE_CUSTOM_NOTES_NOTE_DELETED_MESSAGE}, + {"notesListTitle", IDS_BRAVE_CUSTOM_NOTES_LIST_TITLE}, + {"noNotesMessage", IDS_BRAVE_CUSTOM_NOTES_NO_NOTES_MESSAGE}, + {"notesSearchPlaceholder", + IDS_BRAVE_CUSTOM_NOTES_SEARCH_PLACEHOLDER}, + #endif // !BUILDFLAG(IS_ANDROID) // Rewards widget diff --git a/resources/resource_ids.spec b/resources/resource_ids.spec index 5341953c4eb7..eb905237e72c 100644 --- a/resources/resource_ids.spec +++ b/resources/resource_ids.spec @@ -234,5 +234,9 @@ }, "brave/components/ping_ai_copilot/resources.grd": { "includes": [64600], + }, + "<(SHARED_INTERMEDIATE_DIR)/brave/web-ui-brave_custom_notes/brave_custom_notes.grd": { + "META": {"sizes": {"includes": [10]}}, + "includes": [64620] } }