Compare commits

..
Author SHA1 Message Date
Sam Saffron 13fca098d1 tweaks to open llm implementation
testing mistral at the moment and did the minimum to get it going
2023-10-04 17:20:46 +11:00
Sam Saffron d76309fa17 tweaks to llama2 support in chat, fix message so it conforms to recommendation
by facebook (add <s> )
2023-10-04 16:27:00 +11:00
Sam Saffron facce51f98 FEATURE: add support for an open LLM implementation
This is very much a work in progress, testing at the moment on:

TheBloke/Llama-2-7B-chat-fp16
2023-10-04 16:25:44 +11:00
163 changed files with 626 additions and 33598 deletions
-1
View File
@@ -1,4 +1,3 @@
< 3.2.0.beta2-dev: 3bced1c6f525553fe2b5b79d29bd66c1cf2a47d5
3.1.999: c0415bb7eb878b1b7abf112d65cba981030df8df
< 3.2.0.beta1-dev: 3e7c99de894b03e9161d9481da118fe893991a1d
3.1.0.beta3: 057fbe1ce6
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "eslint-config-discourse"
}
-1
View File
@@ -1 +0,0 @@
module.exports = require("@discourse/lint-configs/eslint");
+1
View File
@@ -0,0 +1 @@
{}
-1
View File
@@ -1 +0,0 @@
module.exports = require("@discourse/lint-configs/prettier");
-2
View File
@@ -1,4 +1,2 @@
inherit_gem:
rubocop-discourse: stree-compat.yml
RSpec/NamedSubject:
Enabled: false
+1 -1
View File
@@ -1,2 +1,2 @@
--print-width=100
--plugins=plugin/trailing_comma,plugin/disable_auto_ternary
--plugins=plugin/trailing_comma,disable_ternary
+4 -1
View File
@@ -1 +1,4 @@
module.exports = require("@discourse/lint-configs/template-lint");
module.exports = {
plugins: ["ember-template-lint-plugin-discourse"],
extends: "discourse:recommended",
};
+1
View File
@@ -5,4 +5,5 @@ source "https://rubygems.org"
group :development do
gem "rubocop-discourse"
gem "syntax_tree"
gem "syntax_tree-disable_ternary"
end
+20 -28
View File
@@ -2,45 +2,36 @@ GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
json (2.6.3)
language_server-protocol (3.17.0.3)
parallel (1.23.0)
parser (3.2.2.4)
json (2.6.2)
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
racc
prettier_print (1.2.1)
racc (1.7.2)
prettier_print (1.2.0)
rainbow (3.1.1)
regexp_parser (2.8.2)
rexml (3.2.6)
rubocop (1.57.2)
regexp_parser (2.6.0)
rexml (3.2.5)
rubocop (1.36.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.2.2.4)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.1, < 2.0)
rubocop-ast (>= 1.20.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.30.0)
parser (>= 3.2.1.0)
rubocop-capybara (2.19.0)
rubocop (~> 1.41)
rubocop-discourse (3.4.0)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.21.0)
parser (>= 3.1.1.0)
rubocop-discourse (3.0)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-factory_bot (2.24.0)
rubocop-rspec (2.13.2)
rubocop (~> 1.33)
rubocop-rspec (2.25.0)
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
syntax_tree (6.2.0)
ruby-progressbar (1.11.0)
syntax_tree (5.1.0)
prettier_print (>= 1.2.0)
unicode-display_width (2.5.0)
syntax_tree-disable_ternary (1.0.0)
unicode-display_width (2.3.0)
PLATFORMS
arm64-darwin-20
@@ -53,6 +44,7 @@ PLATFORMS
DEPENDENCIES
rubocop-discourse
syntax_tree
syntax_tree-disable_ternary
BUNDLED WITH
2.4.21
2.3.10
@@ -96,37 +96,12 @@ module DiscourseAi
end
end
def explain
post_id = get_post_param!
text = get_text_param!
post = Post.find_by(id: post_id)
raise Discourse::InvalidParameters.new(:post_id) unless post
render json:
DiscourseAi::AiHelper::TopicHelper.new(
{ text: text },
current_user,
post: post,
).explain,
status: 200
rescue ::DiscourseAi::Inference::OpenAiCompletions::CompletionFailed,
::DiscourseAi::Inference::HuggingFaceTextGeneration::CompletionFailed,
::DiscourseAi::Inference::AnthropicCompletions::CompletionFailed => e
render_json_error I18n.t("discourse_ai.ai_helper.errors.completion_request_failed"),
status: 502
end
private
def get_text_param!
params[:text].tap { |t| raise Discourse::InvalidParameters.new(:text) if t.blank? }
end
def get_post_param!
params[:post_id].tap { |t| raise Discourse::InvalidParameters.new(:post_id) if t.blank? }
end
def rate_limiter_performed!
RateLimiter.new(current_user, "ai_assistant", 6, 3.minutes).performed!
end
-2
View File
@@ -15,8 +15,6 @@ end
# id :bigint not null, primary key
# provider_id :integer not null
# user_id :integer
# topic_id :integer
# post_id :integer
# request_tokens :integer
# response_tokens :integer
# raw_request_payload :string
-2
View File
@@ -10,8 +10,6 @@ class CompletionPrompt < ActiveRecord::Base
validate :each_message_length
def messages_with_user_input(user_input)
return messages unless user_input.present?
if user_input[:custom_prompt].present?
case ::DiscourseAi::AiHelper::LlmPrompt.new.enabled_provider
when "huggingface"
@@ -1,9 +1,10 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import I18n from "I18n";
import Component from "@glimmer/component";
import { composeAiBotMessage } from "discourse/plugins/discourse-ai/discourse/lib/ai-bot-helper";
import I18n from "I18n";
export default class AiBotHeaderPanel extends Component {
@service siteSettings;
@service composer;
@@ -1,15 +1,47 @@
import Component from "@glimmer/component";
import Component from '@glimmer/component';
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { bind } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import I18n from "I18n";
import DButton from "discourse/components/d-button";
export default class AISuggestionDropdown extends Component {
<template>
{{#if this.showAIButton}}
<DButton
@class="suggestion-button {{if this.loading 'is-loading'}}"
@icon={{this.suggestIcon}}
@title="discourse_ai.ai_helper.suggest"
@action={{this.performSuggestion}}
@disabled={{this.disableSuggestionButton}}
...attributes
/>
{{/if}}
{{#if this.showMenu}}
{{! template-lint-disable modifier-name-case }}
<ul class="popup-menu ai-suggestions-menu" {{didInsert this.handleClickOutside}}>
{{#if this.showErrors}}
<li class="ai-suggestions-menu__errors">{{this.error}}</li>
{{/if}}
{{#each this.generatedSuggestions as |suggestion index|}}
<li data-name={{suggestion}} data-value={{index}}>
<DButton
@class="popup-menu-btn"
@translatedLabel={{suggestion}}
@action={{this.applySuggestion}}
@actionParam={{suggestion}}
/>
</li>
{{/each}}
</ul>
{{/if}}
</template>
@service dialog;
@service siteSettings;
@service composer;
@@ -32,33 +64,13 @@ export default class AISuggestionDropdown extends Component {
get showAIButton() {
const minCharacterCount = 40;
const isShowAIButton = this.composer.model.replyLength > minCharacterCount;
const composerFields = document.querySelector(".composer-fields");
if (composerFields) {
if (isShowAIButton) {
composerFields.classList.add("showing-ai-suggestions");
} else {
composerFields.classList.remove("showing-ai-suggestions");
}
}
return isShowAIButton;
return this.composer.model.replyLength > minCharacterCount;
}
get disableSuggestionButton() {
return this.loading;
}
@action
applyClasses() {
if (this.showAIButton) {
document.querySelector(".composer-fields")?.classList.add("showing-ai-suggestions");
} else {
document.querySelector(".composer-fields")?.classList.remove("showing-ai-suggestions");
}
}
@bind
onClickOutside(event) {
const menu = document.querySelector(".ai-title-suggestions-menu");
@@ -86,17 +98,16 @@ export default class AISuggestionDropdown extends Component {
return;
}
if (this.args.mode === this.SUGGESTION_TYPES.title) {
composer.set("title", suggestion);
return this.#closeMenu();
}
if (this.args.mode === this.SUGGESTION_TYPES.category) {
const selectedCategoryId = this.composer.categories.find(
(c) => c.slug === suggestion
).id;
composer.set("categoryId", selectedCategoryId);
return this.#closeMenu();
const selectedCategoryId = this.composer.categories.find((c) => c.slug === suggestion).id;
composer.set("categoryId", selectedCategoryId);
return this.#closeMenu();
}
if (this.args.mode === this.SUGGESTION_TYPES.tag) {
@@ -111,9 +122,7 @@ export default class AISuggestionDropdown extends Component {
}
if (this.composer.model.replyLength === 0) {
return this.dialog.alert(
I18n.t("discourse_ai.ai_helper.missing_content")
);
return this.dialog.alert(I18n.t("discourse_ai.ai_helper.missing_content"));
}
this.loading = true;
@@ -122,27 +131,16 @@ export default class AISuggestionDropdown extends Component {
return ajax(`/discourse-ai/ai-helper/${this.args.mode}`, {
method: "POST",
data: { text: this.composer.model.reply },
})
.then((data) => {
this.#assignGeneratedSuggestions(data, this.args.mode);
})
.catch(popupAjaxError)
.finally(() => {
this.loading = false;
this.suggestIcon = "sync-alt";
this.showMenu = true;
if (this.args.mode === "suggest_category") {
document.querySelector(".category-input")?.classList.add("showing-ai-suggestion-menu");
}
});
}).then((data) => {
this.#assignGeneratedSuggestions(data, this.args.mode);
}).catch(popupAjaxError).finally(() => {
this.loading = false;
this.suggestIcon = "sync-alt";
this.showMenu = true;
});
}
#closeMenu() {
if (this.showMenu && this.args.mode === "suggest_category") {
document.querySelector(".category-input")?.classList.remove("showing-ai-suggestion-menu");
}
this.suggestIcon = "discourse-sparkles";
this.showMenu = false;
this.showErrors = false;
@@ -155,9 +153,7 @@ export default class AISuggestionDropdown extends Component {
if (!composer.tags) {
composer.set("tags", [suggestion]);
// remove tag from the list of suggestions once added
this.generatedSuggestions = this.generatedSuggestions.filter(
(s) => s !== suggestion
);
this.generatedSuggestions = this.generatedSuggestions.filter((s) => s !== suggestion);
return;
}
const tags = composer.tags;
@@ -165,16 +161,14 @@ export default class AISuggestionDropdown extends Component {
if (tags?.length >= maxTags) {
// Show error if trying to add more tags than allowed
this.showErrors = true;
this.error = I18n.t("select_kit.max_content_reached", { count: maxTags });
this.error = I18n.t("select_kit.max_content_reached", { count: maxTags});
return;
}
tags.push(suggestion);
composer.set("tags", [...tags]);
// remove tag from the list of suggestions once added
return (this.generatedSuggestions = this.generatedSuggestions.filter(
(s) => s !== suggestion
));
return this.generatedSuggestions = this.generatedSuggestions.filter((s) => s !== suggestion);
}
#tagSelectorHasValues() {
@@ -183,7 +177,7 @@ export default class AISuggestionDropdown extends Component {
#assignGeneratedSuggestions(data, mode) {
if (mode === this.SUGGESTION_TYPES.title) {
return (this.generatedSuggestions = data.suggestions);
return this.generatedSuggestions = data.suggestions;
}
const suggestions = data.assistant.map((s) => s.name);
@@ -191,49 +185,12 @@ export default class AISuggestionDropdown extends Component {
if (mode === this.SUGGESTION_TYPES.tag) {
if (this.#tagSelectorHasValues()) {
// Filter out tags if they are already selected in the tag input
return (this.generatedSuggestions = suggestions.filter(
(t) => !this.args.composer.tags.includes(t)
));
return this.generatedSuggestions = suggestions.filter((t) => !this.args.composer.tags.includes(t));
} else {
return (this.generatedSuggestions = suggestions);
return this.generatedSuggestions = suggestions;
}
}
return (this.generatedSuggestions = suggestions);
return this.generatedSuggestions = suggestions;
}
<template>
{{#if this.showAIButton}}
<DButton
@class="suggestion-button {{if this.loading 'is-loading'}}"
@icon={{this.suggestIcon}}
@title="discourse_ai.ai_helper.suggest"
@action={{this.performSuggestion}}
@disabled={{this.disableSuggestionButton}}
...attributes
/>
{{/if}}
{{#if this.showMenu}}
{{! template-lint-disable modifier-name-case }}
<ul
class="popup-menu ai-suggestions-menu"
{{didInsert this.handleClickOutside}}
>
{{#if this.showErrors}}
<li class="ai-suggestions-menu__errors">{{this.error}}</li>
{{/if}}
{{#each this.generatedSuggestions as |suggestion index|}}
<li data-name={{suggestion}} data-value={{index}}>
<DButton
@class="popup-menu-btn"
@translatedLabel={{suggestion}}
@action={{this.applySuggestion}}
@actionParam={{suggestion}}
/>
</li>
{{/each}}
</ul>
{{/if}}
</template>
}
}
@@ -1,25 +1,14 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { htmlSafe } from "@ember/template";
import DButton from "discourse/components/d-button";
import DModal from "discourse/components/d-modal";
import DModalCancel from "discourse/components/d-modal-cancel";
import I18n from "I18n";
import { htmlSafe } from "@ember/template";
const t = I18n.t.bind(I18n);
export default class ModalDiffModal extends Component {
@action
triggerConfirmChanges() {
this.args.closeModal();
this.args.confirm();
}
@action
triggerRevertChanges() {
this.args.closeModal();
this.args.revert();
}
<template>
<DModal
class="composer-ai-helper-modal"
@@ -46,12 +35,14 @@ export default class ModalDiffModal extends Component {
@action={{this.triggerConfirmChanges}}
@label="discourse_ai.ai_helper.context_menu.confirm"
/>
<DButton
class="btn-flat"
@action={{this.triggerRevertChanges}}
@label="discourse_ai.ai_helper.context_menu.revert"
/>
<DModalCancel @close={{@closeModal}} />
</:footer>
</DModal>
</template>
@action
triggerConfirmChanges() {
this.args.closeModal();
this.args.confirm();
}
}
@@ -1,22 +1,19 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import Component from '@glimmer/component';
import AISuggestionDropdown from "../../components/ai-suggestion-dropdown";
import { showComposerAIHelper } from "../../lib/show-ai-helper";
import { inject as service } from "@ember/service";
import showAIHelper from "../../lib/show-ai-helper";
export default class AICategorySuggestion extends Component {
<template>
{{#if this.siteSettings.ai_embeddings_enabled}}
<AISuggestionDropdown @mode="suggest_category" @composer={{@outletArgs.composer}} class="suggest-category-button"/>
{{/if}}
</template>
static shouldRender(outletArgs, helper) {
return showComposerAIHelper(outletArgs, helper);
return showAIHelper(outletArgs, helper);
}
@service siteSettings;
<template>
{{#if this.siteSettings.ai_embeddings_enabled}}
<AISuggestionDropdown
@mode="suggest_category"
@composer={{@outletArgs.composer}}
class="suggest-category-button"
/>
{{/if}}
</template>
}
}
@@ -1,22 +1,19 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import Component from '@glimmer/component';
import AISuggestionDropdown from "../../components/ai-suggestion-dropdown";
import { showComposerAIHelper } from "../../lib/show-ai-helper";
import { inject as service } from "@ember/service";
import showAIHelper from "../../lib/show-ai-helper";
export default class AITagSuggestion extends Component {
<template>
{{#if this.siteSettings.ai_embeddings_enabled}}
<AISuggestionDropdown @mode="suggest_tags" @composer={{@outletArgs.composer}} class="suggest-tags-button"/>
{{/if}}
</template>
static shouldRender(outletArgs, helper) {
return showComposerAIHelper(outletArgs, helper);
return showAIHelper(outletArgs, helper);
}
@service siteSettings;
<template>
{{#if this.siteSettings.ai_embeddings_enabled}}
<AISuggestionDropdown
@mode="suggest_tags"
@composer={{@outletArgs.composer}}
class="suggest-tags-button"
/>
{{/if}}
</template>
}
}
@@ -1,17 +1,13 @@
import Component from "@glimmer/component";
import Component from '@glimmer/component';
import AISuggestionDropdown from "../../components/ai-suggestion-dropdown";
import { showComposerAIHelper } from "../../lib/show-ai-helper";
import showAIHelper from "../../lib/show-ai-helper";
export default class AITitleSuggestion extends Component {
static shouldRender(outletArgs, helper) {
return showComposerAIHelper(outletArgs, helper);
}
<template>
<AISuggestionDropdown
@mode="suggest_title"
@composer={{@outletArgs.composer}}
class="suggest-titles-button"
/>
<AISuggestionDropdown @mode="suggest_title" @composer={{@outletArgs.composer}} class="suggest-titles-button" />
</template>
}
static shouldRender(outletArgs, helper) {
return showAIHelper(outletArgs, helper);
}
}
@@ -23,9 +23,7 @@
>
<Input
@value={{this.customPromptValue}}
placeholder={{i18n
"discourse_ai.ai_helper.context_menu.custom_prompt.placeholder"
}}
placeholder="Enter a custom prompt..."
class="ai-custom-prompt__input"
@enter={{action (fn this.updateSelected option)}}
/>
@@ -125,7 +123,6 @@
@diff={{this.diff}}
@oldValue={{this.selectedText}}
@newValue={{this.newSelectedText}}
@revert={{this.undoAIAction}}
@closeModal={{fn (mut this.showDiffModal) false}}
/>
{{/if}}
@@ -1,18 +1,18 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { createPopper } from "@popperjs/core";
import { afterRender, bind, debounce } from "discourse-common/utils/decorators";
import { tracked } from "@glimmer/tracking";
import { INPUT_DELAY } from "discourse-common/config/environment";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { createPopper } from "@popperjs/core";
import { caretPosition, getCaretPosition } from "discourse/lib/utilities";
import { INPUT_DELAY } from "discourse-common/config/environment";
import { afterRender, bind, debounce } from "discourse-common/utils/decorators";
import { showComposerAIHelper } from "../../lib/show-ai-helper";
import { inject as service } from "@ember/service";
import showAIHelper from "../../lib/show-ai-helper";
export default class AiHelperContextMenu extends Component {
static shouldRender(outletArgs, helper) {
return showComposerAIHelper(outletArgs, helper);
return showAIHelper(outletArgs, helper);
}
@service currentUser;
@@ -79,9 +79,7 @@ export default class AiHelperContextMenu extends Component {
async loadPrompts() {
let prompts = await ajax("/discourse-ai/ai-helper/prompts");
prompts = prompts
.filter((p) => p.location.includes("composer"))
.filter((p) => p.name !== "generate_titles");
prompts = prompts.filter((p) => p.name !== "generate_titles");
// Find the custom_prompt object and move it to the beginning of the array
const customPromptIndex = prompts.findIndex(
@@ -1,5 +1,5 @@
{{#if this.isAiBotChat}}
<DSection @bodyClass={{this.aiBotClasses}} />
<DSection @bodyClass="ai-bot-chat" />
{{#if this.renderChatWarning}}
<div class="ai-bot-chat-warning">{{i18n
"discourse_ai.ai_bot.pm_warning"
@@ -1,7 +1,6 @@
import Component from "@glimmer/component";
import { computed } from "@ember/object";
import { inject as service } from "@ember/service";
import I18n from "discourse-i18n";
import { computed } from "@ember/object";
export default class extends Component {
@service currentUser;
@@ -15,18 +14,6 @@ export default class extends Component {
return this.siteSettings.ai_bot_enable_chat_warning;
}
@computed("composerModel.targetRecipients", "composerModel.title")
get aiBotClasses() {
if (
this.composerModel?.title ===
I18n.t("discourse_ai.ai_bot.default_pm_prefix")
) {
return "ai-bot-chat";
} else {
return "ai-bot-pm";
}
}
@computed("composerModel.targetRecipients")
get isAiBotChat() {
if (
@@ -1,13 +1,13 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action, computed } from "@ember/object";
import { inject as service } from "@ember/service";
import { SEARCH_TYPE_DEFAULT } from "discourse/controllers/full-page-search";
import I18n from "I18n";
import { tracked } from "@glimmer/tracking";
import { ajax } from "discourse/lib/ajax";
import { isValidSearchTerm, translateResults } from "discourse/lib/search";
import discourseDebounce from "discourse-common/lib/debounce";
import { inject as service } from "@ember/service";
import { bind } from "discourse-common/utils/decorators";
import I18n from "I18n";
import { SEARCH_TYPE_DEFAULT } from "discourse/controllers/full-page-search";
export default class extends Component {
static shouldRender(_args, { siteSettings }) {
@@ -1,153 +0,0 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
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";
import I18n from "I18n";
import eq from "truth-helpers/helpers/eq";
import { showPostAIHelper } from "../../lib/show-ai-helper";
const i18n = I18n.t.bind(I18n);
export default class AIHelperOptionsMenu extends Component {
static shouldRender(outletArgs, helper) {
return showPostAIHelper(outletArgs, helper);
}
@tracked helperOptions = [];
@tracked menuState = this.MENU_STATES.triggers;
@tracked loading = false;
@tracked suggestion = "";
@tracked showMainButtons = true;
MENU_STATES = {
triggers: "TRIGGERS",
options: "OPTIONS",
loading: "LOADING",
result: "RESULT",
};
@tracked _activeAIRequest = null;
constructor() {
super(...arguments);
if (this.helperOptions.length === 0) {
this.loadPrompts();
}
}
@action
async showAIHelperOptions() {
this.showMainButtons = false;
this.menuState = this.MENU_STATES.options;
}
@action
async performAISuggestion(option) {
this.menuState = this.MENU_STATES.loading;
if (option.name === "Explain") {
this._activeAIRequest = ajax("/discourse-ai/ai-helper/explain", {
method: "POST",
data: {
mode: option.value,
text: this.args.outletArgs.data.quoteState.buffer,
post_id: this.args.outletArgs.data.quoteState.postId,
},
});
} else {
this._activeAIRequest = ajax("/discourse-ai/ai-helper/suggest", {
method: "POST",
data: {
mode: option.value,
text: this.args.outletArgs.data.quoteState.buffer,
custom_prompt: "",
},
});
}
this._activeAIRequest
.then(({ suggestions }) => {
this.suggestion = suggestions[0];
})
.catch(popupAjaxError)
.finally(() => {
this.loading = false;
this.menuState = this.MENU_STATES.result;
});
return this._activeAIRequest;
}
@action
cancelAIAction() {
if (this._activeAIRequest) {
this._activeAIRequest.abort();
this._activeAIRequest = null;
this.loading = false;
this.menuState = this.MENU_STATES.options;
}
}
async loadPrompts() {
let prompts = await ajax("/discourse-ai/ai-helper/prompts");
this.helperOptions = prompts
.filter((item) => item.location.includes("post"))
.map((p) => {
return {
name: p.translated_name,
value: p.id,
icon: p.icon,
};
});
}
<template>
{{#if this.showMainButtons}}
{{yield}}
{{/if}}
<div class="ai-post-helper">
{{#if (eq this.menuState this.MENU_STATES.triggers)}}
<DButton
@class="btn-flat ai-post-helper__trigger"
@icon="discourse-sparkles"
@label="discourse_ai.ai_helper.post_options_menu.trigger"
@action={{this.showAIHelperOptions}}
/>
{{else if (eq this.menuState this.MENU_STATES.options)}}
<div class="ai-post-helper__options">
{{#each this.helperOptions as |option|}}
<DButton
@class="btn-flat"
@icon={{option.icon}}
@translatedLabel={{option.name}}
@action={{this.performAISuggestion}}
@actionParam={{option}}
data-name={{option.name}}
data-value={{option.value}}
/>
{{/each}}
</div>
{{else if (eq this.menuState this.MENU_STATES.loading)}}
<div class="ai-helper-context-menu__loading">
<div class="dot-falling"></div>
<span>
{{i18n "discourse_ai.ai_helper.context_menu.loading"}}
</span>
<DButton
@icon="times"
@title="discourse_ai.ai_helper.context_menu.cancel"
@action={{this.cancelAIAction}}
class="btn-flat cancel-request"
/>
</div>
{{else if (eq this.menuState this.MENU_STATES.result)}}
<div class="ai-post-helper__suggestion">{{this.suggestion}}</div>
{{/if}}
</div>
</template>
}
@@ -1,6 +1,6 @@
import Component from "@glimmer/component";
import { action, computed } from "@ember/object";
import { inject as service } from "@ember/service";
import { action, computed } from "@ember/object";
import I18n from "I18n";
export default class extends Component {
@@ -30,7 +30,7 @@ export default class extends Component {
this.moreTopicsPreferenceTracking.registerTopicList({
name: I18n.t("discourse_ai.related_topics.pill"),
id: this.listId,
icon: "discourse-sparkles",
icon: "magic",
});
}
@@ -1,38 +1,20 @@
export function showComposerAIHelper(outletArgs, helper) {
const enableHelper = _helperEnabled(helper.siteSettings);
const enableAssistant = _canUseAssistant(
helper.currentUser,
_findAllowedGroups(helper.siteSettings.ai_helper_allowed_groups)
export default function showAIHelper(outletArgs, helper) {
const helperEnabled =
helper.siteSettings.discourse_ai_enabled &&
helper.siteSettings.composer_ai_helper_enabled;
const allowedGroups = helper.siteSettings.ai_helper_allowed_groups
.split("|")
.map((id) => parseInt(id, 10));
const canUseAssistant = helper.currentUser?.groups.some((g) =>
allowedGroups.includes(g.id)
);
const canShowInPM = helper.siteSettings.ai_helper_allowed_in_pm;
if (outletArgs?.composer?.privateMessage) {
return enableHelper && enableAssistant && canShowInPM;
return helperEnabled && canUseAssistant && canShowInPM;
}
return enableHelper && enableAssistant;
}
export function showPostAIHelper(outletArgs, helper) {
return (
_helperEnabled(helper.siteSettings) &&
_canUseAssistant(
helper.currentUser,
_findAllowedGroups(helper.siteSettings.post_ai_helper_allowed_groups)
)
);
}
function _helperEnabled(siteSettings) {
return (
siteSettings.discourse_ai_enabled && siteSettings.composer_ai_helper_enabled
);
}
function _findAllowedGroups(setting) {
return setting.split("|").map((id) => parseInt(id, 10));
}
function _canUseAssistant(user, allowedGroups) {
return user?.groups.some((g) => allowedGroups.includes(g.id));
return helperEnabled && canUseAssistant;
}
@@ -1,7 +1,7 @@
import { action } from "@ember/object";
import { hbs } from "ember-cli-htmlbars";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import Widget from "discourse/widgets/widget";
import RenderGlimmer from "discourse/widgets/render-glimmer";
import { hbs } from "ember-cli-htmlbars";
import { action } from "@ember/object";
export default class AiBotHeaderPanelWrapper extends Widget {
buildAttributes() {
@@ -1,14 +1,14 @@
import { hbs } from "ember-cli-htmlbars";
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 { withPluginApi } from "discourse/lib/plugin-api";
import { cook } from "discourse/lib/text";
import { registerWidgetShim } from "discourse/widgets/render-glimmer";
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);
return user && [-110, -111, -112, -113].includes(user.id);
}
function attachHeaderIcon(api) {
@@ -92,7 +92,7 @@ function initializeAIBotReplies(api) {
if (post) {
if (data.raw) {
cook(data.raw).then((cooked) => {
cookAsync(data.raw).then((cooked) => {
post.set("raw", data.raw);
post.set("cooked", cooked);
@@ -15,18 +15,6 @@ nav.post-controls .actions button.cancel-streaming {
}
}
.ai-bot-pm {
.gpt-persona {
margin-bottom: 5px;
}
#reply-control .composer-fields {
.mini-tag-chooser,
.add-warning {
display: none;
}
}
}
.ai-bot-chat-warning {
color: var(--tertiary);
background-color: var(--tertiary-low);

Some files were not shown because too many files have changed in this diff Show More