《HTMX、WebSockets 与 Hono 的聊天实践探索》

上周我写了一篇有关调整 htmx 以显示即时消息的文章。在使用 HTMX 一周之后,我发觉自己需要更多的功能了。我期望能有更好的办法从服务器流式传输 HTML,并且采用 JSX 组件来代替单纯的 HTML 字符串,以此提升代码的可用性。

温馨提示:倘若你觉得这篇文章有价值,请为它点赞哦!你的支持有助于我创作出更多的内容呢。

我所使用的工具如下:

  • HTMX

  • HTMX Websockets 扩展

  • 后端的 Hono

  • 客户端的 Websockets

我的想法其实非常简单。我的 Conversation 组件被包裹在一个带有 hx-ext="ws" 的 div 之中,当进行渲染时,它就会与我的后端相连接。

export const Conversation = (props: { messages: Message[] }) => (
  <div hx-ext="ws" ws-connect="/chatroom-ws">
      <div id="conversation">
        {props.messages.reverse().map((message) => (
          <div>
            <UserMessage message={message} />
            <AssistantMessage message={message} />
          </div>
        ))}
      </div>
      <InputMessageForm />
  </div>
);

接下来关键的部分是 InputMessageForm。只需要在表单中添加 ws-send,它就会发送一条消息,其中键是 textarea 的 ID(messageInput),值为其内容。

export const InputMessageForm = () => (
  <form id="query-submit-form" ws-send className="relative">
    <textarea
      id="messageInput"
      name="userMessage"
      placeholder="Type your message here..."
      rows={4}
    ></textarea>
    <button type="submit">Send</button>
  </form>
);

Websockets - 服务器

下面是 Hono 服务器的完整代码块。其中有一些用于打开和关闭连接的控制台日志。onMessage 是进行处理的地方。

get(
    '/chatroom-ws',
    upgradeWebSocket((c) => {
      return {
        onOpen: () => {
          console.log('WS Connection open');
        },
        onClose: () => {
          console.log('WS Connection closed');
        },
        onMessage: async (event, ws) => {
          const { userMessage } = JSON.parse(event.data.toString());
          console.log('Got user message', userMessage);
          const inputArea = await c.html(
            <div id="query-submit-form">
              <InputMessageForm />
            </div>,
          );
          ws.send(await inputArea.text());
          const htmlUser = await c.html(
            <div id="conversation" hx-swap-oob="beforeend">
              <UserMessage
                message={{
                  id: v4(), // some random ids used here for placeholder
                  query: userMessage,
                  completion: '',
                  conversationId: v4(),
                  toolsResponse: null,
                  createdAt: new Date(),
                  updatedAt: new Date(),
                }}
              />
            </div>,
          );
          ws.send(await htmlUser.text());
          const response = await talk(userMessage);
          const htmlAgent = await c.html(
            <div id="conversation" hx-swap-oob="beforeend">
              <AssistantMessage message={response} />
            </div>,
          );
          ws.send(await htmlAgent.text());
        },
      };
    }),
);

其流程如下:

  1. 接收查询

  2. 发送回空数据以保持整洁。由于没有指定 hx-swap-oob,所以默认为 True。这就意味着它会找到 ID 为 query-submit-form 的元素并进行替换。

  3. 发送带有用户消息的组件。这里 hx-swap-oob 指定为 beforeend,也就意味着它会被添加到现有消息中。

  4. talk → 这里是你的逻辑部分。因为我在与 AI 助手对话,所以进行了一些外部 API 调用。

  5. 发送带有助手回答的组件。与步骤 3 相同,但组件不同。

我发现的问题:

在发送响应回去时存在一些问题,原因是文档不太容易理解。我觉得有个问题被创建出来是为了解决这个问题的,那就是改进 websocket 扩展的文档。这对我帮助很大!

最为重要的是:

你需要发送回一个字符串,该字符串解析后要具有与要替换的元素相同 ID 的 HTML!

问题 1:

我不小心发送了这样的内容:

JSON.stringify('<div id="someid">test 123</div>')
// '"<div id=\\"someid\\">test 123</div>"'

这是错误的哦。要注意 ID 和转义字符!不要在这里将字符串字符串化。

问题 2:

你可能认为可以返回某些内容,然后它就会在你想要的位置进行替换。但实际情况不完全是这样。第一个 div 只是 HTMX 关于要做什么的信息。至少我是这么理解的。

我返回的 HTML 如下:

<div id="conversation" hx-swap-oob="beforeend">
      <AssistantMessage message={response} />
</div>

​​

它仅仅被追加到客户端的现有 div 中。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻想多巴胺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值