fix(ai): show live reindex progress so the embeddings counter resets to 0 and climbs #242
@@ -5,6 +5,7 @@ import {
|
|||||||
resolveKeyField,
|
resolveKeyField,
|
||||||
nextReindexPollInterval,
|
nextReindexPollInterval,
|
||||||
isReindexComplete,
|
isReindexComplete,
|
||||||
|
isReindexButtonLoading,
|
||||||
} from './ai-provider-settings';
|
} from './ai-provider-settings';
|
||||||
|
|
||||||
describe('resolveCardStatus', () => {
|
describe('resolveCardStatus', () => {
|
||||||
@@ -176,3 +177,49 @@ describe('isReindexComplete', () => {
|
|||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isReindexButtonLoading', () => {
|
||||||
|
it('loads while the POST mutation is pending', () => {
|
||||||
|
expect(
|
||||||
|
isReindexButtonLoading({
|
||||||
|
mutationPending: true,
|
||||||
|
deadline: null,
|
||||||
|
status: false,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does NOT load post-cap: deadline nulled but reindexing left stale-true', () => {
|
||||||
|
// The key case: after the poll cap fires `reindexDeadline` is null while
|
||||||
|
// `settings.reindexing` can be a stale `true` from the last poll. Gating on
|
||||||
|
// the deadline keeps the spinner from sticking forever so the admin can
|
||||||
|
// restart.
|
||||||
|
expect(
|
||||||
|
isReindexButtonLoading({
|
||||||
|
mutationPending: false,
|
||||||
|
deadline: null,
|
||||||
|
status: true,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('loads during an active run within the poll window', () => {
|
||||||
|
expect(
|
||||||
|
isReindexButtonLoading({
|
||||||
|
mutationPending: false,
|
||||||
|
deadline: 10_000,
|
||||||
|
status: true,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not load once the run finished while still polling', () => {
|
||||||
|
expect(
|
||||||
|
isReindexButtonLoading({
|
||||||
|
mutationPending: false,
|
||||||
|
deadline: 10_000,
|
||||||
|
status: false,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -215,6 +215,26 @@ export function isReindexComplete(status?: ReindexStatus): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the reindex button should show its spinner (and stay disabled).
|
||||||
|
*
|
||||||
|
* Spins while the POST is in flight, and for the WHOLE background run while the
|
||||||
|
* server reports `reindexing === true`. The `deadline !== null` gate is the
|
||||||
|
* load-bearing part: once the 120s poll cap fires it nulls `reindexDeadline`
|
||||||
|
* and stops refetching, so `status` (settings?.reindexing) can be a stale
|
||||||
|
* `true` from the last poll. Without the gate the spinner would stick forever
|
||||||
|
* for a run that outlives the cap and block a restart; gating on the active
|
||||||
|
* poll window clears it so the admin can re-trigger.
|
||||||
|
*/
|
||||||
|
export function isReindexButtonLoading(args: {
|
||||||
|
mutationPending: boolean;
|
||||||
|
deadline: number | null;
|
||||||
|
status?: boolean;
|
||||||
|
}): boolean {
|
||||||
|
const { mutationPending, deadline, status } = args;
|
||||||
|
return mutationPending || (deadline !== null && status === true);
|
||||||
|
}
|
||||||
|
|
||||||
// Translate the dot's tooltip label. Kept in one place so all three endpoint
|
// Translate the dot's tooltip label. Kept in one place so all three endpoint
|
||||||
// cards share identical wording.
|
// cards share identical wording.
|
||||||
function cardStatusLabel(status: CardStatus, t: (k: string) => string): string {
|
function cardStatusLabel(status: CardStatus, t: (k: string) => string): string {
|
||||||
@@ -1083,19 +1103,14 @@ export default function AiProviderSettings() {
|
|||||||
// Spin for the WHOLE run: the POST resolves immediately, but the
|
// Spin for the WHOLE run: the POST resolves immediately, but the
|
||||||
// background job keeps running, so also stay loading while the
|
// background job keeps running, so also stay loading while the
|
||||||
// server reports `reindexing` (this also blocks a redundant
|
// server reports `reindexing` (this also blocks a redundant
|
||||||
// re-trigger mid-run; the server de-dupes regardless).
|
// re-trigger mid-run; the server de-dupes regardless). The
|
||||||
//
|
// deadline gate (and why it matters post-cap) lives in
|
||||||
// Gate the `reindexing` part on the active poll window
|
// `isReindexButtonLoading`, which is unit-tested.
|
||||||
// (reindexDeadline !== null): once the 120s poll cap fires it nulls
|
loading={isReindexButtonLoading({
|
||||||
// reindexDeadline and stops refetching, so `settings.reindexing`
|
mutationPending: reindexMutation.isPending,
|
||||||
// can be a stale `true` from the last poll. Without this gate the
|
deadline: reindexDeadline,
|
||||||
// spinner would stay stuck (and the button disabled) forever for a
|
status: settings?.reindexing,
|
||||||
// run that outlives the cap — clearing it here lets the admin
|
})}
|
||||||
// restart.
|
|
||||||
loading={
|
|
||||||
reindexMutation.isPending ||
|
|
||||||
(reindexDeadline !== null && settings?.reindexing === true)
|
|
||||||
}
|
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
reindexMutation.mutate(undefined, {
|
reindexMutation.mutate(undefined, {
|
||||||
// Begin bounded polling so the counter climbs as the async
|
// Begin bounded polling so the counter climbs as the async
|
||||||
|
|||||||
@@ -23,8 +23,12 @@ export function useAiSettingsQuery(
|
|||||||
enabled: boolean = true,
|
enabled: boolean = true,
|
||||||
// While reindexing runs as an async background job, the counter only climbs
|
// While reindexing runs as an async background job, the counter only climbs
|
||||||
// if the client keeps refetching. The component passes a refetchInterval
|
// if the client keeps refetching. The component passes a refetchInterval
|
||||||
// function that polls until indexed === total or a bounded deadline, then
|
// function (`nextReindexPollInterval`) that keeps polling while the server
|
||||||
// returns false to stop. See AiProviderSettings.
|
// reports an active run (reindexing === true) OR we are still within the
|
||||||
|
// bounded deadline and not yet fully indexed; it returns false to stop only
|
||||||
|
// once the run has finished AND indexed >= total, or the deadline cap is hit
|
||||||
|
// (the cap always wins). Note: a transient indexed === total during an active
|
||||||
|
// run does NOT stop polling. See AiProviderSettings.
|
||||||
refetchInterval?:
|
refetchInterval?:
|
||||||
| number
|
| number
|
||||||
| false
|
| false
|
||||||
|
|||||||
Reference in New Issue
Block a user