mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Stream topic summaries. (#23065)
When we receive the stream parameter, we'll queue a job that periodically publishes partial updates, and after the summarization finishes, a final one with the completed version, plus metadata. `summary-box` listens to these updates via MessageBus, and updates state accordingly.
This commit is contained in:
76
spec/jobs/regular/stream_topic_summary_spec.rb
Normal file
76
spec/jobs/regular/stream_topic_summary_spec.rb
Normal file
@@ -0,0 +1,76 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Jobs::StreamTopicSummary do
|
||||
subject(:job) { described_class.new }
|
||||
|
||||
describe "#execute" do
|
||||
fab!(:topic) { Fabricate(:topic) }
|
||||
let(:plugin) { Plugin::Instance.new }
|
||||
let(:strategy) { DummyCustomSummarization.new({ summary: "dummy", chunks: [] }) }
|
||||
fab!(:user) { Fabricate(:leader) }
|
||||
|
||||
before { Group.find(Group::AUTO_GROUPS[:trust_level_3]).add(user) }
|
||||
|
||||
before do
|
||||
plugin.register_summarization_strategy(strategy)
|
||||
SiteSetting.summarization_strategy = strategy.model
|
||||
end
|
||||
|
||||
describe "validates params" do
|
||||
it "does nothing if there is no topic" do
|
||||
messages =
|
||||
MessageBus.track_publish("/summaries/topic/#{topic.id}") do
|
||||
job.execute(topic_id: nil, user_id: user.id)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
end
|
||||
|
||||
it "does nothing if there is no user" do
|
||||
messages =
|
||||
MessageBus.track_publish("/summaries/topic/#{topic.id}") do
|
||||
job.execute(topic_id: topic.id, user_id: nil)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
end
|
||||
|
||||
it "does nothing if the user is not allowed to see the topic" do
|
||||
private_topic = Fabricate(:private_message_topic)
|
||||
|
||||
messages =
|
||||
MessageBus.track_publish("/summaries/topic/#{private_topic.id}") do
|
||||
job.execute(topic_id: private_topic.id, user_id: user.id)
|
||||
end
|
||||
|
||||
expect(messages).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
it "publishes updates with a partial summary" do
|
||||
messages =
|
||||
MessageBus.track_publish("/summaries/topic/#{topic.id}") do
|
||||
job.execute(topic_id: topic.id, user_id: user.id)
|
||||
end
|
||||
|
||||
partial_summary_update = messages.first.data
|
||||
expect(partial_summary_update[:done]).to eq(false)
|
||||
expect(partial_summary_update.dig(:topic_summary, :summarized_text)).to eq("dummy")
|
||||
end
|
||||
|
||||
it "publishes a final update to signal we're done and provide metadata" do
|
||||
messages =
|
||||
MessageBus.track_publish("/summaries/topic/#{topic.id}") do
|
||||
job.execute(topic_id: topic.id, user_id: user.id)
|
||||
end
|
||||
|
||||
final_update = messages.last.data
|
||||
expect(final_update[:done]).to eq(true)
|
||||
|
||||
expect(final_update.dig(:topic_summary, :algorithm)).to eq(strategy.model)
|
||||
expect(final_update.dig(:topic_summary, :outdated)).to eq(false)
|
||||
expect(final_update.dig(:topic_summary, :can_regenerate)).to eq(true)
|
||||
expect(final_update.dig(:topic_summary, :new_posts_since_summary)).to be_zero
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5508,9 +5508,9 @@ RSpec.describe TopicsController do
|
||||
describe "#summary" do
|
||||
fab!(:topic) { Fabricate(:topic) }
|
||||
let(:plugin) { Plugin::Instance.new }
|
||||
let(:strategy) { DummyCustomSummarization.new({ summary: "dummy", chunks: [] }) }
|
||||
|
||||
before do
|
||||
strategy = DummyCustomSummarization.new("dummy")
|
||||
plugin.register_summarization_strategy(strategy)
|
||||
SiteSetting.summarization_strategy = strategy.model
|
||||
end
|
||||
@@ -5536,14 +5536,17 @@ RSpec.describe TopicsController do
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
summary = response.parsed_body
|
||||
expect(summary["summary"]).to eq(section.summarized_text)
|
||||
expect(summary.dig("topic_summary", "summarized_text")).to eq(section.summarized_text)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user is a member of an allowlisted group" do
|
||||
fab!(:user) { Fabricate(:leader) }
|
||||
|
||||
before { sign_in(user) }
|
||||
before do
|
||||
sign_in(user)
|
||||
Group.find(Group::AUTO_GROUPS[:trust_level_3]).add(user)
|
||||
end
|
||||
|
||||
it "returns a 404 if there is no topic" do
|
||||
invalid_topic_id = 999
|
||||
@@ -5560,6 +5563,20 @@ RSpec.describe TopicsController do
|
||||
|
||||
expect(response.status).to eq(403)
|
||||
end
|
||||
|
||||
it "returns a summary" do
|
||||
get "/t/#{topic.id}/strategy-summary.json"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
summary = response.parsed_body["topic_summary"]
|
||||
section = SummarySection.last
|
||||
|
||||
expect(summary["summarized_text"]).to eq(section.summarized_text)
|
||||
expect(summary["algorithm"]).to eq(strategy.model)
|
||||
expect(summary["outdated"]).to eq(false)
|
||||
expect(summary["can_regenerate"]).to eq(true)
|
||||
expect(summary["new_posts_since_summary"]).to be_zero
|
||||
end
|
||||
end
|
||||
|
||||
context "when the user is not a member of an allowlisted group" do
|
||||
@@ -5587,7 +5604,7 @@ RSpec.describe TopicsController do
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
summary = response.parsed_body
|
||||
expect(summary["summary"]).to eq(section.summarized_text)
|
||||
expect(summary.dig("topic_summary", "summarized_text")).to eq(section.summarized_text)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -186,5 +186,17 @@ describe TopicSummarization do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "stream partial updates" do
|
||||
let(:summary) { { summary: "This is the final summary", chunks: [] } }
|
||||
|
||||
it "receives a blk that is passed to the underlying strategy and called with partial summaries" do
|
||||
partial_result = nil
|
||||
|
||||
summarization.summarize(topic, user) { |partial_summary| partial_result = partial_summary }
|
||||
|
||||
expect(partial_result).to eq(summary[:summary])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,6 @@ class DummyCustomSummarization < Summarization::Base
|
||||
end
|
||||
|
||||
def summarize(_content)
|
||||
@summarization_result
|
||||
@summarization_result.tap { |result| yield(result[:summary]) if block_given? }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe "Topic summarization", type: :system, js: true do
|
||||
fab!(:user) { Fabricate(:admin) }
|
||||
|
||||
# has_summary to force topic map to be present.
|
||||
fab!(:topic) { Fabricate(:topic, has_summary: true) }
|
||||
fab!(:post_1) { Fabricate(:post, topic: topic) }
|
||||
fab!(:post_2) { Fabricate(:post, topic: topic) }
|
||||
|
||||
let(:plugin) { Plugin::Instance.new }
|
||||
|
||||
let(:expected_summary) { "This is a summary" }
|
||||
let(:summarization_result) { { summary: expected_summary, chunks: [] } }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
strategy = DummyCustomSummarization.new(summarization_result)
|
||||
plugin.register_summarization_strategy(strategy)
|
||||
SiteSetting.summarization_strategy = strategy.model
|
||||
end
|
||||
|
||||
it "returns a summary using the selected timeframe" do
|
||||
visit("/t/-/#{topic.id}")
|
||||
|
||||
find(".topic-strategy-summarization").click
|
||||
|
||||
summary = find(".summary-box .generated-summary p").text
|
||||
|
||||
expect(summary).to eq(expected_summary)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user