mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
Merge branch 'fix/375-generated-narratives' into release/v1.25.0
This commit is contained in:
commit
c0a91dbbcd
19 files changed
+643
-19
No files matched your search
@@ -48,6 +48,11 @@ vi.mock("@/lib/db", () => ({
|
||||
// generation. The test prisma mock has to surface the call so
|
||||
// the cache-invalidation test can assert against it.
|
||||
deleteMany: vi.fn(async () => ({ count: 0 })),
|
||||
// v1.25 — the GET read path consults the briefing-failure marker
|
||||
// (`readBriefingFailure`) and the failure paths append one
|
||||
// (`recordBriefingFailure`). Default to no marker.
|
||||
findFirst: vi.fn(async () => null),
|
||||
create: vi.fn(async () => ({})),
|
||||
},
|
||||
// v1.12.1 — the server-managed consent gate reads the latest active
|
||||
// receipt before egress. These fixtures resolve to an admin-openai
|
||||
|
||||
@@ -61,6 +61,11 @@ import {
|
||||
type InsightResult,
|
||||
} from "@/lib/ai/types";
|
||||
import { AI_BUDGETS } from "@/lib/ai/ai-budgets";
|
||||
import { resolveEffectiveTimeoutMs } from "@/lib/ai/effective-timeout";
|
||||
import {
|
||||
recordBriefingFailure,
|
||||
readBriefingFailure,
|
||||
} from "@/lib/insights/briefing-failure-marker";
|
||||
import { resolveProvider, resolveProviderChain } from "@/lib/ai/provider";
|
||||
import {
|
||||
AllProvidersFailedError,
|
||||
@@ -307,6 +312,15 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
// connect-an-AI-provider hint instead of implying the read is live.
|
||||
const hasProvider = await hasUsableStatusProvider(userId);
|
||||
|
||||
// v1.25 — surface whether the most recent generation attempt failed (a
|
||||
// marker newer than the last successful generation). The briefing keeps its
|
||||
// last good text on failure, so this is the only honest signal that a shown
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
// provider-less account costs one cheap chain-resolve and shows the
|
||||
@@ -351,6 +365,8 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
// the served briefing can never refresh, so the UI pairs its age
|
||||
// with a connect-provider affordance.
|
||||
hasProvider,
|
||||
// v1.25 — true when the last generation attempt failed (held text).
|
||||
generationFailed,
|
||||
});
|
||||
} catch {
|
||||
// Invalid cache row — fall through to the empty payload below. The
|
||||
@@ -370,6 +386,9 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
revalidating,
|
||||
// v1.18.9 (#4) — see the cached branch above.
|
||||
hasProvider,
|
||||
// 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,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -403,9 +422,17 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
heightCm: true,
|
||||
// v1.22 (W6) — first name for the sparse, hash-gated briefing opener.
|
||||
displayName: true,
|
||||
// v1.25 — per-user response-timeout (seconds), threaded onto the
|
||||
// generation calls so a slow self-hosted backend honours the setting.
|
||||
aiResponseTimeoutSeconds: true,
|
||||
},
|
||||
});
|
||||
|
||||
const effectiveTimeoutMs = resolveEffectiveTimeoutMs(
|
||||
dbUser?.aiResponseTimeoutSeconds,
|
||||
AI_BUDGETS.comprehensive.timeoutMs,
|
||||
);
|
||||
|
||||
const locale = await resolveServerLocale({
|
||||
request,
|
||||
userLocale: dbUser?.locale ?? user.locale ?? null,
|
||||
@@ -717,7 +744,8 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
// v1.21.5 — wider upstream budget so the reasoning-heavy briefing
|
||||
// generation is not aborted at the client's 60 s default on large
|
||||
// accounts (which returned an empty briefing + trend narrative).
|
||||
timeoutMs: AI_BUDGETS.comprehensive.timeoutMs,
|
||||
// v1.25 — honours the per-user response-timeout setting.
|
||||
timeoutMs: effectiveTimeoutMs,
|
||||
// The reply is parsed with `JSON.parse` below, so opt the OpenAI /
|
||||
// Codex chains into their strict JSON mode (gated on this flag).
|
||||
responseFormat: "json",
|
||||
@@ -727,6 +755,18 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
workingProviderType = fallback.workingProvider.providerType;
|
||||
fallbackHopCount = fallback.fallbackHops.length;
|
||||
} catch (e) {
|
||||
// v1.25 — record a dated failure marker (no cache row is written on this
|
||||
// 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.
|
||||
void recordBriefingFailure({
|
||||
userId,
|
||||
reason:
|
||||
e instanceof AllProvidersFailedError
|
||||
? "all-providers-failed"
|
||||
: "provider-error",
|
||||
locale,
|
||||
});
|
||||
if (e instanceof AllProvidersFailedError) {
|
||||
annotate({
|
||||
meta: {
|
||||
@@ -826,6 +866,7 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
// breaks `await res.json()` on the client side. 422 stays passthrough
|
||||
// so the React Query mutation can read the JSON body and surface a
|
||||
// readable message. Same fix pattern as v1.4.5 ai/test.
|
||||
void recordBriefingFailure({ userId, reason: "invalid-json", locale });
|
||||
return apiError("AI response was not valid JSON", 422);
|
||||
}
|
||||
|
||||
@@ -856,7 +897,8 @@ export const POST = apiHandler((request: NextRequest) =>
|
||||
temperature: 0.3,
|
||||
maxTokens: 1500,
|
||||
// v1.21.5 — wider upstream budget; see the first generation call.
|
||||
timeoutMs: AI_BUDGETS.comprehensive.timeoutMs,
|
||||
// v1.25 — honours the per-user response-timeout setting.
|
||||
timeoutMs: effectiveTimeoutMs,
|
||||
// Parsed with `JSON.parse` below — opt OpenAI / Codex into JSON mode.
|
||||
responseFormat: "json",
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user