From 23aa7390219c3487c3728ac7e6af8d5d1d4ab2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Saquetim?= Date: Thu, 31 Oct 2024 19:02:03 -0300 Subject: [PATCH 1/6] DEV: Added compatibility with the Glimmer Post Menu --- .../post-menu/ai-cancel-streaming-button.gjs | 37 ++++ .../components/post-menu/ai-debug-button.gjs | 46 +++++ .../components/post-menu/ai-share-button.gjs | 57 ++++++ .../initializers/ai-bot-replies.js | 162 +++++++++++++----- spec/system/ai_bot/share_spec.rb | 6 +- 5 files changed, 262 insertions(+), 46 deletions(-) create mode 100644 assets/javascripts/discourse/components/post-menu/ai-cancel-streaming-button.gjs create mode 100644 assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs create mode 100644 assets/javascripts/discourse/components/post-menu/ai-share-button.gjs diff --git a/assets/javascripts/discourse/components/post-menu/ai-cancel-streaming-button.gjs b/assets/javascripts/discourse/components/post-menu/ai-cancel-streaming-button.gjs new file mode 100644 index 000000000..aa3cef2c0 --- /dev/null +++ b/assets/javascripts/discourse/components/post-menu/ai-cancel-streaming-button.gjs @@ -0,0 +1,37 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import DButton from "discourse/components/d-button"; +import { ajax } from "discourse/lib/ajax"; +import { popupAjaxError } from "discourse/lib/ajax-error"; + +export default class AiCancelStreamingButton extends Component { + // TODO (glimmer-post-menu): Remove this static function and move the code into the button action after the widget code is removed + static async cancelStreaming(post) { + try { + await ajax(`/discourse-ai/ai-bot/post/${post.id}/stop-streaming`, { + type: "POST", + }); + + document + .querySelector(`#post_${post.post_number}`) + .classList.remove("streaming"); + } catch (e) { + popupAjaxError(e); + } + } + + @action + cancelStreaming() { + this.constructor.cancelStreaming(this.args.post); + } + + +} diff --git a/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs b/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs new file mode 100644 index 000000000..cc53b03e0 --- /dev/null +++ b/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs @@ -0,0 +1,46 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import DebugAiModal from "../modal/debug-ai-modal"; + +const MAX_PERSONA_USER_ID = -1200; + +export default class AiDebugButton extends Component { + static shouldRender(args) { + if ( + !args.state.currentUser.ai_enabled_chat_bots.any( + (bot) => args.post.username === bot.username + ) + ) { + // special handling for personas (persona bot users start at ID -1200 and go down) + if (args.post.user_id > MAX_PERSONA_USER_ID) { + return false; + } + } + + return true; + } + + // TODO (glimmer-post-menu): Remove this static function and move the code into the button action after the widget code is removed + static debugAiResponse(post, modal) { + modal.show(DebugAiModal, { model: post }); + } + + @service modal; + + @action + debugAiResponse() { + this.constructor.debugAiResponse(this.args.post, this.modal); + } + + +} diff --git a/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs b/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs new file mode 100644 index 000000000..d938be207 --- /dev/null +++ b/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs @@ -0,0 +1,57 @@ +import Component from "@glimmer/component"; +import { action } from "@ember/object"; +import { inject as service } from "@ember/service"; +import DButton from "discourse/components/d-button"; +import copyConversation from "../../lib/copy-conversation"; +import ShareModal from "../modal/share-modal"; + +const AUTO_COPY_THRESHOLD = 4; +const MAX_PERSONA_USER_ID = -1200; + +export default class AiDebugButton extends Component { + static shouldRender(args) { + if ( + !args.state.currentUser.ai_enabled_chat_bots.any( + (bot) => args.post.username === bot.username + ) + ) { + // special handling for personas (persona bot users start at ID -1200 and go down) + if (args.post.user_id > MAX_PERSONA_USER_ID) { + return false; + } + } + + return true; + } + + // TODO (glimmer-post-menu): Remove this static function and move the code into the button action after the widget code is removed + static async shareAiResponse(post, modal, showFeedback) { + if (post.post_number <= AUTO_COPY_THRESHOLD) { + await copyConversation(post.topic, 1, post.post_number); + showFeedback("discourse_ai.ai_bot.conversation_shared"); + } else { + modal.show(ShareModal, { model: post }); + } + } + + @service modal; + + @action + shareAiResponse() { + this.constructor.shareAiResponse( + this.args.post, + this.modal, + this.args.showFeedback + ); + } + + +} diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js index 733c595eb..3ad3bf20a 100644 --- a/assets/javascripts/initializers/ai-bot-replies.js +++ b/assets/javascripts/initializers/ai-bot-replies.js @@ -1,18 +1,23 @@ import { hbs } from "ember-cli-htmlbars"; -import { ajax } from "discourse/lib/ajax"; -import { popupAjaxError } from "discourse/lib/ajax-error"; +import { + POST_MENU_COPY_LINK_BUTTON_KEY, + POST_MENU_LIKE_BUTTON_KEY, + POST_MENU_SHARE_BUTTON_KEY, + POST_MENU_SHOW_MORE_BUTTON_KEY, +} from "discourse/components/post/menu"; import { withPluginApi } from "discourse/lib/plugin-api"; import { registerWidgetShim } from "discourse/widgets/render-glimmer"; -import DebugAiModal from "../discourse/components/modal/debug-ai-modal"; -import ShareModal from "../discourse/components/modal/share-modal"; -import { streamPostText } from "../discourse/lib/ai-streamer/progress-handlers"; -import copyConversation from "../discourse/lib/copy-conversation"; -const AUTO_COPY_THRESHOLD = 4; +import { withSilencedDeprecations } from "discourse-common/lib/deprecated"; import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon"; +import AiCancelStreamingButton from "../discourse/components/post-menu/ai-cancel-streaming-button"; +import AiDebugButton from "../discourse/components/post-menu/ai-debug-button"; +import AiShareButton from "../discourse/components/post-menu/ai-share-button"; import { showShareConversationModal } from "../discourse/lib/ai-bot-helper"; +import { streamPostText } from "../discourse/lib/ai-streamer/progress-handlers"; let enabledChatBotIds = []; let allowDebug = false; + function isGPTBot(user) { return user && enabledChatBotIds.includes(user.id); } @@ -22,29 +27,7 @@ function attachHeaderIcon(api) { } function initializeAIBotReplies(api) { - api.addPostMenuButton("cancel-gpt", (post) => { - if (isGPTBot(post.user)) { - return { - icon: "pause", - action: "cancelStreaming", - title: "discourse_ai.ai_bot.cancel_streaming", - className: "btn btn-default cancel-streaming", - position: "first", - }; - } - }); - - api.attachWidgetAction("post", "cancelStreaming", function () { - ajax(`/discourse-ai/ai-bot/post/${this.model.id}/stop-streaming`, { - type: "POST", - }) - .then(() => { - document - .querySelector(`#post_${this.model.post_number}`) - .classList.remove("streaming"); - }) - .catch(popupAjaxError); - }); + initializePauseButton(api); api.modifyClass("controller:topic", { pluginId: "discourse-ai", @@ -104,16 +87,87 @@ function initializePersonaDecorator(api) { const MAX_PERSONA_USER_ID = -1200; +function initializePauseButton(api) { + const transformerRegistered = api.registerValueTransformer( + "post-menu-buttons", + ({ value: dag, context: { post } }) => { + if (isGPTBot(post.user)) { + dag.add("ai-cancel-gpt", AiCancelStreamingButton, { + before: [ + POST_MENU_LIKE_BUTTON_KEY, + POST_MENU_COPY_LINK_BUTTON_KEY, + POST_MENU_SHARE_BUTTON_KEY, + POST_MENU_SHOW_MORE_BUTTON_KEY, + ], + after: ["ai-share", "ai-debug"], + }); + } + + return dag; + } + ); + + const silencedKey = + transformerRegistered && "discourse.post-menu-widget-overrides"; + + withSilencedDeprecations(silencedKey, () => initializePauseWidgetButton(api)); +} + +function initializePauseWidgetButton(api) { + api.addPostMenuButton("cancel-gpt", (post) => { + if (isGPTBot(post.user)) { + return { + icon: "pause", + action: "cancelStreaming", + title: "discourse_ai.ai_bot.cancel_streaming", + className: "btn btn-default cancel-streaming", + position: "first", + }; + } + }); + + api.attachWidgetAction("post", "cancelStreaming", function () { + AiCancelStreamingButton.cancelStreaming(this.model); + }); +} + function initializeDebugButton(api) { const currentUser = api.getCurrentUser(); if (!currentUser || !currentUser.ai_enabled_chat_bots || !allowDebug) { return; } + const transformerRegistered = api.registerValueTransformer( + "post-menu-buttons", + ({ value: dag, context: { post } }) => { + if (post.topic?.archetype === "private_message") { + dag.add("ai-debug", AiDebugButton, { + before: [ + POST_MENU_LIKE_BUTTON_KEY, + POST_MENU_COPY_LINK_BUTTON_KEY, + POST_MENU_SHARE_BUTTON_KEY, + POST_MENU_SHOW_MORE_BUTTON_KEY, + ], + after: "ai-share", + }); + } + + return dag; + } + ); + + const silencedKey = + transformerRegistered && "discourse.post-menu-widget-overrides"; + + withSilencedDeprecations(silencedKey, () => initializeDebugWidgetButton(api)); +} + +function initializeDebugWidgetButton(api) { + const currentUser = api.getCurrentUser(); + let debugAiResponse = async function ({ post }) { const modal = api.container.lookup("service:modal"); - - modal.show(DebugAiModal, { model: post }); + AiDebugButton.debugAiResponse(post, modal); }; api.addPostMenuButton("debugAi", (post) => { @@ -148,14 +202,36 @@ function initializeShareButton(api) { return; } - let shareAiResponse = async function ({ post, showFeedback }) { - if (post.post_number <= AUTO_COPY_THRESHOLD) { - await copyConversation(post.topic, 1, post.post_number); - showFeedback("discourse_ai.ai_bot.conversation_shared"); - } else { - const modal = api.container.lookup("service:modal"); - modal.show(ShareModal, { model: post }); + const transformerRegistered = api.registerValueTransformer( + "post-menu-buttons", + ({ value: dag, context: { post } }) => { + if (post.topic?.archetype === "private_message") { + dag.add("ai-share", AiShareButton, { + before: [ + POST_MENU_LIKE_BUTTON_KEY, + POST_MENU_COPY_LINK_BUTTON_KEY, + POST_MENU_SHARE_BUTTON_KEY, + POST_MENU_SHOW_MORE_BUTTON_KEY, + ], + }); + } + + return dag; } + ); + + const silencedKey = + transformerRegistered && "discourse.post-menu-widget-overrides"; + + withSilencedDeprecations(silencedKey, () => initializeShareWidgetButton(api)); +} + +function initializeShareWidgetButton(api) { + const currentUser = api.getCurrentUser(); + + let shareAiResponse = async function ({ post, showFeedback }) { + const modal = api.container.lookup("service:modal"); + AiShareButton.shareAiResponse(post, modal, showFeedback); }; api.addPostMenuButton("share", (post) => { @@ -178,7 +254,7 @@ function initializeShareButton(api) { return { action: shareAiResponse, icon: "far-copy", - className: "post-action-menu__share", + className: "post-action-menu__share-ai", title: "discourse_ai.ai_bot.share", position: "first", }; @@ -218,10 +294,10 @@ export default { enabledChatBotIds = user.ai_enabled_chat_bots.map((bot) => bot.id); allowDebug = user.can_debug_ai_bot_conversations; withPluginApi("1.6.0", attachHeaderIcon); - withPluginApi("1.6.0", initializeAIBotReplies); + withPluginApi("1.34.0", initializeAIBotReplies); withPluginApi("1.6.0", initializePersonaDecorator); - withPluginApi("1.22.0", (api) => initializeDebugButton(api, container)); - withPluginApi("1.22.0", (api) => initializeShareButton(api, container)); + withPluginApi("1.34.0", (api) => initializeDebugButton(api, container)); + withPluginApi("1.34.0", (api) => initializeShareButton(api, container)); withPluginApi("1.22.0", (api) => initializeShareTopicButton(api, container) ); diff --git a/spec/system/ai_bot/share_spec.rb b/spec/system/ai_bot/share_spec.rb index f963fb327..9e8c68034 100644 --- a/spec/system/ai_bot/share_spec.rb +++ b/spec/system/ai_bot/share_spec.rb @@ -54,7 +54,7 @@ visit(pm.url) - find("#post_2 .post-action-menu__share").click + find("#post_2 .post-action-menu__share-ai").click try_until_success do clip_text = cdp.read_clipboard @@ -89,7 +89,7 @@ visit(pm.url) - find("#post_2 .post-action-menu__share").click + find("#post_2 .post-action-menu__share-ai").click try_until_success do clip_text = cdp.read_clipboard @@ -117,7 +117,7 @@ page.execute_script("window.navigator.clipboard.writeText('')") - find("#post_6 .post-action-menu__share").click + find("#post_6 .post-action-menu__share-ai").click find(".ai-share-modal__slider input").set("2") find(".ai-share-modal button.btn-primary").click From 5b32d54727979c55b8729088e01edb9400038668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Saquetim?= Date: Thu, 31 Oct 2024 19:54:41 -0300 Subject: [PATCH 2/6] Refactor to DRY the logic identifying posts from an AI bot --- .../components/post-menu/ai-debug-button.gjs | 16 +----- .../components/post-menu/ai-share-button.gjs | 15 +---- .../discourse/lib/ai-bot-helper.js | 11 ++++ .../initializers/ai-bot-replies.js | 55 ++++++------------- 4 files changed, 32 insertions(+), 65 deletions(-) diff --git a/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs b/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs index cc53b03e0..f4144d33c 100644 --- a/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs +++ b/assets/javascripts/discourse/components/post-menu/ai-debug-button.gjs @@ -2,24 +2,12 @@ import Component from "@glimmer/component"; import { action } from "@ember/object"; import { inject as service } from "@ember/service"; import DButton from "discourse/components/d-button"; +import { isPostFromAiBot } from "../../lib/ai-bot-helper"; import DebugAiModal from "../modal/debug-ai-modal"; -const MAX_PERSONA_USER_ID = -1200; - export default class AiDebugButton extends Component { static shouldRender(args) { - if ( - !args.state.currentUser.ai_enabled_chat_bots.any( - (bot) => args.post.username === bot.username - ) - ) { - // special handling for personas (persona bot users start at ID -1200 and go down) - if (args.post.user_id > MAX_PERSONA_USER_ID) { - return false; - } - } - - return true; + return isPostFromAiBot(args.post, args.state.currentUser); } // TODO (glimmer-post-menu): Remove this static function and move the code into the button action after the widget code is removed diff --git a/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs b/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs index d938be207..04c378337 100644 --- a/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs +++ b/assets/javascripts/discourse/components/post-menu/ai-share-button.gjs @@ -2,26 +2,15 @@ import Component from "@glimmer/component"; import { action } from "@ember/object"; import { inject as service } from "@ember/service"; import DButton from "discourse/components/d-button"; +import { isPostFromAiBot } from "../../lib/ai-bot-helper"; import copyConversation from "../../lib/copy-conversation"; import ShareModal from "../modal/share-modal"; const AUTO_COPY_THRESHOLD = 4; -const MAX_PERSONA_USER_ID = -1200; export default class AiDebugButton extends Component { static shouldRender(args) { - if ( - !args.state.currentUser.ai_enabled_chat_bots.any( - (bot) => args.post.username === bot.username - ) - ) { - // special handling for personas (persona bot users start at ID -1200 and go down) - if (args.post.user_id > MAX_PERSONA_USER_ID) { - return false; - } - } - - return true; + return isPostFromAiBot(args.post, args.state.currentUser); } // TODO (glimmer-post-menu): Remove this static function and move the code into the button action after the widget code is removed diff --git a/assets/javascripts/discourse/lib/ai-bot-helper.js b/assets/javascripts/discourse/lib/ai-bot-helper.js index 73b312a2a..071b141ed 100644 --- a/assets/javascripts/discourse/lib/ai-bot-helper.js +++ b/assets/javascripts/discourse/lib/ai-bot-helper.js @@ -4,6 +4,17 @@ import Composer from "discourse/models/composer"; import I18n from "I18n"; import ShareFullTopicModal from "../components/modal/share-full-topic-modal"; +const MAX_PERSONA_USER_ID = -1200; + +export function isPostFromAiBot(post, currentUser) { + return ( + post.user_id <= MAX_PERSONA_USER_ID || + !!currentUser?.ai_enabled_chat_bots?.any( + (bot) => post.username === bot.username + ) + ); +} + export function showShareConversationModal(modal, topicId) { ajax(`/discourse-ai/ai-bot/shared-ai-conversations/preview/${topicId}.json`) .then((payload) => { diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js index 3ad3bf20a..8844f6b1f 100644 --- a/assets/javascripts/initializers/ai-bot-replies.js +++ b/assets/javascripts/initializers/ai-bot-replies.js @@ -12,7 +12,10 @@ import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon"; import AiCancelStreamingButton from "../discourse/components/post-menu/ai-cancel-streaming-button"; import AiDebugButton from "../discourse/components/post-menu/ai-debug-button"; import AiShareButton from "../discourse/components/post-menu/ai-share-button"; -import { showShareConversationModal } from "../discourse/lib/ai-bot-helper"; +import { + isPostFromAiBot, + showShareConversationModal, +} from "../discourse/lib/ai-bot-helper"; import { streamPostText } from "../discourse/lib/ai-streamer/progress-handlers"; let enabledChatBotIds = []; @@ -85,7 +88,12 @@ function initializePersonaDecorator(api) { ); } -const MAX_PERSONA_USER_ID = -1200; +const POST_MENU_BUTTONS_POSITION_BEFORE = [ + POST_MENU_LIKE_BUTTON_KEY, + POST_MENU_COPY_LINK_BUTTON_KEY, + POST_MENU_SHARE_BUTTON_KEY, + POST_MENU_SHOW_MORE_BUTTON_KEY, +]; function initializePauseButton(api) { const transformerRegistered = api.registerValueTransformer( @@ -93,12 +101,7 @@ function initializePauseButton(api) { ({ value: dag, context: { post } }) => { if (isGPTBot(post.user)) { dag.add("ai-cancel-gpt", AiCancelStreamingButton, { - before: [ - POST_MENU_LIKE_BUTTON_KEY, - POST_MENU_COPY_LINK_BUTTON_KEY, - POST_MENU_SHARE_BUTTON_KEY, - POST_MENU_SHOW_MORE_BUTTON_KEY, - ], + before: POST_MENU_BUTTONS_POSITION_BEFORE, after: ["ai-share", "ai-debug"], }); } @@ -142,12 +145,7 @@ function initializeDebugButton(api) { ({ value: dag, context: { post } }) => { if (post.topic?.archetype === "private_message") { dag.add("ai-debug", AiDebugButton, { - before: [ - POST_MENU_LIKE_BUTTON_KEY, - POST_MENU_COPY_LINK_BUTTON_KEY, - POST_MENU_SHARE_BUTTON_KEY, - POST_MENU_SHOW_MORE_BUTTON_KEY, - ], + before: POST_MENU_BUTTONS_POSITION_BEFORE, after: "ai-share", }); } @@ -175,15 +173,8 @@ function initializeDebugWidgetButton(api) { return; } - if ( - !currentUser.ai_enabled_chat_bots.any( - (bot) => post.username === bot.username - ) - ) { - // special handling for personas (persona bot users start at ID -1200 and go down) - if (post.user_id > MAX_PERSONA_USER_ID) { - return; - } + if (!isPostFromAiBot(post, currentUser)) { + return; } return { @@ -207,12 +198,7 @@ function initializeShareButton(api) { ({ value: dag, context: { post } }) => { if (post.topic?.archetype === "private_message") { dag.add("ai-share", AiShareButton, { - before: [ - POST_MENU_LIKE_BUTTON_KEY, - POST_MENU_COPY_LINK_BUTTON_KEY, - POST_MENU_SHARE_BUTTON_KEY, - POST_MENU_SHOW_MORE_BUTTON_KEY, - ], + before: POST_MENU_BUTTONS_POSITION_BEFORE, }); } @@ -240,15 +226,8 @@ function initializeShareWidgetButton(api) { return; } - if ( - !currentUser.ai_enabled_chat_bots.any( - (bot) => post.username === bot.username - ) - ) { - // special handling for personas (persona bot users start at ID -1200 and go down) - if (post.user_id > MAX_PERSONA_USER_ID) { - return; - } + if (!isPostFromAiBot(post, currentUser)) { + return; } return { From 895a700a2053015e5fe8196eb7f2c6713e52cf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Saquetim?= Date: Fri, 1 Nov 2024 14:39:13 -0300 Subject: [PATCH 3/6] Removed return from the transformers due to the use of the new mutable transformer --- assets/javascripts/initializers/ai-bot-replies.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js index 8844f6b1f..b478c3a62 100644 --- a/assets/javascripts/initializers/ai-bot-replies.js +++ b/assets/javascripts/initializers/ai-bot-replies.js @@ -105,8 +105,6 @@ function initializePauseButton(api) { after: ["ai-share", "ai-debug"], }); } - - return dag; } ); @@ -149,8 +147,6 @@ function initializeDebugButton(api) { after: "ai-share", }); } - - return dag; } ); @@ -201,8 +197,6 @@ function initializeShareButton(api) { before: POST_MENU_BUTTONS_POSITION_BEFORE, }); } - - return dag; } ); From 942ee5dd1e0f9eda291a4183c9c57010a567561d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Saquetim?= Date: Tue, 5 Nov 2024 21:11:47 -0300 Subject: [PATCH 4/6] Get the core button keys from the transformer context instead of imports --- .../initializers/ai-bot-replies.js | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/assets/javascripts/initializers/ai-bot-replies.js b/assets/javascripts/initializers/ai-bot-replies.js index b478c3a62..fa65c2562 100644 --- a/assets/javascripts/initializers/ai-bot-replies.js +++ b/assets/javascripts/initializers/ai-bot-replies.js @@ -1,10 +1,4 @@ import { hbs } from "ember-cli-htmlbars"; -import { - POST_MENU_COPY_LINK_BUTTON_KEY, - POST_MENU_LIKE_BUTTON_KEY, - POST_MENU_SHARE_BUTTON_KEY, - POST_MENU_SHOW_MORE_BUTTON_KEY, -} from "discourse/components/post/menu"; import { withPluginApi } from "discourse/lib/plugin-api"; import { registerWidgetShim } from "discourse/widgets/render-glimmer"; import { withSilencedDeprecations } from "discourse-common/lib/deprecated"; @@ -88,20 +82,13 @@ function initializePersonaDecorator(api) { ); } -const POST_MENU_BUTTONS_POSITION_BEFORE = [ - POST_MENU_LIKE_BUTTON_KEY, - POST_MENU_COPY_LINK_BUTTON_KEY, - POST_MENU_SHARE_BUTTON_KEY, - POST_MENU_SHOW_MORE_BUTTON_KEY, -]; - function initializePauseButton(api) { const transformerRegistered = api.registerValueTransformer( "post-menu-buttons", - ({ value: dag, context: { post } }) => { + ({ value: dag, context: { post, firstButtonKey } }) => { if (isGPTBot(post.user)) { dag.add("ai-cancel-gpt", AiCancelStreamingButton, { - before: POST_MENU_BUTTONS_POSITION_BEFORE, + before: firstButtonKey, after: ["ai-share", "ai-debug"], }); } @@ -140,10 +127,10 @@ function initializeDebugButton(api) { const transformerRegistered = api.registerValueTransformer( "post-menu-buttons", - ({ value: dag, context: { post } }) => { + ({ value: dag, context: { post, firstButtonKey } }) => { if (post.topic?.archetype === "private_message") { dag.add("ai-debug", AiDebugButton, { - before: POST_MENU_BUTTONS_POSITION_BEFORE, + before: firstButtonKey, after: "ai-share", }); } @@ -191,10 +178,10 @@ function initializeShareButton(api) { const transformerRegistered = api.registerValueTransformer( "post-menu-buttons", - ({ value: dag, context: { post } }) => { + ({ value: dag, context: { post, firstButtonKey } }) => { if (post.topic?.archetype === "private_message") { dag.add("ai-share", AiShareButton, { - before: POST_MENU_BUTTONS_POSITION_BEFORE, + before: firstButtonKey, }); } } From c666533a6cfca9ecf88c07a745233c04530c4b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Saquetim?= Date: Mon, 11 Nov 2024 17:28:28 -0300 Subject: [PATCH 5/6] Test with the glimmer post menu enabled and disabled --- spec/system/ai_bot/share_spec.rb | 160 ++++++++++++++++--------------- 1 file changed, 85 insertions(+), 75 deletions(-) diff --git a/spec/system/ai_bot/share_spec.rb b/spec/system/ai_bot/share_spec.rb index 9e8c68034..64a040612 100644 --- a/spec/system/ai_bot/share_spec.rb +++ b/spec/system/ai_bot/share_spec.rb @@ -13,7 +13,7 @@ Fabricate.build(:topic_allowed_user, user: admin), Fabricate.build(:topic_allowed_user, user: bot_user), ], - ) + ) end let(:pm_posts) do @@ -43,114 +43,124 @@ page.execute_script("window.navigator.clipboard.writeText('')") end - it "can share a conversation with a persona user" do - clip_text = nil + glimmer_post_menu_states = %w[enabled disabled] - persona = Fabricate(:ai_persona, name: "Tester") - persona.create_user! + glimmer_post_menu_states.each do |state| + context "with the glimmer post menu #{state}" do + before do + SiteSetting.glimmer_post_menu_mode = state + end - Fabricate(:post, topic: pm, user: admin, raw: "How do I do stuff?") - Fabricate(:post, topic: pm, user: persona.user, raw: "No idea") + it "can share a conversation with a persona user" do + clip_text = nil - visit(pm.url) + persona = Fabricate(:ai_persona, name: "Tester") + persona.create_user! - find("#post_2 .post-action-menu__share-ai").click + Fabricate(:post, topic: pm, user: admin, raw: "How do I do stuff?") + Fabricate(:post, topic: pm, user: persona.user, raw: "No idea") - try_until_success do - clip_text = cdp.read_clipboard - expect(clip_text).not_to eq("") - end + visit(pm.url) - conversation = (<<~TEXT).strip -
- - This is my special PM - AI - + find("#post_2 .post-action-menu__share-ai").click - **ai_sharer:** + try_until_success do + clip_text = cdp.read_clipboard + expect(clip_text).not_to eq("") + end - How do I do stuff? + conversation = (<<~TEXT).strip +
+ + This is my special PM + AI + - **Tester_bot:** + **ai_sharer:** - No idea -
- TEXT + How do I do stuff? - expect(conversation).to eq(clip_text) - end + **Tester_bot:** - it "can share a conversation" do - clip_text = nil + No idea +
+ TEXT - pm - pm_posts + expect(conversation).to eq(clip_text) + end - visit(pm.url) + it "can share a conversation" do + clip_text = nil - find("#post_2 .post-action-menu__share-ai").click + pm + pm_posts - try_until_success do - clip_text = cdp.read_clipboard - expect(clip_text).not_to eq("") - end + visit(pm.url) - conversation = (<<~TEXT).strip -
- - This is my special PM - AI - + find("#post_2 .post-action-menu__share-ai").click - **ai_sharer:** + try_until_success do + clip_text = cdp.read_clipboard + expect(clip_text).not_to eq("") + end - test test test user reply 1 + conversation = (<<~TEXT).strip +
+ + This is my special PM + AI + - **gpt-4:** + **ai_sharer:** - test test test bot reply 1 -
- TEXT + test test test user reply 1 - expect(conversation).to eq(clip_text) + **gpt-4:** - page.execute_script("window.navigator.clipboard.writeText('')") + test test test bot reply 1 +
+ TEXT - find("#post_6 .post-action-menu__share-ai").click - find(".ai-share-modal__slider input").set("2") - find(".ai-share-modal button.btn-primary").click + expect(conversation).to eq(clip_text) - try_until_success do - clip_text = cdp.read_clipboard - expect(clip_text).not_to eq("") - end + page.execute_script("window.navigator.clipboard.writeText('')") + + find("#post_6 .post-action-menu__share-ai").click + find(".ai-share-modal__slider input").set("2") + find(".ai-share-modal button.btn-primary").click - conversation = (<<~TEXT).strip -
- - This is my special PM - AI - + try_until_success do + clip_text = cdp.read_clipboard + expect(clip_text).not_to eq("") + end - **ai_sharer:** + conversation = (<<~TEXT).strip +
+ + This is my special PM + AI + - test test test user reply 2 + **ai_sharer:** - **gpt-4:** + test test test user reply 2 - test test test bot reply 2 + **gpt-4:** - **ai_sharer:** + test test test bot reply 2 - test test test user reply 3 + **ai_sharer:** - **gpt-4:** + test test test user reply 3 - test test test bot reply 3 -
- TEXT + **gpt-4:** - expect(conversation).to eq(clip_text) + test test test bot reply 3 +
+ TEXT + + expect(conversation).to eq(clip_text) + end + end end end From 09c8c791eebbd7f086ff01446c03c2569c21b8cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Saquetim?= Date: Mon, 11 Nov 2024 18:03:40 -0300 Subject: [PATCH 6/6] Stree --- spec/system/ai_bot/share_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spec/system/ai_bot/share_spec.rb b/spec/system/ai_bot/share_spec.rb index 64a040612..06a5c4d14 100644 --- a/spec/system/ai_bot/share_spec.rb +++ b/spec/system/ai_bot/share_spec.rb @@ -13,7 +13,7 @@ Fabricate.build(:topic_allowed_user, user: admin), Fabricate.build(:topic_allowed_user, user: bot_user), ], - ) + ) end let(:pm_posts) do @@ -47,9 +47,7 @@ glimmer_post_menu_states.each do |state| context "with the glimmer post menu #{state}" do - before do - SiteSetting.glimmer_post_menu_mode = state - end + before { SiteSetting.glimmer_post_menu_mode = state } it "can share a conversation with a persona user" do clip_text = nil