Experimental: The AI extension and its component APIs may change in the future. Please give us feedback on your experience with them.
Demos#
Response#
Your spending in March
You spent , which is more less than last month.
- Groceries:
4 200 kr - Transport:
1 100 kr- Bus
- Train
Tip: Set up a savings agreement at www.dnb.no.
| Category | Amount |
|---|---|
| Groceries | 4 200 |
| Transport | 1 100 |
const total = groceries + transport<Response>{answer}</Response>
Wide tables#
Tables retain horizontal scrolling and keyboard access. A translated hint appears below the table only when some columns are outside the visible area. Resize the example to see the hint appear or disappear.
| Category | This month | Last month | Difference |
|---|---|---|---|
| Groceries | |||
| Transport |
<div style={{ maxWidth: '18rem', }} > <Response parseIncompleteMarkdown={false}> {[ '| Category | This month | Last month | Difference |', '| :-- | --: | --: | --: |', '| Groceries | 4 200 kr | 3 900 kr | 300 kr |', '| Transport | 1 100 kr | 1 300 kr | -200 kr |', ].join('\n')} </Response> </div>
Streaming response#
Unfinished markdown is closed while the text streams in. Finished blocks are not rendered again.
const StreamingResponse = () => { const [length, setLength] = useState(0) useEffect(() => { if (length >= answer.length) { return // stop here } const timeout = setTimeout( () => setLength((current) => current + 3), 30 ) return () => clearTimeout(timeout) }, [length]) return ( <> <Button variant="secondary" bottom onClick={() => setLength(0)}> Replay </Button> <Response>{answer.slice(0, length)}</Response> </> ) } render(<StreamingResponse />)
Formatted currency amounts#
Explicit currencies in text, emphasis and table cells use NumberFormat.Currency. Bare numbers and ambiguous symbols remain text.
Prefix amounts followed by a word also remain text. This includes scaled amounts in any language and ordinary prose such as NOK 150 betales nå. Suffix amounts such as 150 kr betales nå. can still be formatted.
You spent and received a refund of .
| Item | Amount |
|---|---|
| Travel | |
| Books |
Bare numbers like 12345 and ambiguous symbols like $100 stay as text.
Prefix amounts followed by words, such as EUR 1,5 millones and NOK 150 payable today, also stay as text.
<Provider locale="nb-NO"> <Response parseIncompleteMarkdown={false}>{currencyResponse}</Response> </Provider>
Source locale#
Set the source locale when the response uses different separators from the display locale. This example reads English amounts and displays them in Norwegian.
The total is ; the fee is .
<Provider locale="nb-NO"> <Response parseIncompleteMarkdown={false} currencyFormatting={{ locale: 'en-GB', }} > {'The total is USD 1,234.50; the fee is EUR 99.95.'} </Response> </Provider>
Structured amount#
When the application knows the amount and currency, render NumberFormat.Currency directly. This example uses a numeric value from structured data alongside generated prose, without extracting or interpreting the amount from the text.
<Provider locale="nb-NO"> <Message from="assistant"> <Response currencyFormatting={false}> {'The confirmed amount is:'} </Response> <P> <NumberFormat.Currency value={responseAmount.value} currency={responseAmount.currency} decimals={0} /> </P> </Message> </Provider>
Streaming currency amounts#
An amount stays as text until following content confirms its boundary or streaming completes.
const StreamingAmount = () => { const text = 'Your balance is NOK 12 400,50. The refund is 250,00 kr.' const [length, setLength] = useState(0) useEffect(() => { if (length >= text.length) { return undefined } const timeout = setTimeout(() => setLength(length + 1), 100) return () => clearTimeout(timeout) }, [length, text]) return ( <Provider locale="nb-NO"> <Button variant="secondary" bottom onClick={() => setLength(0)}> Replay </Button> <Response parseIncompleteMarkdown={length < text.length}> {text.slice(0, length)} </Response> </Provider> ) } render(<StreamingAmount />)
Images require a trusted origin#
Images render as alternative text by default, without an image request. Applications can allow trusted image origins explicitly.
Untrusted image
<Response> {''} </Response>
Allowed URLs#
Links and images that are not allowed are rendered as text.
Read more on dnb.no, not on example.com or this.
<Response allowedLinkPrefixes={['https://www.dnb.no']}> { 'Read more on [dnb.no](https://www.dnb.no/), not on [example.com](https://example.com) or [this](javascript:alert(1)).' } </Response>
Custom elements#
Use components to override how a markdown element is rendered.
This answer uses a custom element for bold text.
const Strong = ({ children }) => ( <strong style={{ color: 'var(--color-sea-green)', }} > {children} </strong> ) render( <Response components={{ strong: Strong, }} > {'This answer uses a **custom** element for **bold** text.'} </Response> )