mirror of
https://github.com/we-promise/sure.git
synced 2026-08-05 08:32:15 +00:00
* fix(chat): surface and recover from undelivered assistant responses When the background worker that runs AssistantResponseJob is down or not polling the high_priority queue, the eager "pending" assistant message had no safeguard: chat hung on "Thinking…" forever with no error, timeout, or retry, and the LLM provider was never even called. Add three layers of resilience: - Client watchdog (chat_controller.js): a pending "Thinking…" bubble that waits past a threshold (default 90s) with no response asks the server to fail it. Keyed on the pending marker — which disappears the instant a real response streams — so a slow-but-working response is never falsely timed out. - Server failure capture (Chat#handle_undelivered_response! + MessagesController#report_timeout): clears the dead bubble, records a friendly "the assistant didn't respond" error with Retry, and writes a DebugLogEntry so support can see it in /settings/debug. - Worker liveness signal (BackgroundJobHealth + warning banner): reads Sidekiq's process/queue state directly from Redis in the web process — so a down worker is detectable even though the worker is the thing that's broken — and warns self-hosted users when no worker polls high_priority or it's badly backed up. Fails open so a Sidekiq/Redis blip never blocks chat. Tests: Chat model (3), MessagesController (2), BackgroundJobHealth (5). * fix(chat): address review — server-side timeout guard + watchdog retry safety - Chat#handle_undelivered_response!: gate the state change behind a row lock and a server-side minimum age (UNDELIVERED_RESPONSE_TIMEOUT = 60s). The browser watchdog is untrusted, so re-read the row under lock and only fail a bubble that is still pending AND has genuinely waited past the timeout — never racing a worker that is finishing a slow response. (Codex P2 + CodeRabbit) - chat_controller.js: only mark a report URL as reported on response.ok (fetch resolves on HTTP 4xx/5xx, rejecting only on network errors), and guard concurrent duplicate POSTs with an in-flight set, so a failed report retries instead of stranding the bubble. (CodeRabbit) - _worker_health_warning: drop the unused `chat:` local + its render arg. (CodeRabbit)
43 lines
1.4 KiB
Plaintext
43 lines
1.4 KiB
Plaintext
<div data-controller="chat hotkey">
|
|
<%= turbo_frame_tag chat_frame do %>
|
|
<%= turbo_stream_from @chat %>
|
|
|
|
<h1 class="sr-only"><%= @chat.title %></h1>
|
|
|
|
<div class="flex flex-col h-full">
|
|
<div class="md:p-4">
|
|
<%= render "chats/chat_nav", chat: @chat %>
|
|
|
|
<%= render "chats/worker_health_warning" %>
|
|
|
|
<% if show_demo_warning? %>
|
|
<%= render "shared/demo_warning",
|
|
title: t("chats.demo_banner_title"),
|
|
message: t("chats.demo_banner_message") %>
|
|
<% end %>
|
|
</div>
|
|
|
|
<div id="<%= @chat.messages_target %>" class="grow overflow-y-auto p-4 space-y-6 lg:pb-4" data-chat-target="messages">
|
|
<% if @chat.conversation_messages.any? %>
|
|
<% @chat.conversation_messages.ordered.each do |message| %>
|
|
<%= render message %>
|
|
<% end %>
|
|
<% else %>
|
|
<div class="mt-auto">
|
|
<%= render "chats/ai_greeting", context: "chat" %>
|
|
</div>
|
|
<% end %>
|
|
|
|
<% if @chat.error.present? && @chat.needs_assistant_response? %>
|
|
<%= render "chats/error", chat: @chat %>
|
|
<% end %>
|
|
</div>
|
|
|
|
<%# DESKTOP - Chat form %>
|
|
<div class="px-4 pt-4 lg:pt-0 lg:mt-auto sticky lg:static left-0 bottom-0 w-full bg-surface">
|
|
<%= render "messages/chat_form", chat: @chat %>
|
|
</div>
|
|
</div>
|
|
<% end %>
|
|
</div>
|