Skip to content

Fade-In content for AI GPT (Resolves #2648) #2673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/website/source/super-editor/guides/_data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ navigation:
items:
- title: Overview
url: super-editor/guides/ai/overview
- title: Fade-In Content
url: super-editor/guides/ai/fade-in-content

- title: Basics
items:
Expand Down
141 changes: 141 additions & 0 deletions doc/website/source/super-editor/guides/ai/fade-in-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
title: Fade-In GPT Content
---
ChatGPT-style large language models (LLM) produces text and content in snippets.
The answer to a single question might includes 50 pieces of information that are
generated by the LLM. The chat-style experience adds each new piece to the existing
answer, making it appear that the AI is "writing" the answer in real-time.

It's common in these experiences to fade-in the content as its generated.
Super Editor includes a couple of tools that can be used to achieve this
effect, when feeding LLM content to a `SuperEditor` or `SuperReader`.

## How to Fade-In Content
The following example demonstrates how to fade content into a `SuperReader`.
The `SuperReader` widget is typically used for LLMs because LLM output isn't
typically meant to be editable.

First, create a `SuperReader` with a `Document` that will hold the LLM output.
Include a `FadeInStyler`, which will control the opacity of new content.

```dart
class MyState extends State<MyWidget> {
late final MutableDocument _document;
late final MutableDocumentComposer _composer;
late final Editor _editor;
late final FadeInStyler _fadeInStylePhase;

@override
void initState() {
super.initState();

_document = MutableDocument.empty();
_composer = MutableDocumentComposer(
initialSelection: DocumentSelection.collapsed(
position: DocumentPosition(
nodeId: _document.first.id,
nodePosition: TextNodePosition(offset: 0),
),
),
);
_editor = createDefaultDocumentEditor(document: _document, composer: _composer);
_fadeInStylePhase = FadeInStyler(this);
}

@override
void dispose() {
_fadeInStylePhase.dispose();
_composer.dispose();
_editor.dispose();
_document.dispose();

super.dispose();
}

@override
Widget build(BuildContext context) {
return SuperReader(
editor: _editor,
customStylePhases: [
_fadeInStylePhase,
],
);
}
}
```

With a `SuperReader` in place, take the output from an LLM and feed it into the
`Editor` however you'd like. The way you feed content into the `Editor` depends on
the format of the output from the LLM. You can feed the LLM text into the `Editor`
as plain text, or you can parse the LLM text into styled `AttributedText` and
feed that to the `Editor`, or you can also parse out entire blocks like images and
horizontal rules from the LLM and insert those as nodes in the `Editor`.

The most important part about feeding content from the LLM to the `Editor` is to
include the current timestamp when inserted. This timestamp is used by the `FadeInStyler`
to choose the opacity for the content.

```dart
// Insert plain text.
_editor.execute([
InsertPlainTextAtEndOfDocumentRequest(
"...this is a snippet from the LLM...",
createdAt: DateTime.now(),
),
]);

// Insert styled text.
_editor.execute([
InsertStyledTextAtEndOfDocumentRequest(
AttributedText(
"...this is a bold snippet...",
AttributedSpans(attributions: [
const SpanMarker(
attribution: boldAttribution,
offset: 0,
markerType: SpanMarkerType.start,
),
const SpanMarker(
attribution: boldAttribution,
offset: 27,
markerType: SpanMarkerType.end,
),
]),
),
createdAt: DateTime.now(),
),
]);

// Insert a block node.
_editor.execute([
InsertNodeAtEndOfDocumentRequest(
ImageNode(
id: Editor.createNodeId(),
imageUrl: "https://somewhere.com/image.png",
metadata: {
NodeMetadata.createdAt: DateTime.now(),
},
),
),
]);
```

With a `FadeInStyler` plugged into `SuperReader`, and content insertions that
include a `CreatedAtAttribution`, the inserted content will fade-in within the
`SuperReader` widget.

## How to Control the Fade-In
When using the `FadeInStyler` to fade-in content, the fade-in effect is configurable.

```dart
// Customize fade-in times and curves.
final styler = FadeInStyler(
tickerProvider,
blockNodeFadeInDuration: const Duration(milliseconds: 1500),
textSnippetFadeInDuration: const Duration(milliseconds: 250),
fadeCurve: Curves.easeInOut,
);
```

The `FadeInStyler` supports different timing for text snippets vs blocks, like images.
The fade animation curve is also configurable.
4 changes: 0 additions & 4 deletions doc/website/source/super-editor/guides/ai/generate-content.md

This file was deleted.

12 changes: 10 additions & 2 deletions doc/website/source/super-editor/guides/ai/overview.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
---
title: AI / GPT Overview
title: AI / GPT Overview
---
Coming soon!
ChatGPT-style experiences are all the rage right now. These experiences include
a large language model (LLM), which generates text based on a query, and a UI
that presents this text in a chat-style format.

Super Editor doesn't include any LLM-specific functionality, but Super Editor
does offer some tools to help construct experiences that are similar to
ChatGPT, Gemini, etc.

* [Fade-in content as the LLM generates it](/super-editor/guides/ai/fade-in-content)
3 changes: 2 additions & 1 deletion super_clones/quill/lib/editor/code_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class FeatherCodeComponentBuilder implements ComponentBuilder {
class CodeBlockComponentViewModel extends SingleColumnLayoutComponentViewModel with TextComponentViewModel {
CodeBlockComponentViewModel({
required super.nodeId,
super.createdAt,
super.maxWidth,
super.padding = EdgeInsets.zero,
required this.text,
Expand Down Expand Up @@ -126,7 +127,7 @@ class CodeBlockComponentViewModel extends SingleColumnLayoutComponentViewModel w
nodeId: nodeId,
maxWidth: maxWidth,
padding: padding,
text: text,
text: text.copy(),
textStyleBuilder: textStyleBuilder,
inlineWidgetBuilders: inlineWidgetBuilders,
textDirection: textDirection,
Expand Down
Loading
Loading