mirror of
https://github.com/discourse/discourse.git
synced 2025-06-04 23:36:11 +08:00
DEV: chat streaming (#25736)
This commit introduces the possibility to stream messages. To allow plugins to use streaming this commit also ships a `ChatSDK` library to allow to interact with few parts of discourse chat. ```ruby ChatSDK::Message.create_with_stream(raw: "test") do |helper| 5.times do |i| is_streaming = helper.stream(raw: "more #{i}") next if !is_streaming sleep 2 end end ``` This commit also introduces all the frontend parts: - messages can now be marked as streaming - when streaming their content will be updated when a new content is appended - a special UI will be showing (a blinking indicator) - a cancel button allows the user to stop the streaming, when cancelled `helper.stream(...)` will return `false`, and the plugin can decide exit early
This commit is contained in:
43
plugins/chat/spec/lib/chat_sdk/channel_spec.rb
Normal file
43
plugins/chat/spec/lib/chat_sdk/channel_spec.rb
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe ChatSDK::Channel do
|
||||
describe ".messages" do
|
||||
fab!(:channel_1) { Fabricate(:chat_channel) }
|
||||
fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||
|
||||
let(:params) { { channel_id: channel_1.id, guardian: Discourse.system_user.guardian } }
|
||||
|
||||
it "loads the messages" do
|
||||
messages = described_class.messages(**params)
|
||||
|
||||
expect(messages).to eq([message_1, message_2])
|
||||
end
|
||||
|
||||
it "accepts page_size" do
|
||||
messages = described_class.messages(**params, page_size: 1)
|
||||
|
||||
expect(messages).to eq([message_1])
|
||||
end
|
||||
|
||||
context "when guardian can't see the channel" do
|
||||
fab!(:channel_1) { Fabricate(:private_category_channel) }
|
||||
|
||||
it "fails" do
|
||||
params[:guardian] = Fabricate(:user).guardian
|
||||
|
||||
expect { described_class.messages(**params) }.to raise_error("Guardian can't view channel")
|
||||
end
|
||||
end
|
||||
|
||||
context "when target_message doesn’t exist" do
|
||||
it "fails" do
|
||||
expect { described_class.messages(**params, target_message_id: -999) }.to raise_error(
|
||||
"Target message doesn't exist",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
100
plugins/chat/spec/lib/chat_sdk/message_spec.rb
Normal file
100
plugins/chat/spec/lib/chat_sdk/message_spec.rb
Normal file
@ -0,0 +1,100 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe ChatSDK::Message do
|
||||
describe ".create" do
|
||||
fab!(:channel_1) { Fabricate(:chat_channel) }
|
||||
|
||||
let(:guardian) { Discourse.system_user.guardian }
|
||||
let(:params) do
|
||||
{ enforce_membership: false, raw: "something", channel_id: channel_1.id, guardian: guardian }
|
||||
end
|
||||
|
||||
it "creates the message" do
|
||||
message = described_class.create(**params)
|
||||
|
||||
expect(message.message).to eq("something")
|
||||
end
|
||||
|
||||
context "when thread_id is present" do
|
||||
fab!(:thread_1) { Fabricate(:chat_thread, channel: channel_1) }
|
||||
|
||||
it "creates the message in a thread" do
|
||||
message = described_class.create(**params, thread_id: thread_1.id)
|
||||
|
||||
expect(message.thread_id).to eq(thread_1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context "when channel doesn’t exist" do
|
||||
it "fails" do
|
||||
expect { described_class.create(**params, channel_id: -999) }.to raise_error(
|
||||
"Couldn't find channel with id: `-999`",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when user can't join channel" do
|
||||
it "fails" do
|
||||
params[:guardian] = Fabricate(:user).guardian
|
||||
|
||||
expect { described_class.create(**params) }.to raise_error(
|
||||
"User with id: `#{params[:guardian].user.id}` can't join this channel",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when membership is enforced" do
|
||||
it "works" do
|
||||
params[:enforce_membership] = true
|
||||
params[:guardian] = Fabricate(:user).guardian
|
||||
SiteSetting.chat_allowed_groups = [Group::AUTO_GROUPS[:everyone]]
|
||||
|
||||
message = described_class.create(**params)
|
||||
|
||||
expect(message.message).to eq("something")
|
||||
end
|
||||
end
|
||||
|
||||
context "when thread doesn't exist" do
|
||||
it "fails" do
|
||||
expect { described_class.create(**params, thread_id: -999) }.to raise_error(
|
||||
"Couldn't find thread with id: `-999`",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when params are invalid" do
|
||||
it "fails" do
|
||||
expect { described_class.create(**params, raw: nil, channel_id: nil) }.to raise_error(
|
||||
"Chat channel can't be blank, Message can't be blank",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".create_with_stream" do
|
||||
fab!(:channel_1) { Fabricate(:chat_channel) }
|
||||
|
||||
let(:guardian) { Discourse.system_user.guardian }
|
||||
let(:params) { { raw: "something", channel_id: channel_1.id, guardian: guardian } }
|
||||
|
||||
it "allows streaming" do
|
||||
created_message =
|
||||
described_class.create_with_stream(**params) do |helper, message|
|
||||
expect(message.streaming).to eq(true)
|
||||
|
||||
edit =
|
||||
MessageBus
|
||||
.track_publish("/chat/#{channel_1.id}") { helper.stream(raw: "test") }
|
||||
.find { |m| m.data["type"] == "edit" }
|
||||
|
||||
expect(edit.data["chat_message"]["message"]).to eq("something test")
|
||||
end
|
||||
|
||||
expect(created_message.streaming).to eq(false)
|
||||
expect(created_message.message).to eq("something test")
|
||||
end
|
||||
end
|
||||
end
|
81
plugins/chat/spec/lib/chat_sdk/thread_spec.rb
Normal file
81
plugins/chat/spec/lib/chat_sdk/thread_spec.rb
Normal file
@ -0,0 +1,81 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe ChatSDK::Thread do
|
||||
describe ".update_title" do
|
||||
fab!(:thread_1) { Fabricate(:chat_thread) }
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
title: "New Title",
|
||||
channel_id: thread_1.channel_id,
|
||||
thread_id: thread_1.id,
|
||||
guardian: Discourse.system_user.guardian,
|
||||
}
|
||||
end
|
||||
|
||||
it "changes the title" do
|
||||
expect { described_class.update_title(**params) }.to change { thread_1.reload.title }.from(
|
||||
thread_1.title,
|
||||
).to(params[:title])
|
||||
end
|
||||
|
||||
context "when missing param" do
|
||||
it "fails" do
|
||||
params.delete(:thread_id)
|
||||
|
||||
expect { described_class.update_title(**params) }.to raise_error("Thread can't be blank")
|
||||
end
|
||||
end
|
||||
|
||||
context "when guardian can't see the channel" do
|
||||
fab!(:thread_1) { Fabricate(:chat_thread, channel: Fabricate(:private_category_channel)) }
|
||||
|
||||
it "fails" do
|
||||
params[:guardian] = Fabricate(:user).guardian
|
||||
|
||||
expect { described_class.update_title(**params) }.to raise_error(
|
||||
"Guardian can't view channel",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when guardian can't edit the thread" do
|
||||
it "fails" do
|
||||
params[:guardian] = Fabricate(:user).guardian
|
||||
|
||||
expect { described_class.update_title(**params) }.to raise_error(
|
||||
"Guardian can't edit thread",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the threadind is not enabled" do
|
||||
before { thread_1.channel.update!(threading_enabled: false) }
|
||||
|
||||
it "fails" do
|
||||
expect { described_class.update_title(**params) }.to raise_error(
|
||||
"Threading is not enabled for this channel",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the thread doesn't exist" do
|
||||
it "fails" do
|
||||
params[:thread_id] = -999
|
||||
expect { described_class.update_title(**params) }.to raise_error(
|
||||
"Couldn’t find thread with id: `-999`",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "when target_message doesn’t exist" do
|
||||
it "fails" do
|
||||
expect { described_class.messages(**params, target_message_id: -999) }.to raise_error(
|
||||
"Target message doesn't exist",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in New Issue
Block a user