Skip to content

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
    1. Bus
    2. Train

Tip: Set up a savings agreement at www.dnb.no.

CategoryAmount
Groceries4 200
Transport1 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.

CategoryThis monthLast monthDifference
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 .

ItemAmount
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.

The confirmed amount is:

<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>
  {'![Untrusted image](https://untrusted.example/pixel.png)'}
</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>
)
Suggest an edit