如何使用 htmx 礼貌地关闭 SSE 事件流?

pax*_*dae 5 htmx

我正在尝试使用 htmx 设置一个短暂的事件流处理程序。想象一下,例如,流式传输 chatgpt 响应或类似内容。该流将持续 10-30 秒,然后我预计它会耗尽。

有没有办法让 htmx 监听 SSE 事件类型并关闭 SSE 源作为响应?

我有一个笨重的解决方法,我有一个用于流终止事件类型的 htmx 侦听器,然后该侦听器向仅返回一个空 div 的端点发出请求,并将该 div 交换为原始侦听器,以防止它无休止地尝试重新连接到 SSE 源。

但我觉得我一定错过了一些东西,因为这看起来不太优雅。

这就是我现在拥有的,如下。它有效(大部分),但是有更好的吗?谢谢!

<div
  id="chat-sse-listener"
  hx-ext="sse"
  sse-connect="{% url 'stream_test' %}"
>

  {# This listens for the next token in the stream and appends it to the chat. #}
  <div
    sse-swap="message"
    hx-target="#chat-message"
    hx-swap="beforeend"
  ></div>

  {# This listens for my custom EndOfStream SSE event and awkwardly replaces the entirely SSE listener with an empty div. #}
  <div
    hx-trigger="sse:EndOfStream"
    hx-target="#chat-sse-listener"
    hx-swap="outerHTML"
    hx-get="{% url 'empty_div_response' %}"
  ></div>

</div>

<div id="chat-message"></div>
Run Code Online (Sandbox Code Playgroud)

更新:

经过一番尝试后,我有了一个稍微不那么尴尬(但仍然不是很好)的解决方案。我没有使用第二个侦听器和第二个事件类型,而是通过让服务器使用 htmx 的带外交换 API 传递 div 来结束流。然后使用该 div 来替换(从而删除)sse 侦听器。

代码:

    async def __anext__(self):
        await asyncio.sleep(0.25)

        if self.story_tokens_queue.empty():
            if self.sent_close_token:
                raise StopAsyncIteration
            else:
                self.sent_close_token = True
                # this div will be swapped, out-of-band, for the existing #chat-see-listener, effectively removing it
                # and closing the SSE source on the client side
                return 'data: <div id="chat-sse-listener" hx-swap-oob="true"></div>\n\n'
        else:
            token = self.story_tokens_queue.get().replace("\n", "<br/>")
            return f"data: {token}\n\n"
Run Code Online (Sandbox Code Playgroud)

活动部件较少,但不如明确要求客户端关闭源那么清晰。我确信此时认为这需要额外的 JavaScript。

最后的后续工作 这是我在业余项目的制作过程中要做的事情。目前有效!

我的后端代码将其作为 SSE 流发送的最后一个令牌:

            # second, we close the SSE listener on the client side
            elif not self.sent_close_token:
                self.sent_close_token = True
                # this div will be swapped, out-of-band, for the existing #chat-see-listener, effectively removing
                # it and closing the SSE source on the client side
                return 'data: <div id="chat-sse-listener" hx-swap-oob="true"></div>\n\n'
Run Code Online (Sandbox Code Playgroud)

我的 HTML 看起来像这样:

<div
  id="chat-sse-listener"
  hx-ext="sse"
  sse-connect="{% url 'chat_request_answer' id=chat_thread.id %}"
>

  {# This listens for the next token in the stream and appends it to the chat. #}
  <div
    sse-swap="message"
    hx-target="#sage-answer-{{ new_answer_id }}"
    hx-swap="beforeend"
  ></div>

</div>
Run Code Online (Sandbox Code Playgroud)

我正在使用 htmx 1.9.6。

这种设置似乎对我有用。