mirror of
https://github.com/discourse/discourse-ai.git
synced 2025-07-23 14:33:28 +00:00
## 🔍 Overview This update adds a new report page at `admin/reports/sentiment_analysis` where admins can see a sentiment analysis report for the forum grouped by either category or tags. ## ➕ More details The report can breakdown either category or tags into positive/negative/neutral sentiments based on the grouping (category/tag). Clicking on the doughnut visualization will bring up a post list of all the posts that were involved in that classification with further sentiment classifications by post. The report can additionally be sorted in alphabetical order or by size, as well as be filtered by either category/tag based on the grouping. ## 👨🏽💻 Technical Details The new admin report is registered via the pluginAPi with `api.registerReportModeComponent` to register the custom sentiment doughnut report. However, when each doughnut visualization is clicked, a new endpoint found at: `/discourse-ai/sentiment/posts` is fetched to showcase posts classified by sentiments based on the respective params. ## 📸 Screenshots 
80 lines
2.5 KiB
Ruby
80 lines
2.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module Sentiment
|
|
class SentimentController < ::Admin::StaffController
|
|
include Constants
|
|
requires_plugin ::DiscourseAi::PLUGIN_NAME
|
|
|
|
def posts
|
|
group_by = params.required(:group_by)&.to_sym
|
|
group_value = params.required(:group_value).presence
|
|
start_date = params[:start_date].presence
|
|
end_date = params[:end_date]
|
|
threshold = SENTIMENT_THRESHOLD
|
|
|
|
raise Discourse::InvalidParameters if %i[category tag].exclude?(group_by)
|
|
|
|
case group_by
|
|
when :category
|
|
grouping_clause = "c.name"
|
|
grouping_join = "INNER JOIN categories c ON c.id = t.category_id"
|
|
when :tag
|
|
grouping_clause = "tags.name"
|
|
grouping_join =
|
|
"INNER JOIN topic_tags tt ON tt.topic_id = p.topic_id INNER JOIN tags ON tags.id = tt.tag_id"
|
|
end
|
|
|
|
posts =
|
|
DB.query(
|
|
<<~SQL,
|
|
SELECT
|
|
p.id AS post_id,
|
|
p.topic_id,
|
|
t.title AS topic_title,
|
|
p.cooked as post_cooked,
|
|
p.user_id,
|
|
p.post_number,
|
|
u.username,
|
|
u.name,
|
|
u.uploaded_avatar_id,
|
|
(CASE
|
|
WHEN (cr.classification::jsonb->'positive')::float > :threshold THEN 'positive'
|
|
WHEN (cr.classification::jsonb->'negative')::float > :threshold THEN 'negative'
|
|
ELSE 'neutral'
|
|
END) AS sentiment
|
|
FROM posts p
|
|
INNER JOIN topics t ON t.id = p.topic_id
|
|
INNER JOIN classification_results cr ON cr.target_id = p.id AND cr.target_type = 'Post'
|
|
LEFT JOIN users u ON u.id = p.user_id
|
|
#{grouping_join}
|
|
WHERE
|
|
#{grouping_clause} = :group_value AND
|
|
t.archetype = 'regular' AND
|
|
p.user_id > 0 AND
|
|
cr.model_used = 'cardiffnlp/twitter-roberta-base-sentiment-latest' AND
|
|
((:start_date IS NULL OR p.created_at > :start_date) AND (:end_date IS NULL OR p.created_at < :end_date))
|
|
AND p.deleted_at IS NULL
|
|
ORDER BY p.created_at DESC
|
|
SQL
|
|
group_value: group_value,
|
|
start_date: start_date,
|
|
end_date: end_date,
|
|
threshold: threshold,
|
|
)
|
|
|
|
render_json_dump(
|
|
serialize_data(
|
|
posts,
|
|
AiSentimentPostSerializer,
|
|
scope: guardian,
|
|
add_raw: true,
|
|
add_excerpt: true,
|
|
add_title: true,
|
|
),
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|