Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 17e8426

Browse files
committed
UX: move topic summary from DMenu to DModal
1 parent 872e43e commit 17e8426

File tree

10 files changed

+167
-172
lines changed

10 files changed

+167
-172
lines changed
Lines changed: 73 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,29 @@ import didUpdate from "@ember/render-modifiers/modifiers/did-update";
77
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
88
import { cancel, later } from "@ember/runloop";
99
import { service } from "@ember/service";
10+
import { not } from "truth-helpers";
1011
import CookText from "discourse/components/cook-text";
1112
import DButton from "discourse/components/d-button";
13+
import DModal from "discourse/components/d-modal";
1214
import concatClass from "discourse/helpers/concat-class";
15+
import htmlClass from "discourse/helpers/html-class";
1316
import { ajax } from "discourse/lib/ajax";
1417
import { shortDateNoYear } from "discourse/lib/formatter";
1518
import dIcon from "discourse-common/helpers/d-icon";
1619
import i18n from "discourse-common/helpers/i18n";
1720
import { bind } from "discourse-common/utils/decorators";
1821
import I18n from "discourse-i18n";
19-
import DMenu from "float-kit/components/d-menu";
2022
import DTooltip from "float-kit/components/d-tooltip";
2123
import AiSummarySkeleton from "../../components/ai-summary-skeleton";
2224

2325
const STREAMED_TEXT_SPEED = 15;
2426

