Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47fdb1acbf |
@@ -1,7 +1,4 @@
|
|||||||
< 3.4.0.beta3-dev: decf1bb49d737ea15308400f22f89d1d1e71d13d
|
< 3.2.0.beta2-dev: 3bced1c6f525553fe2b5b79d29bd66c1cf2a47d5
|
||||||
< 3.4.0.beta1-dev: 9d887ad4ace8e33c3fe7dbb39237e882c08b4f0b
|
|
||||||
< 3.3.0.beta5-dev: 4d8090002f6dcd8e34d41033606bf131fa221475
|
|
||||||
< 3.3.0.beta2-dev: 61890b667c06299841ae88946f84a112f00060e1
|
|
||||||
< 3.3.0.beta1-dev: d94234d89f50733678a5c62809445198a9fda74b
|
|
||||||
3.2.999: d94234d89f50733678a5c62809445198a9fda74b
|
|
||||||
3.1.999: c0415bb7eb878b1b7abf112d65cba981030df8df
|
3.1.999: c0415bb7eb878b1b7abf112d65cba981030df8df
|
||||||
|
< 3.2.0.beta1-dev: 3e7c99de894b03e9161d9481da118fe893991a1d
|
||||||
|
3.1.0.beta3: 057fbe1ce6
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require("@discourse/lint-configs/eslint");
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
/gems
|
/gems
|
||||||
/auto_generated
|
/auto_generated
|
||||||
.env
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
app/assets/stylesheets/vendor/
|
|
||||||
documentation/
|
|
||||||
package.json
|
|
||||||
config/locales/**/*.yml
|
|
||||||
!config/locales/**/*.en*.yml
|
|
||||||
script/import_scripts/**/*.yml
|
|
||||||
|
|
||||||
plugins/**/lib/javascripts/locale
|
|
||||||
public/
|
|
||||||
!/app/assets/javascripts/discourse/public
|
|
||||||
vendor/
|
|
||||||
app/assets/javascripts/discourse/tests/fixtures
|
|
||||||
spec/
|
|
||||||
node_modules/
|
|
||||||
dist/
|
|
||||||
tmp/
|
|
||||||
|
|
||||||
**/*.rb
|
|
||||||
**/*.html
|
|
||||||
**/*.json
|
|
||||||
**/*.md
|
|
||||||
+27
-55
@@ -1,86 +1,58 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (7.1.3.4)
|
|
||||||
base64
|
|
||||||
bigdecimal
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
||||||
connection_pool (>= 2.2.5)
|
|
||||||
drb
|
|
||||||
i18n (>= 1.6, < 2)
|
|
||||||
minitest (>= 5.1)
|
|
||||||
mutex_m
|
|
||||||
tzinfo (~> 2.0)
|
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
base64 (0.2.0)
|
json (2.6.3)
|
||||||
bigdecimal (3.1.8)
|
|
||||||
concurrent-ruby (1.3.3)
|
|
||||||
connection_pool (2.4.1)
|
|
||||||
drb (2.2.1)
|
|
||||||
i18n (1.14.5)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
json (2.7.2)
|
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
minitest (5.24.1)
|
parallel (1.23.0)
|
||||||
mutex_m (0.2.0)
|
parser (3.2.2.4)
|
||||||
parallel (1.25.1)
|
|
||||||
parser (3.3.3.0)
|
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
prettier_print (1.2.1)
|
prettier_print (1.2.1)
|
||||||
racc (1.8.0)
|
racc (1.7.2)
|
||||||
rack (3.1.6)
|
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
regexp_parser (2.9.2)
|
regexp_parser (2.8.2)
|
||||||
rexml (3.3.9)
|
rexml (3.2.6)
|
||||||
rubocop (1.64.1)
|
rubocop (1.57.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.2.2.4)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.28.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.31.3)
|
rubocop-ast (1.30.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.2.1.0)
|
||||||
rubocop-capybara (2.21.0)
|
rubocop-capybara (2.19.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-discourse (3.8.1)
|
rubocop-discourse (3.4.0)
|
||||||
activesupport (>= 6.1)
|
rubocop (>= 1.1.0)
|
||||||
rubocop (>= 1.59.0)
|
rubocop-rspec (>= 2.0.0)
|
||||||
rubocop-capybara (>= 2.0.0)
|
rubocop-factory_bot (2.24.0)
|
||||||
rubocop-factory_bot (>= 2.0.0)
|
rubocop (~> 1.33)
|
||||||
rubocop-rails (>= 2.25.0)
|
rubocop-rspec (2.25.0)
|
||||||
rubocop-rspec (>= 3.0.1)
|
rubocop (~> 1.40)
|
||||||
rubocop-rspec_rails (>= 2.30.0)
|
rubocop-capybara (~> 2.17)
|
||||||
rubocop-factory_bot (2.26.1)
|
rubocop-factory_bot (~> 2.22)
|
||||||
rubocop (~> 1.61)
|
|
||||||
rubocop-rails (2.25.1)
|
|
||||||
activesupport (>= 4.2.0)
|
|
||||||
rack (>= 1.1)
|
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
|
||||||
rubocop-rspec (3.0.2)
|
|
||||||
rubocop (~> 1.61)
|
|
||||||
rubocop-rspec_rails (2.30.0)
|
|
||||||
rubocop (~> 1.61)
|
|
||||||
rubocop-rspec (~> 3, >= 3.0.1)
|
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
syntax_tree (6.2.0)
|
syntax_tree (6.2.0)
|
||||||
prettier_print (>= 1.2.0)
|
prettier_print (>= 1.2.0)
|
||||||
tzinfo (2.0.6)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.5.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
|
arm64-darwin-20
|
||||||
ruby
|
ruby
|
||||||
|
x86_64-darwin-18
|
||||||
|
x86_64-darwin-19
|
||||||
|
x86_64-darwin-20
|
||||||
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
rubocop-discourse
|
rubocop-discourse
|
||||||
syntax_tree
|
syntax_tree
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.10
|
2.4.21
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class AdminPluginsShowDiscourseAiLlmsNew extends DiscourseRoute {
|
|
||||||
queryParams = {
|
|
||||||
llmTemplate: { refreshModel: true },
|
|
||||||
};
|
|
||||||
|
|
||||||
async model() {
|
|
||||||
const record = this.store.createRecord("ai-llm");
|
|
||||||
record.provider_params = {};
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
super.setupController(controller, model);
|
|
||||||
controller.set(
|
|
||||||
"allLlms",
|
|
||||||
this.modelFor("adminPlugins.show.discourse-ai-llms")
|
|
||||||
);
|
|
||||||
controller.set(
|
|
||||||
"llmTemplate",
|
|
||||||
this.paramsFor(this.routeName).llmTemplate || null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class AdminPluginsShowDiscourseAiLlmsShow extends DiscourseRoute {
|
|
||||||
async model(params) {
|
|
||||||
const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms");
|
|
||||||
const id = parseInt(params.id, 10);
|
|
||||||
const record = allLlms.findBy("id", id);
|
|
||||||
record.provider_params = record.provider_params || {};
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
super.setupController(controller, model);
|
|
||||||
controller.set(
|
|
||||||
"allLlms",
|
|
||||||
this.modelFor("adminPlugins.show.discourse-ai-llms")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiAiLlmsRoute extends DiscourseRoute {
|
|
||||||
model() {
|
|
||||||
return this.store.findAll("ai-llm");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-25
@@ -1,25 +0,0 @@
|
|||||||
import { AUTO_GROUPS } from "discourse/lib/constants";
|
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class AdminPluginsShowDiscourseAiPersonasNew extends DiscourseRoute {
|
|
||||||
async model() {
|
|
||||||
const record = this.store.createRecord("ai-persona");
|
|
||||||
record.set("allowed_group_ids", [AUTO_GROUPS.trust_level_0.id]);
|
|
||||||
record.set("rag_uploads", []);
|
|
||||||
// these match the defaults on the table
|
|
||||||
record.set("rag_chunk_tokens", 374);
|
|
||||||
record.set("rag_chunk_overlap_tokens", 10);
|
|
||||||
record.set("rag_conversation_chunks", 10);
|
|
||||||
record.set("allow_personal_messages", true);
|
|
||||||
record.set("tool_details", false);
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
super.setupController(controller, model);
|
|
||||||
controller.set(
|
|
||||||
"allPersonas",
|
|
||||||
this.modelFor("adminPlugins.show.discourse-ai-personas")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class AdminPluginsShowDiscourseAiPersonasShow extends DiscourseRoute {
|
|
||||||
async model(params) {
|
|
||||||
const allPersonas = this.modelFor(
|
|
||||||
"adminPlugins.show.discourse-ai-personas"
|
|
||||||
);
|
|
||||||
const id = parseInt(params.id, 10);
|
|
||||||
return allPersonas.findBy("id", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller, model) {
|
|
||||||
super.setupController(controller, model);
|
|
||||||
controller.set(
|
|
||||||
"allPersonas",
|
|
||||||
this.modelFor("adminPlugins.show.discourse-ai-personas")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiAiPersonasRoute extends DiscourseRoute {
|
|
||||||
model() {
|
|
||||||
return this.store.findAll("ai-persona");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
|
|
||||||
async model() {
|
|
||||||
return this.store.createRecord("ai-tool");
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller) {
|
|
||||||
super.setupController(...arguments);
|
|
||||||
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
|
||||||
|
|
||||||
controller.set("allTools", toolsModel);
|
|
||||||
controller.set("presets", toolsModel.resultSetMeta.presets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiToolsShowRoute extends DiscourseRoute {
|
|
||||||
async model(params) {
|
|
||||||
const allTools = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
|
||||||
const id = parseInt(params.id, 10);
|
|
||||||
|
|
||||||
return allTools.find((tool) => tool.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupController(controller) {
|
|
||||||
super.setupController(...arguments);
|
|
||||||
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
|
||||||
|
|
||||||
controller.set("allTools", toolsModel);
|
|
||||||
controller.set("presets", toolsModel.resultSetMeta.presets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { service } from "@ember/service";
|
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiToolsRoute extends DiscourseRoute {
|
|
||||||
@service store;
|
|
||||||
|
|
||||||
model() {
|
|
||||||
return this.store.findAll("ai-tool");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { service } from "@ember/service";
|
|
||||||
import { ajax } from "discourse/lib/ajax";
|
|
||||||
import DiscourseRoute from "discourse/routes/discourse";
|
|
||||||
|
|
||||||
export default class DiscourseAiUsageRoute extends DiscourseRoute {
|
|
||||||
@service store;
|
|
||||||
|
|
||||||
model() {
|
|
||||||
return ajax("/admin/plugins/discourse-ai/ai-usage.json");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
<AiLlmsListEditor @llms={{this.model}} />
|
|
||||||
-5
@@ -1,5 +0,0 @@
|
|||||||
<AiLlmsListEditor
|
|
||||||
@llms={{this.allLlms}}
|
|
||||||
@currentLlm={{this.model}}
|
|
||||||
@llmTemplate={{this.llmTemplate}}
|
|
||||||
/>
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
<AiLlmsListEditor @llms={{this.allLlms}} @currentLlm={{this.model}} />
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
<AiPersonaListEditor @personas={{this.model}} />
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
<AiPersonaListEditor
|
|
||||||
@personas={{this.allPersonas}}
|
|
||||||
@currentPersona={{this.model}}
|
|
||||||
/>
|
|
||||||
-4
@@ -1,4 +0,0 @@
|
|||||||
<AiPersonaListEditor
|
|
||||||
@personas={{this.allPersonas}}
|
|
||||||
@currentPersona={{this.model}}
|
|
||||||
/>
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
<AiToolListEditor @tools={{this.model}} />
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
<section class="ai-persona-tool-editor__current admin-detail pull-left">
|
|
||||||
<AiToolEditor
|
|
||||||
@tools={{this.allTools}}
|
|
||||||
@model={{this.model}}
|
|
||||||
@presets={{this.presets}}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
-7
@@ -1,7 +0,0 @@
|
|||||||
<section class="ai-persona-tool-editor__current admin-detail pull-left">
|
|
||||||
<AiToolEditor
|
|
||||||
@tools={{this.allTools}}
|
|
||||||
@model={{this.model}}
|
|
||||||
@presets={{this.presets}}
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<AiUsage @model={{this.model}} />
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseAi
|
|
||||||
module Admin
|
|
||||||
class AiLlmsController < ::Admin::AdminController
|
|
||||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
|
||||||
|
|
||||||
def index
|
|
||||||
llms = LlmModel.all.order(:display_name)
|
|
||||||
|
|
||||||
render json: {
|
|
||||||
ai_llms:
|
|
||||||
ActiveModel::ArraySerializer.new(
|
|
||||||
llms,
|
|
||||||
each_serializer: LlmModelSerializer,
|
|
||||||
root: false,
|
|
||||||
scope: {
|
|
||||||
llm_usage: DiscourseAi::Configuration::LlmEnumerator.global_usage,
|
|
||||||
},
|
|
||||||
).as_json,
|
|
||||||
meta: {
|
|
||||||
provider_params: LlmModel.provider_params,
|
|
||||||
presets: DiscourseAi::Completions::Llm.presets,
|
|
||||||
providers: DiscourseAi::Completions::Llm.provider_names,
|
|
||||||
tokenizers:
|
|
||||||
DiscourseAi::Completions::Llm.tokenizer_names.map { |tn|
|
|
||||||
{ id: tn, name: tn.split("::").last }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
llm_model = LlmModel.find(params[:id])
|
|
||||||
render json: LlmModelSerializer.new(llm_model)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
llm_model = LlmModel.new(ai_llm_params)
|
|
||||||
if llm_model.save
|
|
||||||
llm_model.toggle_companion_user
|
|
||||||
render json: LlmModelSerializer.new(llm_model), status: :created
|
|
||||||
else
|
|
||||||
render_json_error llm_model
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
llm_model = LlmModel.find(params[:id])
|
|
||||||
|
|
||||||
if llm_model.seeded?
|
|
||||||
return render_json_error(I18n.t("discourse_ai.llm.cannot_edit_builtin"), status: 403)
|
|
||||||
end
|
|
||||||
|
|
||||||
if llm_model.update(ai_llm_params(updating: llm_model))
|
|
||||||
llm_model.toggle_companion_user
|
|
||||||
render json: LlmModelSerializer.new(llm_model)
|
|
||||||
else
|
|
||||||
render_json_error llm_model
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
llm_model = LlmModel.find(params[:id])
|
|
||||||
|
|
||||||
if llm_model.seeded?
|
|
||||||
return render_json_error(I18n.t("discourse_ai.llm.cannot_delete_builtin"), status: 403)
|
|
||||||
end
|
|
||||||
|
|
||||||
in_use_by = DiscourseAi::Configuration::LlmValidator.new.modules_using(llm_model)
|
|
||||||
|
|
||||||
if !in_use_by.empty?
|
|
||||||
return(
|
|
||||||
render_json_error(
|
|
||||||
I18n.t(
|
|
||||||
"discourse_ai.llm.delete_failed",
|
|
||||||
settings: in_use_by.join(", "),
|
|
||||||
count: in_use_by.length,
|
|
||||||
),
|
|
||||||
status: 409,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clean up companion users
|
|
||||||
llm_model.enabled_chat_bot = false
|
|
||||||
llm_model.toggle_companion_user
|
|
||||||
|
|
||||||
if llm_model.destroy
|
|
||||||
head :no_content
|
|
||||||
else
|
|
||||||
render_json_error llm_model
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test
|
|
||||||
RateLimiter.new(current_user, "llm_test_#{current_user.id}", 3, 1.minute).performed!
|
|
||||||
|
|
||||||
llm_model = LlmModel.new(ai_llm_params)
|
|
||||||
|
|
||||||
DiscourseAi::Configuration::LlmValidator.new.run_test(llm_model)
|
|
||||||
|
|
||||||
render json: { success: true }
|
|
||||||
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed => e
|
|
||||||
render json: { success: false, error: e.message }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ai_llm_params(updating: nil)
|
|
||||||
return {} if params[:ai_llm].blank?
|
|
||||||
|
|
||||||
permitted =
|
|
||||||
params.require(:ai_llm).permit(
|
|
||||||
:display_name,
|
|
||||||
:name,
|
|
||||||
:provider,
|
|
||||||
:tokenizer,
|
|
||||||
:max_prompt_tokens,
|
|
||||||
:api_key,
|
|
||||||
:enabled_chat_bot,
|
|
||||||
:vision_enabled,
|
|
||||||
)
|
|
||||||
|
|
||||||
provider = updating ? updating.provider : permitted[:provider]
|
|
||||||
permit_url = provider != LlmModel::BEDROCK_PROVIDER_NAME
|
|
||||||
|
|
||||||
new_url = params.dig(:ai_llm, :url)
|
|
||||||
permitted[:url] = new_url if permit_url && new_url
|
|
||||||
|
|
||||||
extra_field_names = LlmModel.provider_params.dig(provider&.to_sym)
|
|
||||||
if extra_field_names.present?
|
|
||||||
received_prov_params =
|
|
||||||
params.dig(:ai_llm, :provider_params)&.slice(*extra_field_names.keys)
|
|
||||||
|
|
||||||
if received_prov_params.present?
|
|
||||||
received_prov_params.each do |pname, value|
|
|
||||||
if extra_field_names[pname.to_sym] == :checkbox
|
|
||||||
received_prov_params[pname] = ActiveModel::Type::Boolean.new.cast(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
permitted[:provider_params] = received_prov_params.permit!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
permitted
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseAi
|
|
||||||
module Admin
|
|
||||||
class AiPersonasController < ::Admin::AdminController
|
|
||||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
|
||||||
|
|
||||||
before_action :find_ai_persona, only: %i[show update destroy create_user]
|
|
||||||
|
|
||||||
def index
|
|
||||||
ai_personas =
|
|
||||||
AiPersona.ordered.map do |persona|
|
|
||||||
# we use a special serializer here cause names and descriptions are
|
|
||||||
# localized for system personas
|
|
||||||
LocalizedAiPersonaSerializer.new(persona, root: false)
|
|
||||||
end
|
|
||||||
tools =
|
|
||||||
DiscourseAi::AiBot::Personas::Persona.all_available_tools.map do |tool|
|
|
||||||
AiToolSerializer.new(tool, root: false)
|
|
||||||
end
|
|
||||||
AiTool
|
|
||||||
.where(enabled: true)
|
|
||||||
.each do |tool|
|
|
||||||
tools << {
|
|
||||||
id: "custom-#{tool.id}",
|
|
||||||
name: I18n.t("discourse_ai.tools.custom_name", name: tool.name.capitalize),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
llms =
|
|
||||||
DiscourseAi::Configuration::LlmEnumerator.values.map do |hash|
|
|
||||||
{ id: hash[:value], name: hash[:name] }
|
|
||||||
end
|
|
||||||
render json: { ai_personas: ai_personas, meta: { tools: tools, llms: llms } }
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
render json: LocalizedAiPersonaSerializer.new(@ai_persona)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
ai_persona = AiPersona.new(ai_persona_params.except(:rag_uploads))
|
|
||||||
if ai_persona.save
|
|
||||||
RagDocumentFragment.link_target_and_uploads(ai_persona, attached_upload_ids)
|
|
||||||
|
|
||||||
render json: {
|
|
||||||
ai_persona: LocalizedAiPersonaSerializer.new(ai_persona, root: false),
|
|
||||||
},
|
|
||||||
status: :created
|
|
||||||
else
|
|
||||||
render_json_error ai_persona
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_user
|
|
||||||
user = @ai_persona.create_user!
|
|
||||||
render json: BasicUserSerializer.new(user, root: "user")
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
if @ai_persona.update(ai_persona_params.except(:rag_uploads))
|
|
||||||
RagDocumentFragment.update_target_uploads(@ai_persona, attached_upload_ids)
|
|
||||||
|
|
||||||
render json: LocalizedAiPersonaSerializer.new(@ai_persona, root: false)
|
|
||||||
else
|
|
||||||
render_json_error @ai_persona
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
if @ai_persona.destroy
|
|
||||||
head :no_content
|
|
||||||
else
|
|
||||||
render_json_error @ai_persona
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def stream_reply
|
|
||||||
persona =
|
|
||||||
AiPersona.find_by(name: params[:persona_name]) ||
|
|
||||||
AiPersona.find_by(id: params[:persona_id])
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.persona_not_found")) if persona.nil?
|
|
||||||
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.persona_disabled")) if !persona.enabled
|
|
||||||
|
|
||||||
if persona.default_llm.blank?
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.no_default_llm"))
|
|
||||||
end
|
|
||||||
|
|
||||||
if params[:query].blank?
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.no_query_specified"))
|
|
||||||
end
|
|
||||||
|
|
||||||
if !persona.user_id
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.no_user_for_persona"))
|
|
||||||
end
|
|
||||||
|
|
||||||
if !params[:username] && !params[:user_unique_id]
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.no_user_specified"))
|
|
||||||
end
|
|
||||||
|
|
||||||
user = nil
|
|
||||||
|
|
||||||
if params[:username]
|
|
||||||
user = User.find_by_username(params[:username])
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.user_not_found")) if user.nil?
|
|
||||||
elsif params[:user_unique_id]
|
|
||||||
user = stage_user
|
|
||||||
end
|
|
||||||
|
|
||||||
raise Discourse::NotFound if user.nil?
|
|
||||||
|
|
||||||
topic_id = params[:topic_id].to_i
|
|
||||||
topic = nil
|
|
||||||
|
|
||||||
if topic_id > 0
|
|
||||||
topic = Topic.find(topic_id)
|
|
||||||
|
|
||||||
if topic.topic_allowed_users.where(user_id: user.id).empty?
|
|
||||||
return render_json_error(I18n.t("discourse_ai.errors.user_not_allowed"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
hijack = request.env["rack.hijack"]
|
|
||||||
io = hijack.call
|
|
||||||
|
|
||||||
DiscourseAi::AiBot::ResponseHttpStreamer.queue_streamed_reply(
|
|
||||||
io: io,
|
|
||||||
persona: persona,
|
|
||||||
user: user,
|
|
||||||
topic: topic,
|
|
||||||
query: params[:query].to_s,
|
|
||||||
custom_instructions: params[:custom_instructions].to_s,
|
|
||||||
current_user: current_user,
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
AI_STREAM_CONVERSATION_UNIQUE_ID = "ai-stream-conversation-unique-id"
|
|
||||||
|
|
||||||
def stage_user
|
|
||||||
unique_id = params[:user_unique_id].to_s
|
|
||||||
field = UserCustomField.find_by(name: AI_STREAM_CONVERSATION_UNIQUE_ID, value: unique_id)
|
|
||||||
|
|
||||||
if field
|
|
||||||
field.user
|
|
||||||
else
|
|
||||||
preferred_username = params[:preferred_username]
|
|
||||||
username = UserNameSuggester.suggest(preferred_username || unique_id)
|
|
||||||
|
|
||||||
user =
|
|
||||||
User.new(
|
|
||||||
username: username,
|
|
||||||
email: "#{SecureRandom.hex}@invalid.com",
|
|
||||||
staged: true,
|
|
||||||
active: false,
|
|
||||||
)
|
|
||||||
user.custom_fields[AI_STREAM_CONVERSATION_UNIQUE_ID] = unique_id
|
|
||||||
user.save!
|
|
||||||
user
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_ai_persona
|
|
||||||
@ai_persona = AiPersona.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def attached_upload_ids
|
|
||||||
ai_persona_params[:rag_uploads].to_a.map { |h| h[:id] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def ai_persona_params
|
|
||||||
permitted =
|
|
||||||
params.require(:ai_persona).permit(
|
|
||||||
:name,
|
|
||||||
:description,
|
|
||||||
:enabled,
|
|
||||||
:system_prompt,
|
|
||||||
:priority,
|
|
||||||
:top_p,
|
|
||||||
:temperature,
|
|
||||||
:default_llm,
|
|
||||||
:user_id,
|
|
||||||
:max_context_posts,
|
|
||||||
:vision_enabled,
|
|
||||||
:vision_max_pixels,
|
|
||||||
:rag_chunk_tokens,
|
|
||||||
:rag_chunk_overlap_tokens,
|
|
||||||
:rag_conversation_chunks,
|
|
||||||
:question_consolidator_llm,
|
|
||||||
:allow_chat_channel_mentions,
|
|
||||||
:allow_chat_direct_messages,
|
|
||||||
:allow_topic_mentions,
|
|
||||||
:allow_personal_messages,
|
|
||||||
:tool_details,
|
|
||||||
:forced_tool_count,
|
|
||||||
:force_default_llm,
|
|
||||||
allowed_group_ids: [],
|
|
||||||
rag_uploads: [:id],
|
|
||||||
)
|
|
||||||
|
|
||||||
if tools = params.dig(:ai_persona, :tools)
|
|
||||||
permitted[:tools] = permit_tools(tools)
|
|
||||||
end
|
|
||||||
|
|
||||||
permitted
|
|
||||||
end
|
|
||||||
|
|
||||||
def permit_tools(tools)
|
|
||||||
return [] if !tools.is_a?(Array)
|
|
||||||
|
|
||||||
tools.filter_map do |tool, options, force_tool|
|
|
||||||
break nil if !tool.is_a?(String)
|
|
||||||
options&.permit! if options && options.is_a?(ActionController::Parameters)
|
|
||||||
|
|
||||||
# this is simpler from a storage perspective, 1 way to store tools
|
|
||||||
[tool, options, !!force_tool]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module DiscourseAi
|
|
||||||
module Admin
|
|
||||||
class AiToolsController < ::Admin::AdminController
|
|
||||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
|
||||||
|
|
||||||
before_action :find_ai_tool, only: %i[test show update destroy]
|
|
||||||
|
|
||||||
def index
|
|
||||||
ai_tools = AiTool.all
|
|
||||||
render_serialized({ ai_tools: ai_tools }, AiCustomToolListSerializer, root: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
def show
|
|
||||||
render_serialized(@ai_tool, AiCustomToolSerializer)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
ai_tool = AiTool.new(ai_tool_params)
|
|
||||||
ai_tool.created_by_id = current_user.id
|
|
||||||
|
|
||||||
if ai_tool.save
|
|
||||||
RagDocumentFragment.link_target_and_uploads(ai_tool, attached_upload_ids)
|
|
||||||
render_serialized(ai_tool, AiCustomToolSerializer, status: :created)
|
|
||||||
else
|
|
||||||
render_json_error ai_tool
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
if @ai_tool.update(ai_tool_params)
|
|
||||||
RagDocumentFragment.update_target_uploads(@ai_tool, attached_upload_ids)
|
|
||||||
render_serialized(@ai_tool, AiCustomToolSerializer)
|
|
||||||
else
|
|
||||||
render_json_error @ai_tool
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
if @ai_tool.destroy
|
|
||||||
head :no_content
|
|
||||||
else
|
|
||||||
render_json_error @ai_tool
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def test
|
|
||||||
@ai_tool.assign_attributes(ai_tool_params) if params[:ai_tool]
|
|
||||||
parameters = params[:parameters].to_unsafe_h
|
|
||||||
|
|
||||||
# we need an llm so we have a tokenizer
|
|
||||||
# but will do without if none is available
|
|
||||||
llm = LlmModel.first&.to_llm
|
|
||||||
runner = @ai_tool.runner(parameters, llm: llm, bot_user: current_user, context: {})
|
|
||||||
result = runner.invoke
|
|
||||||
|
|
||||||
if result.is_a?(Hash) && result[:error]
|
|
||||||
render_json_error result[:error]
|
|
||||||
else
|
|
||||||
render json: { output: result }
|
|
||||||
end
|
|
||||||
rescue ActiveRecord::RecordNotFound => e
|
|
||||||
render_json_error e.message, status: 400
|
|
||||||
rescue => e
|
|
||||||
render_json_error "Error executing the tool: #{e.message}", status: 400
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def attached_upload_ids
|
|
||||||
params[:ai_tool][:rag_uploads].to_a.map { |h| h[:id] }
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_ai_tool
|
|
||||||
@ai_tool = AiTool.find(params[:id].to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ai_tool_params
|
|
||||||
params
|
|
||||||
.require(:ai_tool)
|
|
||||||
.permit(
|
|
||||||
:name,
|
|
||||||
:description,
|
|
||||||
:script,
|
|
||||||
:summary,
|
|
||||||
:rag_chunk_tokens,
|
|
||||||
:rag_chunk_overlap_tokens,
|
|
||||||
rag_uploads: [:id],
|
|
||||||
parameters: [:name, :type, :description, :required, enum: []],
|
|
||||||
)
|
|
||||||
.except(:rag_uploads)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user