diff --git a/app/jobs/regular/stream_topic_ai_summary.rb b/app/jobs/regular/stream_topic_ai_summary.rb
index d3212f58..c9756801 100644
--- a/app/jobs/regular/stream_topic_ai_summary.rb
+++ b/app/jobs/regular/stream_topic_ai_summary.rb
@@ -26,7 +26,7 @@ module Jobs
streamed_summary << partial_summary
# Throttle updates.
- if (Time.now - start > 0.3) || Rails.env.test?
+ if (Time.now - start > 0.5) || Rails.env.test?
payload = { done: false, ai_topic_summary: { summarized_text: streamed_summary } }
publish_update(topic, user, payload)
diff --git a/assets/javascripts/discourse/connectors/topic-map-expanded-after/ai-summary-box.gjs b/assets/javascripts/discourse/connectors/topic-map-expanded-after/ai-summary-box.gjs
index 65619092..40e944ba 100644
--- a/assets/javascripts/discourse/connectors/topic-map-expanded-after/ai-summary-box.gjs
+++ b/assets/javascripts/discourse/connectors/topic-map-expanded-after/ai-summary-box.gjs
@@ -9,6 +9,7 @@ import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import { ajax } from "discourse/lib/ajax";
import { shortDateNoYear } from "discourse/lib/formatter";
+import { cook } from "discourse/lib/text";
import dIcon from "discourse-common/helpers/d-icon";
import i18n from "discourse-common/helpers/i18n";
import { bind } from "discourse-common/utils/decorators";
@@ -16,7 +17,6 @@ import I18n from "discourse-i18n";
import DMenu from "float-kit/components/d-menu";
import DTooltip from "float-kit/components/d-tooltip";
import AiSummarySkeleton from "../../components/ai-summary-skeleton";
-import { streamSummaryText } from "../../lib/ai-streamer";
export default class AiSummaryBox extends Component {
@service siteSettings;
@@ -31,8 +31,6 @@ export default class AiSummaryBox extends Component {
@tracked outdated = false;
@tracked canRegenerate = false;
@tracked loading = false;
- oldRaw = null; // used for comparison in SummaryUpdater in lib/ai-streamer
- finalSummary = null;
get outdatedSummaryWarningText() {
let outdatedText = I18n.t("summary.outdated");
@@ -79,8 +77,7 @@ export default class AiSummaryBox extends Component {
}
const channel = `/discourse-ai/summaries/topic/${this.args.outletArgs.topic.id}`;
this._channel = channel;
- // we attempt to recover the last message in the bus so we subscrcibe at -2
- this.messageBus.subscribe(channel, this._updateSummary, -2);
+ this.messageBus.subscribe(channel, this._updateSummary);
}
@bind
@@ -137,26 +134,26 @@ export default class AiSummaryBox extends Component {
@bind
_updateSummary(update) {
- const topicSummary = {
- done: update.done,
- raw: update.ai_topic_summary?.summarized_text,
- ...update.ai_topic_summary,
- };
- this.loading = false;
+ const topicSummary = update.ai_topic_summary;
- streamSummaryText(topicSummary, this);
-
- if (update.done) {
- this.text = this.finalSummary;
- this.summarizedOn = shortDateNoYear(
- moment(topicSummary.updated_at, "YYYY-MM-DD HH:mm:ss Z")
- );
- this.summarizedBy = topicSummary.algorithm;
- this.newPostsSinceSummary = topicSummary.new_posts_since_summary;
- this.outdated = topicSummary.outdated;
- this.newPostsSinceSummary = topicSummary.new_posts_since_summary;
- this.canRegenerate = topicSummary.outdated && topicSummary.can_regenerate;
- }
+ return cook(topicSummary.summarized_text)
+ .then((cooked) => {
+ this.text = cooked;
+ this.loading = false;
+ })
+ .then(() => {
+ if (update.done) {
+ this.summarizedOn = shortDateNoYear(
+ moment(topicSummary.updated_at, "YYYY-MM-DD HH:mm:ss Z")
+ );
+ this.summarizedBy = topicSummary.algorithm;
+ this.newPostsSinceSummary = topicSummary.new_posts_since_summary;
+ this.outdated = topicSummary.outdated;
+ this.newPostsSinceSummary = topicSummary.new_posts_since_summary;
+ this.canRegenerate =
+ topicSummary.outdated && topicSummary.can_regenerate;
+ }
+ });
}
@action
@@ -212,7 +209,7 @@ export default class AiSummaryBox extends Component {
{{#if this.loading}}
diff --git a/assets/javascripts/discourse/lib/ai-streamer.js b/assets/javascripts/discourse/lib/ai-streamer.js index 494788c5..d6d06af6 100644 --- a/assets/javascripts/discourse/lib/ai-streamer.js +++ b/assets/javascripts/discourse/lib/ai-streamer.js @@ -134,63 +134,6 @@ class PostUpdater extends StreamUpdater { } } -export class SummaryUpdater extends StreamUpdater { - constructor(topicSummary, componentContext) { - super(); - this.topicSummary = topicSummary; - this.componentContext = componentContext; - - if (this.topicSummary) { - this.summaryBox = document.querySelector("article.ai-summary-box"); - } - } - - get element() { - return this.summaryBox; - } - - set streaming(value) { - if (this.element) { - if (value) { - this.element.classList.add("streaming"); - } else { - this.element.classList.remove("streaming"); - } - } - } - - async setRaw(value, done) { - this.componentContext.oldRaw = value; - const cooked = await cook(value); - - // resets animation - this.element.classList.remove("streaming"); - void this.element.offsetWidth; - this.element.classList.add("streaming"); - - const cookedElement = document.createElement("div"); - cookedElement.innerHTML = cooked; - - if (!done) { - addProgressDot(cookedElement); - } - await this.setCooked(cookedElement.innerHTML); - - if (done) { - this.componentContext.finalSummary = cooked; - } - } - - async setCooked(value) { - const cookedContainer = this.element.querySelector(".generated-summary"); - cookedContainer.innerHTML = value; - } - - get raw() { - return this.componentContext.oldRaw || ""; - } -} - export async function applyProgress(status, updater) { status.startTime = status.startTime || Date.now(); @@ -205,6 +148,7 @@ export async function applyProgress(status, updater) { } const oldRaw = updater.raw; + if (status.raw === oldRaw && !status.done) { const hasProgressDot = updater.element.querySelector(".progress-dot"); if (hasProgressDot) { @@ -269,22 +213,6 @@ async function handleProgress(postStream) { return keepPolling; } -export function streamSummaryText(topicSummary, context) { - const summaryUpdater = new SummaryUpdater(topicSummary, context); - - if (!progressTimer) { - progressTimer = later(async () => { - await applyProgress(topicSummary, summaryUpdater); - - progressTimer = null; - - if (!topicSummary.done) { - await applyProgress(topicSummary, summaryUpdater); - } - }, PROGRESS_INTERVAL); - } -} - function ensureProgress(postStream) { if (!progressTimer) { progressTimer = later(async () => { diff --git a/assets/stylesheets/common/streaming.scss b/assets/stylesheets/common/streaming.scss deleted file mode 100644 index 9608749a..00000000 --- a/assets/stylesheets/common/streaming.scss +++ /dev/null @@ -1,31 +0,0 @@ -@keyframes flashing { - 0%, - 100% { - opacity: 0; - } - 50% { - opacity: 1; - } -} - -article.streaming .cooked { - .progress-dot::after { - content: "\25CF"; - font-family: Söhne Circle, system-ui, -apple-system, Segoe UI, Roboto, - Ubuntu, Cantarell, Noto Sans, sans-serif; - line-height: normal; - margin-left: 0.25rem; - vertical-align: baseline; - animation: flashing 1.5s 3s infinite; - display: inline-block; - font-size: 1rem; - color: var(--tertiary-medium); - } - - > .progress-dot:only-child::after { - // if the progress dot is the only content - // we are likely waiting longer for a response - // so it can start animating instantly - animation: flashing 1.5s infinite; - } -} diff --git a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss index e2a2518a..aa404fe3 100644 --- a/assets/stylesheets/modules/ai-bot/common/bot-replies.scss +++ b/assets/stylesheets/modules/ai-bot/common/bot-replies.scss @@ -51,6 +51,38 @@ article.streaming nav.post-controls .actions button.cancel-streaming { display: inline-block; } +@keyframes flashing { + 0%, + 100% { + opacity: 0; + } + 50% { + opacity: 1; + } +} + +article.streaming .cooked { + .progress-dot::after { + content: "\25CF"; + font-family: Söhne Circle, system-ui, -apple-system, Segoe UI, Roboto, + Ubuntu, Cantarell, Noto Sans, sans-serif; + line-height: normal; + margin-left: 0.25rem; + vertical-align: baseline; + animation: flashing 1.5s 3s infinite; + display: inline-block; + font-size: 1rem; + color: var(--tertiary-medium); + } + + > .progress-dot:only-child::after { + // if the progress dot is the only content + // we are likely waiting longer for a response + // so it can start animating instantly + animation: flashing 1.5s infinite; + } +} + .ai-bot-available-bot-options { padding: 0.5em; diff --git a/plugin.rb b/plugin.rb index a221f9db..501a0304 100644 --- a/plugin.rb +++ b/plugin.rb @@ -13,8 +13,6 @@ gem "tiktoken_ruby", "0.0.9" enabled_site_setting :discourse_ai_enabled -register_asset "stylesheets/common/streaming.scss" - register_asset "stylesheets/modules/ai-helper/common/ai-helper.scss" register_asset "stylesheets/modules/ai-helper/mobile/ai-helper.scss", :mobile diff --git a/test/javascripts/acceptance/topic-summary-test.js b/test/javascripts/acceptance/topic-summary-test.js index 8a915b95..b051471a 100644 --- a/test/javascripts/acceptance/topic-summary-test.js +++ b/test/javascripts/acceptance/topic-summary-test.js @@ -1,5 +1,5 @@ import { click, visit } from "@ember/test-helpers"; -import { skip } from "qunit"; +import { test } from "qunit"; import topicFixtures from "discourse/tests/fixtures/topic"; import { acceptance, @@ -30,7 +30,7 @@ acceptance("Topic - Summary", function (needs) { updateCurrentUser({ id: currentUserId }); }); - skip("displays streamed summary", async function (assert) { + test("displays streamed summary", async function (assert) { await visit("/t/-/1"); const partialSummary = "This a"; @@ -67,7 +67,7 @@ acceptance("Topic - Summary", function (needs) { .exists("summary metadata exists"); }); - skip("clicking summary links", async function (assert) { + test("clicking summary links", async function (assert) { await visit("/t/-/1"); const partialSummary = "In this post,"; @@ -125,7 +125,7 @@ acceptance("Topic - Summary - Anon", function (needs) { }); }); - skip("displays cached summary immediately", async function (assert) { + test("displays cached summary immediately", async function (assert) { await visit("/t/-/1"); await click(".ai-topic-summarization");