Compare commits

..
Author SHA1 Message Date
David Taylor 593e8dc2a5 DEV: Update modifyClass calls
- Update `controller:topic` modification to native class syntax
- Remove search-result-entry modification in favour of plugin-outlet-based solution
2024-12-02 11:49:22 +00:00
612 changed files with 8829 additions and 872986 deletions
-3
View File
@@ -1,6 +1,3 @@
< 3.5.0.beta1-dev: bdef136080074a993e7c4f5ca562edc31a8ba756
< 3.4.0.beta4-dev: a53719ab8eb071459f215227421b3ea4987e5f87
< 3.4.0.beta4-dev: 20612fde52d3f740cad64823ef8aadb0748b567f
< 3.4.0.beta3-dev: decf1bb49d737ea15308400f22f89d1d1e71d13d < 3.4.0.beta3-dev: decf1bb49d737ea15308400f22f89d1d1e71d13d
< 3.4.0.beta1-dev: 9d887ad4ace8e33c3fe7dbb39237e882c08b4f0b < 3.4.0.beta1-dev: 9d887ad4ace8e33c3fe7dbb39237e882c08b4f0b
< 3.3.0.beta5-dev: 4d8090002f6dcd8e34d41033606bf131fa221475 < 3.3.0.beta5-dev: 4d8090002f6dcd8e34d41033606bf131fa221475
-3
View File
@@ -2,6 +2,3 @@ node_modules
/gems /gems
/auto_generated /auto_generated
.env .env
evals/log
evals/cases
config/eval-llms.local.yml
-3
View File
@@ -2,6 +2,3 @@ inherit_gem:
rubocop-discourse: stree-compat.yml rubocop-discourse: stree-compat.yml
RSpec/NamedSubject: RSpec/NamedSubject:
Enabled: false Enabled: false
Style/GlobalVars:
AllowedVariables: [$prometheus_client]
+45 -59
View File
@@ -1,93 +1,79 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (8.0.2) activesupport (7.1.3.4)
base64 base64
benchmark (>= 0.3)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) mutex_m
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0)
uri (>= 0.13.1)
ast (2.4.2) ast (2.4.2)
base64 (0.2.0) base64 (0.2.0)
benchmark (0.4.0) bigdecimal (3.1.8)
bigdecimal (3.1.9) concurrent-ruby (1.3.3)
concurrent-ruby (1.3.5) connection_pool (2.4.1)
connection_pool (2.5.0)
drb (2.2.1) drb (2.2.1)
i18n (1.14.7) i18n (1.14.5)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
json (2.10.2) json (2.7.2)
language_server-protocol (3.17.0.4) language_server-protocol (3.17.0.3)
lint_roller (1.1.0) minitest (5.24.1)
logger (1.6.6) mutex_m (0.2.0)
minitest (5.25.5) parallel (1.25.1)
parallel (1.26.3) parser (3.3.3.0)
parser (3.3.7.1)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
prettier_print (1.2.1) prettier_print (1.2.1)
racc (1.8.1) racc (1.8.0)
rack (3.1.12) rack (3.1.6)
rainbow (3.1.1) rainbow (3.1.1)
regexp_parser (2.10.0) regexp_parser (2.9.2)
rubocop (1.74.0) rexml (3.3.9)
rubocop (1.64.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (>= 3.17.0)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 1.8, < 3.0)
rubocop-ast (>= 1.38.0, < 2.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.38.1) rubocop-ast (1.31.3)
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-capybara (2.22.1) rubocop-capybara (2.21.0)
lint_roller (~> 1.1) rubocop (~> 1.41)
rubocop (~> 1.72, >= 1.72.1) rubocop-discourse (3.8.1)
rubocop-discourse (3.12.1)
activesupport (>= 6.1) activesupport (>= 6.1)
lint_roller (>= 1.1.0) rubocop (>= 1.59.0)
rubocop (>= 1.73.2) rubocop-capybara (>= 2.0.0)
rubocop-capybara (>= 2.22.0) rubocop-factory_bot (>= 2.0.0)
rubocop-factory_bot (>= 2.27.0) rubocop-rails (>= 2.25.0)
rubocop-rails (>= 2.30.3)
rubocop-rspec (>= 3.0.1) rubocop-rspec (>= 3.0.1)
rubocop-rspec_rails (>= 2.31.0) rubocop-rspec_rails (>= 2.30.0)
rubocop-factory_bot (2.27.1) rubocop-factory_bot (2.26.1)
lint_roller (~> 1.1) rubocop (~> 1.61)
rubocop (~> 1.72, >= 1.72.1) rubocop-rails (2.25.1)
rubocop-rails (2.30.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.72.1, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.5.0) rubocop-rspec (3.0.2)
lint_roller (~> 1.1) rubocop (~> 1.61)
rubocop (~> 1.72, >= 1.72.1) rubocop-rspec_rails (2.30.0)
rubocop-rspec_rails (2.31.0) rubocop (~> 1.61)
lint_roller (~> 1.1) rubocop-rspec (~> 3, >= 3.0.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
securerandom (0.4.1)
syntax_tree (6.2.0) syntax_tree (6.2.0)
prettier_print (>= 1.2.0) prettier_print (>= 1.2.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (3.1.4) unicode-display_width (2.5.0)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uri (1.0.3)
PLATFORMS PLATFORMS
ruby ruby
@@ -97,4 +83,4 @@ DEPENDENCIES
syntax_tree syntax_tree
BUNDLED WITH BUNDLED WITH
2.6.6 2.5.10
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Civilized Discourse Construction Kit, Inc. Copyright (c) 2021 Civilized Discourse Construction Kit
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
-24
View File
@@ -3,27 +3,3 @@
**Plugin Summary** **Plugin Summary**
For more information, please see: https://meta.discourse.org/t/discourse-ai/259214?u=falco For more information, please see: https://meta.discourse.org/t/discourse-ai/259214?u=falco
### Evals
The directory `evals` contains AI evals for the Discourse AI plugin.
You may create a local config by copying `config/eval-llms.yml` to `config/eval-llms.local.yml` and modifying the values.
To run them use:
cd evals
./run --help
```
Usage: evals/run [options]
-e, --eval NAME Name of the evaluation to run
--list-models List models
-m, --model NAME Model to evaluate (will eval all models if not specified)
-l, --list List evals
```
To run evals you will need to configure API keys in your environment:
OPENAI_API_KEY=your_openai_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
GEMINI_API_KEY=your_gemini_api_key
-7
View File
@@ -1,7 +0,0 @@
{
"tests": {
"requiredPlugins": [
"https://github.com/discourse/discourse-prometheus"
]
}
}
@@ -1,21 +0,0 @@
import DiscourseRoute from "discourse/routes/discourse";
export default class AdminPluginsShowDiscourseAiEmbeddingsEdit extends DiscourseRoute {
async model(params) {
const allEmbeddings = this.modelFor(
"adminPlugins.show.discourse-ai-embeddings"
);
const id = parseInt(params.id, 10);
const record = allEmbeddings.findBy("id", id);
record.provider_params = record.provider_params || {};
return record;
}
setupController(controller, model) {
super.setupController(controller, model);
controller.set(
"allEmbeddings",
this.modelFor("adminPlugins.show.discourse-ai-embeddings")
);
}
}
@@ -1,17 +0,0 @@
import DiscourseRoute from "discourse/routes/discourse";
export default class AdminPluginsShowDiscourseAiEmbeddingsNew extends DiscourseRoute {
async model() {
const record = this.store.createRecord("ai-embedding");
record.provider_params = {};
return record;
}
setupController(controller, model) {
super.setupController(controller, model);
controller.set(
"allEmbeddings",
this.modelFor("adminPlugins.show.discourse-ai-embeddings")
);
}
}
@@ -1,7 +0,0 @@
import DiscourseRoute from "discourse/routes/discourse";
export default class DiscourseAiAiEmbeddingsRoute extends DiscourseRoute {
model() {
return this.store.findAll("ai-embedding");
}
}
@@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class AdminPluginsShowDiscourseAiLlmsEdit extends DiscourseRoute { export default class AdminPluginsShowDiscourseAiLlmsShow extends DiscourseRoute {
async model(params) { async model(params) {
const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms"); const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms");
const id = parseInt(params.id, 10); const id = parseInt(params.id, 10);
@@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class AdminPluginsShowDiscourseAiPersonasEdit extends DiscourseRoute { export default class AdminPluginsShowDiscourseAiPersonasShow extends DiscourseRoute {
async model(params) { async model(params) {
const allPersonas = this.modelFor( const allPersonas = this.modelFor(
"adminPlugins.show.discourse-ai-personas" "adminPlugins.show.discourse-ai-personas"
@@ -1,11 +0,0 @@
import { service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import DiscourseRoute from "discourse/routes/discourse";
export default class DiscourseAiSpamRoute extends DiscourseRoute {
@service store;
model() {
return ajax("/admin/plugins/discourse-ai/ai-spam.json");
}
}
@@ -1,10 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class DiscourseAiToolsNewRoute extends DiscourseRoute { export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
beforeModel(transition) {
this.preset = transition.to.queryParams.presetId || "empty_tool";
}
async model() { async model() {
return this.store.createRecord("ai-tool"); return this.store.createRecord("ai-tool");
} }
@@ -12,10 +8,8 @@ export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
setupController(controller) { setupController(controller) {
super.setupController(...arguments); super.setupController(...arguments);
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools"); const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
controller.set("allTools", toolsModel); controller.set("allTools", toolsModel);
controller.set("presets", toolsModel.resultSetMeta.presets); controller.set("presets", toolsModel.resultSetMeta.presets);
controller.set("llms", toolsModel.resultSetMeta.llms);
controller.set("settings", toolsModel.resultSetMeta.settings);
controller.set("selectedPreset", this.preset);
} }
} }
@@ -1,6 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
export default class DiscourseAiToolsEditRoute extends DiscourseRoute { export default class DiscourseAiToolsShowRoute extends DiscourseRoute {
async model(params) { async model(params) {
const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools"); const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools");
const id = parseInt(params.id, 10); const id = parseInt(params.id, 10);
@@ -14,7 +14,5 @@ export default class DiscourseAiToolsEditRoute extends DiscourseRoute {
controller.set("allTools", toolsModel); controller.set("allTools", toolsModel);
controller.set("presets", toolsModel.resultSetMeta.presets); controller.set("presets", toolsModel.resultSetMeta.presets);
controller.set("llms", toolsModel.resultSetMeta.llms);
controller.set("settings", toolsModel.resultSetMeta.settings);
} }
} }
@@ -1,4 +0,0 @@
<AiEmbeddingsListEditor
@embeddings={{this.allEmbeddings}}
@currentEmbedding={{this.model}}
/>
@@ -1 +0,0 @@
<AiEmbeddingsListEditor @embeddings={{this.model}} />
@@ -1,4 +0,0 @@
<AiEmbeddingsListEditor
@embeddings={{this.allEmbeddings}}
@currentEmbedding={{this.model}}
/>
@@ -1 +0,0 @@
<AiSpam @model={{this.model}} />
@@ -3,8 +3,5 @@
@tools={{this.allTools}} @tools={{this.allTools}}
@model={{this.model}} @model={{this.model}}
@presets={{this.presets}} @presets={{this.presets}}
@llms={{this.llms}}
@settings={{this.settings}}
@selectedPreset={{this.selectedPreset}}
/> />
</section> </section>
@@ -3,7 +3,5 @@
@tools={{this.allTools}} @tools={{this.allTools}}
@model={{this.model}} @model={{this.model}}
@presets={{this.presets}} @presets={{this.presets}}
@llms={{this.llms}}
@settings={{this.settings}}
/> />
</section> </section>
@@ -1,133 +0,0 @@
# frozen_string_literal: true
module DiscourseAi
module Admin
class AiEmbeddingsController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME
def index
embedding_defs = EmbeddingDefinition.all.order(:display_name)
render json: {
ai_embeddings:
ActiveModel::ArraySerializer.new(
embedding_defs,
each_serializer: AiEmbeddingDefinitionSerializer,
root: false,
).as_json,
meta: {
provider_params: EmbeddingDefinition.provider_params,
providers: EmbeddingDefinition.provider_names,
distance_functions: EmbeddingDefinition.distance_functions,
tokenizers:
EmbeddingDefinition.tokenizer_names.map { |tn|
{ id: tn, name: tn.split("::").last }
},
presets: EmbeddingDefinition.presets,
},
}
end
def new
end
def edit
embedding_def = EmbeddingDefinition.find(params[:id])
render json: AiEmbeddingDefinitionSerializer.new(embedding_def)
end
def create
embedding_def = EmbeddingDefinition.new(ai_embeddings_params)
if embedding_def.save
render json: AiEmbeddingDefinitionSerializer.new(embedding_def), status: :created
else
render_json_error embedding_def
end
end
def update
embedding_def = EmbeddingDefinition.find(params[:id])
if embedding_def.seeded?
return(
render_json_error(I18n.t("discourse_ai.embeddings.cannot_edit_builtin"), status: 403)
)
end
if embedding_def.update(ai_embeddings_params.except(:dimensions))
render json: AiEmbeddingDefinitionSerializer.new(embedding_def)
else
render_json_error embedding_def
end
end
def destroy
embedding_def = EmbeddingDefinition.find(params[:id])
if embedding_def.seeded?
return(
render_json_error(I18n.t("discourse_ai.embeddings.cannot_edit_builtin"), status: 403)
)
end
if embedding_def.id == SiteSetting.ai_embeddings_selected_model.to_i
return render_json_error(I18n.t("discourse_ai.embeddings.delete_failed"), status: 409)
end
if embedding_def.destroy
head :no_content
else
render_json_error embedding_def
end
end
def test
RateLimiter.new(
current_user,
"ai_embeddings_test_#{current_user.id}",
3,
1.minute,
).performed!
embedding_def = EmbeddingDefinition.new(ai_embeddings_params)
DiscourseAi::Embeddings::Vector.new(embedding_def).vector_from("this is a test")
render json: { success: true }
rescue Net::HTTPBadResponse => e
render json: { success: false, error: e.message }
end
private
def ai_embeddings_params
permitted =
params.require(:ai_embedding).permit(
:display_name,
:dimensions,
:max_sequence_length,
:pg_function,
:provider,
:url,
:api_key,
:tokenizer_class,
:embed_prompt,
:search_prompt,
:matryoshka_dimensions,
)
extra_field_names = EmbeddingDefinition.provider_params.dig(permitted[:provider]&.to_sym)
if extra_field_names.present?
received_prov_params =
params.dig(:ai_embedding, :provider_params)&.slice(*extra_field_names.keys)
if received_prov_params.present?
permitted[:provider_params] = received_prov_params.permit!
end
end
permitted
end
end
end
end
@@ -1,59 +0,0 @@
# frozen_string_literal: true
module DiscourseAi
module Admin
class AiLlmQuotasController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME
def index
quotas = LlmQuota.includes(:group)
render json: {
quotas:
ActiveModel::ArraySerializer.new(quotas, each_serializer: LlmQuotaSerializer),
}
end
def create
quota = LlmQuota.new(quota_params)
if quota.save
render json: LlmQuotaSerializer.new(quota), status: :created
else
render_json_error quota
end
end
def update
quota = LlmQuota.find(params[:id])
if quota.update(quota_params)
render json: LlmQuotaSerializer.new(quota)
else
render_json_error quota
end
end
def destroy
quota = LlmQuota.find(params[:id])
quota.destroy!
head :no_content
rescue ActiveRecord::RecordNotFound
render json: { error: I18n.t("not_found") }, status: 404
end
private
def quota_params
params.require(:quota).permit(
:group_id,
:llm_model_id,
:max_tokens,
:max_usages,
:duration_seconds,
)
end
end
end
end
@@ -6,7 +6,7 @@ module DiscourseAi
requires_plugin ::DiscourseAi::PLUGIN_NAME requires_plugin ::DiscourseAi::PLUGIN_NAME
def index def index
llms = LlmModel.all.includes(:llm_quotas).order(:display_name) llms = LlmModel.all.order(:display_name)
render json: { render json: {
ai_llms: ai_llms:
@@ -30,21 +30,13 @@ module DiscourseAi
} }
end end
def new def show
end
def edit
llm_model = LlmModel.find(params[:id]) llm_model = LlmModel.find(params[:id])
render json: LlmModelSerializer.new(llm_model) render json: LlmModelSerializer.new(llm_model)
end end
def create def create
llm_model = LlmModel.new(ai_llm_params) llm_model = LlmModel.new(ai_llm_params)
# we could do nested attributes but the mechanics are not ideal leading
# to lots of complex debugging, this is simpler
quota_params.each { |quota| llm_model.llm_quotas.build(quota) } if quota_params
if llm_model.save if llm_model.save
llm_model.toggle_companion_user llm_model.toggle_companion_user
render json: LlmModelSerializer.new(llm_model), status: :created render json: LlmModelSerializer.new(llm_model), status: :created
@@ -56,25 +48,6 @@ module DiscourseAi
def update def update
llm_model = LlmModel.find(params[:id]) llm_model = LlmModel.find(params[:id])
if params[:ai_llm].key?(:llm_quotas)
if quota_params
existing_quota_group_ids = llm_model.llm_quotas.pluck(:group_id)
new_quota_group_ids = quota_params.map { |q| q[:group_id] }
llm_model
.llm_quotas
.where(group_id: existing_quota_group_ids - new_quota_group_ids)
.destroy_all
quota_params.each do |quota_param|
quota = llm_model.llm_quotas.find_or_initialize_by(group_id: quota_param[:group_id])
quota.update!(quota_param)
end
else
llm_model.llm_quotas.destroy_all
end
end
if llm_model.seeded? if llm_model.seeded?
return render_json_error(I18n.t("discourse_ai.llm.cannot_edit_builtin"), status: 403) return render_json_error(I18n.t("discourse_ai.llm.cannot_edit_builtin"), status: 403)
end end
@@ -134,19 +107,6 @@ module DiscourseAi
private private
def quota_params
if params[:ai_llm][:llm_quotas].present?
params[:ai_llm][:llm_quotas].map do |quota|
mapped = {}
mapped[:group_id] = quota[:group_id].to_i
mapped[:max_tokens] = quota[:max_tokens].to_i if quota[:max_tokens].present?
mapped[:max_usages] = quota[:max_usages].to_i if quota[:max_usages].present?
mapped[:duration_seconds] = quota[:duration_seconds].to_i
mapped
end
end
end
def ai_llm_params(updating: nil) def ai_llm_params(updating: nil)
return {} if params[:ai_llm].blank? return {} if params[:ai_llm].blank?
@@ -5,7 +5,7 @@ module DiscourseAi
class AiPersonasController < ::Admin::AdminController class AiPersonasController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME requires_plugin ::DiscourseAi::PLUGIN_NAME
before_action :find_ai_persona, only: %i[edit update destroy create_user] before_action :find_ai_persona, only: %i[show update destroy create_user]
def index def index
ai_personas = ai_personas =
@@ -23,34 +23,17 @@ module DiscourseAi
.each do |tool| .each do |tool|
tools << { tools << {
id: "custom-#{tool.id}", id: "custom-#{tool.id}",
name: name: I18n.t("discourse_ai.tools.custom_name", name: tool.name.capitalize),
I18n.t(
"discourse_ai.tools.custom_name",
name: tool.name.capitalize,
tool_name: tool.tool_name,
),
} }
end end
llms = llms =
DiscourseAi::Configuration::LlmEnumerator.values_for_serialization( DiscourseAi::Configuration::LlmEnumerator.values.map do |hash|
allowed_seeded_llm_ids: SiteSetting.ai_bot_allowed_seeded_models_map, { id: hash[:value], name: hash[:name] }
) end
render json: { render json: { ai_personas: ai_personas, meta: { tools: tools, llms: llms } }
ai_personas: ai_personas,
meta: {
tools: tools,
llms: llms,
settings: {
rag_images_enabled: SiteSetting.ai_rag_images_enabled,
},
},
}
end end
def new def show
end
def edit
render json: LocalizedAiPersonaSerializer.new(@ai_persona) render json: LocalizedAiPersonaSerializer.new(@ai_persona)
end end
@@ -196,7 +179,7 @@ module DiscourseAi
:priority, :priority,
:top_p, :top_p,
:temperature, :temperature,
:default_llm_id, :default_llm,
:user_id, :user_id,
:max_context_posts, :max_context_posts,
:vision_enabled, :vision_enabled,
@@ -204,8 +187,7 @@ module DiscourseAi
:rag_chunk_tokens, :rag_chunk_tokens,
:rag_chunk_overlap_tokens, :rag_chunk_overlap_tokens,
:rag_conversation_chunks, :rag_conversation_chunks,
:rag_llm_model_id, :question_consolidator_llm,
:question_consolidator_llm_id,
:allow_chat_channel_mentions, :allow_chat_channel_mentions,
:allow_chat_direct_messages, :allow_chat_direct_messages,
:allow_topic_mentions, :allow_topic_mentions,
@@ -1,137 +0,0 @@
# frozen_string_literal: true
module DiscourseAi
module Admin
class AiSpamController < ::Admin::AdminController
requires_plugin "discourse-ai"
def show
render json: AiSpamSerializer.new(spam_config, root: false)
end
def update
updated_params = {}
if allowed_params.key?(:llm_model_id)
llm_model_id = updated_params[:llm_model_id] = allowed_params[:llm_model_id]
if llm_model_id.to_i < 0 &&
!SiteSetting.ai_spam_detection_model_allowed_seeded_models_map.include?(
llm_model_id.to_s,
)
return(
render_json_error(
I18n.t("discourse_ai.llm.configuration.invalid_seeded_model"),
status: 422,
)
)
end
end
updated_params[:data] = {
custom_instructions: allowed_params[:custom_instructions],
} if allowed_params.key?(:custom_instructions)
if updated_params.present?
# not using upsert cause we will not get the correct validation errors
if AiModerationSetting.spam
AiModerationSetting.spam.update!(updated_params)
else
AiModerationSetting.create!(updated_params.merge(setting_type: :spam))
end
end
is_enabled = ActiveModel::Type::Boolean.new.cast(allowed_params[:is_enabled])
if allowed_params.key?(:is_enabled)
if is_enabled && !AiModerationSetting.spam&.llm_model_id
return(
render_json_error(
I18n.t("discourse_ai.llm.configuration.must_select_model"),
status: 422,
)
)
end
SiteSetting.ai_spam_detection_enabled = is_enabled
end
render json: AiSpamSerializer.new(spam_config, root: false)
end
def test
url = params[:post_url].to_s
post = nil
if url.match?(/^\d+$/)
post_id = url.to_i
post = Post.find_by(id: post_id)
end
route = UrlHelper.rails_route_from_url(url) if !post
if route
if route[:controller] == "topics"
post_number = route[:post_number] || 1
post = Post.with_deleted.find_by(post_number: post_number, topic_id: route[:topic_id])
end
end
raise Discourse::NotFound if !post
result =
DiscourseAi::AiModeration::SpamScanner.test_post(
post,
custom_instructions: params[:custom_instructions],
llm_id: params[:llm_id],
)
render json: result
end
def fix_errors
case params[:error]
when "spam_scanner_not_admin"
begin
DiscourseAi::AiModeration::SpamScanner.fix_spam_scanner_not_admin
render json: success_json
rescue ActiveRecord::RecordInvalid
render_json_error(
I18n.t("discourse_ai.spam_detection.bot_user_update_failed"),
status: :unprocessable_entity,
)
rescue StandardError
render_json_error(
I18n.t("discourse_ai.spam_detection.unexpected"),
status: :internal_server_error,
)
end
else
render_json_error(
I18n.t("discourse_ai.spam_detection.invalid_error_type"),
status: :bad_request,
)
end
end
private
def allowed_params
params.permit(:is_enabled, :llm_model_id, :custom_instructions)
end
def spam_config
spam_config = {
enabled: SiteSetting.ai_spam_detection_enabled,
settings: AiModerationSetting.spam,
}
spam_config[:stats] = DiscourseAi::AiModeration::SpamReport.generate(min_date: 1.week.ago)
if spam_config[:stats].scanned_count > 0
spam_config[
:flagging_username
] = DiscourseAi::AiModeration::SpamScanner.flagging_user&.username
end
spam_config
end
end
end
end
@@ -5,17 +5,14 @@ module DiscourseAi
class AiToolsController < ::Admin::AdminController class AiToolsController < ::Admin::AdminController
requires_plugin ::DiscourseAi::PLUGIN_NAME requires_plugin ::DiscourseAi::PLUGIN_NAME
before_action :find_ai_tool, only: %i[test edit update destroy] before_action :find_ai_tool, only: %i[test show update destroy]
def index def index
ai_tools = AiTool.all ai_tools = AiTool.all
render_serialized({ ai_tools: ai_tools }, AiCustomToolListSerializer, root: false) render_serialized({ ai_tools: ai_tools }, AiCustomToolListSerializer, root: false)
end end
def new def show
end
def edit
render_serialized(@ai_tool, AiCustomToolSerializer) render_serialized(@ai_tool, AiCustomToolSerializer)
end end
@@ -84,13 +81,11 @@ module DiscourseAi
.require(:ai_tool) .require(:ai_tool)
.permit( .permit(
:name, :name,
:tool_name,
:description, :description,
:script, :script,
:summary, :summary,
:rag_chunk_tokens, :rag_chunk_tokens,
:rag_chunk_overlap_tokens, :rag_chunk_overlap_tokens,
:rag_llm_model_id,
rag_uploads: [:id], rag_uploads: [:id],
parameters: [:name, :type, :description, :required, enum: []], parameters: [:name, :type, :description, :required, enum: []],
) )
@@ -6,9 +6,6 @@ module DiscourseAi
requires_plugin "discourse-ai" requires_plugin "discourse-ai"
def show def show
end
def report
render json: AiUsageSerializer.new(create_report, root: false) render json: AiUsageSerializer.new(create_report, root: false)
end end

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