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

Commit 718da58

Browse files
committed
DEV: consolidate into class lib and apply to personas
1 parent 21af1bd commit 718da58

File tree

4 files changed

+176
-12
lines changed

4 files changed

+176
-12
lines changed

app/controllers/discourse_ai/admin/ai_llms_controller.rb

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,8 @@ def log_llm_model_creation(llm_model)
235235
end.join("; ")
236236
end
237237

238-
StaffActionLogger.new(current_user).log_custom(
239-
"create_ai_llm_model",
240-
log_details
241-
)
238+
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
239+
logger.log_custom("create_ai_llm_model", log_details)
242240
end
243241

244242
def log_llm_model_update(llm_model, initial_attributes, initial_quotas)
@@ -282,18 +280,14 @@ def log_llm_model_update(llm_model, initial_attributes, initial_quotas)
282280
model_name: llm_model.display_name || llm_model.name,
283281
}.merge(changes)
284282

285-
StaffActionLogger.new(current_user).log_custom(
286-
"update_ai_llm_model",
287-
log_details
288-
)
283+
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
284+
logger.log_custom("update_ai_llm_model", log_details)
289285
end
290286
end
291287

292288
def log_llm_model_deletion(model_details)
293-
StaffActionLogger.new(current_user).log_custom(
294-
"delete_ai_llm_model",
295-
model_details
296-
)
289+
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
290+
logger.log_deletion("llm_model", model_details)
297291
end
298292
end
299293
end

app/controllers/discourse_ai/admin/ai_personas_controller.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def create
5858
ai_persona = AiPersona.new(ai_persona_params.except(:rag_uploads))
5959
if ai_persona.save
6060
RagDocumentFragment.link_target_and_uploads(ai_persona, attached_upload_ids)
61+
log_ai_persona_creation(ai_persona)
6162

