193 lines
5.1 KiB
Ruby
193 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module AiHelper
|
|
class LlmPrompt
|
|
def available_prompts(name_filter: nil)
|
|
cp = CompletionPrompt
|
|
cp = cp.where(name: name_filter) if name_filter.present?
|
|
cp
|
|
.where(provider: enabled_provider)
|
|
.where(enabled: true)
|
|
.map do |prompt|
|
|
translation =
|
|
I18n.t("discourse_ai.ai_helper.prompts.#{prompt.name}", default: nil) ||
|
|
prompt.translated_name || prompt.name
|
|
|
|
{
|
|
id: prompt.id,
|
|
name: prompt.name,
|
|
translated_name: translation,
|
|
prompt_type: prompt.prompt_type,
|
|
icon: icon_map(prompt.name),
|
|
location: location_map(prompt.name),
|
|
}
|
|
end
|
|
end
|
|
|
|
def generate_and_send_prompt(prompt, params)
|
|
case enabled_provider
|
|
when "openai"
|
|
openai_call(prompt, params)
|
|
when "anthropic"
|
|
anthropic_call(prompt, params)
|
|
when "huggingface"
|
|
huggingface_call(prompt, params)
|
|
end
|
|
end
|
|
|
|
def enabled_provider
|
|
case SiteSetting.ai_helper_model
|
|
when /gpt/
|
|
"openai"
|
|
when /claude/
|
|
"anthropic"
|
|
else
|
|
"huggingface"
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def icon_map(name)
|
|
case name
|
|
when "translate"
|
|
"language"
|
|
when "generate_titles"
|
|
"heading"
|
|
when "proofread"
|
|
"spell-check"
|
|
when "markdown_table"
|
|
"table"
|
|
when "tone"
|
|
"microphone"
|
|
when "custom_prompt"
|
|
"comment"
|
|
when "rewrite"
|
|
"pen"
|
|
when "explain"
|
|
"question"
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def location_map(name)
|
|
case name
|
|
when "translate"
|
|
%w[composer post]
|
|
when "generate_titles"
|
|
%w[composer]
|
|
when "proofread"
|
|
%w[composer]
|
|
when "markdown_table"
|
|
%w[composer]
|
|
when "tone"
|
|
%w[composer]
|
|
when "custom_prompt"
|
|
%w[composer]
|
|
when "rewrite"
|
|
%w[composer]
|
|
when "explain"
|
|
%w[post]
|
|
when "summarize"
|
|
%w[post]
|
|
else
|
|
%w[composer post]
|
|
end
|
|
end
|
|
|
|
def generate_diff(text, suggestion)
|
|
cooked_text = PrettyText.cook(text)
|
|
cooked_suggestion = PrettyText.cook(suggestion)
|
|
|
|
DiscourseDiff.new(cooked_text, cooked_suggestion).inline_html
|
|
end
|
|
|
|
def parse_content(prompt, content)
|
|
return "" if content.blank?
|
|
|
|
case enabled_provider
|
|
when "openai"
|
|
return content.strip if !prompt.list?
|
|
|
|
content.gsub("\"", "").gsub(/\d./, "").split("\n").map(&:strip)
|
|
when "anthropic"
|
|
parse_antropic_content(prompt, content)
|
|
when "huggingface"
|
|
return [content.strip.delete_prefix('"').delete_suffix('"')] if !prompt.list?
|
|
|
|
content.gsub("\"", "").gsub(/\d./, "").split("\n").map(&:strip)
|
|
end
|
|
end
|
|
|
|
def openai_call(prompt, params)
|
|
result = { type: prompt.prompt_type }
|
|
|
|
messages = prompt.messages_with_user_input(params)
|
|
|
|
result[:suggestions] = DiscourseAi::Inference::OpenAiCompletions
|
|
.perform!(messages, SiteSetting.ai_helper_model)
|
|
.dig(:choices)
|
|
.to_a
|
|
.flat_map { |choice| parse_content(prompt, choice.dig(:message, :content).to_s) }
|
|
.compact_blank
|
|
|
|
result[:diff] = generate_diff(params[:text], result[:suggestions].first) if prompt.diff?
|
|
|
|
result
|
|
end
|
|
|
|
def anthropic_call(prompt, params)
|
|
result = { type: prompt.prompt_type }
|
|
|
|
filled_message = prompt.messages_with_user_input(params)
|
|
|
|
message =
|
|
filled_message.map { |msg| "#{msg["role"]}: #{msg["content"]}" }.join("\n\n") +
|
|
"Assistant:"
|
|
|
|
response = DiscourseAi::Inference::AnthropicCompletions.perform!(message)
|
|
|
|
result[:suggestions] = parse_content(prompt, response.dig(:completion))
|
|
|
|
result[:diff] = generate_diff(params[:text], result[:suggestions].first) if prompt.diff?
|
|
|
|
result
|
|
end
|
|
|
|
def huggingface_call(prompt, params)
|
|
result = { type: prompt.prompt_type }
|
|
|
|
message = prompt.messages_with_user_input(params)
|
|
|
|
response =
|
|
DiscourseAi::Inference::HuggingFaceTextGeneration.perform!(
|
|
message,
|
|
SiteSetting.ai_helper_model,
|
|
)
|
|
|
|
result[:suggestions] = parse_content(prompt, response.dig(:generated_text))
|
|
|
|
result[:diff] = generate_diff(params[:text], result[:suggestions].first) if prompt.diff?
|
|
|
|
result
|
|
end
|
|
|
|
def parse_antropic_content(prompt, content)
|
|
if prompt.list?
|
|
suggestions = Nokogiri::HTML5.fragment(content).search("ai").map(&:text)
|
|
|
|
if suggestions.length > 1
|
|
suggestions
|
|
else
|
|
suggestions.first.split("\n").map(&:strip)
|
|
end
|
|
else
|
|
[Nokogiri::HTML5.fragment(content).at("ai").text]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|