Chat History — Quick Start

Give signed-in readers a chat page that remembers.

Since 1.14.0

Put two panels on your page: a list of the reader's past conversations, and the conversation itself. A signed-in reader asks a question, gets an answer with citations, and finds the thread again on the next visit.

This takes about fifteen minutes. At the end you have a working page.

Before you start

Get your publishable API key. Copy it from Overview in the Miso Dashboard. The SDK runs in the browser, so it takes the publishable key, never the secret key. See Authentication.

Put your content in Miso. Chat History works from your catalog. Upload your content first — see Integrating Your Data — and confirm the result in Dashboard ▸ Data Sets ▸ Catalog.

No content of your own yet? Load a real one in a few minutes. Example: Wikinews parses a free public archive of about 20,900 news articles and sends it to Miso, so you have something to ask questions about.

Ask Miso to turn on User History. Miso enables it per app. Until then, the history endpoints return 403. See User History.

Add an endpoint that signs a JWT for the reader. History belongs to one reader, so the browser must prove who the reader is. Your server signs a short-lived JWT with your secret key and returns it to the page. See Create a browser JWT.


1. Install the SDK

As a node module

In your project directory, run:

npm install --save @miso.ai/client-sdk

Then import MisoClient from the SDK:

import MisoClient from '@miso.ai/client-sdk';

Using a script tag

<script async src="https://cdn.jsdelivr.net/npm/@miso.ai/client-sdk@latest/dist/umd/miso.min.js"></script>

The script tag loads the SDK asynchronously, so your code must wait for it. Use the misocmd queue, which runs your function as soon as the SDK is ready:

const misocmd = window.misocmd || (window.misocmd = []);
misocmd.push(() => {
  const MisoClient = window.MisoClient;
  // Your code goes here.
});

Beta. The history panels ship in the beta release, 1.14.0-beta.4 or later. Until they reach the stable release, write @beta where the snippets above say @latest. For npm, install @miso.ai/client-sdk@beta.

2. Add the panels

<miso-history> is the list of threads. <miso-conversation> is the thread the reader has open. Put the two side by side:

<div id="chat">
  <miso-history class="miso-history">
    <miso-new-thread></miso-new-thread>
    <miso-threads></miso-threads>
  </miso-history>

  <miso-conversation class="miso-conversation">
    <div class="miso-conversation__header" visible-when="nonempty">
      <div class="miso-conversation__header-row">
        <miso-title></miso-title>
        <miso-rename></miso-rename>
        <miso-delete></miso-delete>
        <miso-subscription></miso-subscription>
      </div>
    </div>
    <div class="miso-conversation__intro" visible-when="ready+empty">
      What can I help with?
    </div>
    <miso-messages></miso-messages>
    <miso-query visible-when="ready"></miso-query>
  </miso-conversation>
</div>
ElementWhat the reader sees
<miso-new-thread>The New chat button.
<miso-threads>The list of threads, newest first. Each item has a menu to rename, subscribe, or delete.
<miso-title>, <miso-rename>, <miso-delete>The title of the open thread, and its rename and delete buttons.
<miso-subscription>The toggle for Answer Updates on the open thread.
<miso-messages>The questions and answers of the open thread.
<miso-query>The box where the reader asks the next question.

The class names miso-history and miso-conversation apply Miso's default styles. Each panel fills its container and scrolls inside it, so the page decides the size and position:

html, body {
  height: 100%;
  margin: 0;
}
#chat {
  display: flex;
  gap: 1rem;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}
#chat miso-history {
  flex: 0 0 20rem;
}
#chat miso-conversation {
  flex: 1 1 auto;
}

3. Sign the reader in and start

