Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ea1e8efd8 | ||
|
|
2b98e84acf |
@@ -1,6 +1,8 @@
|
||||
< 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.3.0.beta1-dev: a65c4076a63104b61d213534db7f89aa3c8acd9e
|
||||
< 3.2.0.beta5-dev: c70f43f130fb98e4a1adb1afd9b621aaa49088e3
|
||||
< 3.2.0.beta4-dev: 493b48477a9288f2f1b6f021954088705a6673d6
|
||||
< 3.2.0.beta2-dev: 3bced1c6f525553fe2b5b79d29bd66c1cf2a47d5
|
||||
3.1.999: c0415bb7eb878b1b7abf112d65cba981030df8df
|
||||
< 3.2.0.beta1-dev: 3e7c99de894b03e9161d9481da118fe893991a1d
|
||||
3.1.0.beta3: 057fbe1ce6
|
||||
|
||||
+26
-35
@@ -1,7 +1,7 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (7.1.3.4)
|
||||
activesupport (7.1.3.2)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
@@ -13,28 +13,27 @@ GEM
|
||||
tzinfo (~> 2.0)
|
||||
ast (2.4.2)
|
||||
base64 (0.2.0)
|
||||
bigdecimal (3.1.8)
|
||||
concurrent-ruby (1.3.3)
|
||||
bigdecimal (3.1.6)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
drb (2.2.1)
|
||||
i18n (1.14.5)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.7.2)
|
||||
json (2.7.1)
|
||||
language_server-protocol (3.17.0.3)
|
||||
minitest (5.24.1)
|
||||
minitest (5.22.2)
|
||||
mutex_m (0.2.0)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.3.0)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.0.5)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
prettier_print (1.2.1)
|
||||
racc (1.8.0)
|
||||
rack (3.1.6)
|
||||
racc (1.7.3)
|
||||
rainbow (3.1.1)
|
||||
regexp_parser (2.9.2)
|
||||
rexml (3.3.3)
|
||||
strscan
|
||||
rubocop (1.64.1)
|
||||
regexp_parser (2.9.0)
|
||||
rexml (3.2.8)
|
||||
strscan (>= 3.0.9)
|
||||
rubocop (1.61.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
@@ -42,33 +41,25 @@ GEM
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-capybara (2.21.0)
|
||||
rubocop-ast (1.31.1)
|
||||
parser (>= 3.3.0.4)
|
||||
rubocop-capybara (2.20.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-discourse (3.8.1)
|
||||
rubocop-discourse (3.7.1)
|
||||
activesupport (>= 6.1)
|
||||
rubocop (>= 1.59.0)
|
||||
rubocop-capybara (>= 2.0.0)
|
||||
rubocop-factory_bot (>= 2.0.0)
|
||||
rubocop-rails (>= 2.25.0)
|
||||
rubocop-rspec (>= 3.0.1)
|
||||
rubocop-rspec_rails (>= 2.30.0)
|
||||
rubocop-factory_bot (2.26.1)
|
||||
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)
|
||||
rubocop-rspec (>= 2.25.0)
|
||||
rubocop-factory_bot (2.25.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-rspec (2.27.1)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-progressbar (1.13.0)
|
||||
strscan (3.1.0)
|
||||
syntax_tree (6.2.0)
|
||||
@@ -85,4 +76,4 @@ DEPENDENCIES
|
||||
syntax_tree
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.10
|
||||
2.5.4
|
||||
|
||||
@@ -3,7 +3,6 @@ import DiscourseRoute from "discourse/routes/discourse";
|
||||
export default DiscourseRoute.extend({
|
||||
async model() {
|
||||
const record = this.store.createRecord("ai-llm");
|
||||
record.provider_params = {};
|
||||
return record;
|
||||
},
|
||||
|
||||
|
||||
+1
-3
@@ -4,9 +4,7 @@ export default DiscourseRoute.extend({
|
||||
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;
|
||||
return allLlms.findBy("id", id);
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
|
||||
-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");
|
||||
}
|
||||
}
|
||||
+6
@@ -1,3 +1,9 @@
|
||||
<DBreadcrumbsItem as |linkClass|>
|
||||
<LinkTo @route="adminPlugins.show.discourse-ai-personas" class={{linkClass}}>
|
||||
{{@model.plugin.nameTitleized}}
|
||||
</LinkTo>
|
||||
</DBreadcrumbsItem>
|
||||
|
||||
<AiPersonaListEditor
|
||||
@personas={{this.allPersonas}}
|
||||
@currentPersona={{this.model}}
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
<AiToolListEditor @tools={{this.model}} />
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
<AiToolEditor
|
||||
@tools={{this.allTools}}
|
||||
@model={{this.model}}
|
||||
@presets={{this.presets}}
|
||||
/>
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
<AiToolEditor
|
||||
@tools={{this.allTools}}
|
||||
@model={{this.model}}
|
||||
@presets={{this.presets}}
|
||||
/>
|
||||
@@ -6,7 +6,7 @@ module DiscourseAi
|
||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
||||
|
||||
def index
|
||||
llms = LlmModel.all.order(:display_name)
|
||||
llms = LlmModel.all
|
||||
|
||||
render json: {
|
||||
ai_llms:
|
||||
@@ -16,8 +16,6 @@ module DiscourseAi
|
||||
root: false,
|
||||
).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|
|
||||
@@ -35,8 +33,7 @@ module DiscourseAi
|
||||
def create
|
||||
llm_model = LlmModel.new(ai_llm_params)
|
||||
if llm_model.save
|
||||
llm_model.toggle_companion_user
|
||||
render json: { ai_persona: LlmModelSerializer.new(llm_model) }, status: :created
|
||||
render json: { ai_persona: llm_model }, status: :created
|
||||
else
|
||||
render_json_error llm_model
|
||||
end
|
||||
@@ -45,46 +42,8 @@ module DiscourseAi
|
||||
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
|
||||
if llm_model.update(ai_llm_params)
|
||||
render json: llm_model
|
||||
else
|
||||
render_json_error llm_model
|
||||
end
|
||||
@@ -95,7 +54,11 @@ module DiscourseAi
|
||||
|
||||
llm_model = LlmModel.new(ai_llm_params)
|
||||
|
||||
DiscourseAi::Configuration::LlmValidator.new.run_test(llm_model)
|
||||
DiscourseAi::Completions::Llm.proxy_from_obj(llm_model).generate(
|
||||
"How much is 1 + 1?",
|
||||
user: current_user,
|
||||
feature_name: "llm_validator",
|
||||
)
|
||||
|
||||
render json: { success: true }
|
||||
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed => e
|
||||
@@ -104,44 +67,16 @@ module DiscourseAi
|
||||
|
||||
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
|
||||
def ai_llm_params
|
||||
params.require(:ai_llm).permit(
|
||||
:display_name,
|
||||
:name,
|
||||
:provider,
|
||||
:tokenizer,
|
||||
:max_prompt_tokens,
|
||||
:url,
|
||||
:api_key,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -19,19 +19,11 @@ module DiscourseAi
|
||||
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 } }
|
||||
render json: { ai_personas: ai_personas, meta: { commands: tools, llms: llms } }
|
||||
end
|
||||
|
||||
def show
|
||||
@@ -43,10 +35,7 @@ module DiscourseAi
|
||||
if ai_persona.save
|
||||
RagDocumentFragment.link_persona_and_uploads(ai_persona, attached_upload_ids)
|
||||
|
||||
render json: {
|
||||
ai_persona: LocalizedAiPersonaSerializer.new(ai_persona, root: false),
|
||||
},
|
||||
status: :created
|
||||
render json: { ai_persona: ai_persona }, status: :created
|
||||
else
|
||||
render_json_error ai_persona
|
||||
end
|
||||
@@ -61,7 +50,7 @@ module DiscourseAi
|
||||
if @ai_persona.update(ai_persona_params.except(:rag_uploads))
|
||||
RagDocumentFragment.update_persona_uploads(@ai_persona, attached_upload_ids)
|
||||
|
||||
render json: LocalizedAiPersonaSerializer.new(@ai_persona, root: false)
|
||||
render json: @ai_persona
|
||||
else
|
||||
render_json_error @ai_persona
|
||||
end
|
||||
@@ -137,29 +126,28 @@ module DiscourseAi
|
||||
:rag_conversation_chunks,
|
||||
:question_consolidator_llm,
|
||||
:allow_chat,
|
||||
:tool_details,
|
||||
allowed_group_ids: [],
|
||||
rag_uploads: [:id],
|
||||
)
|
||||
|
||||
if tools = params.dig(:ai_persona, :tools)
|
||||
permitted[:tools] = permit_tools(tools)
|
||||
if commands = params.dig(:ai_persona, :commands)
|
||||
permitted[:commands] = permit_commands(commands)
|
||||
end
|
||||
|
||||
permitted
|
||||
end
|
||||
|
||||
def permit_tools(tools)
|
||||
return [] if !tools.is_a?(Array)
|
||||
def permit_commands(commands)
|
||||
return [] if !commands.is_a?(Array)
|
||||
|
||||
tools.filter_map do |tool, options|
|
||||
break nil if !tool.is_a?(String)
|
||||
commands.filter_map do |command, options|
|
||||
break nil if !command.is_a?(String)
|
||||
options&.permit! if options && options.is_a?(ActionController::Parameters)
|
||||
|
||||
if options
|
||||
[tool, options]
|
||||
[command, options]
|
||||
else
|
||||
tool
|
||||
command
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,89 +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[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
|
||||
render_serialized(ai_tool, AiCustomToolSerializer, status: :created)
|
||||
else
|
||||
render_json_error ai_tool
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
if @ai_tool.update(ai_tool_params)
|
||||
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
|
||||
if params[:id].present?
|
||||
ai_tool = AiTool.find(params[:id])
|
||||
else
|
||||
ai_tool = AiTool.new(ai_tool_params)
|
||||
end
|
||||
|
||||
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 find_ai_tool
|
||||
@ai_tool = AiTool.find(params[:id])
|
||||
end
|
||||
|
||||
def ai_tool_params
|
||||
params.require(:ai_tool).permit(
|
||||
:name,
|
||||
:description,
|
||||
:script,
|
||||
:summary,
|
||||
parameters: [:name, :type, :description, :required, enum: []],
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,7 +18,7 @@ module DiscourseAi
|
||||
|
||||
debug_info = AiApiAuditLog.where(post: posts).order(created_at: :desc).first
|
||||
|
||||
render json: AiApiAuditLogSerializer.new(debug_info, root: false), status: 200
|
||||
render json: debug_info, status: 200
|
||||
end
|
||||
|
||||
def stop_streaming_response
|
||||
@@ -31,10 +31,12 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def show_bot_username
|
||||
bot_user = DiscourseAi::AiBot::EntryPoint.find_user_from_model(params[:username])
|
||||
raise Discourse::InvalidParameters.new(:username) if !bot_user
|
||||
bot_user_id = DiscourseAi::AiBot::EntryPoint.map_bot_model_to_user_id(params[:username])
|
||||
raise Discourse::InvalidParameters.new(:username) if !bot_user_id
|
||||
|
||||
render json: { bot_username: bot_user.username_lower }, status: 200
|
||||
bot_username_lower = User.find(bot_user_id).username_lower
|
||||
|
||||
render json: { bot_username: bot_username_lower }, status: 200
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,7 @@ module DiscourseAi
|
||||
requires_login only: %i[create update destroy]
|
||||
before_action :require_site_settings!
|
||||
|
||||
skip_before_action :preload_json, :check_xhr, only: %i[show]
|
||||
skip_before_action :preload_json, :check_xhr, :redirect_to_login_if_required, only: %i[show]
|
||||
|
||||
def create
|
||||
ensure_allowed_create!
|
||||
|
||||
@@ -12,7 +12,6 @@ module DiscourseAi
|
||||
|
||||
def suggest
|
||||
input = get_text_param!
|
||||
force_default_locale = params[:force_default_locale] || false
|
||||
|
||||
prompt = CompletionPrompt.find_by(id: params[:mode])
|
||||
|
||||
@@ -32,7 +31,6 @@ module DiscourseAi
|
||||
prompt,
|
||||
input,
|
||||
current_user,
|
||||
force_default_locale,
|
||||
),
|
||||
status: 200
|
||||
end
|
||||
@@ -89,26 +87,18 @@ module DiscourseAi
|
||||
end
|
||||
end
|
||||
|
||||
def stream_suggestion
|
||||
def explain
|
||||
post_id = get_post_param!
|
||||
text = get_text_param!
|
||||
term_to_explain = get_text_param!
|
||||
post = Post.includes(:topic).find_by(id: post_id)
|
||||
prompt = CompletionPrompt.find_by(id: params[:mode])
|
||||
|
||||
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
|
||||
raise Discourse::InvalidParameters.new(:post_id) unless post
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
|
||||
end
|
||||
|
||||
Jobs.enqueue(
|
||||
:stream_post_helper,
|
||||
post_id: post.id,
|
||||
user_id: current_user.id,
|
||||
text: text,
|
||||
prompt: prompt.name,
|
||||
custom_prompt: params[:custom_prompt],
|
||||
term_to_explain: term_to_explain,
|
||||
)
|
||||
|
||||
render json: { success: true }, status: 200
|
||||
@@ -117,6 +107,21 @@ module DiscourseAi
|
||||
status: 502
|
||||
end
|
||||
|
||||
def random_caption
|
||||
captions = [
|
||||
"A beautiful landscape",
|
||||
"An adorable puppy",
|
||||
"A delicious meal",
|
||||
"A cozy fireplace",
|
||||
"A stunning sunset",
|
||||
"A charming cityscape",
|
||||
"A peaceful garden",
|
||||
"A majestic mountain range",
|
||||
"A captivating work of art",
|
||||
]
|
||||
captions.sample
|
||||
end
|
||||
|
||||
def caption_image
|
||||
image_url = params[:image_url]
|
||||
image_url_type = params[:image_url_type]
|
||||
@@ -133,12 +138,19 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
raise Discourse::NotFound if image.blank?
|
||||
|
||||
check_secure_upload_permission(image) if image.secure?
|
||||
user = current_user
|
||||
final_image_url = get_caption_url(image, image_url)
|
||||
|
||||
hijack do
|
||||
caption = DiscourseAi::AiHelper::Assistant.new.generate_image_caption(image, user)
|
||||
if Rails.env.development?
|
||||
sleep 2 # Simulate a delay of 2 seconds
|
||||
caption = random_caption
|
||||
else
|
||||
caption =
|
||||
DiscourseAi::AiHelper::Assistant.new.generate_image_caption(
|
||||
final_image_url,
|
||||
current_user,
|
||||
)
|
||||
end
|
||||
render json: {
|
||||
caption:
|
||||
"#{caption} (#{I18n.t("discourse_ai.ai_helper.image_caption.attribution")})",
|
||||
@@ -165,13 +177,18 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def ensure_can_request_suggestions
|
||||
allowed_groups =
|
||||
(
|
||||
SiteSetting.composer_ai_helper_allowed_groups_map |
|
||||
SiteSetting.post_ai_helper_allowed_groups_map
|
||||
)
|
||||
if !current_user.in_any_groups?(SiteSetting.ai_helper_allowed_groups_map)
|
||||
raise Discourse::InvalidAccess
|
||||
end
|
||||
end
|
||||
|
||||
raise Discourse::InvalidAccess if !current_user.in_any_groups?(allowed_groups)
|
||||
def get_caption_url(image_upload, image_url)
|
||||
if image_upload.secure?
|
||||
check_secure_upload_permission(image_upload)
|
||||
return Discourse.store.url_for(image_upload)
|
||||
end
|
||||
|
||||
UrlHelper.absolute(image_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,6 @@ module DiscourseAi
|
||||
|
||||
def search
|
||||
query = params[:q].to_s
|
||||
skip_hyde = params[:hyde].downcase.to_s == "false" || params[:hyde].to_s == "0"
|
||||
|
||||
if query.length < SiteSetting.min_search_term_length
|
||||
raise Discourse::InvalidParameters.new(:q)
|
||||
@@ -32,7 +31,7 @@ module DiscourseAi
|
||||
|
||||
hijack do
|
||||
semantic_search
|
||||
.search_for_topics(query, _page = 1, hyde: !skip_hyde)
|
||||
.search_for_topics(query)
|
||||
.each { |topic_post| grouped_results.add(topic_post) }
|
||||
|
||||
render_serialized(grouped_results, GroupedSearchResultSerializer, result: grouped_results)
|
||||
@@ -40,9 +39,6 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def quick_search
|
||||
# this search function searches posts (vs: topics)
|
||||
# it requires post embeddings and a reranker
|
||||
# it will not perform a hyde expantion
|
||||
query = params[:q].to_s
|
||||
|
||||
if query.length < SiteSetting.min_search_term_length
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Summarization
|
||||
class ChatSummaryController < ::Chat::ApiController
|
||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
||||
requires_plugin ::Chat::PLUGIN_NAME
|
||||
|
||||
VALID_SINCE_VALUES = [1, 3, 6, 12, 24, 72, 168]
|
||||
|
||||
def show
|
||||
since = params[:since].to_i
|
||||
raise Discourse::InvalidParameters.new(:since) if !VALID_SINCE_VALUES.include?(since)
|
||||
|
||||
channel = ::Chat::Channel.find(params[:channel_id])
|
||||
guardian.ensure_can_join_chat_channel!(channel)
|
||||
|
||||
strategy = DiscourseAi::Summarization.default_strategy
|
||||
raise Discourse::NotFound.new unless strategy
|
||||
|
||||
guardian.ensure_can_request_summary!
|
||||
|
||||
RateLimiter.new(current_user, "channel_summary", 6, 5.minutes).performed!
|
||||
|
||||
hijack do
|
||||
content = { content_title: channel.name }
|
||||
|
||||
content[:contents] = channel
|
||||
.chat_messages
|
||||
.where("chat_messages.created_at > ?", since.hours.ago)
|
||||
.includes(:user)
|
||||
.order(created_at: :asc)
|
||||
.pluck(:id, :username_lower, :message)
|
||||
.map { { id: _1, poster: _2, text: _3 } }
|
||||
|
||||
summarized_text =
|
||||
if content[:contents].empty?
|
||||
I18n.t("discourse_ai.summarization.chat.no_targets")
|
||||
else
|
||||
strategy.summarize(content, current_user).dig(:summary)
|
||||
end
|
||||
|
||||
render json: { summary: summarized_text }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,49 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module DiscourseAi
|
||||
module Summarization
|
||||
class SummaryController < ::ApplicationController
|
||||
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
||||
|
||||
def show
|
||||
topic = Topic.find(params[:topic_id])
|
||||
guardian.ensure_can_see!(topic)
|
||||
|
||||
raise Discourse::NotFound if !guardian.can_see_summary?(topic)
|
||||
|
||||
RateLimiter.new(current_user, "summary", 6, 5.minutes).performed! if current_user
|
||||
|
||||
opts = params.permit(:skip_age_check)
|
||||
skip_age_check = opts[:skip_age_check] == "true"
|
||||
|
||||
if params[:stream] && current_user
|
||||
cached_summary = DiscourseAi::TopicSummarization.cached_summary(topic, current_user)
|
||||
|
||||
if cached_summary && !skip_age_check
|
||||
render_serialized(cached_summary, AiTopicSummarySerializer)
|
||||
return
|
||||
end
|
||||
|
||||
Jobs.enqueue(
|
||||
:stream_topic_ai_summary,
|
||||
topic_id: topic.id,
|
||||
user_id: current_user.id,
|
||||
skip_age_check: skip_age_check,
|
||||
)
|
||||
|
||||
render json: success_json
|
||||
else
|
||||
hijack do
|
||||
summary =
|
||||
DiscourseAi::TopicSummarization.summarize(
|
||||
topic,
|
||||
current_user,
|
||||
skip_age_check: skip_age_check,
|
||||
)
|
||||
render_serialized(summary, AiTopicSummarySerializer)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ module DiscourseAi
|
||||
VERSION = "1"
|
||||
|
||||
def share_asset_url(short_path)
|
||||
::UrlHelper.absolute("/plugins/discourse-ai/ai-share/#{short_path}?#{VERSION}")
|
||||
::UrlHelper.local_cdn_url("/plugins/discourse-ai/ai-share/#{short_path}?#{VERSION}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,7 +12,7 @@ module Jobs
|
||||
|
||||
topic = target.is_a?(Topic) ? target : target.topic
|
||||
post = target.is_a?(Post) ? target : target.first_post
|
||||
return if topic.blank? || post.blank?
|
||||
return unless topic.present? && post.present?
|
||||
return if topic.private_message? && !SiteSetting.ai_embeddings_generate_for_pms
|
||||
return if post.raw.blank?
|
||||
|
||||
|
||||
@@ -7,35 +7,27 @@ module Jobs
|
||||
def execute(args)
|
||||
return unless post = Post.includes(:topic).find_by(id: args[:post_id])
|
||||
return unless user = User.find_by(id: args[:user_id])
|
||||
return unless args[:text]
|
||||
return unless args[:term_to_explain]
|
||||
|
||||
topic = post.topic
|
||||
reply_to = post.reply_to_post
|
||||
|
||||
return unless user.guardian.can_see?(post)
|
||||
|
||||
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
|
||||
prompt = CompletionPrompt.enabled_by_name("explain")
|
||||
|
||||
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
||||
prompt.custom_instruction = args[:custom_prompt]
|
||||
end
|
||||
|
||||
if prompt.name == "explain"
|
||||
input = <<~TEXT
|
||||
<term>#{args[:text]}</term>
|
||||
input = <<~TEXT
|
||||
<term>#{args[:term_to_explain]}</term>
|
||||
<context>#{post.raw}</context>
|
||||
<topic>#{topic.title}</topic>
|
||||
#{reply_to ? "<replyTo>#{reply_to.raw}</replyTo>" : nil}
|
||||
TEXT
|
||||
else
|
||||
input = args[:text]
|
||||
end
|
||||
|
||||
DiscourseAi::AiHelper::Assistant.new.stream_prompt(
|
||||
prompt,
|
||||
input,
|
||||
user,
|
||||
"/discourse-ai/ai-helper/stream_suggestion/#{post.id}",
|
||||
"/discourse-ai/ai-helper/explain/#{post.id}",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Jobs
|
||||
class StreamTopicAiSummary < ::Jobs::Base
|
||||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
return unless topic = Topic.find_by(id: args[:topic_id])
|
||||
return unless user = User.find_by(id: args[:user_id])
|
||||
|
||||
strategy = DiscourseAi::Summarization.default_strategy
|
||||
return if strategy.nil? || !Guardian.new(user).can_see_summary?(topic)
|
||||
|
||||
guardian = Guardian.new(user)
|
||||
return unless guardian.can_see?(topic)
|
||||
|
||||
skip_age_check = !!args[:skip_age_check]
|
||||
|
||||
streamed_summary = +""
|
||||
start = Time.now
|
||||
|
||||
summary =
|
||||
DiscourseAi::TopicSummarization
|
||||
.new(strategy, topic, user)
|
||||
.summarize(skip_age_check: skip_age_check) do |partial_summary|
|
||||
streamed_summary << partial_summary
|
||||
|
||||
# Throttle updates.
|
||||
if (Time.now - start > 0.5) || Rails.env.test?
|
||||
payload = { done: false, ai_topic_summary: { summarized_text: streamed_summary } }
|
||||
|
||||
publish_update(topic, user, payload)
|
||||
start = Time.now
|
||||
end
|
||||
end
|
||||
|
||||
publish_update(
|
||||
topic,
|
||||
user,
|
||||
AiTopicSummarySerializer.new(summary, { scope: guardian }).as_json.merge(done: true),
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def publish_update(topic, user, payload)
|
||||
MessageBus.publish("/discourse-ai/summaries/topic/#{topic.id}", payload, user_ids: [user.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,13 +12,6 @@ module ::Jobs
|
||||
return unless post.topic.custom_fields[DiscourseAi::AiBot::EntryPoint::REQUIRE_TITLE_UPDATE]
|
||||
|
||||
DiscourseAi::AiBot::Playground.new(bot).title_playground(post)
|
||||
|
||||
publish_update(post.topic, { title: post.topic.title })
|
||||
end
|
||||
|
||||
def publish_update(topic, payload)
|
||||
allowed_users = topic.topic_allowed_users.pluck(:user_id)
|
||||
MessageBus.publish("/discourse-ai/ai-bot/topic/#{topic.id}", payload, user_ids: allowed_users)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+208
-87
File diff suppressed because it is too large
Load Diff
@@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AiSummary < ActiveRecord::Base
|
||||
belongs_to :target, polymorphic: true
|
||||
|
||||
def mark_as_outdated
|
||||
@outdated = true
|
||||
end
|
||||
|
||||
def outdated
|
||||
@outdated || false
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: ai_summaries
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# target_id :integer not null
|
||||
# target_type :string not null
|
||||
# content_range :int4range
|
||||
# summarized_text :string not null
|
||||
# original_content_sha :string not null
|
||||
# algorithm :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_ai_summaries_on_target_type_and_target_id (target_type,target_id)
|
||||
#
|
||||
@@ -1,186 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AiTool < ActiveRecord::Base
|
||||
validates :name, presence: true, length: { maximum: 100 }
|
||||
validates :description, presence: true, length: { maximum: 1000 }
|
||||
validates :summary, presence: true, length: { maximum: 255 }
|
||||
validates :script, presence: true, length: { maximum: 100_000 }
|
||||
validates :created_by_id, presence: true
|
||||
belongs_to :created_by, class_name: "User"
|
||||
|
||||
def signature
|
||||
{ name: name, description: description, parameters: parameters.map(&:symbolize_keys) }
|
||||
end
|
||||
|
||||
def runner(parameters, llm:, bot_user:, context: {})
|
||||
DiscourseAi::AiBot::ToolRunner.new(
|
||||
parameters: parameters,
|
||||
llm: llm,
|
||||
bot_user: bot_user,
|
||||
context: context,
|
||||
tool: self,
|
||||
)
|
||||
end
|
||||
|
||||
after_commit :bump_persona_cache
|
||||
|
||||
def bump_persona_cache
|
||||
AiPersona.persona_cache.flush!
|
||||
end
|
||||
|
||||
def self.presets
|
||||
[
|
||||
{
|
||||
preset_id: "browse_web_jina",
|
||||
name: "browse_web",
|
||||
description: "Browse the web as a markdown document",
|
||||
parameters: [
|
||||
{ name: "url", type: "string", required: true, description: "The URL to browse" },
|
||||
],
|
||||
script: <<~SCRIPT,
|
||||
let url;
|
||||
function invoke(p) {
|
||||
url = p.url;
|
||||
result = http.get(`https://r.jina.ai/${url}`);
|
||||
// truncates to 15000 tokens
|
||||
return llm.truncate(result.body, 15000);
|
||||
}
|
||||
function details() {
|
||||
return "Read: " + url
|
||||
}
|
||||
SCRIPT
|
||||
},
|
||||
{
|
||||
preset_id: "exchange_rate",
|
||||
name: "exchange_rate",
|
||||
description: "Get current exchange rates for various currencies",
|
||||
parameters: [
|
||||
{
|
||||
name: "base_currency",
|
||||
type: "string",
|
||||
required: true,
|
||||
description: "The base currency code (e.g., USD, EUR)",
|
||||
},
|
||||
{
|
||||
name: "target_currency",
|
||||
type: "string",
|
||||
required: true,
|
||||
description: "The target currency code (e.g., EUR, JPY)",
|
||||
},
|
||||
{ name: "amount", type: "number", description: "Amount to convert eg: 123.45" },
|
||||
],
|
||||
script: <<~SCRIPT,
|
||||
// note: this script uses the open.er-api.com service, it is only updated
|
||||
// once every 24 hours, for more up to date rates see: https://www.exchangerate-api.com
|
||||
function invoke(params) {
|
||||
const url = `https://open.er-api.com/v6/latest/${params.base_currency}`;
|
||||
const result = http.get(url);
|
||||
if (result.status !== 200) {
|
||||
return { error: "Failed to fetch exchange rates" };
|
||||
}
|
||||
const data = JSON.parse(result.body);
|
||||
const rate = data.rates[params.target_currency];
|
||||
if (!rate) {
|
||||
return { error: "Target currency not found" };
|
||||
}
|
||||
|
||||
const rval = {
|
||||
base_currency: params.base_currency,
|
||||
target_currency: params.target_currency,
|
||||
exchange_rate: rate,
|
||||
last_updated: data.time_last_update_utc
|
||||
};
|
||||
|
||||
if (params.amount) {
|
||||
rval.original_amount = params.amount;
|
||||
rval.converted_amount = params.amount * rate;
|
||||
}
|
||||
|
||||
return rval;
|
||||
}
|
||||
|
||||
function details() {
|
||||
return "<a href='https://www.exchangerate-api.com'>Rates By Exchange Rate API</a>";
|
||||
}
|
||||
SCRIPT
|
||||
summary: "Get current exchange rates between two currencies",
|
||||
},
|
||||
{
|
||||
preset_id: "stock_quote",
|
||||
name: "stock_quote",
|
||||
description: "Get real-time stock quote information using AlphaVantage API",
|
||||
parameters: [
|
||||
{
|
||||
name: "symbol",
|
||||
type: "string",
|
||||
required: true,
|
||||
description: "The stock symbol (e.g., AAPL, GOOGL)",
|
||||
},
|
||||
],
|
||||
script: <<~SCRIPT,
|
||||
function invoke(params) {
|
||||
const apiKey = 'YOUR_ALPHAVANTAGE_API_KEY'; // Replace with your actual API key
|
||||
const url = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${params.symbol}&apikey=${apiKey}`;
|
||||
|
||||
const result = http.get(url);
|
||||
if (result.status !== 200) {
|
||||
return { error: "Failed to fetch stock quote" };
|
||||
}
|
||||
|
||||
const data = JSON.parse(result.body);
|
||||
if (data['Error Message']) {
|
||||
return { error: data['Error Message'] };
|
||||
}
|
||||
|
||||
const quote = data['Global Quote'];
|
||||
if (!quote || Object.keys(quote).length === 0) {
|
||||
return { error: "No data found for the given symbol" };
|
||||
}
|
||||
|
||||
return {
|
||||
symbol: quote['01. symbol'],
|
||||
price: parseFloat(quote['05. price']),
|
||||
change: parseFloat(quote['09. change']),
|
||||
change_percent: quote['10. change percent'],
|
||||
volume: parseInt(quote['06. volume']),
|
||||
latest_trading_day: quote['07. latest trading day']
|
||||
};
|
||||
}
|
||||
|
||||
function details() {
|
||||
return "<a href='https://www.alphavantage.co'>Stock data provided by AlphaVantage</a>";
|
||||
}
|
||||
SCRIPT
|
||||
summary: "Get real-time stock quotes using AlphaVantage API",
|
||||
},
|
||||
{ preset_id: "empty_tool", script: <<~SCRIPT },
|
||||
function invoke(params) {
|
||||
// logic here
|
||||
return params;
|
||||
}
|
||||
function details() {
|
||||
return "Details about this tool";
|
||||
}
|
||||
SCRIPT
|
||||
].map do |preset|
|
||||
preset[:preset_name] = I18n.t("discourse_ai.tools.presets.#{preset[:preset_id]}.name")
|
||||
preset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: ai_tools
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# name :string not null
|
||||
# description :string not null
|
||||
# summary :string not null
|
||||
# parameters :jsonb not null
|
||||
# script :text not null
|
||||
# created_by_id :integer not null
|
||||
# enabled :boolean default(TRUE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
@@ -6,7 +6,7 @@ end
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: chat_message_custom_prompts
|
||||
# Table name: message_custom_prompts
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# message_id :bigint not null
|
||||
@@ -16,5 +16,5 @@ end
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_chat_message_custom_prompts_on_message_id (message_id) UNIQUE
|
||||
# index_message_custom_prompts_on_message_id (message_id) UNIQUE
|
||||
#
|
||||
|
||||
@@ -1,109 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class LlmModel < ActiveRecord::Base
|
||||
FIRST_BOT_USER_ID = -1200
|
||||
BEDROCK_PROVIDER_NAME = "aws_bedrock"
|
||||
|
||||
belongs_to :user
|
||||
|
||||
validates :display_name, presence: true, length: { maximum: 100 }
|
||||
validates :tokenizer, presence: true, inclusion: DiscourseAi::Completions::Llm.tokenizer_names
|
||||
validates :provider, presence: true, inclusion: DiscourseAi::Completions::Llm.provider_names
|
||||
validates :url, presence: true, unless: -> { provider == BEDROCK_PROVIDER_NAME }
|
||||
validates_presence_of :name, :api_key
|
||||
validates :max_prompt_tokens, numericality: { greater_than: 0 }
|
||||
validate :required_provider_params
|
||||
|
||||
def self.provider_params
|
||||
{
|
||||
aws_bedrock: {
|
||||
access_key_id: :text,
|
||||
region: :text,
|
||||
},
|
||||
open_ai: {
|
||||
organization: :text,
|
||||
},
|
||||
hugging_face: {
|
||||
disable_system_prompt: :checkbox,
|
||||
},
|
||||
vllm: {
|
||||
disable_system_prompt: :checkbox,
|
||||
},
|
||||
ollama: {
|
||||
disable_system_prompt: :checkbox,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def to_llm
|
||||
DiscourseAi::Completions::Llm.proxy("custom:#{id}")
|
||||
end
|
||||
|
||||
def toggle_companion_user
|
||||
return if name == "fake" && Rails.env.production?
|
||||
|
||||
enable_check = SiteSetting.ai_bot_enabled && enabled_chat_bot
|
||||
|
||||
if enable_check
|
||||
if !user
|
||||
next_id = DB.query_single(<<~SQL).first
|
||||
SELECT min(id) - 1 FROM users
|
||||
SQL
|
||||
|
||||
new_user =
|
||||
User.new(
|
||||
id: [FIRST_BOT_USER_ID, next_id].min,
|
||||
email: "no_email_#{SecureRandom.hex}",
|
||||
name: name.titleize,
|
||||
username: UserNameSuggester.suggest(name),
|
||||
active: true,
|
||||
approved: true,
|
||||
admin: true,
|
||||
moderator: true,
|
||||
trust_level: TrustLevel[4],
|
||||
)
|
||||
new_user.save!(validate: false)
|
||||
self.update!(user: new_user)
|
||||
else
|
||||
user.active = true
|
||||
user.save!(validate: false)
|
||||
end
|
||||
elsif user
|
||||
# will include deleted
|
||||
has_posts = DB.query_single("SELECT 1 FROM posts WHERE user_id = #{user.id} LIMIT 1").present?
|
||||
|
||||
if has_posts
|
||||
user.update!(active: false) if user.active
|
||||
else
|
||||
user.destroy!
|
||||
self.update!(user: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tokenizer_class
|
||||
tokenizer.constantize
|
||||
end
|
||||
|
||||
def lookup_custom_param(key)
|
||||
provider_params&.dig(key)
|
||||
end
|
||||
|
||||
def seeded?
|
||||
id < 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def required_provider_params
|
||||
return if provider != BEDROCK_PROVIDER_NAME
|
||||
|
||||
%w[access_key_id region].each do |field|
|
||||
if lookup_custom_param(field).blank?
|
||||
errors.add(:base, I18n.t("discourse_ai.llm_models.missing_provider_param", param: field))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
@@ -120,8 +20,4 @@ end
|
||||
# updated_at :datetime not null
|
||||
# url :string
|
||||
# api_key :string
|
||||
# user_id :integer
|
||||
# enabled_chat_bot :boolean default(FALSE), not null
|
||||
# provider_params :jsonb
|
||||
# vision_enabled :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user