Introducing the idea of Web Handlers in ChatCraft
Tags: webdev, opensource, react, programming
Published: 2024-03-24 17:23:52

Have you ever considered the concept of a Chatbot that not only enables interaction with [Language Models](https://www.cloudflare.com/en-ca/learning/ai/what-is-large-language-model/) (LLMs), but also offers the capability to integrate your own microservices for the most personalized experience imaginable?

This week, I came across [an issue](https://github.com/tarasglek/chatcraft.org/issues/507) in [ChatCraft](https://chatcraft.org/) that was asking for something similar.

In this post, I'll discuss how I was able to design and implement an initial version of this feature.

## Table of Contents
&nbsp;1. [Background 💭](#background)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.1. [Spin Off 🧐](#spin-off)
&nbsp;2. [Implementation 🧑🏻‍💻](#implementation)
&nbsp;3. [Upcoming 🔮](#upcoming)

## Background 💭

I've been working on ChatCraft for a couple of months now and have already written [many posts](https://dev.to/amnish04/series/26122) about it. It comes with many cool features apart from what a regular AI Chatbot offers, like ability to define custom [ChatCraft Functions](https://chatcraft.org/f/new), in-built **utility commands** (use `/commands` to get a list), text-to-speech, and many more.

This is related to `/import` command (an existing feature) that allows you to make request to any URL and loads the text response as a system chat message.

![Example Command](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbf9r5e3tfzouixdczt2.png)

**Result:**

![Import Result 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kg63ai4pgtsxiag1z8ih.png)

![Import Result 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7n2mqt9l9swi5vqzt75z.png)

### Spin Off 🧐

The idea is to build something that extends this functionality.

Here's how it would work:

1. We can allow the users to register various **Web Handlers** with a **match pattern**.
2. If the message matches any of the registered web handlers, an HTTP request is sent to the Web Handlers's **handler url** based on the specified configuration.
3. The received response is streamed into a new **Chat Message** in [Markdown](https://en.wikipedia.org/wiki/Markdown) format.

This might sound a little confusing, so let's try to understand with an example.

Let's say you want the YouTube URLs to be handled in a certain way. You can write your own service or a [serverless cloud function](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) for processing. Then configure a **ChatCraft Web Handler** matching YouTube URL chat messages (use match pattern) that makes a request to your service for custom functionality.
This would allow you to **plug and play** any custom behaviour in ChatCraft that is not supported by default. And if that feature becomes popular, maybe we can add if by default in ChatCraft 😉

## Implementation 🧑🏻‍💻

To implement an initial version of this concept, I started by designing a **Web Handler** class defining the model and behaviour of this new entity.

The model consists of 3 main properties - `handlerUrl`, `request method` and `matchPattern`.

```ts
export class WebHandler {
  handlerUrl: string;
  method: HttpMethod;
  matchPattern: RegExp;

  constructor({
    handlerUrl,
    method,
    matchPattern,
  }: {
    handlerUrl: string;
    method: HttpMethod;
    matchPattern: RegExp;
  }) {
    this.handlerUrl = handlerUrl;
    this.method = method;
    this.matchPattern = matchPattern;
  }
  ...
  ...
  ...
}

export enum HttpMethod {
  CONNECT = "CONNECT",
  DELETE = "DELETE",
  GET = "GET",
  HEAD = "HEAD",
  OPTIONS = "OPTIONS",
  PATCH = "PATCH",
  POST = "POST",
  PUT = "PUT",
  TRACE = "TRACE",
}
```

and some methods defining its behavior

```ts
export class WebHandler {
  ...
  ...
  isMatchingHandler(message: string) {
    return this.matchPattern.test(message);
  }

  async executeHandler(message: string): Promise<string> {
    const requestUrl = new URL(this.handlerUrl);

    const params = new URLSearchParams();
    params.append("url", message);

    requestUrl.search = params.toString();

    const response = await fetch(requestUrl, {
      method: this.method,
    });

    const content = (await response.text()).trim();

    const resultHeader = `**Web Handler**: [${this.handlerUrl}](${this.handlerUrl})?url=[${message}](${message})`;
    const text = `${resultHeader}\n\n` + content;

    return text;
  }

  static getMatchingHandler(message: string): WebHandler | null {
    return (
      this.getRegisteredHandlers().find((handler) => handler.isMatchingHandler(message)) ?? null
    );
  }

  static getRegisteredHandlers(): WebHandler[] {
    // TODO: Fetch from localStorage

    const supportedHandlers = [
      new WebHandler({
        handlerUrl: "https://taras-scrape2md.web.val.run/",
        method: HttpMethod.GET,
        matchPattern: /^https:\/\/\S+$/,
      }),
    ];

    return supportedHandlers;
  }
```

This is a pretty simple model, and currently I just have a generic handler for all `https` urls that redirects to a service written by **Taras** (the product owner) i.e. https://taras-scrape2md.web.val.run/.

Here's a detailed blog post for this tool
https://www.val.town/v/taras/scrape2md

With Web Handlers, we can now use the power of this tool in ChatCraft itself. Isn't that cool?

## Upcoming 🔮

There's still a lot of potential in this idea that needs to be harnessed. I am thinking of introducing a custom UI for registering and configuring these web handlers, just like we currently do with [ChatCraft Functions](https://chatcraft.org/f/new).

![ChatCraft Functions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nr125hz1nmkfu2cmippo.png)

I am pretty sure, there are things I can do better and maybe a better direction I can take this in. This concept is still too young for [ChatCraft](https://github.com/tarasglek/chatcraft.org) and if you guys have any suggestions, let me know in comments.

I'll follow up with another post soon!