By Bernat Sampera 4 min read Follow:

Create a chatbot: Part 3 - Adding the frontend

This post explains how to use assistant ui to render a chatbot and handle the calls with the backend

This posts start from the end of the part 2, you can see it here, https://github.com/bernatsampera/chatbot/tree/part2

For assistant ui is needed that the project has an alias, we’ll add the following configuration in the tsconfig.app.json and vite.config.ts

// tsconfig.json (TODO: Look which of the three is really responsible for the aliases)
{
  ...
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
...
}
// tsconfig.app.json
{
  ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
...
}
// vite.config.ts
defineConfig({
...
  resolve: {
    alias: {
      "@": new URL("./src", import.meta.url).pathname
    }
  }
...
})

Add assistant ui dependencies

This will install some predefined chat components, which will save us of all the hassle of building the chat for ourselves

npx assistant-ui add thread      
(You may have to update a couple of import adding "type" before the import, seems to be an error with the assistant ui tool)

Initialize Assistant UI

In the App.tsx, replace everything for the following, this will handle the message sent to the backend and will output the messages in the chat

import {
  AssistantRuntimeProvider,
  useLocalRuntime,
  type ChatModelAdapter,
  type ThreadMessage
} from "@assistant-ui/react";
import {Thread} from "./components/assistant-ui/thread";

// API Configuration
const API_BASE_URL = "http://localhost:8008";
const CHAT_ENDPOINT = `${API_BASE_URL}/chat`;

// Error messages
const ERROR_MESSAGES = {
  NO_RESPONSE: "No response received from the chat service",
  NETWORK_ERROR: "Failed to connect to chat service",
  API_ERROR: "Chat service error"
} as const;

/**
 * Extracts the latest user message from the messages array
 */
const getLatestUserMessage = (messages: readonly ThreadMessage[]): string => {
  const lastMessage = messages[messages.length - 1];
  const firstContent = lastMessage?.content?.[0];

  // Check if the content is a text message part
  if (firstContent && firstContent.type === "text") {
    return firstContent.text || "";
  }

  return "";
};

/**
 * Handles API errors and returns user-friendly error messages
 */
const handleApiError = (error: unknown): string => {
  if (error instanceof Error) {
    if (error.name === "AbortError") {
      return "Request was cancelled";
    }
    if (error.message.includes("fetch")) {
      return ERROR_MESSAGES.NETWORK_ERROR;
    }
    return `${ERROR_MESSAGES.API_ERROR}: ${error.message}`;
  }
  return "An unexpected error occurred";
};

/**
 * Creates a chat model adapter that communicates with the backend API
 */
const createChatModelAdapter = (): ChatModelAdapter => ({
  async run({messages, abortSignal}) {
    try {
      const userMessage = getLatestUserMessage(messages);

      const response = await fetch(CHAT_ENDPOINT, {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          message: userMessage
        }),
        signal: abortSignal
      });

      if (!response.ok) {
        throw new Error(`${response.status} ${response.statusText}`);
      }

      const data = await response.json();
      const responseText = data.response || ERROR_MESSAGES.NO_RESPONSE;

      return {
        content: [
          {
            type: "text",
            text: responseText
          }
        ]
      };
    } catch (error) {
      console.error("Chat API error:", error);

      return {
        content: [
          {
            type: "text",
            text: handleApiError(error)
          }
        ]
      };
    }
  }
});

/**
 * Main App component that provides the chat interface
 */
function App() {
  const chatRuntime = useLocalRuntime(createChatModelAdapter());

  return (
    <div className="h-screen w-screen">
      <AssistantRuntimeProvider runtime={chatRuntime}>
        <Thread />
      </AssistantRuntimeProvider>
    </div>
  );
}

export default App;

You should see the following,

chatbot demo with samperalabs explanation

Let's connect !!

Get in touch if you want updates, examples, and insights on how AI agents, Langchain and more are evolving and where they’re going next.