Keep every hook call's response, searchable by the user and the bot
A hook call used to record only how it was taken; its run's answer went to the chat alone and was lost to the call once the job was pruned. Now `bot_hook_events` keeps `response`, `response_message_id` and `responded_at`, written on every call a run took in when that run ends (completed, failed, lost, stopped or cancelled) by `recordHookCallResponse` in the job helpers, and calls turn done or failed. The Calls view searches what calls sent and what runs answered, filters by status and pages; Show all loads one call whole. Bots get `search_hook_calls` and `read_hook_call` for their own hooks (never in a hook call's own run), with payloads always marked as outside data. Calls are kept for the app setting `hook_events_keep_days` (Settings > General, default now 30 days). The caller still only gets 202 Queued. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,48 +1,85 @@
|
||||
import fetchApi from "@moduletrace/twui/utils/fetch/fetchApi";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import { useEffect, useState } from "react";
|
||||
import type { BUN_SQLITE_COBBLE_BOT_HOOK_EVENTS } from "@/db/types/db";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
import type { BotHookEventStatus } from "@/src/dict/bot-hook-dict";
|
||||
import type { BotHookCall } from "@/src/types";
|
||||
|
||||
type Params = {
|
||||
hook_id: number;
|
||||
/**
|
||||
* Words to find in what a call sent or what its run answered.
|
||||
*/
|
||||
query?: string;
|
||||
status?: BotHookEventStatus | "";
|
||||
};
|
||||
|
||||
function callsUrl({ hook_id, query, status, before_id }: Params & { before_id?: number }) {
|
||||
const search = new URLSearchParams({ hook_id: String(hook_id), limit: String(AppData.BotHookCallsPageSize) });
|
||||
|
||||
if (query) search.set("q", query);
|
||||
if (status) search.set("status", status);
|
||||
if (before_id) search.set("before_id", String(before_id));
|
||||
|
||||
return `/api/admin/hooks/hook-events?${search}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* One hook's latest calls, loaded when the view showing them opens.
|
||||
* One hook's calls, newest first, loaded when the view showing them opens
|
||||
* and again when the search or status changes, a page at a time.
|
||||
*/
|
||||
export default function useHookEvents({ hook_id }: Params) {
|
||||
const [events, setEvents] = useState<BUN_SQLITE_COBBLE_BOT_HOOK_EVENTS[]>([]);
|
||||
export default function useHookEvents({ hook_id, query = "", status = "" }: Params) {
|
||||
const [events, setEvents] = useState<BotHookCall[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [more, setMore] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const load = async ({ before_id }: { before_id?: number }) => {
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
|
||||
const res = await fetchApi<{}, APIResponseObject<BotHookCall>>(
|
||||
callsUrl({ hook_id, query, status, before_id }),
|
||||
{ method: "GET" },
|
||||
).catch(() => null);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!res?.success) {
|
||||
setError(res?.msg || `Couldn't load the calls.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const page = res.payload || [];
|
||||
|
||||
setMore(page.length >= AppData.BotHookCallsPageSize);
|
||||
|
||||
return page;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let live = true;
|
||||
|
||||
setLoading(true);
|
||||
setError(undefined);
|
||||
setEvents([]);
|
||||
|
||||
fetchApi<{}, APIResponseObject<BUN_SQLITE_COBBLE_BOT_HOOK_EVENTS>>(
|
||||
`/api/admin/hooks/hook-events?hook_id=${hook_id}`,
|
||||
{ method: "GET" },
|
||||
)
|
||||
.catch(() => null)
|
||||
.then((res) => {
|
||||
if (!live) return;
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (!res?.success) {
|
||||
setError(res?.msg || `Couldn't load the calls.`);
|
||||
return;
|
||||
}
|
||||
|
||||
setEvents(res.payload || []);
|
||||
});
|
||||
load({}).then((page) => {
|
||||
if (live && page) setEvents(page);
|
||||
});
|
||||
|
||||
return () => {
|
||||
live = false;
|
||||
};
|
||||
}, [hook_id]);
|
||||
}, [hook_id, query, status]);
|
||||
|
||||
return { events, loading, error };
|
||||
const loadOlder = async () => {
|
||||
const last = events[events.length - 1];
|
||||
|
||||
if (!last?.id) return;
|
||||
|
||||
const page = await load({ before_id: Number(last.id) });
|
||||
|
||||
if (page) setEvents((current) => [...current, ...page]);
|
||||
};
|
||||
|
||||
return { events, loading, error, more, loadOlder };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user