mirror of
https://ghfast.top/https://github.com/discourse/discourse-ai.git
synced 2026-07-16 11:36:23 +08:00
This takes the logic used in summarization/discoveries for streaming and consolidates it into a single helper lib for smooth streaming. It introduces a new lib: `SmoothStreamer` that can be used by components for smooth streaming text from message bus updates. Additionally, the PR makes use of that new lib in the AI post menu helper.
116 lines
3.1 KiB
Text
116 lines
3.1 KiB
Text
import { tracked } from "@glimmer/tracking";
|
|
import { cancel, later } from "@ember/runloop";
|
|
|
|
const DEFAULT_TYPING_DELAY = 15;
|
|
|
|
/**
|
|
* SmoothStreamer provides a typing animation effect for streamed text updates.
|
|
*/
|
|
export default class SmoothStreamer {
|
|
@tracked isStreaming = false;
|
|
@tracked streamedText = "";
|
|
typingTimer = null;
|
|
streamedTextLength = 0;
|
|
|
|
/**
|
|
* @param {() => string} getRealtimeText - Function to retrieve the latest realtime text
|
|
* @param {(value: string) => void} setRealtimeText - Function to update the realtime text
|
|
* @param {number} [typingDelay] - Delay (in ms) between each character reveal
|
|
*/
|
|
constructor(getRealtimeText, setRealtimeText, typingDelay) {
|
|
this.getRealtimeText = getRealtimeText;
|
|
this.setRealtimeText = setRealtimeText;
|
|
this.typingDelay = typingDelay || DEFAULT_TYPING_DELAY;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the appropriate text: either the animated stream or the full realtime text.
|
|
* @returns {string}
|
|
*/
|
|
get renderedText() {
|
|
return this.isStreaming ? this.streamedText : this.realtimeText;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current realtime text.
|
|
* @returns {string}
|
|
*/
|
|
get realtimeText() {
|
|
return this.getRealtimeText();
|
|
}
|
|
|
|
/**
|
|
* Updates the realtime text.
|
|
* @param {string} value - The new text value
|
|
*/
|
|
set realtimeText(value) {
|
|
this.setRealtimeText(value);
|
|
}
|
|
|
|
/**
|
|
* Resets the streaming state, clearing all animation progress.
|
|
*/
|
|
resetStreaming() {
|
|
this.#cancelTypingTimer();
|
|
this.isStreaming = false;
|
|
this.streamedText = "";
|
|
this.streamedTextLength = 0;
|
|
}
|
|
|
|
/**
|
|
* Processes an update result (typically from MessageBus)
|
|
* either completing the stream or continuing animation.
|
|
* @param {object} result - The result object containing the new text and status
|
|
* @param {string} newTextKey - The key in result that holds the new text value (e.g. if the JSON is { text: "Hello", done: false }, newTextKey would be "text")
|
|
*/
|
|
async updateResult(result, newTextKey) {
|
|
const newText = result[newTextKey];
|
|
|
|
if (result?.done) {
|
|
this.streamedText = newText;
|
|
this.realtimeText = newText;
|
|
this.isStreaming = false;
|
|
|
|
// Clear pending animations
|
|
this.#cancelTypingTimer();
|
|
} else if (newText.length > this.realtimeText.length) {
|
|
this.realtimeText = newText;
|
|
this.isStreaming = true;
|
|
await this.#onTextUpdate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Types out the next character in the streaming text.
|
|
* Private method.
|
|
*/
|
|
#typeCharacter() {
|
|
if (this.streamedTextLength < this.realtimeText.length) {
|
|
this.streamedText += this.realtimeText.charAt(this.streamedTextLength);
|
|
this.streamedTextLength++;
|
|
|
|
this.typingTimer = later(this, this.#typeCharacter, this.typingDelay);
|
|
} else {
|
|
this.typingTimer = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles text updates and restarts the typing animation.
|
|
* Private method.
|
|
*/
|
|
#onTextUpdate() {
|
|
this.#cancelTypingTimer();
|
|
this.#typeCharacter();
|
|
}
|
|
|
|
/**
|
|
* Cancels any pending typing animation.
|
|
* Private method.
|
|
*/
|
|
#cancelTypingTimer() {
|
|
if (this.typingTimer) {
|
|
cancel(this.typingTimer);
|
|
}
|
|
}
|
|
}
|