mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-06-19 09:24:23 +08:00
Previously, sentiment and emotion classification only used configured classification model endpoints, which blocked sites that could not run those models. This change lets admins choose agent-backed LLM classifiers for sentiment and emotion while storing results under stable model keys so LLM changes do not force historic reclassification.
102 lines
3.4 KiB
Ruby
Vendored
102 lines
3.4 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
module DiscourseAi
|
|
module Sentiment
|
|
class SentimentController < ::Admin::StaffController
|
|
include Constants
|
|
requires_plugin PLUGIN_NAME
|
|
|
|
DEFAULT_POSTS_LIMIT = 50
|
|
MAX_POSTS_LIMIT = 100
|
|
|
|
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
|
|
model_name = DiscourseAi::Sentiment::PostClassification.active_model_name_for(:sentiment)
|
|
|
|
raise Discourse::InvalidParameters if %i[category tag].exclude?(group_by)
|
|
|
|
limit = fetch_limit_from_params(default: DEFAULT_POSTS_LIMIT, max: MAX_POSTS_LIMIT)
|
|
offset = params[:offset].to_i || 0
|
|
|
|
case group_by
|
|
when :category
|
|
grouping_clause = "c.name"
|
|
grouping_join = "" # categories already joined
|
|
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.fancy_title AS topic_title,
|
|
p.cooked as post_cooked,
|
|
p.user_id,
|
|
p.post_number,
|
|
p.created_at AS created_at,
|
|
u.username,
|
|
u.name,
|
|
u.uploaded_avatar_id,
|
|
c.id AS category_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
|
|
LEFT JOIN categories c ON c.id = t.category_id
|
|
#{grouping_join}
|
|
WHERE
|
|
#{grouping_clause} = :group_value AND
|
|
t.archetype = 'regular' AND
|
|
p.user_id > 0 AND
|
|
cr.model_used = :model_name 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
|
|
AND t.deleted_at IS NULL
|
|
AND (t.category_id IS NULL OR t.category_id IN (:allowed_category_ids))
|
|
ORDER BY p.created_at DESC
|
|
LIMIT :limit OFFSET :offset
|
|
SQL
|
|
group_value: group_value,
|
|
start_date: start_date,
|
|
end_date: end_date,
|
|
threshold: threshold,
|
|
limit: limit + 1,
|
|
offset: offset,
|
|
allowed_category_ids: guardian.allowed_category_ids,
|
|
model_name: model_name,
|
|
)
|
|
|
|
has_more = posts.length > limit
|
|
posts.pop if has_more
|
|
|
|
render_json_dump(
|
|
posts:
|
|
serialize_data(
|
|
posts,
|
|
AiSentimentPostSerializer,
|
|
scope: guardian,
|
|
add_raw: true,
|
|
add_excerpt: true,
|
|
add_title: true,
|
|
),
|
|
has_more: has_more,
|
|
next_offset: has_more ? offset + limit : nil,
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|