FIX: Don't lose SummaryBox state through widget re-renders. (#24020)

* FIX: Don't lose SummaryBox state through widget re-renders.

The <SummaryBox /> component state will get lost when scrolling to the bottom of a topic. Due to the widget being re-rendered, it will go back to the collapsed state, and we need to fetch the summary again.

This change moves all the state updates to the postStream model, which also refreshes the widget to keep it updated.

* Reify topic summary using a pojo
This commit is contained in:
Roman Rizzi 2023-10-19 17:35:23 -03:00 committed by GitHub
parent 0604dc7d3e
commit a72e5fa763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 101 deletions

View File

@ -1,14 +1,10 @@
<div
class="summary-box__container"
{{did-insert this.subscribe}}
{{will-destroy this.unsubscribe}}
>
<div class="summary-box__container">
{{#if @postAttrs.hasTopRepliesSummary}}
<p>{{html-safe this.topRepliesSummaryInfo}}</p>
{{/if}}
<div class="summarization-buttons">
{{#if @postAttrs.summarizable}}
{{#if this.canCollapseSummary}}
{{#if this.summary.showSummaryBox}}
<DButton
@action={{this.collapseSummary}}
@title="summary.buttons.hide"
@ -22,7 +18,7 @@
@translatedLabel={{this.generateSummaryTitle}}
@translatedTitle={{this.generateSummaryTitle}}
@icon={{this.generateSummaryIcon}}
@disabled={{this.loadingSummary}}
@disabled={{this.summary.loading}}
class="btn-primary topic-strategy-summarization"
/>
{{/if}}
@ -39,29 +35,29 @@
{{/if}}
</div>
{{#if this.showSummaryBox}}
{{#if this.summary.showSummaryBox}}
<article class="summary-box">
{{#if (and this.loadingSummary (not this.summary))}}
{{#if (and this.summary.loading (not this.summary.text))}}
<AiSummarySkeleton />
{{else}}
<div class="generated-summary">{{this.summary}}</div>
<div class="generated-summary">{{this.summary.text}}</div>
{{#if this.summarizedOn}}
{{#if this.summary.summarizedOn}}
<div class="summarized-on">
<p>
{{i18n "summary.summarized_on" date=this.summarizedOn}}
{{i18n "summary.summarized_on" date=this.summary.summarizedOn}}
<DTooltip @placements={{array "top-end"}}>
<:trigger>
{{d-icon "info-circle"}}
</:trigger>
<:content>
{{i18n "summary.model_used" model=this.summarizedBy}}
{{i18n "summary.model_used" model=this.summary.summarizedBy}}
</:content>
</DTooltip>
</p>
{{#if this.outdated}}
{{#if this.summary.outdated}}
<p class="outdated-summary">
{{this.outdatedSummaryWarningText}}
</p>

View File

@ -1,69 +1,19 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { shortDateNoYear } from "discourse/lib/formatter";
import { cook } from "discourse/lib/text";
import { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n";
const MIN_POST_READ_TIME = 4;
export default class SummaryBox extends Component {
@service siteSettings;
@service messageBus;
@service currentUser;
@tracked summary = "";
@tracked summarizedOn = null;
@tracked summarizedBy = null;
@tracked newPostsSinceSummary = null;
@tracked outdated = false;
@tracked canRegenerate = false;
@tracked regenerated = false;
@tracked showSummaryBox = false;
@tracked canCollapseSummary = false;
@tracked loadingSummary = false;
@bind
subscribe() {
const channel = `/summaries/topic/${this.args.postAttrs.topicId}`;
this.messageBus.subscribe(channel, this._updateSummary);
}
@bind
unsubscribe() {
this.messageBus.unsubscribe("/summaries/topic/*", this._updateSummary);
}
@bind
_updateSummary(update) {
const topicSummary = update.topic_summary;
if (topicSummary.summarized_text) {
cook(topicSummary.summarized_text).then((cooked) => {
this.summary = cooked;
});
}
if (update.done) {
this.summarizedOn = shortDateNoYear(topicSummary.summarized_on);
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;
this.canCollapseSummary = !this.canRegenerate;
this.loadingSummary = false;
}
get summary() {
return this.args.postAttrs.summary;
}
get generateSummaryTitle() {
const title = this.canRegenerate
const title = this.summary.canRegenerate
? "summary.buttons.regenerate"
: "summary.buttons.generate";
@ -71,7 +21,7 @@ export default class SummaryBox extends Component {
}
get generateSummaryIcon() {
return this.canRegenerate ? "sync" : "magic";
return this.summary.canRegenerate ? "sync" : "magic";
}
get outdatedSummaryWarningText() {
@ -79,11 +29,11 @@ export default class SummaryBox extends Component {
if (
!this.args.postAttrs.hasTopRepliesSummary &&
this.newPostsSinceSummary > 0
this.summary.newPostsSinceSummary > 0
) {
outdatedText += " ";
outdatedText += I18n.t("summary.outdated_posts", {
count: this.newPostsSinceSummary,
count: this.summary.newPostsSinceSummary,
});
}
@ -147,43 +97,16 @@ export default class SummaryBox extends Component {
? "cancelFilter"
: "showTopReplies";
this.args.topRepliesToggle(filterFunction);
this.args.actionDispatchFunc(filterFunction);
}
@action
collapseSummary() {
this.showSummaryBox = false;
this.canCollapseSummary = false;
this.args.actionDispatchFunc("collapseSummary");
}
@action
generateSummary() {
this.showSummaryBox = true;
if (this.summary && !this.canRegenerate) {
this.canCollapseSummary = true;
return;
} else {
this.loadingSummary = true;
}
let fetchURL = `/t/${this.args.postAttrs.topicId}/strategy-summary?`;
if (this.currentUser) {
fetchURL += `stream=true`;
if (this.canRegenerate) {
fetchURL += "&skip_age_check=true";
}
}
ajax(fetchURL)
.then((data) => {
if (!this.currentUser) {
data.done = true;
this._updateSummary(data);
}
})
.catch(popupAjaxError);
this.args.actionDispatchFunc("showSummary");
}
}

View File

@ -567,6 +567,14 @@ export default Controller.extend(bufferedProperty("model"), {
});
},
collapseSummary() {
this.get("model.postStream").collapseSummary();
},
showSummary() {
this.get("model.postStream").showSummary(this.currentUser);
},
removeAllowedUser(user) {
return this.get("model.details")
.removeAllowedUser(user)
@ -1617,6 +1625,9 @@ export default Controller.extend(bufferedProperty("model"), {
this.onMessage,
this.get("model.message_bus_last_id")
);
const summariesChannel = `/summaries/topic/${this.get("model.id")}`;
this.messageBus.subscribe(summariesChannel, this._updateSummary);
},
unsubscribe() {
@ -1626,6 +1637,13 @@ export default Controller.extend(bufferedProperty("model"), {
}
this.messageBus.unsubscribe("/topic/*", this.onMessage);
this.messageBus.unsubscribe("/summaries/topic/*", this._updateSummary);
},
@bind
_updateSummary(update) {
const postStream = this.get("model.postStream");
postStream.processSummaryUpdate(update);
},
@bind

View File

@ -0,0 +1,72 @@
import { tracked } from "@glimmer/tracking";
import { ajax } from "discourse/lib/ajax";
import { shortDateNoYear } from "discourse/lib/formatter";
import { cook } from "discourse/lib/text";
export default class TopicSummary {
@tracked text = "";
@tracked summarizedOn = null;
@tracked summarizedBy = null;
@tracked newPostsSinceSummary = null;
@tracked outdated = false;
@tracked canRegenerate = false;
@tracked regenerated = false;
@tracked showSummaryBox = false;
@tracked canCollapseSummary = false;
@tracked loadingSummary = false;
processUpdate(update) {
const topicSummary = update.topic_summary;
return cook(topicSummary.summarized_text)
.then((cooked) => {
this.text = cooked;
this.loading = false;
})
.then(() => {
if (update.done) {
this.summarizedOn = shortDateNoYear(topicSummary.summarized_on);
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;
}
});
}
collapse() {
this.showSummaryBox = false;
this.canCollapseSummary = false;
}
generateSummary(currentUser, topicId) {
this.showSummaryBox = true;
if (this.text && !this.canRegenerate) {
this.canCollapseSummary = false;
return;
}
let fetchURL = `/t/${topicId}/strategy-summary?`;
if (currentUser) {
fetchURL += `stream=true`;
if (this.canRegenerate) {
fetchURL += "&skip_age_check=true";
}
}
this.loading = true;
return ajax(fetchURL).then((data) => {
if (!currentUser) {
data.done = true;
this.processUpdate(data);
}
});
}
}

View File

@ -220,6 +220,10 @@ export default function transformPost(
postAtts.topicWordCount = topic.word_count;
postAtts.hasTopRepliesSummary = topic.has_summary;
postAtts.summarizable = topic.summarizable;
if (post.post_number === 1) {
postAtts.summary = postStream.topicSummary;
}
}
if (postAtts.isDeleted) {

View File

@ -5,6 +5,7 @@ import { isEmpty } from "@ember/utils";
import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax";
import PostsWithPlaceholders from "discourse/lib/posts-with-placeholders";
import TopicSummary from "discourse/lib/topic-summary";
import DiscourseURL from "discourse/lib/url";
import { highlightPost } from "discourse/lib/utilities";
import RestModel from "discourse/models/rest";
@ -48,6 +49,7 @@ export default RestModel.extend({
filterRepliesToPostNumber: null,
filterUpwardsPostID: null,
filter: null,
topicSummary: null,
init() {
this._identityMap = {};
@ -71,6 +73,7 @@ export default RestModel.extend({
loadingFilter: false,
stagingPost: false,
timelineLookup: [],
topicSummary: new TopicSummary(),
});
},
@ -1260,6 +1263,18 @@ export default RestModel.extend({
}
},
collapseSummary() {
this.topicSummary.collapse();
},
showSummary(currentUser) {
this.topicSummary.generateSummary(currentUser, this.get("topic.id"));
},
processSummaryUpdate(update) {
this.topicSummary.processUpdate(update);
},
_initUserModels(post) {
post.user = User.create({
id: post.user_id,

View File

@ -359,6 +359,8 @@
@toggleWiki={{action "toggleWiki"}}
@showTopReplies={{action "showTopReplies"}}
@cancelFilter={{action "cancelFilter"}}
@collapseSummary={{action "collapseSummary"}}
@showSummary={{action "showSummary"}}
@removeAllowedUser={{action "removeAllowedUser"}}
@removeAllowedGroup={{action "removeAllowedGroup"}}
@topVisibleChanged={{action "topVisibleChanged"}}

View File

@ -408,7 +408,7 @@ export default createWidget("topic-map", {
"section.information.toggle-summary",
hbs`<SummaryBox
@postAttrs={{@data.postAttrs}}
@topRepliesToggle={{@data.actionDispatchFunc}}
@actionDispatchFunc={{@data.actionDispatchFunc}}
/>`,
{
postAttrs: attrs,