diff --git a/components/ai_chat/core/browser/conversation_handler.cc b/components/ai_chat/core/browser/conversation_handler.cc index d202be44c0d..196092be92d 100644 --- a/components/ai_chat/core/browser/conversation_handler.cc +++ b/components/ai_chat/core/browser/conversation_handler.cc @@ -1247,80 +1247,93 @@ void ConversationHandler::UpdateOrCreateLastAssistantEntry( auto& entry = chat_history_.back(); auto& event = result.event; - if (event->is_completion_event()) { - if (!engine_->SupportsDeltaTextResponses() || entry->events->size() == 0 || - !entry->events->back()->is_completion_event()) { - // The start of completion responses needs whitespace trim - // TODO(petemill): This should happen server-side? - event->get_completion_event()->completion = base::TrimWhitespaceASCII( - event->get_completion_event()->completion, base::TRIM_LEADING); + + // Only update if verification status is pending, or did not already fail. + if (result.is_near_verified.has_value() && + (!entry->near_verification_status || + entry->near_verification_status->verified)) { + entry->near_verification_status = + mojom::NEARVerificationStatus::New(*result.is_near_verified); + } + + if (event) { + if (event->is_completion_event()) { + if (!engine_->SupportsDeltaTextResponses() || + entry->events->size() == 0 || + !entry->events->back()->is_completion_event()) { + // The start of completion responses needs whitespace trim + // TODO(petemill): This should happen server-side? + event->get_completion_event()->completion = base::TrimWhitespaceASCII( + event->get_completion_event()->completion, base::TRIM_LEADING); + } + + // Optimize by merging with previous completion events if delta updates + // are supported or otherwise replacing the previous event. + if (entry->events->size() > 0) { + auto& last_event = entry->events->back(); + if (last_event->is_completion_event()) { + // Merge completion events + if (engine_->SupportsDeltaTextResponses()) { + event->get_completion_event()->completion = + base::StrCat({last_event->get_completion_event()->completion, + event->get_completion_event()->completion}); + } + // Remove the last event because we'll replace in both delta and + // non-delta cases + entry->events->pop_back(); + } + } + + // TODO(petemill): Remove ConversationTurn.text backwards compatibility + // when all UI is updated to instead use ConversationEntryEvent items. + entry->text = event->get_completion_event()->completion; } - // Optimize by merging with previous completion events if delta updates - // are supported or otherwise replacing the previous event. - if (entry->events->size() > 0) { + if (event->is_tool_use_event() && entry->events->size() > 0) { + // Tool use events can be partial and may need to be combined with the + // previous event. auto& last_event = entry->events->back(); - if (last_event->is_completion_event()) { - // Merge completion events - if (engine_->SupportsDeltaTextResponses()) { - event->get_completion_event()->completion = - base::StrCat({last_event->get_completion_event()->completion, - event->get_completion_event()->completion}); - } - // Remove the last event because we'll replace in both delta and - // non-delta cases - entry->events->pop_back(); + auto& tool_use_event = event->get_tool_use_event(); + + DVLOG(2) << __func__ + << " Got event for tool use: " << tool_use_event->tool_name + << " is empty? " << tool_use_event->tool_name.empty() + << " with input: " << tool_use_event->arguments_json; + + if (last_event->is_tool_use_event() && + tool_use_event->tool_name.empty()) { + last_event->get_tool_use_event()->arguments_json = + base::StrCat({last_event->get_tool_use_event()->arguments_json, + tool_use_event->arguments_json}); + // TODO(petemill): Don't clone + OnHistoryUpdate(entry.Clone()); + return; } } - // TODO(petemill): Remove ConversationTurn.text backwards compatibility when - // all UI is updated to instead use ConversationEntryEvent items. - entry->text = event->get_completion_event()->completion; - } - - if (event->is_tool_use_event() && entry->events->size() > 0) { - // Tool use events can be partial and may need to be combined with the - // previous event. - auto& last_event = entry->events->back(); - auto& tool_use_event = event->get_tool_use_event(); - - DVLOG(2) << __func__ - << " Got event for tool use: " << tool_use_event->tool_name - << " is empty? " << tool_use_event->tool_name.empty() - << " with input: " << tool_use_event->arguments_json; - - if (last_event->is_tool_use_event() && tool_use_event->tool_name.empty()) { - last_event->get_tool_use_event()->arguments_json = - base::StrCat({last_event->get_tool_use_event()->arguments_json, - tool_use_event->arguments_json}); - // TODO(petemill): Don't clone - OnHistoryUpdate(entry.Clone()); + if (event->is_conversation_title_event()) { + OnConversationTitleChanged(event->get_conversation_title_event()->title); + // Don't add this event to history return; } - } - if (event->is_conversation_title_event()) { - OnConversationTitleChanged(event->get_conversation_title_event()->title); - // Don't add this event to history - return; - } + if (event->is_selected_language_event()) { + OnSelectedLanguageChanged( + event->get_selected_language_event()->selected_language); + // Don't add this event to history + return; + } - if (event->is_selected_language_event()) { - OnSelectedLanguageChanged( - event->get_selected_language_event()->selected_language); - // Don't add this event to history - return; - } + if (event->is_content_receipt_event()) { + OnConversationTokenInfoChanged( + event->get_content_receipt_event()->total_tokens, + event->get_content_receipt_event()->trimmed_tokens); + // Don't add this event to history + return; + } - if (event->is_content_receipt_event()) { - OnConversationTokenInfoChanged( - event->get_content_receipt_event()->total_tokens, - event->get_content_receipt_event()->trimmed_tokens); - // Don't add this event to history - return; + entry->events->push_back(std::move(event)); } - - entry->events->push_back(std::move(event)); // Update clients for partial entries but not observers, who will get notified // when we know this is a complete entry. OnHistoryUpdate(entry.Clone()); @@ -1573,8 +1586,9 @@ void ConversationHandler::OnEngineCompletionComplete( // Handle success, which might mean do nothing much since all data was passed // in the streaming "received" callback. DVLOG(2) << __func__ << ": With value"; - if (result->event && result->event->is_completion_event() && - !result->event->get_completion_event()->completion.empty()) { + if ((result->event && result->event->is_completion_event() && + !result->event->get_completion_event()->completion.empty()) || + result->is_near_verified.has_value()) { UpdateOrCreateLastAssistantEntry(std::move(*result)); } else { // This is a workaround for any occasions where the engine returns diff --git a/components/ai_chat/core/browser/conversation_handler_unittest.cc b/components/ai_chat/core/browser/conversation_handler_unittest.cc index c66d27cd88c..46d466d72a5 100644 --- a/components/ai_chat/core/browser/conversation_handler_unittest.cc +++ b/components/ai_chat/core/browser/conversation_handler_unittest.cc @@ -542,6 +542,72 @@ TEST_F(ConversationHandlerUnitTest, SubmitSelectedText) { ExpectConversationHistoryEquals(FROM_HERE, history, expected_history, false); } +TEST_F(ConversationHandlerUnitTest, SubmitSelectedText_WithNEARVerification) { + MockEngineConsumer* engine = static_cast( + conversation_handler_->GetEngineForTesting()); + + std::string selected_text = "Verified content."; + std::string expected_turn_text = + l10n_util::GetStringUTF8(IDS_AI_CHAT_QUESTION_SUMMARIZE_SELECTED_TEXT); + const std::string expected_response = "This is verified."; + + EXPECT_CALL(*engine, GenerateAssistantResponse( + _, LastTurnHasSelectedText(selected_text), StrEq(""), + _, _, _, _, _, _)) + .WillOnce(::testing::DoAll( + base::test::RunOnceCallback<7>(EngineConsumer::GenerationResultData( + mojom::ConversationEntryEvent::NewCompletionEvent( + mojom::CompletionEvent::New(expected_response)), + std::nullopt /* model_key */, true /* is_near_verified */)), + base::test::RunOnceCallback<8>( + base::ok(EngineConsumer::GenerationResultData( + nullptr, std::nullopt /* model_key */, + true /* is_near_verified */))))); + + conversation_handler_->associated_content_manager()->ClearContent(); + + std::vector expected_history; + + expected_history.push_back(mojom::ConversationTurn::New( + std::nullopt, mojom::CharacterType::HUMAN, + mojom::ActionType::SUMMARIZE_SELECTED_TEXT, expected_turn_text, + std::nullopt, selected_text, std::nullopt, base::Time::Now(), + std::nullopt, std::nullopt, nullptr /* skill */, false, + std::nullopt /* model_key */, nullptr /* near_verification_status */)); + + std::vector response_events; + response_events.push_back(mojom::ConversationEntryEvent::NewCompletionEvent( + mojom::CompletionEvent::New(expected_response))); + expected_history.push_back(mojom::ConversationTurn::New( + std::nullopt, mojom::CharacterType::ASSISTANT, + mojom::ActionType::RESPONSE, expected_response, std::nullopt, + std::nullopt, std::move(response_events), base::Time::Now(), std::nullopt, + std::nullopt, nullptr /* skill */, false, std::nullopt /* model_key */, + mojom::NEARVerificationStatus::New(true))); + + NiceMock client(conversation_handler_.get()); + EXPECT_CALL(client, OnAPIRequestInProgress(true)).Times(1); + EXPECT_CALL(client, OnConversationHistoryUpdate( + TurnEq(mojom::ConversationTurnPtr().get()))) + .Times(1); + EXPECT_CALL(client, + OnConversationHistoryUpdate(TurnEq(expected_history[1].get()))) + .Times(3); + EXPECT_CALL(client, OnAPIRequestInProgress(false)).Times(1); + EXPECT_CALL(*engine, SanitizeInput(StrEq(selected_text))); + EXPECT_CALL(*engine, SanitizeInput(StrEq(expected_turn_text))); + + conversation_handler_->SubmitSelectedText( + selected_text, mojom::ActionType::SUMMARIZE_SELECTED_TEXT); + + task_environment_.RunUntilIdle(); + testing::Mock::VerifyAndClearExpectations(&client); + + EXPECT_TRUE(conversation_handler_->HasAnyHistory()); + const auto& history = conversation_handler_->GetConversationHistory(); + ExpectConversationHistoryEquals(FROM_HERE, history, expected_history, false); +} + TEST_F(ConversationHandlerUnitTest, SubmitSelectedText_WithAssociatedContent) { // Test with page contents. MockEngineConsumer* engine = static_cast( diff --git a/components/ai_chat/core/browser/engine/conversation_api_client.cc b/components/ai_chat/core/browser/engine/conversation_api_client.cc index 47d86a8221d..2889aad90f5 100644 --- a/components/ai_chat/core/browser/engine/conversation_api_client.cc +++ b/components/ai_chat/core/browser/engine/conversation_api_client.cc @@ -15,10 +15,10 @@ #include #include "base/check.h" -#include "base/command_line.h" #include "base/containers/checked_iterators.h" #include "base/containers/fixed_flat_map.h" #include "base/containers/flat_map.h" +#include "base/containers/map_util.h" #include "base/functional/bind.h" #include "base/json/json_writer.h" #include "base/logging.h" @@ -31,11 +31,11 @@ #include "base/strings/string_util.h" #include "base/types/expected.h" #include "base/values.h" -#include "brave/brave_domains/service_domains.h" #include "brave/components/ai_chat/core/browser/ai_chat_credential_manager.h" #include "brave/components/ai_chat/core/browser/engine/conversation_api_parsing.h" #include "brave/components/ai_chat/core/browser/engine/oai_parsing.h" #include "brave/components/ai_chat/core/browser/model_service.h" +#include "brave/components/ai_chat/core/browser/utils.h" #include "brave/components/ai_chat/core/common/buildflags/buildflags.h" #include "brave/components/ai_chat/core/common/features.h" #include "brave/components/ai_chat/core/common/mojom/ai_chat.mojom.h" @@ -63,10 +63,6 @@ constexpr char kRemotePath[] = "v1/conversation"; constexpr char kAllowedWebSourceFaviconHost[] = "imgs.search.brave.com"; -#if !defined(OFFICIAL_BUILD) -constexpr char kAIChatServerUrl[] = "ai-chat-server-url"; -#endif - net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotationTag() { return net::DefineNetworkTrafficAnnotation("ai_chat", R"( semantics { @@ -192,33 +188,6 @@ base::Value::List ConversationEventsToList( return events; } -GURL GetEndpointUrl(bool premium, const std::string& path) { - CHECK(!path.starts_with("/")); - -#if !defined(OFFICIAL_BUILD) - // If a runtime AI Chat URL is provided, use it. - std::string ai_chat_url = - base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - kAIChatServerUrl); - if (!ai_chat_url.empty()) { - GURL url = GURL(base::StrCat({ai_chat_url, "/", path})); - CHECK(url.is_valid()) << "Invalid API Url: " << url.spec(); - return url; - } -#endif - - auto* prefix = premium ? "ai-chat-premium.bsg" : "ai-chat.bsg"; - auto hostname = brave_domains::GetServicesDomain( - prefix, brave_domains::ServicesEnvironment::DEV); - - GURL url{base::StrCat( - {url::kHttpsScheme, url::kStandardSchemeSeparator, hostname, "/", path})}; - - CHECK(url.is_valid()) << "Invalid API Url: " << url.spec(); - - return url; -} - } // namespace ConversationAPIClient::ConversationEvent::ConversationEvent( @@ -409,6 +378,7 @@ void ConversationAPIClient::OnQueryCompleted( if (success) { std::string completion = ""; std::optional model_key = std::nullopt; + std::optional is_near_verified = std::nullopt; mojom::ConversationEntryEventPtr completion_event = nullptr; // We're checking for a value body in case for non-streaming API results. // TODO(petemill): server should provide parseable history events even for @@ -428,10 +398,16 @@ void ConversationAPIClient::OnQueryCompleted( } } + const auto& headers = result.headers(); + if (const auto* header_value = + base::FindOrNull(headers, kBraveNearVerifiedHeader)) { + is_near_verified = *header_value == "true"; + } + completion_event = mojom::ConversationEntryEvent::NewCompletionEvent( mojom::CompletionEvent::New(completion)); - GenerationResultData data(std::move(completion_event), - std::move(model_key)); + GenerationResultData data(std::move(completion_event), std::move(model_key), + is_near_verified); std::move(callback).Run(base::ok(std::move(data))); return; } diff --git a/components/ai_chat/core/browser/engine/conversation_api_client.h b/components/ai_chat/core/browser/engine/conversation_api_client.h index 7170e4df513..a780436db2a 100644 --- a/components/ai_chat/core/browser/engine/conversation_api_client.h +++ b/components/ai_chat/core/browser/engine/conversation_api_client.h @@ -39,6 +39,8 @@ namespace ai_chat { class AIChatCredentialManager; struct CredentialCacheEntry; +inline constexpr char kBraveNearVerifiedHeader[] = "brave-near-verified"; + // Performs remote request to the remote HTTP Brave Conversation API. class ConversationAPIClient { public: diff --git a/components/ai_chat/core/browser/engine/conversation_api_client_unittest.cc b/components/ai_chat/core/browser/engine/conversation_api_client_unittest.cc index 5020f13974c..e031c9238fd 100644 --- a/components/ai_chat/core/browser/engine/conversation_api_client_unittest.cc +++ b/components/ai_chat/core/browser/engine/conversation_api_client_unittest.cc @@ -582,6 +582,7 @@ TEST_F(ConversationAPIUnitTest, PerformRequest_PremiumHeaders) { mojom::ConversationEntryEvent::NewCompletionEvent( mojom::CompletionEvent::New("")), std::nullopt)); + EXPECT_FALSE(result->is_near_verified.has_value()); }); // Begin request @@ -1348,6 +1349,70 @@ TEST_F(ConversationAPIUnitTest, testing::Mock::VerifyAndClearExpectations(mock_request_helper); } +TEST_F(ConversationAPIUnitTest, PerformRequest_NEARVerification) { + std::string expected_completion_response = "Verified response"; + auto events_and_body = GetMockEventsAndExpectedEventsBody(); + std::vector events = + std::move(events_and_body.first); + + MockAPIRequestHelper* mock_request_helper = + client_->GetMockAPIRequestHelper(); + testing::StrictMock mock_callbacks; + base::RunLoop run_loop; + + EXPECT_CALL(*mock_request_helper, RequestSSE(_, _, _, _, _, _, _, _)) + .WillOnce([&](const std::string& method, const GURL& url, + const std::string& body, const std::string& content_type, + DataReceivedCallback data_received_callback, + ResultCallback result_callback, + const base::flat_map& headers, + const api_request_helper::APIRequestOptions& options) { + base::Value result(base::Value::Type::DICT); + result.GetDict().Set("type", "completion"); + result.GetDict().Set("model", "llama-3-8b-instruct"); + result.GetDict().Set("completion", expected_completion_response); + data_received_callback.Run(base::ok(std::move(result))); + + base::flat_map response_headers; + response_headers[kBraveNearVerifiedHeader] = "true"; + std::move(result_callback) + .Run(api_request_helper::APIRequestResult(200, {}, response_headers, + net::OK, GURL())); + run_loop.Quit(); + return Ticket(); + }); + + EXPECT_CALL(mock_callbacks, OnDataReceived(_)) + .WillOnce([&](EngineConsumer::GenerationResultData result) { + ASSERT_TRUE(result.event); + EXPECT_TRUE(result.event->is_completion_event()); + EXPECT_EQ(result.event->get_completion_event()->completion, + expected_completion_response); + EXPECT_FALSE(result.is_near_verified); + }); + + EXPECT_CALL(mock_callbacks, OnCompleted(_)) + .WillOnce([](EngineConsumer::GenerationResult result) { + ASSERT_TRUE(result.has_value()); + EXPECT_TRUE(result.value().is_near_verified.has_value()); + EXPECT_TRUE(result.value().is_near_verified.value()); + }); + + client_->PerformRequest( + std::move(events), "" /* selected_language */, + std::nullopt, /* oai_tool_definitions */ + std::nullopt, /* preferred_tool_name */ + mojom::ConversationCapability::CONTENT_AGENT, + base::BindRepeating(&MockCallbacks::OnDataReceived, + base::Unretained(&mock_callbacks)), + base::BindOnce(&MockCallbacks::OnCompleted, + base::Unretained(&mock_callbacks))); + + run_loop.Run(); + testing::Mock::VerifyAndClearExpectations(client_.get()); + testing::Mock::VerifyAndClearExpectations(mock_request_helper); +} + TEST_F(ConversationAPIUnitTest, FailNoConversationEvents) { // Tests handling invalid request parameters std::vector events; diff --git a/components/ai_chat/core/browser/engine/engine_consumer.cc b/components/ai_chat/core/browser/engine/engine_consumer.cc index df497aa7c05..fd4a79c8458 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer.cc +++ b/components/ai_chat/core/browser/engine/engine_consumer.cc @@ -17,8 +17,11 @@ namespace ai_chat { EngineConsumer::GenerationResultData::GenerationResultData( mojom::ConversationEntryEventPtr event, - std::optional&& model_key) - : event(std::move(event)), model_key(std::move(model_key)) {} + std::optional&& model_key, + std::optional is_near_verified) + : event(std::move(event)), + model_key(std::move(model_key)), + is_near_verified(is_near_verified) {} EngineConsumer::GenerationResultData::GenerationResultData( GenerationResultData&& other) = default; diff --git a/components/ai_chat/core/browser/engine/engine_consumer.h b/components/ai_chat/core/browser/engine/engine_consumer.h index d5feb072d93..9655615b43c 100644 --- a/components/ai_chat/core/browser/engine/engine_consumer.h +++ b/components/ai_chat/core/browser/engine/engine_consumer.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -43,7 +44,8 @@ class EngineConsumer { struct GenerationResultData { GenerationResultData(mojom::ConversationEntryEventPtr event, - std::optional&& model_key); + std::optional&& model_key, + std::optional is_near_verified = std::nullopt); ~GenerationResultData(); GenerationResultData(GenerationResultData&&); @@ -55,6 +57,7 @@ class EngineConsumer { mojom::ConversationEntryEventPtr event; std::optional model_key; + std::optional is_near_verified; }; using GenerationResult = diff --git a/components/ai_chat/core/browser/utils.cc b/components/ai_chat/core/browser/utils.cc index fd9f542ddb6..cc14d4ccf16 100644 --- a/components/ai_chat/core/browser/utils.cc +++ b/components/ai_chat/core/browser/utils.cc @@ -11,9 +11,11 @@ #include #include "base/check.h" +#include "base/command_line.h" #include "base/containers/flat_map.h" #include "base/functional/bind.h" #include "base/no_destructor.h" +#include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "base/time/time.h" #include "brave/brave_domains/service_domains.h" @@ -253,4 +255,31 @@ SkBitmap ScaleDownBitmap(const SkBitmap& bitmap) { return scaled_bitmap; } +GURL GetEndpointUrl(bool premium, const std::string& path) { + CHECK(!path.starts_with("/")); + +#if !defined(OFFICIAL_BUILD) + // If a runtime AI Chat URL is provided, use it. + std::string ai_chat_url = + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + "ai-chat-server-url"); + if (!ai_chat_url.empty()) { + GURL url = GURL(base::StrCat({ai_chat_url, "/", path})); + CHECK(url.is_valid()) << "Invalid API Url: " << url.spec(); + return url; + } +#endif + + auto* prefix = premium ? "ai-chat-premium.bsg" : "ai-chat.bsg"; + auto hostname = brave_domains::GetServicesDomain( + prefix, brave_domains::ServicesEnvironment::DEV); + + GURL url{base::StrCat( + {url::kHttpsScheme, url::kStandardSchemeSeparator, hostname, "/", path})}; + + CHECK(url.is_valid()) << "Invalid API Url: " << url.spec(); + + return url; +} + } // namespace ai_chat diff --git a/components/ai_chat/core/browser/utils.h b/components/ai_chat/core/browser/utils.h index 2b7577add6b..71f4c92fc6c 100644 --- a/components/ai_chat/core/browser/utils.h +++ b/components/ai_chat/core/browser/utils.h @@ -48,6 +48,8 @@ EngineConsumer::GenerationDataCallback BindParseRewriteReceivedData( // 1024x768 SkBitmap ScaleDownBitmap(const SkBitmap& bitmap); +GURL GetEndpointUrl(bool premium, const std::string& path); + } // namespace ai_chat #endif // BRAVE_COMPONENTS_AI_CHAT_CORE_BROWSER_UTILS_H_ diff --git a/components/ai_chat/resources/untrusted_conversation_frame/components/context_actions_assistant/index.tsx b/components/ai_chat/resources/untrusted_conversation_frame/components/context_actions_assistant/index.tsx index 03e912ada2e..2f728de10a0 100644 --- a/components/ai_chat/resources/untrusted_conversation_frame/components/context_actions_assistant/index.tsx +++ b/components/ai_chat/resources/untrusted_conversation_frame/components/context_actions_assistant/index.tsx @@ -20,6 +20,7 @@ type RatingStatus = (typeof statuses)[number] interface ContextActionsAssistantProps { turnUuid?: string turnModelKey?: string + turnNEARVerified?: boolean onEditAnswerClicked?: () => void onCopyTextClicked?: () => void } @@ -121,6 +122,7 @@ export default function ContextActionsAssistant( onRegenerate={handleRegenerateAnswer} leoModels={leoModels} turnModelKey={props.turnModelKey} + turnNEARVerified={props.turnNEARVerified} /> )} diff --git a/components/ai_chat/resources/untrusted_conversation_frame/components/conversation_entries/index.tsx b/components/ai_chat/resources/untrusted_conversation_frame/components/conversation_entries/index.tsx index f11ee271be9..b9af80862ed 100644 --- a/components/ai_chat/resources/untrusted_conversation_frame/components/conversation_entries/index.tsx +++ b/components/ai_chat/resources/untrusted_conversation_frame/components/conversation_entries/index.tsx @@ -379,6 +379,9 @@ function ConversationEntries() { setEditInputId(index) diff --git a/components/ai_chat/resources/untrusted_conversation_frame/components/regenerate_answer_menu/index.tsx b/components/ai_chat/resources/untrusted_conversation_frame/components/regenerate_answer_menu/index.tsx index b53bc5d3c97..47eb20797d7 100644 --- a/components/ai_chat/resources/untrusted_conversation_frame/components/regenerate_answer_menu/index.tsx +++ b/components/ai_chat/resources/untrusted_conversation_frame/components/regenerate_answer_menu/index.tsx @@ -13,6 +13,7 @@ import * as Mojom from '../../../common/mojom' import { ModelMenuItem, // } from '../../../page/components/model_menu_item/model_menu_item' +import { NearLabel } from '../../../page/components/near_label/near_label' import styles from './style.module.scss' interface Props { @@ -22,11 +23,19 @@ interface Props { onRegenerate: (selectedModelKey: string) => void leoModels: Mojom.Model[] turnModelKey: string + turnNEARVerified?: boolean } export function RegenerateAnswerMenu(props: Props) { - const { isOpen, onOpen, onClose, onRegenerate, leoModels, turnModelKey } = - props + const { + isOpen, + onOpen, + onClose, + onRegenerate, + leoModels, + turnModelKey, + turnNEARVerified, + } = props const modelDisplayName = leoModels.find((model) => model.key === turnModelKey)?.displayName ?? '' @@ -68,6 +77,7 @@ export function RegenerateAnswerMenu(props: Props) { >
{modelDisplayName} + {turnNEARVerified && }