Files
searchi/apps/desktop/src/semantic-setup-state.ts

55 lines
1.8 KiB
TypeScript

/** Setup and search readiness are independent: media downloads need not block text. */
export type SemanticCoverage = "text_only" | "full_media";
export interface SemanticSetupStatus {
ready: boolean;
generation: number | null;
pending: number;
downloading: boolean;
warming: boolean;
embedding: boolean;
error: string | null;
indexing_ready?: boolean;
text_model_ready?: boolean;
media_model_ready?: boolean;
media_model_error?: string | null;
requested_coverage?: SemanticCoverage;
speech?: {
ready: boolean;
pending: boolean;
requested_policy: string | null;
corpus_revision: number;
};
}
export function semanticIndexReady(status: SemanticSetupStatus) {
return Boolean(
status.ready &&
!status.warming &&
!status.embedding &&
(status.indexing_ready ?? !status.downloading),
);
}
/** Promotion must receive its own automatic attempt even with unchanged counts. */
export function semanticIndexKey(
status: SemanticSetupStatus,
folderRevision: number,
) {
if (status.speech?.ready && status.speech.pending) {
// Failed optional speech can still publish raw media: publication must not
// create an infinite retry loop for the same source corpus and policy.
return `speech:${status.speech.requested_policy}:${status.speech.corpus_revision}:${folderRevision}`;
}
return `${status.generation}:${status.pending}:${folderRevision}:${status.requested_coverage ?? "full_media"}`;
}
export function semanticRetryVisible(status: SemanticSetupStatus) {
return Boolean(status.media_model_error || (status.error && !status.ready));
}
export function semanticMediaReady(status: SemanticSetupStatus) {
// Legacy capture fixtures and older full-pair status did not expose this field.
return status.media_model_ready ?? (status.ready && !status.downloading);
}