mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-03-07 01:39:54 +00:00
Previous to this change we relied on client side settings to determine if an end user has access to the ai bot. This meant that if a user was not aware they are a member of a group (as it is with restricted visibility ones) they would not see the bot button. All checking has now moved to the server side, and tests were added to cover.
185 lines
5.3 KiB
JavaScript
185 lines
5.3 KiB
JavaScript
import { withPluginApi } from "discourse/lib/plugin-api";
|
|
import { cookAsync } from "discourse/lib/text";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import loadScript from "discourse/lib/load-script";
|
|
import { composeAiBotMessage } from "discourse/plugins/discourse-ai/discourse/lib/ai-bot-helper";
|
|
import { registerWidgetShim } from "discourse/widgets/render-glimmer";
|
|
import { hbs } from "ember-cli-htmlbars";
|
|
|
|
function isGPTBot(user) {
|
|
return user && [-110, -111, -112].includes(user.id);
|
|
}
|
|
|
|
function attachHeaderIcon(api) {
|
|
const settings = api.container.lookup("service:site-settings");
|
|
|
|
const enabledBots = settings.ai_bot_add_to_header
|
|
? settings.ai_bot_enabled_chat_bots.split("|").filter(Boolean)
|
|
: [];
|
|
if (enabledBots.length > 0) {
|
|
api.attachWidgetAction("header", "showAiBotPanel", function () {
|
|
this.state.botSelectorVisible = true;
|
|
});
|
|
|
|
api.attachWidgetAction("header", "hideAiBotPanel", function () {
|
|
this.state.botSelectorVisible = false;
|
|
});
|
|
|
|
api.decorateWidget("header-icons:before", (helper) => {
|
|
return helper.attach("header-dropdown", {
|
|
title: "discourse_ai.ai_bot.shortcut_title",
|
|
icon: "robot",
|
|
action: "clickStartAiBotChat",
|
|
active: false,
|
|
classNames: ["ai-bot-button"],
|
|
});
|
|
});
|
|
|
|
if (enabledBots.length === 1) {
|
|
api.attachWidgetAction("header", "clickStartAiBotChat", function () {
|
|
composeAiBotMessage(
|
|
enabledBots[0],
|
|
api.container.lookup("service:composer")
|
|
);
|
|
});
|
|
} else {
|
|
api.attachWidgetAction("header", "clickStartAiBotChat", function () {
|
|
this.sendWidgetAction("showAiBotPanel");
|
|
});
|
|
}
|
|
|
|
api.addHeaderPanel(
|
|
"ai-bot-header-panel-wrapper",
|
|
"botSelectorVisible",
|
|
function () {
|
|
return {};
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
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);
|
|
});
|
|
|
|
api.modifyClass("controller:topic", {
|
|
pluginId: "discourse-ai",
|
|
|
|
onAIBotStreamedReply: function (data) {
|
|
const post = this.model.postStream.findLoadedPost(data.post_id);
|
|
|
|
if (post) {
|
|
if (data.raw) {
|
|
cookAsync(data.raw).then((cooked) => {
|
|
post.set("raw", data.raw);
|
|
post.set("cooked", cooked);
|
|
|
|
document
|
|
.querySelector(`#post_${data.post_number}`)
|
|
.classList.add("streaming");
|
|
|
|
const cookedElement = document.createElement("div");
|
|
cookedElement.innerHTML = cooked;
|
|
|
|
let element = document.querySelector(
|
|
`#post_${data.post_number} .cooked`
|
|
);
|
|
|
|
loadScript("/javascripts/diffhtml.min.js").then(() => {
|
|
window.diff.innerHTML(element, cookedElement.innerHTML);
|
|
});
|
|
});
|
|
}
|
|
if (data.done) {
|
|
document
|
|
.querySelector(`#post_${data.post_number}`)
|
|
.classList.remove("streaming");
|
|
}
|
|
}
|
|
},
|
|
subscribe: function () {
|
|
this._super();
|
|
|
|
if (
|
|
this.model.isPrivateMessage &&
|
|
this.model.details.allowed_users &&
|
|
this.model.details.allowed_users.filter(isGPTBot).length >= 1
|
|
) {
|
|
this.messageBus.subscribe(
|
|
`discourse-ai/ai-bot/topic/${this.model.id}`,
|
|
this.onAIBotStreamedReply.bind(this)
|
|
);
|
|
}
|
|
},
|
|
unsubscribe: function () {
|
|
this.messageBus.unsubscribe("discourse-ai/ai-bot/topic/*");
|
|
this._super();
|
|
},
|
|
});
|
|
}
|
|
|
|
function initializePersonaDecorator(api) {
|
|
let topicController = null;
|
|
api.decorateWidget(`poster-name:after`, (dec) => {
|
|
if (!isGPTBot(dec.attrs.user)) {
|
|
return;
|
|
}
|
|
// this is hacky and will need to change
|
|
// trouble is we need to get the model for the topic
|
|
// and it is not available in the decorator
|
|
// long term this will not be a problem once we remove widgets and
|
|
// have a saner structure for our model
|
|
topicController =
|
|
topicController || api.container.lookup("controller:topic");
|
|
|
|
return dec.widget.attach("persona-flair", {
|
|
topicController,
|
|
});
|
|
});
|
|
|
|
registerWidgetShim(
|
|
"persona-flair",
|
|
"span.persona-flair",
|
|
hbs`{{@data.topicController.model.ai_persona_name}}`
|
|
);
|
|
}
|
|
|
|
export default {
|
|
name: "discourse-ai-bot-replies",
|
|
|
|
initialize(container) {
|
|
const settings = container.lookup("service:site-settings");
|
|
const user = container.lookup("service:current-user");
|
|
|
|
if (user?.ai_enabled_chat_bots) {
|
|
if (settings.ai_bot_add_to_header) {
|
|
withPluginApi("1.6.0", attachHeaderIcon);
|
|
}
|
|
withPluginApi("1.6.0", initializeAIBotReplies);
|
|
withPluginApi("1.6.0", initializePersonaDecorator);
|
|
}
|
|
},
|
|
};
|