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

Commit 2d6ec5e

Browse files
authored
FIX: apply diffs more consistently (#1367)
* FIX: apply diffs more consistently 1. Do escaping direct in diff streamer, that way HTML tags and other unsafe chars can be displayed and fixed 2. Add safeguard to ensure streaming always stops when it was terminated elsewhere * lint * bug unsubscribe should unsubscribe
1 parent cead887 commit 2d6ec5e

File tree

3 files changed

+29
-12
lines changed

3 files changed

+29
-12
lines changed

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

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export default class ModalDiffModal extends Component {
2424

2525
@tracked loading = false;
2626
@tracked finalResult = "";
27-
@tracked selectedText = escapeExpression(this.args.model.selectedText);
28-
@tracked diffStreamer = new DiffStreamer(this.selectedText);
27+
@tracked escapedSelectedText = escapeExpression(this.args.model.selectedText);
28+
@tracked diffStreamer = new DiffStreamer(this.args.model.selectedText);
2929
@tracked suggestion = "";
3030
@tracked
3131
smoothStreamer = new SmoothStreamer(
@@ -45,11 +45,16 @@ export default class ModalDiffModal extends Component {
4545

4646
// Prevents flash by showing the
4747
// original text when the diff is empty
48-
return this.selectedText;
48+
return this.escapedSelectedText;
4949
}
5050

5151
get isStreaming() {
52-
return this.diffStreamer.isStreaming || this.smoothStreamer.isStreaming;
52+
// diffStreamer stops "streaming" when it is finished with a chunk
53+
return (
54+
this.diffStreamer.isStreaming ||
55+
!this.diffStreamer.isDone ||
56+
this.smoothStreamer.isStreaming
57+
);
5358
}
5459

5560
get primaryBtnLabel() {
@@ -71,7 +76,7 @@ export default class ModalDiffModal extends Component {
7176
@bind
7277
unsubscribe() {
7378
const channel = "/discourse-ai/ai-helper/stream_composer_suggestion";
74-
this.messageBus.subscribe(channel, this.updateResult);
79+
this.messageBus.unsubscribe(channel, this.updateResult);
7580
}
7681

7782
@action
@@ -105,7 +110,7 @@ export default class ModalDiffModal extends Component {
105110
data: {
106111
location: "composer",
107112
mode: this.args.model.mode,
108-
text: this.selectedText,
113+
text: this.args.model.selectedText,
109114
custom_prompt: this.args.model.customPromptValue,
110115
force_default_locale: true,
111116
client_id: this.messageBus.clientId,
@@ -122,7 +127,7 @@ export default class ModalDiffModal extends Component {
122127

123128
if (this.suggestion) {
124129
this.args.model.toolbarEvent.replaceText(
125-
this.selectedText,
130+
this.args.model.selectedText,
126131
this.suggestion
127132
);
128133
}
@@ -131,8 +136,12 @@ export default class ModalDiffModal extends Component {
131136
this.finalResult?.length > 0
132137
? this.finalResult
133138
: this.diffStreamer.suggestion;
139+
134140
if (this.args.model.showResultAsDiff && finalResult) {
135-
this.args.model.toolbarEvent.replaceText(this.selectedText, finalResult);
141+
this.args.model.toolbarEvent.replaceText(
142+
this.args.model.selectedText,
143+
finalResult
144+
);
136145
}
137146
}
138147

assets/javascripts/discourse/lib/diff-streamer.gjs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { tracked } from "@glimmer/tracking";
22
import { cancel, later } from "@ember/runloop";
33
import loadJSDiff from "discourse/lib/load-js-diff";
44
import { parseAsync } from "discourse/lib/text";
5+
import { escapeExpression } from "discourse/lib/utilities";
56

6-
const DEFAULT_CHAR_TYPING_DELAY = 30;
7+
const DEFAULT_CHAR_TYPING_DELAY = 10;
78
const STREAMING_DIFF_TRUNCATE_THRESHOLD = 0.1;
89
const STREAMING_DIFF_TRUNCATE_BUFFER = 10;
910

@@ -51,7 +52,7 @@ export default class DiffStreamer {
5152

5253
const originalDiff = this.jsDiff.diffWordsWithSpace(
5354
this.selectedText,
54-
this.suggestion
55+
newText
5556
);
5657
this.diff = this.#formatDiffWithTags(originalDiff, false);
5758
return;
@@ -172,6 +173,10 @@ export default class DiffStreamer {
172173
}
173174

174175
async #streamNextChar() {
176+
if (!this.isStreaming || this.isDone) {
177+
return;
178+
}
179+
175180
if (this.currentWordIndex < this.words.length) {
176181
const currentToken = this.words[this.currentWordIndex];
177182

@@ -252,6 +257,7 @@ export default class DiffStreamer {
252257
return `<span>${text}</span>`;
253258
}
254259

260+
// returns an HTML safe diff (escaping all internals)
255261
#formatDiffWithTags(diffArray, highlightLastWord = true) {
256262
const wordsWithType = [];
257263
const output = [];
@@ -280,7 +286,8 @@ export default class DiffStreamer {
280286
}
281287

282288
for (let i = 0; i <= lastWordIndex; i++) {
283-
const { text, type } = wordsWithType[i];
289+
let { text, type } = wordsWithType[i];
290+
text = escapeExpression(text);
284291

285292
if (/^\s+$/.test(text)) {
286293
output.push(text);
@@ -310,6 +317,7 @@ export default class DiffStreamer {
310317
i++;
311318
}
312319

320+
chunkText = escapeExpression(chunkText);
313321
output.push(this.#wrapChunk(chunkText, chunkType));
314322
}
315323
}

assets/stylesheets/modules/ai-helper/common/ai-helper.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@
647647

648648
.desktop-view & {
649649
// a little extra space for extra narrow desktop view
650-
@media screen and (width <= 675px) {
650+
@media screen and (max-width: 675px) {
651651
span {
652652
display: none;
653653
}

0 commit comments

Comments
 (0)