DEV: Added compatibility with the Glimmer Post Menu (#887)
This commit is contained in:
parent
2fc05685bb
commit
9583964676
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DButton
|
||||||
|
class="post-action-menu__ai-cancel-streaming cancel-streaming"
|
||||||
|
...attributes
|
||||||
|
@action={{this.cancelStreaming}}
|
||||||
|
@icon="pause"
|
||||||
|
@title="discourse_ai.ai_bot.cancel_streaming"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default class AiDebugButton extends Component {
|
||||||
|
static shouldRender(args) {
|
||||||
|
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
|
||||||
|
static debugAiResponse(post, modal) {
|
||||||
|
modal.show(DebugAiModal, { model: post });
|
||||||
|
}
|
||||||
|
|
||||||
|
@service modal;
|
||||||
|
|
||||||
|
@action
|
||||||
|
debugAiResponse() {
|
||||||
|
this.constructor.debugAiResponse(this.args.post, this.modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DButton
|
||||||
|
class="post-action-menu__debug-ai"
|
||||||
|
...attributes
|
||||||
|
@action={{this.debugAiResponse}}
|
||||||
|
@icon="info"
|
||||||
|
@title="discourse_ai.ai_bot.debug_ai"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -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 { isPostFromAiBot } from "../../lib/ai-bot-helper";
|
||||||
|
import copyConversation from "../../lib/copy-conversation";
|
||||||
|
import ShareModal from "../modal/share-modal";
|
||||||
|
|
||||||
|
const AUTO_COPY_THRESHOLD = 4;
|
||||||
|
|
||||||
|
export default class AiDebugButton extends Component {
|
||||||
|
static shouldRender(args) {
|
||||||
|
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
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DButton
|
||||||
|
class="post-action-menu__share-ai"
|
||||||
|
...attributes
|
||||||
|
@action={{this.shareAiResponse}}
|
||||||
|
@icon="far-copy"
|
||||||
|
@title="discourse_ai.ai_bot.share"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -4,6 +4,17 @@ import Composer from "discourse/models/composer";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import ShareFullTopicModal from "../components/modal/share-full-topic-modal";
|
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) {
|
export function showShareConversationModal(modal, topicId) {
|
||||||
ajax(`/discourse-ai/ai-bot/shared-ai-conversations/preview/${topicId}.json`)
|
ajax(`/discourse-ai/ai-bot/shared-ai-conversations/preview/${topicId}.json`)
|
||||||
.then((payload) => {
|
.then((payload) => {
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
import { registerWidgetShim } from "discourse/widgets/render-glimmer";
|
import { registerWidgetShim } from "discourse/widgets/render-glimmer";
|
||||||
import DebugAiModal from "../discourse/components/modal/debug-ai-modal";
|
import { withSilencedDeprecations } from "discourse-common/lib/deprecated";
|
||||||
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 AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon";
|
import AiBotHeaderIcon from "../discourse/components/ai-bot-header-icon";
|
||||||
import { showShareConversationModal } from "../discourse/lib/ai-bot-helper";
|
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 {
|
||||||
|
isPostFromAiBot,
|
||||||
|
showShareConversationModal,
|
||||||
|
} from "../discourse/lib/ai-bot-helper";
|
||||||
|
import { streamPostText } from "../discourse/lib/ai-streamer/progress-handlers";
|
||||||
|
|
||||||
let enabledChatBotIds = [];
|
let enabledChatBotIds = [];
|
||||||
let allowDebug = false;
|
let allowDebug = false;
|
||||||
|
|
||||||
function isGPTBot(user) {
|
function isGPTBot(user) {
|
||||||
return user && enabledChatBotIds.includes(user.id);
|
return user && enabledChatBotIds.includes(user.id);
|
||||||
}
|
}
|
||||||
|
@ -22,29 +24,7 @@ function attachHeaderIcon(api) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeAIBotReplies(api) {
|
function initializeAIBotReplies(api) {
|
||||||
api.addPostMenuButton("cancel-gpt", (post) => {
|
initializePauseButton(api);
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
api.modifyClass("controller:topic", {
|
api.modifyClass("controller:topic", {
|
||||||
pluginId: "discourse-ai",
|
pluginId: "discourse-ai",
|
||||||
|
@ -102,7 +82,42 @@ function initializePersonaDecorator(api) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_PERSONA_USER_ID = -1200;
|
function initializePauseButton(api) {
|
||||||
|
const transformerRegistered = api.registerValueTransformer(
|
||||||
|
"post-menu-buttons",
|
||||||
|
({ value: dag, context: { post, firstButtonKey } }) => {
|
||||||
|
if (isGPTBot(post.user)) {
|
||||||
|
dag.add("ai-cancel-gpt", AiCancelStreamingButton, {
|
||||||
|
before: firstButtonKey,
|
||||||
|
after: ["ai-share", "ai-debug"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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) {
|
function initializeDebugButton(api) {
|
||||||
const currentUser = api.getCurrentUser();
|
const currentUser = api.getCurrentUser();
|
||||||
|
@ -110,10 +125,30 @@ function initializeDebugButton(api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const transformerRegistered = api.registerValueTransformer(
|
||||||
|
"post-menu-buttons",
|
||||||
|
({ value: dag, context: { post, firstButtonKey } }) => {
|
||||||
|
if (post.topic?.archetype === "private_message") {
|
||||||
|
dag.add("ai-debug", AiDebugButton, {
|
||||||
|
before: firstButtonKey,
|
||||||
|
after: "ai-share",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const silencedKey =
|
||||||
|
transformerRegistered && "discourse.post-menu-widget-overrides";
|
||||||
|
|
||||||
|
withSilencedDeprecations(silencedKey, () => initializeDebugWidgetButton(api));
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeDebugWidgetButton(api) {
|
||||||
|
const currentUser = api.getCurrentUser();
|
||||||
|
|
||||||
let debugAiResponse = async function ({ post }) {
|
let debugAiResponse = async function ({ post }) {
|
||||||
const modal = api.container.lookup("service:modal");
|
const modal = api.container.lookup("service:modal");
|
||||||
|
AiDebugButton.debugAiResponse(post, modal);
|
||||||
modal.show(DebugAiModal, { model: post });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
api.addPostMenuButton("debugAi", (post) => {
|
api.addPostMenuButton("debugAi", (post) => {
|
||||||
|
@ -121,16 +156,9 @@ function initializeDebugButton(api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!isPostFromAiBot(post, currentUser)) {
|
||||||
!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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: debugAiResponse,
|
action: debugAiResponse,
|
||||||
|
@ -148,14 +176,29 @@ function initializeShareButton(api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let shareAiResponse = async function ({ post, showFeedback }) {
|
const transformerRegistered = api.registerValueTransformer(
|
||||||
if (post.post_number <= AUTO_COPY_THRESHOLD) {
|
"post-menu-buttons",
|
||||||
await copyConversation(post.topic, 1, post.post_number);
|
({ value: dag, context: { post, firstButtonKey } }) => {
|
||||||
showFeedback("discourse_ai.ai_bot.conversation_shared");
|
if (post.topic?.archetype === "private_message") {
|
||||||
} else {
|
dag.add("ai-share", AiShareButton, {
|
||||||
const modal = api.container.lookup("service:modal");
|
before: firstButtonKey,
|
||||||
modal.show(ShareModal, { model: post });
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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) => {
|
api.addPostMenuButton("share", (post) => {
|
||||||
|
@ -164,21 +207,14 @@ function initializeShareButton(api) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (!isPostFromAiBot(post, currentUser)) {
|
||||||
!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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
action: shareAiResponse,
|
action: shareAiResponse,
|
||||||
icon: "far-copy",
|
icon: "far-copy",
|
||||||
className: "post-action-menu__share",
|
className: "post-action-menu__share-ai",
|
||||||
title: "discourse_ai.ai_bot.share",
|
title: "discourse_ai.ai_bot.share",
|
||||||
position: "first",
|
position: "first",
|
||||||
};
|
};
|
||||||
|
@ -218,10 +254,10 @@ export default {
|
||||||
enabledChatBotIds = user.ai_enabled_chat_bots.map((bot) => bot.id);
|
enabledChatBotIds = user.ai_enabled_chat_bots.map((bot) => bot.id);
|
||||||
allowDebug = user.can_debug_ai_bot_conversations;
|
allowDebug = user.can_debug_ai_bot_conversations;
|
||||||
withPluginApi("1.6.0", attachHeaderIcon);
|
withPluginApi("1.6.0", attachHeaderIcon);
|
||||||
withPluginApi("1.6.0", initializeAIBotReplies);
|
withPluginApi("1.34.0", initializeAIBotReplies);
|
||||||
withPluginApi("1.6.0", initializePersonaDecorator);
|
withPluginApi("1.6.0", initializePersonaDecorator);
|
||||||
withPluginApi("1.22.0", (api) => initializeDebugButton(api, container));
|
withPluginApi("1.34.0", (api) => initializeDebugButton(api, container));
|
||||||
withPluginApi("1.22.0", (api) => initializeShareButton(api, container));
|
withPluginApi("1.34.0", (api) => initializeShareButton(api, container));
|
||||||
withPluginApi("1.22.0", (api) =>
|
withPluginApi("1.22.0", (api) =>
|
||||||
initializeShareTopicButton(api, container)
|
initializeShareTopicButton(api, container)
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,6 +43,12 @@ RSpec.describe "Share conversation", type: :system do
|
||||||
page.execute_script("window.navigator.clipboard.writeText('')")
|
page.execute_script("window.navigator.clipboard.writeText('')")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
glimmer_post_menu_states = %w[enabled disabled]
|
||||||
|
|
||||||
|
glimmer_post_menu_states.each do |state|
|
||||||
|
context "with the glimmer post menu #{state}" do
|
||||||
|
before { SiteSetting.glimmer_post_menu_mode = state }
|
||||||
|
|
||||||
it "can share a conversation with a persona user" do
|
it "can share a conversation with a persona user" do
|
||||||
clip_text = nil
|
clip_text = nil
|
||||||
|
|
||||||
|
@ -54,7 +60,7 @@ RSpec.describe "Share conversation", type: :system do
|
||||||
|
|
||||||
visit(pm.url)
|
visit(pm.url)
|
||||||
|
|
||||||
find("#post_2 .post-action-menu__share").click
|
find("#post_2 .post-action-menu__share-ai").click
|
||||||
|
|
||||||
try_until_success do
|
try_until_success do
|
||||||
clip_text = cdp.read_clipboard
|
clip_text = cdp.read_clipboard
|
||||||
|
@ -89,7 +95,7 @@ RSpec.describe "Share conversation", type: :system do
|
||||||
|
|
||||||
visit(pm.url)
|
visit(pm.url)
|
||||||
|
|
||||||
find("#post_2 .post-action-menu__share").click
|
find("#post_2 .post-action-menu__share-ai").click
|
||||||
|
|
||||||
try_until_success do
|
try_until_success do
|
||||||
clip_text = cdp.read_clipboard
|
clip_text = cdp.read_clipboard
|
||||||
|
@ -117,7 +123,7 @@ RSpec.describe "Share conversation", type: :system do
|
||||||
|
|
||||||
page.execute_script("window.navigator.clipboard.writeText('')")
|
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__slider input").set("2")
|
||||||
find(".ai-share-modal button.btn-primary").click
|
find(".ai-share-modal button.btn-primary").click
|
||||||
|
|
||||||
|
@ -154,3 +160,5 @@ RSpec.describe "Share conversation", type: :system do
|
||||||
expect(conversation).to eq(clip_text)
|
expect(conversation).to eq(clip_text)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in New Issue