Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 55250e7

Browse files
committed
Clean up and organize code
1 parent 4124c0c commit 55250e7

File tree

6 files changed

+142
-36
lines changed

6 files changed

+142
-36
lines changed

app/controllers/discourse_ai/discord/bot_controller.rb

Lines changed: 5 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,48 +15,18 @@ def interactions
1515
return head :unauthorized
1616
end
1717

18-
body = JSON.parse(request.body.read)
18+
body = request.body.read
19+
interaction = JSON.parse(body, object_class: OpenStruct)
1920

20-
if body["type"] == 1
21+
if interaction.type == 1
2122
# Respond to Discord PING request
2223
render json: { type: 1 }
2324
else
2425
response = { type: 5, data: { content: "Searching..." } }
2526
hijack { render json: response }
2627

27-
pp request.headers
28-
pp body
29-
30-
# Respond to /commands
31-
persona = DiscourseAi::AiBot::Personas::DiscourseHelper
32-
bot =
33-
DiscourseAi::AiBot::Bot.as(
34-
Discourse.system_user,
35-
persona: persona.new,
36-
model: "custom:6",
37-
)
38-
39-
query = body["data"]["options"].first["value"]
40-
reply = ""
41-
reply =
42-
bot.reply({ conversation_context: [{ type: :user, content: query }] }) { |a, b, c| nil }
43-
44-
pp reply.last.first
45-
46-
discord_reply = reply.last.first
47-
48-
api_endpoint =
49-
"https://discord.com/api/webhooks/#{SiteSetting.ai_discord_app_id}/#{body["token"]}"
50-
51-
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
52-
response =
53-
conn.post(
54-
api_endpoint,
55-
{ content: discord_reply }.to_json,
56-
{ "Content-Type" => "application/json" },
57-
)
58-
59-
pp response
28+
# Respond to Discord command
29+
Jobs.enqueue(:stream_discord_reply, interaction: body.dup)
6030
end
6131
end
6232

@@ -69,7 +39,6 @@ def verify_request!
6939
end
7040

7141
def verify_key
72-
# TODO remove this gem dependency
7342
Ed25519::VerifyKey.new([SiteSetting.ai_discord_app_public_key].pack("H*")).freeze
7443
end
7544
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
module Jobs
4+
class StreamDiscordReply < ::Jobs::Base
5+
sidekiq_options retry: false
6+
7+
def execute(args)
8+
interaction = args[:interaction]
9+
10+
DiscourseAi::Discord::Bot::PersonaReplier.new(interaction).handle_interaction!
11+
end
12+
end
13+
end

config/settings.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,3 +438,13 @@ discourse_ai:
438438
ai_discord_app_public_key:
439439
default: ""
440440
client: false
441+
ai_discord_search_mode:
442+
default: "search"
443+
type: enum
444+
choices:
445+
- search
446+
- persona
447+
ai_discord_search_persona:
448+
default: ""
449+
type: enum
450+
enum: "DiscourseAi::Configuration::PersonaEnumerator"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
require "enum_site_setting"
4+
5+
module DiscourseAi
6+
module Configuration
7+
class PersonaEnumerator < ::EnumSiteSetting
8+
def self.valid_value?(val)
9+
true
10+
end
11+
12+
def self.values
13+
AiPersona.all_personas.map { |persona| { name: persona.name, value: persona.id } }
14+
end
15+
end
16+
end
17+
end

lib/discord/bot/base.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Discord::Bot
5+
class Base
6+
def initialize(body)
7+
@interaction = JSON.parse(body, object_class: OpenStruct)
8+
@query = @interaction.data.options.first.value
9+
@token = @interaction.token
10+
end
11+
12+
def handle_interaction!
13+
raise NotImplementedError
14+
end
15+
16+
def create_reply(reply)
17+
api_endpoint = "https://discord.com/api/webhooks/#{SiteSetting.ai_discord_app_id}/#{@token}"
18+
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
19+
response =
20+
conn.post(
21+
api_endpoint,
22+
{ content: reply }.to_json,
23+
{ "Content-Type" => "application/json" },
24+
)
25+
@reply_response = JSON.parse(response.body, symbolize_names: true)
26+
Rails.logger.info("Discord reply created: #{@reply_response}")
27+
@reply_response
28+
end
29+
30+
def update_reply(reply)
31+
api_endpoint =
32+
"https://discord.com/api/webhooks/#{SiteSetting.ai_discord_app_id}/#{@token}/messages/@original"
33+
conn = Faraday.new { |f| f.adapter FinalDestination::FaradayAdapter }
34+
response =
35+
conn.patch(
36+
api_endpoint,
37+
{ content: reply }.to_json,
38+
{ "Content-Type" => "application/json" },
39+
)
40+
@last_update_response = JSON.parse(response.body, symbolize_names: true)
41+
Rails.logger.info("Discord reply updated: #{@last_update_response}")
42+
@last_update_response
43+
end
44+
end
45+
end
46+
end

lib/discord/bot/persona_replier.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Discord::Bot
5+
class PersonaReplier < Base
6+
def initialize(body)
7+
@persona =
8+
AiPersona
9+
.all_personas
10+
.find { |persona| persona.id == SiteSetting.ai_discord_search_persona.to_i }
11+
.new
12+
@bot =
13+
DiscourseAi::AiBot::Bot.as(
14+
Discourse.system_user,
15+
persona: @persona,
16+
model: AiPersona.find(@persona.id).default_llm, # TODO this is weird
17+
)
18+
super(body)
19+
end
20+
21+
def handle_interaction!
22+
last_update_sent_at = Time.now - 1
23+
reply = +""
24+
full_reply =
25+
@bot.reply(
26+
{ conversation_context: [{ type: :user, content: @query }], skip_tool_details: true },
27+
) do |partial, _cancel, _something|
28+
reply << partial
29+
next if reply.blank?
30+
31+
if @reply_response.nil?
32+
create_reply(reply.dup)
33+
elsif @last_update_response.nil?
34+
update_reply(reply.dup)
35+
elsif Time.now - last_update_sent_at > 1
36+
update_reply(reply.dup)
37+
last_update_sent_at = Time.now
38+
end
39+
end
40+
41+
discord_reply = full_reply.last.first
42+
43+
if @reply_response.nil?
44+
create_reply(discord_reply)
45+
else
46+
update_reply(discord_reply)
47+
end
48+
end
49+
end
50+
end
51+
end

0 commit comments

Comments
 (0)