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

Commit c29183f

Browse files
FEATURE: Add diff streaming animation (#1355)
Previously we attempted to add a diff streaming animation to the AI composer helper: #1332, but it resulted in issues despite attempted fixes (#1338) so we temporarily suppressed the diff animation (#1341). This update makes a second attempt at implementing the diff streaming animation. Instead of creating a custom diff algorithm, we make use of a third-party library [`jsDiff`](https://github.com/kpdecker/jsdiff) (which we added to core here: discourse/discourse#32833). While streaming, the diff animation often struggles with markdown links and images, so we make use of `markdown-it` parser to detect those cases and prevent breaking the animation. --------- Co-authored-by: Sam Saffron <[email protected]>
1 parent d72ad84 commit c29183f

File tree

3 files changed

+250
-109
lines changed

3 files changed

+250
-109
lines changed

assets/javascripts/discourse/components/modal/diff-modal.gjs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,23 @@ export default class ModalDiffModal extends Component {
2323

2424
@tracked loading = false;
2525
@tracked finalResult = "";
26-
@tracked showcasedDiff = "";
2726
@tracked diffStreamer = new DiffStreamer(this.args.model.selectedText);
2827
@tracked suggestion = "";
2928
@tracked
3029
smoothStreamer = new SmoothStreamer(
3130
() => this.suggestion,
3231
(newValue) => (this.suggestion = newValue)
3332
);
34-
@tracked isStreaming = false;
3533

3634
constructor() {
3735
super(...arguments);
3836
this.suggestChanges();
3937
}
4038

39+
get isStreaming() {
40+
return this.diffStreamer.isStreaming || this.smoothStreamer.isStreaming;
41+
}
42+
4143
get primaryBtnLabel() {
4244
return this.loading
4345
? i18n("discourse_ai.ai_helper.context_menu.loading")
@@ -62,23 +64,14 @@ export default class ModalDiffModal extends Component {
6264

6365
@action
6466
async updateResult(result) {
65-
// TODO(@keegan)
66-
// Temporarily we are removing the animation using the diff streamer
67-
// and simply showing the diff streamed without a proper animation
68-
// while we figure things out
69-
// so that things are not too janky in the meantime.
7067
this.loading = false;
71-
this.isStreaming = true;
7268

7369
if (result.done) {
7470
this.finalResult = result.result;
7571
}
7672

77-
this.showcasedDiff = result.diff;
78-
7973
if (result.done) {
8074
this.loading = false;
81-
this.isStreaming = false;
8275
}
8376

8477
if (this.args.model.showResultAsDiff) {
@@ -143,7 +136,7 @@ export default class ModalDiffModal extends Component {
143136
<div {{didInsert this.subscribe}} {{willDestroy this.unsubscribe}}>
144137
{{#if this.loading}}
145138
<div class="composer-ai-helper-modal__loading">
146-
<CookText @rawText={{@model.selectedText}} />
139+
{{~@model.selectedText~}}
147140
</div>
148141
{{else}}
149142
<div
@@ -152,10 +145,13 @@ export default class ModalDiffModal extends Component {
152145
"streamable-content"
153146
(if this.isStreaming "streaming")
154147
(if @model.showResultAsDiff "inline-diff")
148+
(if this.diffStreamer.isThinking "thinking")
155149
}}
156150
>
157-
{{#if @model.showResultAsDiff}}
158-
{{htmlSafe this.showcasedDiff}}
151+
{{~#if @model.showResultAsDiff~}}
152+
<span class="diff-inner">{{htmlSafe
153+
this.diffStreamer.diff
154+
}}</span>
159155
{{else}}
160156
{{#if this.smoothStreamer.isStreaming}}
161157
<CookText

0 commit comments

Comments
 (0)