-
Notifications
You must be signed in to change notification settings - Fork 87
feat: message-list markdown support #9080
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
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Messages AI Chat</title> | ||
<script type="module" src="./common.js"></script> | ||
|
||
<style> | ||
html, | ||
body { | ||
height: 100%; | ||
margin: 0; | ||
} | ||
|
||
#chat { | ||
display: flex; | ||
flex-direction: column; | ||
height: 100%; | ||
} | ||
|
||
vaadin-scroller { | ||
flex: 1; | ||
scroll-snap-type: y proximity; | ||
} | ||
|
||
vaadin-scroller::after { | ||
display: block; | ||
content: ''; | ||
scroll-snap-align: end; | ||
min-height: 1px; | ||
} | ||
</style> | ||
|
||
<script type="module"> | ||
import '@vaadin/message-input'; | ||
import '@vaadin/message-list'; | ||
import '@vaadin/scroller'; | ||
|
||
/** | ||
* Simulates streaming text from an AI model | ||
* @returns {Object} Subscription object with onNext and onComplete methods | ||
*/ | ||
function simulateMessageStream() { | ||
// Sample response to simulate an AI assistant message | ||
const answerMarkdown = ` | ||
I can help you with: | ||
|
||
1. **Answering questions** – from quick facts to in-depth explanations. | ||
2. **Explaining concepts** – breaking down complex ideas into clear, step-by-step logic. | ||
3. **Brainstorming & creativity** – generating outlines, stories, code snippets, or design ideas. | ||
4. **Guidance & troubleshooting** – walking you through processes or helping debug issues. | ||
|
||
--- | ||
|
||
### How to get the most out of me 🛠️ | ||
|
||
| Step | What to do | Why it matters | | ||
|------|------------|----------------| | ||
| 1️⃣ | **State your goal clearly.** | A precise prompt yields a precise answer. | | ||
| 2️⃣ | **Add constraints or context.** <br>*(e.g., audience, length, tone)* | Tailors the response to your needs. | | ||
| 3️⃣ | **Ask follow-ups.** | We can iterate until you're satisfied. | | ||
|
||
--- | ||
|
||
#### Example | ||
|
||
> **You:** "Explain quantum entanglement in simple terms." | ||
|
||
> **Me:** | ||
> *Imagine two coins spun so perfectly in sync that the moment you look at one and see "heads," the other coin—no matter how far away—will instantly show "tails." In quantum physics, particles can become linked in just that way…* | ||
|
||
--- | ||
|
||
Need anything else? Just let me know, and I'll jump right in! ✨`; | ||
|
||
let onNextCallback = null; | ||
let onCompleteCallback = null; | ||
|
||
// Create subscription interface | ||
const subscription = { | ||
onNext: (callback) => { | ||
onNextCallback = callback; | ||
|
||
// Simulate token-by-token streaming with a small delay | ||
const tokenLength = 10; | ||
|
||
setTimeout(async () => { | ||
let tokenIndex = 0; | ||
while (tokenIndex < answerMarkdown.length) { | ||
const token = answerMarkdown.substring(tokenIndex, tokenIndex + tokenLength); | ||
tokenIndex += tokenLength; | ||
onNextCallback(token); | ||
await new Promise((resolve) => setTimeout(resolve, 100)); | ||
} | ||
if (onCompleteCallback) { | ||
onCompleteCallback(); | ||
} | ||
}, 1000); | ||
|
||
return subscription; | ||
}, | ||
onComplete: (callback) => { | ||
onCompleteCallback = callback; | ||
return subscription; | ||
}, | ||
}; | ||
|
||
return subscription; | ||
} | ||
|
||
function createItem(text, assistant = false) { | ||
return { | ||
text, | ||
time: 'Just now', | ||
userName: assistant ? 'Assistant' : 'User', | ||
userColorIndex: assistant ? 2 : 1, | ||
}; | ||
} | ||
|
||
const list = document.querySelector('vaadin-message-list'); | ||
const input = document.querySelector('vaadin-message-input'); | ||
|
||
// Set initial messages | ||
list.items = [ | ||
createItem('Hello! Can you help me with a question?'), | ||
createItem("Of course! I'm here to help. What's your question?", true), | ||
]; | ||
|
||
// Handle new messages from user | ||
input.addEventListener('submit', async (e) => { | ||
// Add user message to the list | ||
list.items = [...list.items, createItem(e.detail.value)]; | ||
input.disabled = true; | ||
|
||
// Create empty assistant message that will be populated gradually | ||
const newAssistantItem = createItem('', true); | ||
|
||
// Simulate AI typing response token by token | ||
simulateMessageStream() | ||
.onNext((token) => { | ||
newAssistantItem.text += token; | ||
// Force UI update by creating a new array | ||
list.items = list.items.includes(newAssistantItem) ? [...list.items] : [...list.items, newAssistantItem]; | ||
}) | ||
.onComplete(() => { | ||
input.disabled = false; | ||
}); | ||
}); | ||
</script> | ||
</head> | ||
|
||
<body> | ||
<div id="chat"> | ||
<vaadin-scroller> | ||
<vaadin-message-list markdown></vaadin-message-list> | ||
</vaadin-scroller> | ||
<vaadin-message-input></vaadin-message-input> | ||
</div> | ||
</body> | ||
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { expect } from '@vaadin/chai-plugins'; | ||
import { fixtureSync, nextUpdate } from '@vaadin/testing-helpers'; | ||
import '../src/vaadin-message-list.js'; | ||
|
||
describe('message-list-markdown', () => { | ||
let messageList; | ||
const messages = [ | ||
{ | ||
text: 'This is a **bold text** in Markdown', | ||
time: '10:00 AM', | ||
userName: 'Markdown User', | ||
userAbbr: 'MU', | ||
}, | ||
]; | ||
|
||
beforeEach(async () => { | ||
messageList = fixtureSync('<vaadin-message-list markdown></vaadin-message-list>'); | ||
messageList.items = messages; | ||
await nextUpdate(messageList); | ||
}); | ||
|
||
it('should render the message items as markdown', () => { | ||
const strongElement = messageList.querySelector('vaadin-message strong'); | ||
expect(strongElement).to.exist; | ||
expect(strongElement.textContent).to.equal('bold text'); | ||
}); | ||
|
||
it('should toggle markdown rendering when property changes', async () => { | ||
// First check with markdown enabled | ||
expect(messageList.querySelector('vaadin-message strong')).to.exist; | ||
|
||
// Disable markdown | ||
messageList.markdown = false; | ||
await nextUpdate(messageList); | ||
|
||
// Verify markdown is disabled on messages | ||
expect(messageList.querySelector('vaadin-message strong')).to.not.exist; | ||
|
||
// Re-enable markdown | ||
messageList.markdown = true; | ||
await nextUpdate(messageList); | ||
|
||
// Verify markdown is re-enabled | ||
expect(messageList.querySelector('vaadin-message strong')).to.exist; | ||
}); | ||
|
||
it('should toggle markdown attribute', async () => { | ||
expect(messageList.hasAttribute('markdown')).to.be.true; | ||
messageList.markdown = false; | ||
await nextUpdate(messageList); | ||
expect(messageList.hasAttribute('markdown')).to.be.false; | ||
}); | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+15.3 KB
...essage-list/test/visual/lumo/screenshots/message-list/baseline/ltr-markdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+14.8 KB
...essage-list/test/visual/lumo/screenshots/message-list/baseline/rtl-markdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+15.8 KB
...ge-list/test/visual/material/screenshots/message-list/baseline/ltr-markdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+15.8 KB
...ge-list/test/visual/material/screenshots/message-list/baseline/rtl-markdown.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
import '@vaadin/markdown/theme/lumo/vaadin-markdown.js'; | ||
import './vaadin-message-list-styles.js'; | ||
import '../../src/vaadin-message-list.js'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
import '@vaadin/markdown/theme/material/vaadin-markdown.js'; | ||
import './vaadin-message-list-styles.js'; | ||
import '../../src/vaadin-message-list.js'; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Instead of markdown boolean - what would you think about a property / enum
mode
(orformat
) allowing to switch between mode=text (default), mode=markdown
orhtml
or any other thing you guys might come up with?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This option was discussed during API design. We ended up with a
markdown
flag due to simplicity and because markdown also allowshtml
content.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We also discussed later adding some kind of a
renderer
API which would allow you to decide how an item text gets rendered inside the message. That's not included in the current scope though.