mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-03-09 11:48:47 +00:00
This PR updates the rate limits for AI helper so that image caption follows a specific rate limit of 20 requests per minute. This should help when uploading multiple files that need to be captioned. This PR also updates the UI so that it shows toast message with the extracted error message instead of having a blocking `popupAjaxError` error dialog. --------- Co-authored-by: Rafael dos Santos Silva <xfalcox@gmail.com> Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
196 lines
6.2 KiB
Ruby
196 lines
6.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module AiHelper
|
|
class AssistantController < ::ApplicationController
|
|
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
|
requires_login
|
|
before_action :ensure_can_request_suggestions
|
|
before_action :rate_limiter_performed!
|
|
|
|
include SecureUploadEndpointHelpers
|
|
|
|
RATE_LIMITS = {
|
|
"default" => {
|
|
amount: 6,
|
|
interval: 3.minutes,
|
|
},
|
|
"caption_image" => {
|
|
amount: 20,
|
|
interval: 1.minute,
|
|
},
|
|
}.freeze
|
|
|
|
def suggest
|
|
input = get_text_param!
|
|
force_default_locale = params[:force_default_locale] || false
|
|
|
|
prompt = CompletionPrompt.find_by(id: params[:mode])
|
|
|
|
raise Discourse::InvalidParameters.new(:mode) if !prompt || !prompt.enabled?
|
|
|
|
if prompt.id == CompletionPrompt::CUSTOM_PROMPT
|
|
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
|
|
|
|
prompt.custom_instruction = params[:custom_prompt]
|
|
end
|
|
|
|
suggest_thumbnails(input) if prompt.id == CompletionPrompt::ILLUSTRATE_POST
|
|
|
|
hijack do
|
|
render json:
|
|
DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
|
|
prompt,
|
|
input,
|
|
current_user,
|
|
force_default_locale,
|
|
),
|
|
status: 200
|
|
end
|
|
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed
|
|
render_json_error I18n.t("discourse_ai.ai_helper.errors.completion_request_failed"),
|
|
status: 502
|
|
end
|
|
|
|
def suggest_title
|
|
input = get_text_param!
|
|
|
|
prompt = CompletionPrompt.enabled_by_name("generate_titles")
|
|
raise Discourse::InvalidParameters.new(:mode) if !prompt
|
|
|
|
hijack do
|
|
render json:
|
|
DiscourseAi::AiHelper::Assistant.new.generate_and_send_prompt(
|
|
prompt,
|
|
input,
|
|
current_user,
|
|
),
|
|
status: 200
|
|
end
|
|
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed
|
|
render_json_error I18n.t("discourse_ai.ai_helper.errors.completion_request_failed"),
|
|
status: 502
|
|
end
|
|
|
|
def suggest_category
|
|
input = get_text_param!
|
|
input_hash = { text: input }
|
|
|
|
render json:
|
|
DiscourseAi::AiHelper::SemanticCategorizer.new(
|
|
input_hash,
|
|
current_user,
|
|
).categories,
|
|
status: 200
|
|
end
|
|
|
|
def suggest_tags
|
|
input = get_text_param!
|
|
input_hash = { text: input }
|
|
|
|
render json: DiscourseAi::AiHelper::SemanticCategorizer.new(input_hash, current_user).tags,
|
|
status: 200
|
|
end
|
|
|
|
def suggest_thumbnails(input)
|
|
hijack do
|
|
thumbnails = DiscourseAi::AiHelper::Painter.new.commission_thumbnails(input, current_user)
|
|
|
|
render json: { thumbnails: thumbnails }, status: 200
|
|
end
|
|
end
|
|
|
|
def stream_suggestion
|
|
post_id = get_post_param!
|
|
text = 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],
|
|
)
|
|
|
|
render json: { success: true }, status: 200
|
|
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed
|
|
render_json_error I18n.t("discourse_ai.ai_helper.errors.completion_request_failed"),
|
|
status: 502
|
|
end
|
|
|
|
def caption_image
|
|
image_url = params[:image_url]
|
|
image_url_type = params[:image_url_type]
|
|
|
|
raise Discourse::InvalidParameters.new(:image_url) if !image_url
|
|
raise Discourse::InvalidParameters.new(:image_url) if !image_url_type
|
|
|
|
if image_url_type == "short_path"
|
|
image = Upload.find_by(sha1: Upload.sha1_from_short_path(image_url))
|
|
elsif image_url_type == "short_url"
|
|
image = Upload.find_by(sha1: Upload.sha1_from_short_url(image_url))
|
|
else
|
|
image = upload_from_full_url(image_url)
|
|
end
|
|
|
|
raise Discourse::NotFound if image.blank?
|
|
|
|
check_secure_upload_permission(image) if image.secure?
|
|
user = current_user
|
|
|
|
hijack do
|
|
caption = DiscourseAi::AiHelper::Assistant.new.generate_image_caption(image, user)
|
|
render json: {
|
|
caption:
|
|
"#{caption} (#{I18n.t("discourse_ai.ai_helper.image_caption.attribution")})",
|
|
},
|
|
status: 200
|
|
end
|
|
rescue DiscourseAi::Completions::Endpoints::Base::CompletionFailed, Net::HTTPBadResponse
|
|
render_json_error I18n.t("discourse_ai.ai_helper.errors.completion_request_failed"),
|
|
status: 502
|
|
end
|
|
|
|
private
|
|
|
|
def get_text_param!
|
|
params[:text].tap { |t| raise Discourse::InvalidParameters.new(:text) if t.blank? }
|
|
end
|
|
|
|
def get_post_param!
|
|
params[:post_id].tap { |t| raise Discourse::InvalidParameters.new(:post_id) if t.blank? }
|
|
end
|
|
|
|
def rate_limiter_performed!
|
|
action_rate_limit = RATE_LIMITS[action_name] || RATE_LIMITS["default"]
|
|
RateLimiter.new(
|
|
current_user,
|
|
"ai_assistant",
|
|
action_rate_limit[:amount],
|
|
action_rate_limit[:interval],
|
|
).performed!
|
|
end
|
|
|
|
def ensure_can_request_suggestions
|
|
allowed_groups =
|
|
(
|
|
SiteSetting.composer_ai_helper_allowed_groups_map |
|
|
SiteSetting.post_ai_helper_allowed_groups_map
|
|
)
|
|
|
|
raise Discourse::InvalidAccess if !current_user.in_any_groups?(allowed_groups)
|
|
end
|
|
end
|
|
end
|
|
end
|