25-
export default class AiSummaryBox extends Component {
27+
export default class AiSummaryModal extends Component {
2628
@service siteSettings;
2729
@service messageBus;
2830
@service currentUser;
2931
@service site;
32+
@service modal;
3033

3134
@tracked text = "";
3235
@tracked summarizedOn = null;
@@ -68,25 +71,20 @@ export default class AiSummaryBox extends Component {
6871
}
6972

7073
get topRepliesSummaryEnabled() {
71-
return this.args.outletArgs.postStream.summary;
74+
return this.args.model.postStream.summary;
7275
}
7376

7477
get topicId() {
75-
return this.args.outletArgs.topic.id;
78+
return this.args.model.topic.id;
7679
}
7780

7881
get baseSummarizationURL() {
7982
return `/discourse-ai/summarization/t/${this.topicId}`;
8083
}
8184

8285
@bind
83-
subscribe(unsubscribe, [topicId]) {
84-
const sameTopicId = this.args.outletArgs.topic.id === topicId;
85-
86-
if (unsubscribe && this._channel && !sameTopicId) {
87-
this.unsubscribe();
88-
}
89-
const channel = `/discourse-ai/summaries/topic/${this.args.outletArgs.topic.id}`;
86+
subscribe() {
87+
const channel = `/discourse-ai/summaries/topic/${this.args.model.topic.id}`;
9088
this._channel = channel;
9189
this.messageBus.subscribe(channel, this._updateSummary);
9290
}
@@ -206,100 +204,72 @@ export default class AiSummaryBox extends Component {
206204
}
207205

208206
@action
209-
async onClose() {
210-
await this.dMenu.close();
211-
this.unsubscribe();
207+
handleDidInsert() {
208+
this.subscribe();
209+
this.generateSummary();
210+
}
211+
212+
@action
213+
handleClose() {
214+
this.modal.triggerElement = null; // prevent refocus of trigger, which changes scroll position
215+
this.args.closeModal();
212216
}
213217

214218
<template>
215-
{{#if @outletArgs.topic.summarizable}}
216-
<div
217-
class="ai-summarization-button"
218-
{{didInsert this.subscribe}}
219-
{{didUpdate this.subscribe @outletArgs.topic.id}}
220-
{{willDestroy this.unsubscribe}}
221-
>
222-
<DMenu
223-
@onShow={{this.generateSummary}}
224-
@arrow={{false}}
225-
@identifier="topic-map__ai-summary"
226-
@onRegisterApi={{this.onRegisterApi}}
227-
@interactive={{true}}
228-
@triggers="click"
229-
@placement="left"
230-
@modalForMobile={{true}}
231-
@groupIdentifier="topic-map"
232-
@inline={{true}}
233-
@label={{i18n "summary.buttons.generate"}}
234-
@title={{i18n "summary.buttons.generate"}}
235-
@icon="discourse-sparkles"
236-
@triggerClass="ai-topic-summarization"
237-
@closeOnClickOutside={{false}}
238-
>
239-
<:content>
240-
<div class="ai-summary-container">
241-
<header class="ai-summary__header">
242-
<h3>{{i18n "discourse_ai.summarization.topic.title"}}</h3>
243-
{{#if this.site.desktopView}}
244-
<DButton
245-
@title="discourse_ai.summarization.topic.close"
246-
@action={{this.onClose}}
247-
@icon="times"
248-
class="btn-transparent ai-summary__close"
249-
/>
250-
{{/if}}
251-
</header>
252-
253-
<article
254-
class={{concatClass
255-
"ai-summary-box"
256-
"streamable-content"
257-
(if this.isStreaming "streaming")
258-
}}
259-
>
260-
{{#if this.loading}}
261-
<AiSummarySkeleton />
262-
{{else}}
263-
<div class="generated-summary cooked">
264-
<CookText @rawText={{this.streamedText}} />
265-
</div>
266-
{{#if this.summarizedOn}}
267-
<div class="summarized-on">
268-
<p>
269-
{{i18n "summary.summarized_on" date=this.summarizedOn}}
270-
<DTooltip @placements={{array "top-end"}}>
271-
<:trigger>
272-
{{dIcon "info-circle"}}
273-
</:trigger>
274-
<:content>
275-
{{i18n
276-
"summary.model_used"
277-
model=this.summarizedBy
278-
}}
279-
</:content>
280-
</DTooltip>
281-
</p>
282-
<div class="outdated-summary">
283-
{{#if this.outdated}}
284-
<p>{{this.outdatedSummaryWarningText}}</p>
285-
{{/if}}
286-
{{#if this.canRegenerate}}
287-
<DButton
288-
@label="summary.buttons.regenerate"
289-
@title="summary.buttons.regenerate"
290-
@action={{this.regenerateSummary}}
291-
@icon="sync"
292-
/>
293-
{{/if}}
294-
</div>
295-
</div>
296-
{{/if}}
297-
{{/if}}
298-
</article>
299-
</div>
300-
</:content>
301-
</DMenu>
302-
</div>
303-
{{/if}}
219+
<DModal
220+
@title={{i18n "discourse_ai.summarization.topic.title"}}
221+
@closeModal={{this.handleClose}}
222+
@bodyClass="ai-summary-modal__body"
223+
class="ai-summary-modal"
224+
{{didInsert this.handleDidInsert}}
225+
{{didUpdate this.subscribe @model.topic.id}}
226+
{{willDestroy this.unsubscribe}}
227+
@hideFooter={{not this.summarizedOn}}
228+
>
229+
<:body>
230+
{{htmlClass "scrollable-modal"}}
231+
<div class="ai-summary-container">
232+
<article
233+
class={{concatClass
234+
"ai-summary-box"
235+
"streamable-content"
236+
(if this.isStreaming "streaming")
237+
}}
238+
>
239+
{{#if this.loading}}
240+
<AiSummarySkeleton />
241+
{{else}}
242+
<div class="generated-summary cooked">
243+
<CookText @rawText={{this.streamedText}} />
244+
</div>
245+
{{/if}}
246+
</article>
247+
</div>
248+
</:body>
249+
<:footer>
250+
<p class="summarized-on">
251+
{{i18n "summary.summarized_on" date=this.summarizedOn}}
252+
<DTooltip @placements={{array "top-end"}}>
253+
<:trigger>
254+
{{dIcon "info-circle"}}
255+
</:trigger>
256+
<:content>
257+
{{i18n "summary.model_used" model=this.summarizedBy}}
258+
</:content>
259+
</DTooltip>
260+
</p>
261+
{{#if this.outdated}}
262+
<p class="summary-outdated">{{this.outdatedSummaryWarningText}}</p>
263+
{{/if}}
264+
{{#if this.canRegenerate}}
265+
<DButton
266+
@label="summary.buttons.regenerate"
267+
@title="summary.buttons.regenerate"
268+
@action={{this.regenerateSummary}}
269+
@icon="sync"
270+
/>
271+
{{/if}}
272+
</:footer>
273+
</DModal>
304274
</template>
305275
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Component from "@glimmer/component";
2+
import { action } from "@ember/object";
3+
import { service } from "@ember/service";
4+
import DButton from "discourse/components/d-button";
5+
import AiSummaryModal from "../../components/modal/ai-summary-modal";
6+
7+
export default class AiSummaryTrigger extends Component {
8+
@service modal;
9+
10+
@action
11+
openAiSummaryModal() {
12+
this.modal.show(AiSummaryModal, {
13+
model: {
14+
topic: this.args.outletArgs.topic,
15+
postStream: this.args.outletArgs.postStream,
16+
},
17+
});
18+
}
19+
20+
<template>
21+
{{#if @outletArgs.topic.summarizable}}
22+
<DButton
23+
@label="summary.buttons.generate"
24+
@icon="discourse-sparkles"
25+
@action={{this.openAiSummaryModal}}
26+
class="ai-summarization-button"
27+
/>
28+
{{/if}}
29+
</template>
30+
}

assets/stylesheets/modules/summarization/common/ai-gists.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
margin-bottom: 0.15em;
4242
&__contents {
4343
max-width: 70ch;
44+
overflow-wrap: break-word;
4445
}
4546
}
4647
&:not(.visited) {

assets/stylesheets/modules/summarization/common/ai-summary.scss

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,9 @@
2424
}
2525
}
2626
}
27-
28-
.topic-map__additional-contents {
29-
.ai-summarization-button {
30-
padding-block: 0.5em;
31-
display: flex;
32-
gap: 0.5em;
33-
34-
button span {
35-
white-space: nowrap;
36-
}
37-
}
38-
}
3927
}
4028

41-
.topic-map__ai-summary-content {
42-
.ai-summary-container {
43-
width: 100vw;
44-
}
45-
29+
.ai-summary-modal {
4630
.ai-summary {
4731
&__list {
4832
list-style: none;
@@ -174,14 +158,6 @@
174158
margin: 0;
175159
}
176160

177-
.summarized-on p {
178-
display: flex;
179-
align-items: center;
180-
justify-content: flex-end;
181-
gap: 0.25em;
182-
margin-bottom: 0;
183-
}
184-
185161
.outdated-summary {
186162
display: flex;
187163
flex-direction: column;
@@ -193,6 +169,40 @@
193169
color: var(--primary-medium);
194170
}
195171
}
172+
173+
.d-modal__footer {
174+
display: grid;
175+
gap: 0;
176+
grid-template-areas: "summarized regenerate" " outdated regenerate";
177+
grid-template-columns: 1fr auto;
178+
@include breakpoint(mobile-large) {
179+
gap: 0.25em 0.5em;
180+
grid-template-areas: "summarized summarized" "regenerate outdated";
181+
}
182+
183+
p {
184+
margin: 0;
185+
}
186+
187+
.fk-d-tooltip__trigger {
188+
vertical-align: text-top;
189+
}
190+
191+
.summary-outdated {
192+
color: var(--primary-high);
193+
font-size: var(--font-down-1);
194+
line-height: var(--line-height-medium);
195+
}
196+
197+
.summarized-on {
198+
grid-area: summarized;
199+
}
200+
201+
button {
202+
grid-area: regenerate;
203+
justify-self: start;
204+
}
205+
}
196206
}
197207

198208
@keyframes appear {

0 commit comments

Comments
 (0)