Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13fca098d1 | ||
|
|
d76309fa17 | ||
|
|
facce51f98 |
@@ -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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require("@discourse/lint-configs/eslint");
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = require("@discourse/lint-configs/prettier");
|
||||
@@ -1,4 +1,2 @@
|
||||
inherit_gem:
|
||||
rubocop-discourse: stree-compat.yml
|
||||
RSpec/NamedSubject:
|
||||
Enabled: false
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
--print-width=100
|
||||
--plugins=plugin/trailing_comma,plugin/disable_auto_ternary
|
||||
--plugins=plugin/trailing_comma,disable_ternary
|
||||
|
||||
+4
-1
@@ -1 +1,4 @@
|
||||
module.exports = require("@discourse/lint-configs/template-lint");
|
||||
module.exports = {
|
||||
plugins: ["ember-template-lint-plugin-discourse"],
|
||||
extends: "discourse:recommended",
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Admin
|
||||
class DashboardController < ::Admin::StaffController
|
||||
requires_plugin DiscourseAi::PLUGIN_NAME
|
||||
|
||||
def sentiment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AiPersona < ActiveRecord::Base
|
||||
# places a hard limit, so per site we cache a maximum of 500 classes
|
||||
MAX_PERSONAS_PER_SITE = 500
|
||||
|
||||
class MultisiteHash
|
||||
def initialize(id)
|
||||
@hash = Hash.new { |h, k| h[k] = {} }
|
||||
@id = id
|
||||
|
||||
MessageBus.subscribe(channel_name) { |message| @hash[message.data] = {} }
|
||||
end
|
||||
|
||||
def channel_name
|
||||
"/multisite-hash-#{@id}"
|
||||
end
|
||||
|
||||
def current_db
|
||||
RailsMultisite::ConnectionManagement.current_db
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@hash.dig(current_db, key)
|
||||
end
|
||||
|
||||
def []=(key, val)
|
||||
@hash[current_db][key] = val
|
||||
end
|
||||
|
||||
def flush!
|
||||
@hash[current_db] = {}
|
||||
MessageBus.publish(channel_name, current_db)
|
||||
end
|
||||
end
|
||||
|
||||
def self.persona_cache
|
||||
@persona_cache ||= MultisiteHash.new("persona_cache")
|
||||
end
|
||||
|
||||
def self.all_personas
|
||||
persona_cache[:value] ||= AiPersona
|
||||
.order(:name)
|
||||
.where(enabled: true)
|
||||
.all
|
||||
.limit(MAX_PERSONAS_PER_SITE)
|
||||
.map do |ai_persona|
|
||||
name = ai_persona.name
|
||||
description = ai_persona.description
|
||||
ai_persona_id = ai_persona.id
|
||||
allowed_group_ids = ai_persona.allowed_group_ids
|
||||
commands =
|
||||
ai_persona.commands.filter_map do |inner_name|
|
||||
begin
|
||||
("DiscourseAi::AiBot::Commands::#{inner_name}").constantize
|
||||
rescue StandardError
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
Class.new(DiscourseAi::AiBot::Personas::Persona) do
|
||||
define_singleton_method :name do
|
||||
name
|
||||
end
|
||||
|
||||
define_singleton_method :description do
|
||||
description
|
||||
end
|
||||
|
||||
define_singleton_method :allowed_group_ids do
|
||||
allowed_group_ids
|
||||
end
|
||||
|
||||
define_singleton_method :to_s do
|
||||
"#<DiscourseAi::AiBot::Personas::Persona::Custom @name=#{self.name} @allowed_group_ids=#{self.allowed_group_ids.join(",")}>"
|
||||
end
|
||||
|
||||
define_singleton_method :inspect do
|
||||
"#<DiscourseAi::AiBot::Personas::Persona::Custom @name=#{self.name} @allowed_group_ids=#{self.allowed_group_ids.join(",")}>"
|
||||
end
|
||||
|
||||
define_method :initialize do |*args, **kwargs|
|
||||
@ai_persona = AiPersona.find_by(id: ai_persona_id)
|
||||
super(*args, **kwargs)
|
||||
end
|
||||
|
||||
define_method :commands do
|
||||
commands
|
||||
end
|
||||
|
||||
define_method :system_prompt do
|
||||
@ai_persona&.system_prompt || "You are a helpful bot."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
after_commit :bump_cache
|
||||
|
||||
def bump_cache
|
||||
self.class.persona_cache.flush!
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: ai_personas
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# name :string(100) not null
|
||||
# description :string(2000) not null
|
||||
# commands :string default([]), not null, is an Array
|
||||
# system_prompt :string not null
|
||||
# allowed_group_ids :integer default([]), not null, is an Array
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_ai_personas_on_name (name) UNIQUE
|
||||
#
|
||||
@@ -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,10 +0,0 @@
|
||||
export default {
|
||||
resource: "admin.dashboard",
|
||||
path: "/dashboard",
|
||||
map() {
|
||||
this.route("admin.dashboardSentiment", {
|
||||
path: "/dashboard/sentiment",
|
||||
resetNamespace: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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,8 +1,13 @@
|
||||
import Component from "@glimmer/component";
|
||||
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ReviewableAiChatMessage extends Component {
|
||||
@service store;
|
||||
|
||||
get chatChannel() {
|
||||
return ChatChannel.create(this.args.reviewable.chat_channel);
|
||||
return this.store.createRecord(
|
||||
"chat-channel",
|
||||
this.args.reviewable.chat_channel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
const i18n = I18n.t.bind(I18n);
|
||||
|
||||
export default class AISentimentDashboard extends Component {
|
||||
<template>
|
||||
<li class="navigation-item sentiment">
|
||||
<LinkTo @route="admin.dashboardSentiment" class="navigation-link">
|
||||
{{i18n "discourse_ai.sentiments.dashboard.title"}}
|
||||
</LinkTo>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
static shouldRender(_outletArgs, helper) {
|
||||
return helper.siteSettings.ai_sentiment_enabled;
|
||||
}
|
||||
}
|
||||
+12
-15
@@ -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>
|
||||
}
|
||||
}
|
||||
+12
-15
@@ -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>
|
||||
}
|
||||
}
|
||||
+8
-12
@@ -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 (
|
||||
|
||||
+4
-4
@@ -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>
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user