mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-17 08:14:33 +00:00
fix(pending-messages): run compaction in background, return 202 Accepted
LLM summarization (30-120s) was blocking HTTP response causing browser timeouts. Now runs in goroutine and returns immediately. UI polls every 5s until completion.
This commit is contained in:
1 parent
06ac35eeb1
commit
bec670ead0
6 files changed
+45
-23
No files matched your search
@@ -151,22 +151,25 @@ func (h *PendingMessagesHandler) handleCompact(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// Detach from HTTP request context so LLM summarization isn't
|
||||
// cancelled when the browser closes the connection.
|
||||
ctx, cancel := context.WithTimeout(context.WithoutCancel(r.Context()), 180*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Run compaction in background so the HTTP response returns immediately.
|
||||
// The long-running LLM call (30-120s) was blocking the response, which
|
||||
// caused browser WebSocket connections to drop (pong timeout).
|
||||
keepRecent := h.keepRecent
|
||||
if keepRecent <= 0 {
|
||||
keepRecent = 15
|
||||
}
|
||||
remaining, err := channels.CompactGroup(ctx, h.store, req.ChannelName, req.HistoryKey, provider, model, keepRecent, h.maxTokens)
|
||||
if err != nil {
|
||||
slog.Warn("compact.failed", "channel", req.ChannelName, "key", req.HistoryKey, "error", err)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{"status": "ok", "method": "summarized", "remaining": remaining})
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 180*time.Second)
|
||||
defer cancel()
|
||||
remaining, err := channels.CompactGroup(ctx, h.store, req.ChannelName, req.HistoryKey, provider, model, keepRecent, h.maxTokens)
|
||||
if err != nil {
|
||||
slog.Warn("compact.failed", "channel", req.ChannelName, "key", req.HistoryKey, "error", err)
|
||||
} else {
|
||||
slog.Info("compact.done", "channel", req.ChannelName, "key", req.HistoryKey, "remaining", remaining)
|
||||
}
|
||||
}()
|
||||
|
||||
writeJSON(w, http.StatusAccepted, map[string]interface{}{"status": "accepted", "method": "summarizing"})
|
||||
}
|
||||
|
||||
// resolveProviderAndModel resolves the LLM provider+model for pending message compaction.
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
"compacted": "Compaction complete",
|
||||
"compactedSummarized": "Summarized to {{count}} messages",
|
||||
"compactedDeleted": "All messages deleted (no LLM provider available)",
|
||||
"compacting": "Compacting…",
|
||||
"compactingDesc": "LLM summarization running in background. List will refresh automatically.",
|
||||
"failedCompact": "Compaction failed",
|
||||
"cleared": "Messages cleared",
|
||||
"failedClear": "Failed to clear messages"
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
"compacted": "Tóm tắt hoàn tất",
|
||||
"compactedSummarized": "Đã tóm tắt còn {{count}} tin nhắn",
|
||||
"compactedDeleted": "Đã xóa tất cả (không có LLM provider)",
|
||||
"compacting": "Đang tóm tắt…",
|
||||
"compactingDesc": "LLM đang tóm tắt ở nền. Danh sách sẽ tự động cập nhật.",
|
||||
"failedCompact": "Tóm tắt thất bại",
|
||||
"cleared": "Đã xóa tin nhắn",
|
||||
"failedClear": "Không thể xóa tin nhắn"
|
||||
|
||||
@@ -41,6 +41,8 @@
|
||||
"compacted": "压缩完成",
|
||||
"compactedSummarized": "已压缩为 {{count}} 条消息",
|
||||
"compactedDeleted": "已删除全部(无可用LLM Provider)",
|
||||
"compacting": "压缩中…",
|
||||
"compactingDesc": "LLM正在后台进行摘要。列表将自动刷新。",
|
||||
"failedCompact": "压缩失败",
|
||||
"cleared": "消息已清除",
|
||||
"failedClear": "清除消息失败"
|
||||
|
||||
@@ -48,13 +48,19 @@ export function usePendingMessages() {
|
||||
"/v1/pending-messages/compact",
|
||||
{ channel_name: channel, history_key: key },
|
||||
);
|
||||
const method = res?.method ?? "summarized";
|
||||
toast.success(
|
||||
i18next.t("pending-messages:toast.compacted"),
|
||||
method === "deleted"
|
||||
? i18next.t("pending-messages:toast.compactedDeleted")
|
||||
: i18next.t("pending-messages:toast.compactedSummarized", { count: res?.remaining ?? 0 }),
|
||||
);
|
||||
const method = res?.method ?? "summarizing";
|
||||
if (method === "deleted") {
|
||||
toast.success(
|
||||
i18next.t("pending-messages:toast.compacted"),
|
||||
i18next.t("pending-messages:toast.compactedDeleted"),
|
||||
);
|
||||
} else {
|
||||
// Backend runs LLM in background — show info toast and let caller poll
|
||||
toast.info(
|
||||
i18next.t("pending-messages:toast.compacting"),
|
||||
i18next.t("pending-messages:toast.compactingDesc"),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
toast.error(
|
||||
|
||||
@@ -45,10 +45,17 @@ export function PendingMessagesPage() {
|
||||
const key = `${group.channel_name}/${group.history_key}`;
|
||||
setActionLoading(key);
|
||||
setCompactingKey(key);
|
||||
await compactGroup(group.channel_name, group.history_key);
|
||||
setCompactingKey(null);
|
||||
setActionLoading(null);
|
||||
loadGroups();
|
||||
const ok = await compactGroup(group.channel_name, group.history_key);
|
||||
if (!ok) {
|
||||
setCompactingKey(null);
|
||||
setActionLoading(null);
|
||||
return;
|
||||
}
|
||||
// Backend runs LLM in background — poll until done (has_summary changes).
|
||||
// Compact button is already disabled when has_summary=true.
|
||||
const poll = setInterval(() => loadGroups(), 5000);
|
||||
setTimeout(() => loadGroups(), 2000); // quick first check
|
||||
setTimeout(() => { clearInterval(poll); setCompactingKey(null); setActionLoading(null); }, 120_000);
|
||||
};
|
||||
|
||||
const handleClear = async (group: PendingMessageGroup) => {
|
||||
|
||||
Reference in new issue
Block a user