6263
render json: {
6364
ai_persona: LocalizedAiPersonaSerializer.new(ai_persona, root: false),
@@ -74,8 +75,12 @@ def create_user
7475
end
7576

7677
def update
78+
# Capture initial state for logging
79+
initial_attributes = @ai_persona.attributes.dup
80+
7781
if @ai_persona.update(ai_persona_params.except(:rag_uploads))
7882
RagDocumentFragment.update_target_uploads(@ai_persona, attached_upload_ids)
83+
log_ai_persona_update(@ai_persona, initial_attributes)
7984

8085
render json: LocalizedAiPersonaSerializer.new(@ai_persona, root: false)
8186
else
@@ -84,7 +89,15 @@ def update
8489
end
8590

8691
def destroy
92+
# Capture persona details for logging before destruction
93+
persona_details = {
94+
persona_id: @ai_persona.id,
95+
name: @ai_persona.name,
96+
description: @ai_persona.description
97+
}
98+
8799
if @ai_persona.destroy
100+
log_ai_persona_deletion(persona_details)
88101
head :no_content
89102
else
90103
render_json_error @ai_persona
@@ -261,6 +274,48 @@ def permit_examples(examples)
261274

262275
examples.map { |example_arr| example_arr.take(2).map(&:to_s) }
263276
end
277+
278+
def log_ai_persona_creation(ai_persona)
279+
# Extract standard attributes
280+
log_details = {
281+
persona_id: ai_persona.id,
282+
name: ai_persona.name,
283+
description: ai_persona.description,
284+
enabled: ai_persona.enabled,
285+
priority: ai_persona.priority,
286+
system_prompt: ai_persona.system_prompt&.truncate(100),
287+
default_llm_id: ai_persona.default_llm_id,
288+
temperature: ai_persona.temperature,
289+
top_p: ai_persona.top_p,
290+
user_id: ai_persona.user_id,
291+
vision_enabled: ai_persona.vision_enabled,
292+
tools_count: (ai_persona.tools || []).size,
293+
allowed_group_ids: ai_persona.allowed_group_ids
294+
}
295+
296+
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
297+
logger.log_custom("create_ai_persona", log_details)
298+
end
299+
300+
def log_ai_persona_update(ai_persona, initial_attributes)
301+
trackable_fields = %w[
302+
name description enabled system_prompt priority temperature top_p default_llm_id
303+
user_id max_context_posts vision_enabled vision_max_pixels rag_chunk_tokens
304+
rag_chunk_overlap_tokens rag_conversation_chunks rag_llm_model_id
305+
question_consolidator_llm_id tool_details forced_tool_count
306+
allow_chat_channel_mentions allow_chat_direct_messages allow_topic_mentions allow_personal_messages
307+
]
308+
309+
json_fields = %w[allowed_group_ids tools response_format examples]
310+
311+
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
312+
logger.log_update("persona", ai_persona, initial_attributes, trackable_fields, json_fields)
313+
end
314+
315+
def log_ai_persona_deletion(persona_details)
316+
logger = DiscourseAi::Utils::AiStaffActionLogger.new(current_user)
317+
logger.log_deletion("persona", persona_details)
318+
end
264319
end
265320
end
266321
end

config/locales/client.en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ en:
3030
create_ai_llm_model: "Create LLM model"
3131
update_ai_llm_model: "Update LLM model"
3232
delete_ai_llm_model: "Delete LLM model"
33+
create_ai_persona: "Create AI persona"
34+
update_ai_persona: "Update AI persona"
35+
delete_ai_persona: "Delete AI persona"
3336

3437
js:
3538
discourse_automation:
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# frozen_string_literal: true
2+
3+
module DiscourseAi
4+
module Utils
5+
class AiStaffActionLogger
6+
def initialize(current_user)
7+
@current_user = current_user
8+
@staff_logger = ::StaffActionLogger.new(current_user)
9+
end
10+
11+
# Log creation of an AI entity (LLM model or persona)
12+
def log_creation(entity_type, entity, attributes_to_log)
13+
log_details = extract_entity_attributes(entity, attributes_to_log)
14+
15+
@staff_logger.log_custom("create_ai_#{entity_type}", log_details)
16+
end
17+
18+
# Log update of an AI entity with before/after comparison
19+
def log_update(entity_type, entity, initial_attributes, trackable_fields, json_fields = [])
20+
current_attributes = entity.attributes
21+
changes = {}
22+
23+
# Track changes to standard fields
24+
trackable_fields.each do |field|
25+
initial_value = initial_attributes[field]
26+
current_value = current_attributes[field]
27+
28+
if initial_value != current_value
29+
# For large text fields, don't show the entire content
30+
if should_simplify_field?(field, initial_value, current_value)
31+
changes[field] = "updated"
32+
else
33+
changes[field] = "#{initial_value}#{current_value}"
34+
end
35+
end
36+
end
37+
38+
# Track changes to arrays and JSON fields
39+
json_fields.each do |field|
40+
if initial_attributes[field].to_s != current_attributes[field].to_s
41+
changes[field] = "updated"
42+
end
43+
end
44+
45+
# Only log if there are actual changes
46+
if changes.any?
47+
log_details = entity_identifier(entity, entity_type).merge(changes)
48+
@staff_logger.log_custom("update_ai_#{entity_type}", log_details)
49+
end
50+
end
51+
52+
# Log deletion of an AI entity
53+
def log_deletion(entity_type, entity_details)
54+
@staff_logger.log_custom("delete_ai_#{entity_type}", entity_details)
55+
end
56+
57+
# Direct custom logging for complex cases
58+
def log_custom(action_type, log_details)
59+
@staff_logger.log_custom(action_type, log_details)
60+
end
61+
62+
private
63+
64+
def extract_entity_attributes(entity, attributes_to_log)
65+
result = {}
66+
67+
attributes_to_log.each do |attr|
68+
value = entity.public_send(attr)
69+
70+
# Handle large text fields
71+
if attr == :system_prompt && value.is_a?(String) && value.length > 100
72+
result[attr] = value.truncate(100)
73+
else
74+
result[attr] = value
75+
end
76+
end
77+
78+
result
79+
end
80+
81+
def should_simplify_field?(field, initial_value, current_value)
82+
# For large text fields, or sensitive data, don't show the entire content
83+
return true if field == "system_prompt" &&
84+
initial_value.present? &&
85+
current_value.present? &&
86+
(initial_value.length > 100 || current_value.length > 100)
87+
88+
return true if field.include?("api_key") || field.include?("secret") || field.include?("password")
89+
90+
false
91+
end
92+
93+
def entity_identifier(entity, entity_type)
94+
case entity_type
95+
when "llm_model"
96+
{
97+
model_id: entity.id,
98+
model_name: entity.name,
99+
display_name: entity.display_name
100+
}
101+
when "persona"
102+
{
103+
persona_id: entity.id,
104+
persona_name: entity.name
105+
}
106+
else
107+
{ id: entity.id }
108+
end
109+
end
110+
end
111+
end
112+
end

0 commit comments

Comments
 (0)