mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
fix(insights): derive briefing warm budget from the response timeout
The comprehensive briefing warm/cron path wrapped generation in a fixed 130 s outer abort, but the per-user response-timeout setting accepts 10-600 s and the AI settings tell users to raise it for slow local models. Any account whose response timeout was >= ~130 s had every comprehensive warm clipped by the warm bound, not the provider, so the generation never landed: no cached briefing row was written and the dashboard fell to the hard empty state. Size the warm outer abort from the resolved effective timeout (`aiResponseTimeoutSeconds * 1000`, falling back to the comprehensive surface budget) plus a fixed headroom for feature extraction, prompt assembly, and the cache write. A raised setting now extends the warm and nightly budgets instead of being silently capped. An absolute ceiling (the 600 s write-time maximum plus the headroom) keeps a wedged call from pinning the worker while staying above the largest value a user can set. Both the nightly loop and the forced on-visit warm read the per-user setting and use the derived budget. Make the empty state more useful: record the upstream HTTP status on a failure marker and classify it (timeout / auth / rate-limit / provider / format), so the "couldn't generate" state points at the right lever — raise the response timeout for a timeout, re-check the provider for an auth rejection — instead of one generic line. Keep-last-good behaviour is unchanged. Align the response-timeout field copy with the actual default budget (~120 s, not ~180 s) across the placeholder, the OpenAPI description, and the inline note.
This commit is contained in:
1 parent
4266203d3a
commit
4ad4707be9
18 files changed
+426
-41
No files matched your search
@@ -319,8 +319,14 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
// briefing is held rather than freshly refreshed — and, when there is no
|
||||
// last good text, that the empty state should read "couldn't generate" with
|
||||
// a retry rather than the generic "no briefing yet".
|
||||
const generationFailed =
|
||||
(await readBriefingFailure({ userId, since: cachedAt })) !== null;
|
||||
const briefingFailure = await readBriefingFailure({
|
||||
userId,
|
||||
since: cachedAt,
|
||||
});
|
||||
const generationFailed = briefingFailure !== null;
|
||||
// v1.25.3 — the failure class lets the empty state point its hint at the
|
||||
// right lever (raise the response timeout vs re-check the provider).
|
||||
const generationFailureClass = briefingFailure?.failureClass ?? null;
|
||||
|
||||
// Read-only: never block on the provider. Warm out of band only when the
|
||||
// cached briefing is stale / missing AND a provider is configured (a
|
||||
@@ -368,6 +374,9 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
hasProvider,
|
||||
// v1.25 — true when the last generation attempt failed (held text).
|
||||
generationFailed,
|
||||
// v1.25.3 — coarse failure class for the empty-state hint (null when
|
||||
// the last attempt succeeded).
|
||||
generationFailureClass,
|
||||
});
|
||||
} catch {
|
||||
// Invalid cache row — fall through to the empty payload below. The
|
||||
@@ -390,6 +399,8 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
// v1.25 — no last-good text AND the last attempt failed: the UI shows a
|
||||
// "couldn't generate" empty state with a retry instead of the generic one.
|
||||
generationFailed,
|
||||
// v1.25.3 — coarse failure class for the empty-state hint.
|
||||
generationFailureClass,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -760,6 +771,16 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
// path, so the last good briefing stays intact). The read path pairs the
|
||||
// preserved text with a discreet "couldn't refresh" hint, or shows a
|
||||
// retry empty state when there is no last good text.
|
||||
// v1.25.3 — carry the upstream status so the read-path hint can tell an
|
||||
// auth / rate-limit failure apart from a plain timeout. For an
|
||||
// all-providers-failed error the first hop's status is the most
|
||||
// representative; a single provider error exposes its own `httpStatus`.
|
||||
const failureHttpStatus =
|
||||
e instanceof AllProvidersFailedError
|
||||
? (e.attempts[0]?.httpStatus ?? null)
|
||||
: typeof (e as { httpStatus?: number }).httpStatus === "number"
|
||||
? (e as { httpStatus?: number }).httpStatus
|
||||
: null;
|
||||
void recordBriefingFailure({
|
||||
userId,
|
||||
reason:
|
||||
@@ -767,6 +788,7 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
? "all-providers-failed"
|
||||
: "provider-error",
|
||||
locale,
|
||||
httpStatus: failureHttpStatus,
|
||||
});
|
||||
if (e instanceof AllProvidersFailedError) {
|
||||
annotate({
|
||||
|
||||
Reference in new issue
Block a user