const misocmd = window.misocmd || (window.misocmd = []);
misocmd.push(async () => {
  const client = new MisoClient('YOUR_PUBLISHABLE_API_KEY');
  const workflow = client.ui.history;

  // Your endpoint. It returns a JWT for the signed-in reader.
  async function fetchToken() {
    const response = await fetch('/api/miso-token');
    return (await response.json()).token;
  }

  // The styles load while your server signs the token.
  const [token] = await Promise.all([fetchToken(), client.ui.ready]);
  client.context.auth = `Bearer ${token}`;

  workflow.start();                         // load the thread list
});

client.ui.ready resolves when the styles are loaded. The token request runs at the same time, so neither waits for the other.

The SDK sends client.context.auth as the Authorization header on every request. start() loads the reader's threads. The conversation panel opens on a new, empty thread and needs no call of its own.

4. Renew the token when it expires

A short-lived JWT expires while the page is open, and Miso then answers 401. Set client.api.onError to get a new token and send the request again:

let renewing;

client.api.onError = async (error, { resend }) => {
  // Any other error, or a second 401 for the same request: stop here.
  if (error.status !== 401 || error.resent) {
    throw error;
  }
  // Requests that fail together share one renewal.
  renewing = renewing || fetchToken().finally(() => {
    renewing = undefined;
  });
  client.context.auth = `Bearer ${await renewing}`;
  return resend();
};

error.resent counts how many times the SDK sent this request again. The check stops a loop when the new token is rejected too.


The complete page

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Miso Chat History</title>
  <script async src="https://cdn.jsdelivr.net/npm/@miso.ai/client-sdk@beta/dist/umd/miso.min.js"></script>
  <style>
    html, body {
      height: 100%;
      margin: 0;
    }
    #chat {
      display: flex;
      gap: 1rem;
      height: 100%;
      padding: 1rem;
      box-sizing: border-box;
    }
    #chat miso-history {
      flex: 0 0 20rem;
    }
    #chat miso-conversation {
      flex: 1 1 auto;
    }
  </style>
</head>
<body>
  <div id="chat">
    <miso-history class="miso-history">
      <miso-new-thread></miso-new-thread>
      <miso-threads></miso-threads>
    </miso-history>

    <miso-conversation class="miso-conversation">
      <div class="miso-conversation__header" visible-when="nonempty">
        <div class="miso-conversation__header-row">
          <miso-title></miso-title>
          <miso-rename></miso-rename>
          <miso-delete></miso-delete>
          <miso-subscription></miso-subscription>
        </div>
      </div>
      <div class="miso-conversation__intro" visible-when="ready+empty">
        What can I help with?
      </div>
      <miso-messages></miso-messages>
      <miso-query visible-when="ready"></miso-query>
    </miso-conversation>
  </div>

  <script>
    const misocmd = window.misocmd || (window.misocmd = []);
    misocmd.push(async () => {
      const client = new MisoClient('YOUR_PUBLISHABLE_API_KEY');
      const workflow = client.ui.history;

      async function fetchToken() {
        const response = await fetch('/api/miso-token');
        return (await response.json()).token;
      }

      const [token] = await Promise.all([fetchToken(), client.ui.ready]);
      client.context.auth = `Bearer ${token}`;

      let renewing;
      client.api.onError = async (error, { resend }) => {
        if (error.status !== 401 || error.resent) {
          throw error;
        }
        renewing = renewing || fetchToken().finally(() => {
          renewing = undefined;
        });
        client.context.auth = `Bearer ${await renewing}`;
        return resend();
      };

      workflow.start();
    });
  </script>
</body>
</html>

Check it works

Open the page as a signed-in reader and ask a question. The answer streams into the conversation panel, and a new thread appears at the top of the list. Reload the page: the thread is still in the list, and a click opens it again.

SymptomCause
The list shows an error, and the requests return 403User History is off for this app. Ask Miso to enable it.
The list shows "Your session has expired"The JWT is missing or expired, or it was signed with a different key.
The panels render with no stylesThe miso-history and miso-conversation class names are missing.
The panels never renderThe page loads a release older than 1.14.0-beta.4, or your code ran before the SDK loaded.

Next