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

Commit 0d6d9a6

Browse files
authored
FEATURE: allow access to private topics if tool permits (#673)
Previously read tool only had access to public topics, this allows access to all topics user has access to, if admin opts for the option Also - Fixes VLLM migration - Display which llms have bot enabled
1 parent 3c45335 commit 0d6d9a6

File tree

10 files changed

+93
-16
lines changed

10 files changed

+93
-16
lines changed

app/controllers/discourse_ai/admin/ai_llms_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class AiLlmsController < ::Admin::AdminController
66
requires_plugin ::DiscourseAi::PLUGIN_NAME
77

88
def index
9-
llms = LlmModel.all
9+
llms = LlmModel.all.order(:display_name)
1010

1111
render json: {
1212
ai_llms:

app/models/llm_model.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ def self.enable_or_disable_srv_llm!
1818
provider: "vllm",
1919
tokenizer: "DiscourseAi::Tokenizer::MixtralTokenizer",
2020
url: RESERVED_VLLM_SRV_URL,
21-
vllm_key: "",
22-
user_id: nil,
23-
enabled_chat_bot: false,
21+
max_prompt_tokens: 8000,
2422
)
2523

2624
record.save(validate: false) # Ignore reserved URL validation
@@ -55,7 +53,8 @@ def toggle_companion_user
5553
new_user.save!(validate: false)
5654
self.update!(user: new_user)
5755
else
58-
user.update!(active: true)
56+
user.active = true
57+
user.save!(validate: false)
5958
end
6059
elsif user
6160
# will include deleted

assets/javascripts/discourse/components/ai-llms-list-editor.gjs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Component from "@glimmer/component";
2-
import { concat } from "@ember/helper";
2+
import { concat, fn } from "@ember/helper";
3+
import { on } from "@ember/modifier";
4+
import { action } from "@ember/object";
35
import { LinkTo } from "@ember/routing";
6+
import DToggleSwitch from "discourse/components/d-toggle-switch";
7+
import { popupAjaxError } from "discourse/lib/ajax-error";
48
import icon from "discourse-common/helpers/d-icon";
59
import i18n from "discourse-common/helpers/i18n";
610
import I18n from "discourse-i18n";
@@ -11,6 +15,21 @@ export default class AiLlmsListEditor extends Component {
1115
return this.args.llms.length !== 0;
1216
}
1317

18+
@action
19+
async toggleEnabledChatBot(llm) {
20+
const oldValue = llm.enabled_chat_bot;
21+
const newValue = !oldValue;
22+
try {
23+
llm.set("enabled_chat_bot", newValue);
24+
await llm.update({
25+
enabled_chat_bot: newValue,
26+
});
27+
} catch (err) {
28+
llm.set("enabled_chat_bot", oldValue);
29+
popupAjaxError(err);
30+
}
31+
}
32+
1433
<template>
1534
<section class="ai-llms-list-editor admin-detail pull-left">
1635
{{#if @currentLlm}}
@@ -35,6 +54,7 @@ export default class AiLlmsListEditor extends Component {
3554
<tr>
3655
<th>{{i18n "discourse_ai.llms.display_name"}}</th>
3756
<th>{{i18n "discourse_ai.llms.provider"}}</th>
57+
<th>{{i18n "discourse_ai.llms.enabled_chat_bot"}}</th>
3858
<th></th>
3959
</tr>
4060
</thead>
@@ -45,6 +65,12 @@ export default class AiLlmsListEditor extends Component {
4565
<td>{{i18n
4666
(concat "discourse_ai.llms.providers." llm.provider)
4767
}}</td>
68+
<td>
69+
<DToggleSwitch
70+
@state={{llm.enabled_chat_bot}}
71+
{{on "click" (fn this.toggleEnabledChatBot llm)}}
72+
/>
73+
</td>
4874
<td>
4975
<LinkTo
5076
@route="adminPlugins.show.discourse-ai-llms.show"

config/locales/client.en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ en:
210210
max_prompt_tokens: "Number of tokens for the prompt"
211211
url: "URL of the service hosting the model"
212212
api_key: "API Key of the service hosting the model"
213-
enabled_chat_bot: "Allow Companion user to act as an AI Bot"
213+
enabled_chat_bot: "Allow AI Bot"
214214
save: "Save"
215215
edit: "Edit"
216216
saved: "LLM Model Saved"

config/locales/server.en.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ en:
207207
summarizing: "Summarizing topic"
208208
searching: "Searching for: '%{query}'"
209209
tool_options:
210+
read:
211+
read_private:
212+
name: "Read Private"
213+
description: "Allow access to all topics user has access to (by default only public topics are included)"
210214
search:
211215
search_private:
212216
name: "Search Private"

db/post_migrate/20240603143158_seed_oss_models.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def up
4040
reserved: srv_reserved_url,
4141
).first
4242

43-
if vllm_srv.present? && srv.record.nil?
43+
if vllm_srv.present? && srv_record.nil?
4444
url = "https://vllm.shadowed-by-srv.invalid"
4545
name = "mistralai/Mixtral"
4646

lib/ai_bot/tools/read.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ def self.signature
2828
}
2929
end
3030

31+
def self.accepted_options
32+
[option(:read_private, type: :boolean)]
33+
end
34+
3135
def self.name
3236
"read"
3337
end
@@ -44,20 +48,18 @@ def post_numbers
4448

4549
def invoke
4650
not_found = { topic_id: topic_id, description: "Topic not found" }
51+
guardian = Guardian.new(context[:user]) if options[:read_private] && context[:user]
52+
guardian ||= Guardian.new
4753

4854
@title = ""
4955

5056
topic = Topic.find_by(id: topic_id.to_i)
51-
return not_found if !topic || !Guardian.new.can_see?(topic)
57+
return not_found if !topic || !guardian.can_see?(topic)
5258

5359
@title = topic.title
5460

5561
posts =
56-
Post
57-
.secured(Guardian.new)
58-
.where(topic_id: topic_id)
59-
.order(:post_number)
60-
.limit(MAX_POSTS)
62+
Post.secured(guardian).where(topic_id: topic_id).order(:post_number).limit(MAX_POSTS)
6163

6264
post_number = 1
6365
post_number = post_numbers.first if post_numbers.present?

lib/ai_bot/tools/tool.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def options
7272
if val
7373
case option.type
7474
when :boolean
75-
val = val == "true"
75+
val = (val.to_s == "true")
7676
when :integer
7777
val = val.to_i
7878
end

spec/lib/modules/ai_bot/tools/read_spec.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,52 @@
3030
before { SiteSetting.ai_bot_enabled = true }
3131

3232
describe "#process" do
33+
it "can read private topics if allowed to" do
34+
category = topic_with_tags.category
35+
category.set_permissions(Group::AUTO_GROUPS[:staff] => :full)
36+
category.save!
37+
38+
tool =
39+
described_class.new(
40+
{ topic_id: topic_with_tags.id, post_numbers: [post1.post_number] },
41+
bot_user: bot_user,
42+
llm: llm,
43+
)
44+
results = tool.invoke
45+
46+
expect(results[:description]).to eq("Topic not found")
47+
48+
admin = Fabricate(:admin)
49+
50+
tool =
51+
described_class.new(
52+
{ topic_id: topic_with_tags.id, post_numbers: [post1.post_number] },
53+
bot_user: bot_user,
54+
llm: llm,
55+
persona_options: {
56+
"read_private" => true,
57+
},
58+
context: {
59+
user: admin,
60+
},
61+
)
62+
results = tool.invoke
63+
expect(results[:content]).to include("hello there")
64+
65+
tool =
66+
described_class.new(
67+
{ topic_id: topic_with_tags.id, post_numbers: [post1.post_number] },
68+
bot_user: bot_user,
69+
llm: llm,
70+
context: {
71+
user: admin,
72+
},
73+
)
74+
75+
results = tool.invoke
76+
expect(results[:description]).to eq("Topic not found")
77+
end
78+
3379
it "can read specific posts" do
3480
tool =
3581
described_class.new(

spec/system/ai_bot/persona_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
expect(persona.name).to eq("Test Persona")
5454
expect(persona.description).to eq("I am a test persona")
5555
expect(persona.system_prompt).to eq("You are a helpful bot")
56-
expect(persona.tools).to eq(["Read"])
56+
expect(persona.tools).to eq([["Read", { "read_private" => nil }]])
5757
end
5858

5959
it "will not allow deletion or editing of system personas" do

0 commit comments

Comments
 (0)