71 lines
2.8 KiB
TypeScript
71 lines
2.8 KiB
TypeScript
import type { KbIngestStateValue } from '../../types/kb_ingest_state.js'
|
|
|
|
/**
|
|
* Decision returned by `decideScanAction` describing what scanAndSyncStorage
|
|
* should do for one file given its current state row (if any), whether Qdrant
|
|
* already has chunks for it, and the global ingest policy.
|
|
*
|
|
* - `skip` — file is in a settled state (already indexed, deliberately not
|
|
* indexed, or in a manual-recovery state); no auto-dispatch.
|
|
* - `dispatch` — file needs to be (re-)embedded; an EmbedFileJob should be
|
|
* dispatched. `createStateRow` indicates whether a new state row needs to
|
|
* be created before dispatch (i.e. first time the scanner has seen it).
|
|
* - `backfill_indexed` — Qdrant has chunks but no state row exists yet
|
|
* (pre-RFC install, or new admin instance pointed at an existing Qdrant
|
|
* volume). Create a row in `indexed` state without re-embedding.
|
|
* - `create_pending` — Manual mode: record that we've seen the file but
|
|
* don't dispatch. Frontend surfaces a per-card "Index" affordance.
|
|
*/
|
|
export type ScanAction =
|
|
| { kind: 'skip' }
|
|
| { kind: 'dispatch'; createStateRow: boolean }
|
|
| { kind: 'backfill_indexed' }
|
|
| { kind: 'create_pending' }
|
|
|
|
export interface KbIngestStateRow {
|
|
state: KbIngestStateValue
|
|
}
|
|
|
|
/**
|
|
* Global auto-index policy stored at KV `rag.defaultIngestPolicy`. Unset is
|
|
* treated as `Always` so existing installs keep their current behavior until
|
|
* the user opts into Manual mode through the KB panel.
|
|
*/
|
|
export type IngestPolicy = 'Always' | 'Manual'
|
|
|
|
/**
|
|
* Decide what scanAndSyncStorage should do for a single embeddable file.
|
|
*
|
|
* Replaces the earlier `!sourcesInQdrant.has(filePath)` binary check, which
|
|
* couldn't tell a fully-indexed file from a stalled mid-batch ingestion, and
|
|
* couldn't honor a user's "browse only" choice. The state row is now the
|
|
* authoritative answer; Qdrant chunk presence is corroborating evidence.
|
|
*/
|
|
export function decideScanAction(
|
|
stateRow: KbIngestStateRow | null,
|
|
hasChunksInQdrant: boolean,
|
|
policy: IngestPolicy = 'Always'
|
|
): ScanAction {
|
|
if (!stateRow) {
|
|
if (hasChunksInQdrant) return { kind: 'backfill_indexed' }
|
|
return policy === 'Always'
|
|
? { kind: 'dispatch', createStateRow: true }
|
|
: { kind: 'create_pending' }
|
|
}
|
|
|
|
switch (stateRow.state) {
|
|
case 'indexed':
|
|
return hasChunksInQdrant ? { kind: 'skip' } : { kind: 'dispatch', createStateRow: false }
|
|
case 'pending_decision':
|
|
// Manual mode: file is waiting for the user to opt in via per-card Index.
|
|
// Always mode: treat as "user-equivalent of auto-index" and dispatch.
|
|
return policy === 'Always'
|
|
? { kind: 'dispatch', createStateRow: false }
|
|
: { kind: 'skip' }
|
|
case 'browse_only':
|
|
case 'failed':
|
|
case 'stalled':
|
|
return { kind: 'skip' }
|
|
}
|
|
}
|