videobook-engine 4.2.0 → 5.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -39
- package/dist/artifacts.d.ts +5 -10
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +79 -142
- package/dist/artifacts.js.map +1 -1
- package/dist/books.d.ts +1 -1
- package/dist/books.d.ts.map +1 -1
- package/dist/books.js +16 -9
- package/dist/books.js.map +1 -1
- package/dist/cas.d.ts +2 -1
- package/dist/cas.d.ts.map +1 -1
- package/dist/cas.js +33 -4
- package/dist/cas.js.map +1 -1
- package/dist/catalog-gc-state.d.ts +4 -0
- package/dist/catalog-gc-state.d.ts.map +1 -0
- package/dist/catalog-gc-state.js +49 -0
- package/dist/catalog-gc-state.js.map +1 -0
- package/dist/catalog-metadata.d.ts +2 -2
- package/dist/catalog-metadata.js +3 -3
- package/dist/catalog-metadata.js.map +1 -1
- package/dist/context.d.ts +5 -5
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +17 -49
- package/dist/context.js.map +1 -1
- package/dist/domain.d.ts +35 -2
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +131 -161
- package/dist/domain.js.map +1 -1
- package/dist/edits.js +2 -2
- package/dist/edits.js.map +1 -1
- package/dist/engine-types.d.ts +159 -39
- package/dist/engine-types.d.ts.map +1 -1
- package/dist/engine-types.js +2 -1
- package/dist/engine-types.js.map +1 -1
- package/dist/engine.d.ts +30 -20
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +46 -4
- package/dist/engine.js.map +1 -1
- package/dist/files.d.ts +10 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +49 -2
- package/dist/files.js.map +1 -1
- package/dist/fork.d.ts +14 -30
- package/dist/fork.d.ts.map +1 -1
- package/dist/fork.js +18 -27
- package/dist/fork.js.map +1 -1
- package/dist/generations.d.ts +9 -0
- package/dist/generations.d.ts.map +1 -0
- package/dist/generations.js +175 -0
- package/dist/generations.js.map +1 -0
- package/dist/history.d.ts +2 -1
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +30 -57
- package/dist/history.js.map +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/inline-temporal-models.d.ts +43 -0
- package/dist/inline-temporal-models.d.ts.map +1 -0
- package/dist/inline-temporal-models.js +236 -0
- package/dist/inline-temporal-models.js.map +1 -0
- package/dist/isolated-models.d.ts +8 -0
- package/dist/isolated-models.d.ts.map +1 -0
- package/dist/isolated-models.js +25 -0
- package/dist/isolated-models.js.map +1 -0
- package/dist/job-queue.d.ts +1 -1
- package/dist/job-queue.d.ts.map +1 -1
- package/dist/job-queue.js +20 -6
- package/dist/job-queue.js.map +1 -1
- package/dist/media-image.d.ts +8 -0
- package/dist/media-image.d.ts.map +1 -0
- package/dist/media-image.js +41 -0
- package/dist/media-image.js.map +1 -0
- package/dist/media-process.d.ts +13 -0
- package/dist/media-process.d.ts.map +1 -0
- package/dist/media-process.js +89 -0
- package/dist/media-process.js.map +1 -0
- package/dist/media.d.ts +3 -3
- package/dist/media.d.ts.map +1 -1
- package/dist/media.js +5 -5
- package/dist/media.js.map +1 -1
- package/dist/merge-policy.d.ts +16 -36
- package/dist/merge-policy.d.ts.map +1 -1
- package/dist/merge-policy.js +13 -125
- package/dist/merge-policy.js.map +1 -1
- package/dist/metadata.d.ts +1 -0
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +13 -3
- package/dist/metadata.js.map +1 -1
- package/dist/migrate-grid-text.d.ts +17 -0
- package/dist/migrate-grid-text.d.ts.map +1 -0
- package/dist/migrate-grid-text.js +117 -0
- package/dist/migrate-grid-text.js.map +1 -0
- package/dist/migrate-grid-v22.d.ts +12 -0
- package/dist/migrate-grid-v22.d.ts.map +1 -0
- package/dist/migrate-grid-v22.js +166 -0
- package/dist/migrate-grid-v22.js.map +1 -0
- package/dist/migrate-grid-v23.d.ts +10 -0
- package/dist/migrate-grid-v23.d.ts.map +1 -0
- package/dist/migrate-grid-v23.js +31 -0
- package/dist/migrate-grid-v23.js.map +1 -0
- package/dist/migration-notebooks.d.ts +25 -0
- package/dist/migration-notebooks.d.ts.map +1 -0
- package/dist/migration-notebooks.js +131 -0
- package/dist/migration-notebooks.js.map +1 -0
- package/dist/migration-source.d.ts +6 -0
- package/dist/migration-source.d.ts.map +1 -0
- package/dist/migration-source.js +37 -0
- package/dist/migration-source.js.map +1 -0
- package/dist/migration-timeline.d.ts +36 -0
- package/dist/migration-timeline.d.ts.map +1 -0
- package/dist/migration-timeline.js +187 -0
- package/dist/migration-timeline.js.map +1 -0
- package/dist/migration-validation.d.ts +12 -0
- package/dist/migration-validation.d.ts.map +1 -0
- package/dist/migration-validation.js +68 -0
- package/dist/migration-validation.js.map +1 -0
- package/dist/migration.d.ts.map +1 -1
- package/dist/migration.js +220 -119
- package/dist/migration.js.map +1 -1
- package/dist/model-cache-paths.d.ts +2 -0
- package/dist/model-cache-paths.d.ts.map +1 -0
- package/dist/model-cache-paths.js +5 -0
- package/dist/model-cache-paths.js.map +1 -0
- package/dist/model-checksums.json +338 -0
- package/dist/model-file-resolver.d.ts +30 -0
- package/dist/model-file-resolver.d.ts.map +1 -0
- package/dist/model-file-resolver.js +304 -0
- package/dist/model-file-resolver.js.map +1 -0
- package/dist/model-identity.d.ts +13 -0
- package/dist/model-identity.d.ts.map +1 -0
- package/dist/model-identity.js +44 -0
- package/dist/model-identity.js.map +1 -0
- package/dist/model-worker-errors.d.ts +3 -0
- package/dist/model-worker-errors.d.ts.map +1 -0
- package/dist/model-worker-errors.js +12 -0
- package/dist/model-worker-errors.js.map +1 -0
- package/dist/model-worker-pool.d.ts +32 -0
- package/dist/model-worker-pool.d.ts.map +1 -0
- package/dist/model-worker-pool.js +269 -0
- package/dist/model-worker-pool.js.map +1 -0
- package/dist/model-worker-protocol.d.ts +42 -0
- package/dist/model-worker-protocol.d.ts.map +1 -0
- package/dist/model-worker-protocol.js +2 -0
- package/dist/model-worker-protocol.js.map +1 -0
- package/dist/model-worker.d.ts +2 -0
- package/dist/model-worker.d.ts.map +1 -0
- package/dist/model-worker.js +91 -0
- package/dist/model-worker.js.map +1 -0
- package/dist/mvp-contract-fixtures.js +1 -1
- package/dist/mvp-contract-fixtures.js.map +1 -1
- package/dist/mvp-contracts.d.ts +32 -8
- package/dist/mvp-contracts.d.ts.map +1 -1
- package/dist/mvp-contracts.js +1 -1
- package/dist/mvp-contracts.js.map +1 -1
- package/dist/notebook/types.d.ts +1 -29
- package/dist/notebook/types.d.ts.map +1 -1
- package/dist/notebook-grid.d.ts +46 -0
- package/dist/notebook-grid.d.ts.map +1 -0
- package/dist/notebook-grid.js +187 -0
- package/dist/notebook-grid.js.map +1 -0
- package/dist/notebook-media-artifact.d.ts +23 -0
- package/dist/notebook-media-artifact.d.ts.map +1 -0
- package/dist/notebook-media-artifact.js +67 -0
- package/dist/notebook-media-artifact.js.map +1 -0
- package/dist/notebook-mentions.d.ts +20 -0
- package/dist/notebook-mentions.d.ts.map +1 -0
- package/dist/notebook-mentions.js +54 -0
- package/dist/notebook-mentions.js.map +1 -0
- package/dist/runtime-services.js +4 -9
- package/dist/runtime-services.js.map +1 -1
- package/dist/schema.d.ts +4 -19
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +66 -92
- package/dist/schema.js.map +1 -1
- package/dist/search-provider-access.d.ts +11 -0
- package/dist/search-provider-access.d.ts.map +1 -0
- package/dist/search-provider-access.js +42 -0
- package/dist/search-provider-access.js.map +1 -0
- package/dist/similarity.d.ts +60 -1
- package/dist/similarity.d.ts.map +1 -1
- package/dist/similarity.js +174 -154
- package/dist/similarity.js.map +1 -1
- package/dist/status.js +8 -10
- package/dist/status.js.map +1 -1
- package/dist/store.d.ts +16 -2
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +171 -17
- package/dist/store.js.map +1 -1
- package/dist/temporal-index-cache.d.ts +13 -0
- package/dist/temporal-index-cache.d.ts.map +1 -0
- package/dist/temporal-index-cache.js +59 -0
- package/dist/temporal-index-cache.js.map +1 -0
- package/dist/temporal-model-manifests.d.ts +17 -0
- package/dist/temporal-model-manifests.d.ts.map +1 -0
- package/dist/temporal-model-manifests.js +33 -0
- package/dist/temporal-model-manifests.js.map +1 -0
- package/dist/temporal-models.d.ts +20 -35
- package/dist/temporal-models.d.ts.map +1 -1
- package/dist/temporal-models.js +23 -257
- package/dist/temporal-models.js.map +1 -1
- package/dist/temporal-search.d.ts +6 -3
- package/dist/temporal-search.d.ts.map +1 -1
- package/dist/temporal-search.js +438 -122
- package/dist/temporal-search.js.map +1 -1
- package/dist/temporal-vector-index.d.ts +46 -0
- package/dist/temporal-vector-index.d.ts.map +1 -0
- package/dist/temporal-vector-index.js +220 -0
- package/dist/temporal-vector-index.js.map +1 -0
- package/dist/third-party/@huggingface-jinja.LICENSE +21 -0
- package/dist/third-party/@huggingface-tokenizers.LICENSE +201 -0
- package/dist/third-party/@huggingface-transformers.LICENSE +202 -0
- package/dist/third-party/onnxruntime-common.LICENSE +21 -0
- package/dist/third-party/versions.json +23 -0
- package/dist/transformers-runtime.d.ts +2 -0
- package/dist/transformers-runtime.d.ts.map +1 -0
- package/dist/transformers-runtime.js +33014 -0
- package/dist/transformers-runtime.js.LEGAL.txt +12 -0
- package/dist/transformers-runtime.js.map +7 -0
- package/docs/doltlite-staging.md +69 -0
- package/docs/edit-performance.md +83 -0
- package/docs/engine-layout.md +56 -50
- package/docs/media-limits.md +57 -0
- package/docs/model-download-policy.md +15 -0
- package/docs/model-integrity.md +72 -0
- package/docs/model-isolation.md +72 -0
- package/docs/model-revisions.md +54 -0
- package/docs/mvp-prd.md +2 -2
- package/docs/release-evidence.md +300 -0
- package/docs/search-provider-consent.md +67 -0
- package/docs/semantic-durability.md +55 -0
- package/docs/temporal-search-performance.md +260 -0
- package/docs/v4-migration.md +83 -0
- package/fixtures/v5/contract-fixtures.json +4 -4
- package/package.json +31 -13
- package/dist/artifact-names.d.ts +0 -6
- package/dist/artifact-names.d.ts.map +0 -1
- package/dist/artifact-names.js +0 -56
- package/dist/artifact-names.js.map +0 -1
- package/dist/resolver.d.ts +0 -15
- package/dist/resolver.d.ts.map +0 -1
- package/dist/resolver.js +0 -81
- package/dist/resolver.js.map +0 -1
package/dist/temporal-search.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
|
+
import { guardSearchProvider } from "./search-provider-access.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { normalizeSourcePoint, normalizeSourceRange, rationalEquals, sourceRangeEndTick, } from "./mvp-time.js";
|
|
3
5
|
import { resultOf, syncResultOf } from "./context.js";
|
|
4
6
|
import { canonicalJson, EngineFault, parseJson } from "./store.js";
|
|
7
|
+
import { TemporalVectorIndex } from "./temporal-vector-index.js";
|
|
8
|
+
const retrievalStates = new WeakMap();
|
|
9
|
+
const VECTOR_CANDIDATES = 1_000;
|
|
10
|
+
const continuityPrefixes = new WeakMap();
|
|
11
|
+
const SEGMENT_SELECT = `SELECT s.segment_id, s.artifact_id, a.label AS artifact_label,
|
|
12
|
+
a.kind AS artifact_kind, a.created_at AS artifact_created_at,
|
|
13
|
+
s.stream_id, s.object_hash, s.source_range_json, s.source_path, s.segment_kind,
|
|
14
|
+
s.representative_tick, s.generation, s.manifest_id
|
|
15
|
+
FROM runtime_media_segments s JOIN artifacts a ON a.artifact_id=s.artifact_id`;
|
|
16
|
+
export function clearTemporalSearchCache(context) {
|
|
17
|
+
const state = retrievalStates.get(context);
|
|
18
|
+
retrievalStates.delete(context);
|
|
19
|
+
for (const index of state?.vectors.values() ?? []) {
|
|
20
|
+
try {
|
|
21
|
+
index.dispose();
|
|
22
|
+
}
|
|
23
|
+
catch { /* Cache writes cannot prevent closing the catalog. */ }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
5
26
|
const INDEX_PHASES = [
|
|
6
27
|
"probe",
|
|
7
28
|
"segment",
|
|
@@ -14,13 +35,28 @@ const INDEX_PHASES = [
|
|
|
14
35
|
];
|
|
15
36
|
export function createTemporalSearchApi(context) {
|
|
16
37
|
const providers = new Map();
|
|
38
|
+
const registrations = new Map();
|
|
17
39
|
return {
|
|
18
40
|
providers: {
|
|
19
|
-
register: (provider) => {
|
|
20
|
-
requiredText(provider.manifestId, "Provider manifest ID");
|
|
21
|
-
|
|
41
|
+
register: (provider, consent = {}) => {
|
|
42
|
+
const id = requiredText(provider.manifestId, "Provider manifest ID");
|
|
43
|
+
const existing = registrations.get(id);
|
|
44
|
+
if (existing?.matches(provider, consent)) {
|
|
45
|
+
existing.validate();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
let registered = false;
|
|
49
|
+
const guarded = guardSearchProvider(provider, consent, () => !registered || providers.get(id) === guarded.provider);
|
|
50
|
+
guarded.validate();
|
|
51
|
+
providers.set(id, guarded.provider);
|
|
52
|
+
registrations.set(id, guarded);
|
|
53
|
+
registered = true;
|
|
22
54
|
},
|
|
23
55
|
list: () => [...providers.keys()].sort(),
|
|
56
|
+
unregister: (manifestId) => {
|
|
57
|
+
registrations.delete(manifestId);
|
|
58
|
+
return providers.delete(manifestId);
|
|
59
|
+
},
|
|
24
60
|
},
|
|
25
61
|
manifests: {
|
|
26
62
|
register: (manifest) => syncResultOf(() => registerManifest(context, manifest)),
|
|
@@ -31,8 +67,9 @@ export function createTemporalSearchApi(context) {
|
|
|
31
67
|
commitBatch: (input) => syncResultOf(() => commitIndexBatch(context, input)),
|
|
32
68
|
activate: (manifestId, generation) => syncResultOf(() => activateGeneration(context, manifestId, generation)),
|
|
33
69
|
coverage: (artifact) => syncResultOf(() => searchCoverage(context, artifact)),
|
|
34
|
-
|
|
35
|
-
|
|
70
|
+
prepare: (options = {}) => context.withLocalMedia(() => resultOf(() => prepareTemporalIndexes(context, options))),
|
|
71
|
+
query: (query) => context.withLocalMedia(() => queryTemporalIndex(context, providers, query)),
|
|
72
|
+
queryPrepared: (query, reference, options = {}) => context.withLocalMedia(() => queryTemporalIndex(context, providers, query, { reference, options })),
|
|
36
73
|
invalidate: (artifact, objectHash) => syncResultOf(() => invalidateCoverage(context, artifact, objectHash)),
|
|
37
74
|
cleanup: () => syncResultOf(() => cleanupRetired(context)),
|
|
38
75
|
stats: () => temporalStats(context),
|
|
@@ -226,6 +263,11 @@ function commitIndexBatch(context, input) {
|
|
|
226
263
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
227
264
|
.run(batchKey, input.artifactId, input.objectHash, input.manifestId, input.generation, input.phase, input.cursor ?? null, canonicalJson(result), now);
|
|
228
265
|
});
|
|
266
|
+
const cache = retrievalStates.get(context);
|
|
267
|
+
if (cache?.generations.has(input.generation)) {
|
|
268
|
+
for (const observation of observations)
|
|
269
|
+
cache.pending.add(observation.segmentId);
|
|
270
|
+
}
|
|
229
271
|
return result;
|
|
230
272
|
}
|
|
231
273
|
function normalizeObservation(context, manifest, batch, input, index, streamCache) {
|
|
@@ -445,7 +487,8 @@ async function queryTemporalIndex(context, providers, query, prepared) {
|
|
|
445
487
|
const active = activeGenerations(context);
|
|
446
488
|
const generationKey = hashJson(active.map((item) => [item.embedding_space, item.generation]));
|
|
447
489
|
const offset = decodeCursor(query.cursor, generationKey);
|
|
448
|
-
const
|
|
490
|
+
const orderedVideo = prepared?.reference.kind === "video" || query.reference?.kind === "video";
|
|
491
|
+
const segments = querySegments(context, active, orderedVideo ? { ...query, durationMs: undefined } : query);
|
|
449
492
|
const candidates = new Map(segments.map((segment) => [
|
|
450
493
|
segment.segment_id,
|
|
451
494
|
{ segment, signals: [], score: 0 },
|
|
@@ -468,7 +511,8 @@ async function queryTemporalIndex(context, providers, query, prepared) {
|
|
|
468
511
|
.filter((candidate) => (!query.sourceArtifactIds
|
|
469
512
|
|| query.sourceArtifactIds.includes(candidate.segment.artifact_id))
|
|
470
513
|
&& (!query.artifactKinds
|
|
471
|
-
|| query.artifactKinds.includes(candidate.segment.artifact_kind))
|
|
514
|
+
|| query.artifactKinds.includes(candidate.segment.artifact_kind))
|
|
515
|
+
&& segmentMatchesDuration(candidate.segment, query.durationMs))
|
|
472
516
|
.map((candidate) => ({
|
|
473
517
|
...candidate,
|
|
474
518
|
signals: candidate.signals.filter((signal) => allowsSignal(query.modalities, signal.kind)),
|
|
@@ -519,22 +563,200 @@ function activeGenerations(context) {
|
|
|
519
563
|
ORDER BY embedding_space, generation`)
|
|
520
564
|
.all();
|
|
521
565
|
}
|
|
566
|
+
async function prepareTemporalIndexes(context, options) {
|
|
567
|
+
const active = activeGenerations(context);
|
|
568
|
+
const selected = active.filter((generation) => (!options.manifestId || generation.manifest_id === options.manifestId)
|
|
569
|
+
&& (!options.generation || generation.generation === options.generation));
|
|
570
|
+
if ((options.manifestId || options.generation) && selected.length === 0) {
|
|
571
|
+
throw new EngineFault({ code: "NOT_READY", message: "Activate a generation before preparing its search index" });
|
|
572
|
+
}
|
|
573
|
+
const state = retrievalState(context, active, false);
|
|
574
|
+
const result = { indexes: 0, vectors: 0, updatedVectors: 0, loadedIndexes: 0, persistedIndexes: 0 };
|
|
575
|
+
for (const generation of selected) {
|
|
576
|
+
for (const modality of requiredManifest(context, generation.manifest_id).modalities) {
|
|
577
|
+
if (modality === "metadata")
|
|
578
|
+
continue;
|
|
579
|
+
const prepared = await vectorIndex(context, generation, modality, state).prepare(options.signal, options.checkpoint === "periodic");
|
|
580
|
+
result.indexes++;
|
|
581
|
+
result.vectors += prepared.vectors;
|
|
582
|
+
result.updatedVectors += prepared.updatedVectors;
|
|
583
|
+
result.loadedIndexes += Number(prepared.loaded);
|
|
584
|
+
result.persistedIndexes += Number(prepared.persisted);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
function retrievalState(context, active = activeGenerations(context), refreshMetadata = true) {
|
|
590
|
+
const generationKey = canonicalJson(active.map((item) => [item.generation, item.embedding_space, item.manifest_id]));
|
|
591
|
+
let state = retrievalStates.get(context);
|
|
592
|
+
if (!state || state.generationKey !== generationKey) {
|
|
593
|
+
const generations = [...new Set(active.map((item) => item.generation))];
|
|
594
|
+
const rows = generations.length === 0 ? [] : context.store.db.prepare(`${SEGMENT_SELECT} WHERE s.generation IN (${generations.map(() => "?").join(",")}) ORDER BY s.segment_id`).all(...generations);
|
|
595
|
+
const segments = new Map(rows.map((row) => [row.segment_id, row]));
|
|
596
|
+
const vectors = state?.vectors ?? new Map();
|
|
597
|
+
for (const [key, index] of vectors) {
|
|
598
|
+
const [generation, space] = JSON.parse(key);
|
|
599
|
+
if (!active.some((item) => item.generation === generation && item.embedding_space === space)) {
|
|
600
|
+
try {
|
|
601
|
+
index.dispose();
|
|
602
|
+
}
|
|
603
|
+
catch { /* Retired cache persistence is optional. */ }
|
|
604
|
+
vectors.delete(key);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
for (const [segmentId, entry] of index.entries) {
|
|
608
|
+
const segment = segments.get(segmentId);
|
|
609
|
+
if (segment)
|
|
610
|
+
entry.value.segment = segment;
|
|
611
|
+
else
|
|
612
|
+
index.delete(segmentId);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
state = {
|
|
616
|
+
semanticVersion: context.store.semanticVersion, generationKey, generations: new Set(generations),
|
|
617
|
+
segments, vectors, ordered: new Map(), pending: state?.pending ?? new Set(),
|
|
618
|
+
};
|
|
619
|
+
retrievalStates.set(context, state);
|
|
620
|
+
}
|
|
621
|
+
if (refreshMetadata && state.semanticVersion !== context.store.semanticVersion) {
|
|
622
|
+
const generations = [...state.generations];
|
|
623
|
+
const rows = generations.length === 0 ? [] : context.store.db.prepare(`${SEGMENT_SELECT} WHERE s.generation IN (${generations.map(() => "?").join(",")}) ORDER BY s.segment_id`).all(...generations);
|
|
624
|
+
state.segments = new Map(rows.map((row) => [row.segment_id, row]));
|
|
625
|
+
for (const [key, index] of state.vectors) {
|
|
626
|
+
for (const [segmentId, entry] of index.entries) {
|
|
627
|
+
const segment = state.segments.get(segmentId);
|
|
628
|
+
if (segment)
|
|
629
|
+
entry.value.segment = segment;
|
|
630
|
+
else
|
|
631
|
+
index.delete(segmentId);
|
|
632
|
+
}
|
|
633
|
+
if (index.entries.size === 0) {
|
|
634
|
+
try {
|
|
635
|
+
index.dispose();
|
|
636
|
+
}
|
|
637
|
+
catch { /* Empty cache persistence is optional. */ }
|
|
638
|
+
state.vectors.delete(key);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
state.texts = undefined;
|
|
642
|
+
state.ordered.clear();
|
|
643
|
+
state.semanticVersion = context.store.semanticVersion;
|
|
644
|
+
}
|
|
645
|
+
if (state.pending.size > 0)
|
|
646
|
+
refreshRetrievalState(context, state);
|
|
647
|
+
return state;
|
|
648
|
+
}
|
|
649
|
+
function refreshRetrievalState(context, state) {
|
|
650
|
+
const pending = [...state.pending];
|
|
651
|
+
for (let start = 0; start < pending.length; start += 400) {
|
|
652
|
+
const keys = pending.slice(start, start + 400);
|
|
653
|
+
const placeholders = keys.map(() => "?").join(",");
|
|
654
|
+
for (const key of keys) {
|
|
655
|
+
state.segments.delete(key);
|
|
656
|
+
state.texts?.delete(key);
|
|
657
|
+
for (const index of state.vectors.values())
|
|
658
|
+
index.delete(key);
|
|
659
|
+
}
|
|
660
|
+
const rows = context.store.db.prepare(`${SEGMENT_SELECT} WHERE s.segment_id IN (${placeholders}) ORDER BY s.segment_id`).all(...keys);
|
|
661
|
+
for (const row of rows) {
|
|
662
|
+
if (state.generations.has(row.generation))
|
|
663
|
+
state.segments.set(row.segment_id, row);
|
|
664
|
+
}
|
|
665
|
+
if (state.texts) {
|
|
666
|
+
const texts = context.store.db.prepare(`SELECT segment_id, kind, text FROM runtime_segment_text
|
|
667
|
+
WHERE segment_id IN (${placeholders}) ORDER BY segment_id, kind, text`).all(...keys);
|
|
668
|
+
for (const row of texts)
|
|
669
|
+
addCachedText(state.texts, row);
|
|
670
|
+
}
|
|
671
|
+
for (const [key, index] of state.vectors) {
|
|
672
|
+
const [generation, embeddingSpace, modality] = JSON.parse(key);
|
|
673
|
+
const vectors = context.store.db.prepare(`SELECT segment_id, dimensions, vector_blob FROM runtime_segment_embeddings
|
|
674
|
+
WHERE generation=? AND embedding_space=? AND modality=?
|
|
675
|
+
AND segment_id IN (${placeholders}) ORDER BY segment_id`).all(generation, embeddingSpace, modality, ...keys);
|
|
676
|
+
for (const row of vectors)
|
|
677
|
+
addCachedVector(state, index, row);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
state.ordered.clear();
|
|
681
|
+
state.pending.clear();
|
|
682
|
+
}
|
|
683
|
+
function cachedTextRows(context, active) {
|
|
684
|
+
const state = retrievalState(context, active);
|
|
685
|
+
if (!state.texts) {
|
|
686
|
+
state.texts = new Map();
|
|
687
|
+
const generations = [...state.generations];
|
|
688
|
+
const rows = generations.length === 0 ? [] : context.store.db.prepare(`SELECT segment_id, kind, text FROM runtime_segment_text
|
|
689
|
+
WHERE generation IN (${generations.map(() => "?").join(",")}) ORDER BY segment_id, kind, text`).all(...generations);
|
|
690
|
+
for (const row of rows)
|
|
691
|
+
addCachedText(state.texts, row);
|
|
692
|
+
}
|
|
693
|
+
return [...state.texts.values()].flat();
|
|
694
|
+
}
|
|
695
|
+
function addCachedText(texts, row) {
|
|
696
|
+
const rows = texts.get(row.segment_id) ?? [];
|
|
697
|
+
rows.push(row);
|
|
698
|
+
texts.set(row.segment_id, rows);
|
|
699
|
+
}
|
|
700
|
+
function addCachedVector(state, index, row) {
|
|
701
|
+
const segment = state.segments.get(row.segment_id);
|
|
702
|
+
if (!segment)
|
|
703
|
+
return;
|
|
704
|
+
if (row.dimensions !== index.dimensions) {
|
|
705
|
+
throw new EngineFault({ code: "MANIFEST_INCOMPATIBLE", message: "Stored vector dimensions do not match the index manifest" });
|
|
706
|
+
}
|
|
707
|
+
const vector = blobToVector(row.vector_blob, row.dimensions);
|
|
708
|
+
index.set(row.segment_id, vector, { segment, vector, range: segmentRange(segment) });
|
|
709
|
+
}
|
|
710
|
+
function vectorIndex(context, generation, modality, state = retrievalState(context)) {
|
|
711
|
+
const key = canonicalJson([generation.generation, generation.embedding_space, modality]);
|
|
712
|
+
const cached = state.vectors.get(key);
|
|
713
|
+
if (cached)
|
|
714
|
+
return cached;
|
|
715
|
+
const manifest = requiredManifest(context, generation.manifest_id);
|
|
716
|
+
const identity = canonicalJson([key, manifest]);
|
|
717
|
+
const index = new TemporalVectorIndex(manifest.dimensions, {
|
|
718
|
+
identity, basePath: join(context.store.dataDir, "runtime-search", hashJson(identity)),
|
|
719
|
+
});
|
|
720
|
+
let after = "";
|
|
721
|
+
for (;;) {
|
|
722
|
+
const rows = context.store.db.prepare(`SELECT segment_id, dimensions, vector_blob FROM runtime_segment_embeddings
|
|
723
|
+
WHERE generation=? AND embedding_space=? AND modality=? AND segment_id>?
|
|
724
|
+
ORDER BY segment_id LIMIT 512`).all(generation.generation, generation.embedding_space, modality, after);
|
|
725
|
+
for (const row of rows)
|
|
726
|
+
addCachedVector(state, index, row);
|
|
727
|
+
if (rows.length < 512)
|
|
728
|
+
break;
|
|
729
|
+
after = rows.at(-1).segment_id;
|
|
730
|
+
}
|
|
731
|
+
state.vectors.set(key, index);
|
|
732
|
+
return index;
|
|
733
|
+
}
|
|
734
|
+
function orderedStreams(context, generation) {
|
|
735
|
+
const state = retrievalState(context);
|
|
736
|
+
const key = canonicalJson([generation.generation, generation.embedding_space]);
|
|
737
|
+
const cached = state.ordered.get(key);
|
|
738
|
+
if (cached)
|
|
739
|
+
return cached;
|
|
740
|
+
const streams = new Map();
|
|
741
|
+
for (const { value } of vectorIndex(context, generation, "visual").entries.values()) {
|
|
742
|
+
if (!value.range)
|
|
743
|
+
continue;
|
|
744
|
+
const stream = streams.get(value.range.streamId) ?? [];
|
|
745
|
+
stream.push({ segment: value.segment, vector: value.vector, range: value.range });
|
|
746
|
+
streams.set(value.range.streamId, stream);
|
|
747
|
+
}
|
|
748
|
+
for (const stream of streams.values())
|
|
749
|
+
stream.sort((left, right) => left.range.startTick - right.range.startTick || left.segment.segment_id.localeCompare(right.segment.segment_id));
|
|
750
|
+
state.ordered.set(key, streams);
|
|
751
|
+
return streams;
|
|
752
|
+
}
|
|
522
753
|
function querySegments(context, active, query) {
|
|
523
754
|
if (active.length === 0)
|
|
524
755
|
return [];
|
|
525
|
-
const
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
a.kind AS artifact_kind, a.created_at AS artifact_created_at,
|
|
530
|
-
s.stream_id, s.object_hash,
|
|
531
|
-
s.source_range_json, s.source_path, s.segment_kind,
|
|
532
|
-
s.representative_tick, s.generation, s.manifest_id
|
|
533
|
-
FROM runtime_media_segments s
|
|
534
|
-
JOIN artifacts a ON a.artifact_id=s.artifact_id
|
|
535
|
-
WHERE s.generation IN (${placeholders})
|
|
536
|
-
ORDER BY s.artifact_id, s.stream_id, s.representative_tick, s.segment_id`)
|
|
537
|
-
.all(...generations);
|
|
756
|
+
const rows = [...retrievalState(context, active).segments.values()];
|
|
757
|
+
const labelMatches = new Map();
|
|
758
|
+
const orientations = new Map();
|
|
759
|
+
const indexingStates = new Map();
|
|
538
760
|
return rows.filter((row) => {
|
|
539
761
|
if (query.artifactKinds && !query.artifactKinds.includes(row.artifact_kind)) {
|
|
540
762
|
return false;
|
|
@@ -551,33 +773,38 @@ function querySegments(context, active, query) {
|
|
|
551
773
|
&& row.artifact_created_at >= query.createdBefore) {
|
|
552
774
|
return false;
|
|
553
775
|
}
|
|
554
|
-
if (query.labels
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return false;
|
|
776
|
+
if (query.labels) {
|
|
777
|
+
if (!labelMatches.has(row.artifact_id))
|
|
778
|
+
labelMatches.set(row.artifact_id, artifactHasLabels(context, row.artifact_id, query.labels));
|
|
779
|
+
if (!labelMatches.get(row.artifact_id))
|
|
780
|
+
return false;
|
|
560
781
|
}
|
|
561
|
-
if (query.
|
|
562
|
-
|
|
563
|
-
|
|
782
|
+
if (query.orientations) {
|
|
783
|
+
if (!orientations.has(row.artifact_id))
|
|
784
|
+
orientations.set(row.artifact_id, artifactOrientation(context, row.artifact_id));
|
|
785
|
+
if (!query.orientations.includes(orientations.get(row.artifact_id)))
|
|
786
|
+
return false;
|
|
564
787
|
}
|
|
565
|
-
if (query.
|
|
566
|
-
const
|
|
567
|
-
if (
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
&& durationMs < query.durationMs.min)
|
|
572
|
-
|| (query.durationMs.max !== undefined
|
|
573
|
-
&& durationMs > query.durationMs.max)) {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
788
|
+
if (query.indexingStates) {
|
|
789
|
+
const key = `${row.artifact_id}:${row.generation}`;
|
|
790
|
+
if (!indexingStates.has(key))
|
|
791
|
+
indexingStates.set(key, artifactGenerationState(context, row.artifact_id, row.generation));
|
|
792
|
+
if (!query.indexingStates.includes(indexingStates.get(key)))
|
|
793
|
+
return false;
|
|
577
794
|
}
|
|
578
|
-
return
|
|
795
|
+
return segmentMatchesDuration(row, query.durationMs);
|
|
579
796
|
});
|
|
580
797
|
}
|
|
798
|
+
function segmentMatchesDuration(segment, requested) {
|
|
799
|
+
if (!requested)
|
|
800
|
+
return true;
|
|
801
|
+
const range = segmentRange(segment);
|
|
802
|
+
if (!range)
|
|
803
|
+
return true;
|
|
804
|
+
const durationMs = sourceTickToMs(range.durationTicks, range);
|
|
805
|
+
return (requested.min === undefined || durationMs >= requested.min)
|
|
806
|
+
&& (requested.max === undefined || durationMs <= requested.max);
|
|
807
|
+
}
|
|
581
808
|
function artifactHasLabels(context, artifactId, requested) {
|
|
582
809
|
const row = context.store.db
|
|
583
810
|
.prepare(`SELECT value_json FROM artifact_metadata
|
|
@@ -612,12 +839,7 @@ function addLexicalSignals(context, candidates, text, active) {
|
|
|
612
839
|
const generations = active.map((item) => item.generation);
|
|
613
840
|
if (generations.length === 0)
|
|
614
841
|
return;
|
|
615
|
-
const rows = context
|
|
616
|
-
.prepare(`SELECT segment_id, kind, text
|
|
617
|
-
FROM runtime_segment_text
|
|
618
|
-
WHERE generation IN (${generations.map(() => "?").join(",")})
|
|
619
|
-
ORDER BY segment_id, kind, text`)
|
|
620
|
-
.all(...generations);
|
|
842
|
+
const rows = cachedTextRows(context, active);
|
|
621
843
|
const normalized = text.trim().toLocaleLowerCase();
|
|
622
844
|
const quoted = [...text.matchAll(/"([^"]+)"/g)]
|
|
623
845
|
.map((match) => match[1]?.trim().toLocaleLowerCase())
|
|
@@ -705,8 +927,11 @@ function addOrderedVideoSignals(context, candidates, rangeInput, generation) {
|
|
|
705
927
|
&& rangesOverlap(segmentRangeValue.startTick, sourceRangeEndTick(segmentRangeValue), range.startTick, sourceRangeEndTick(range)));
|
|
706
928
|
if (query.length === 0)
|
|
707
929
|
return;
|
|
708
|
-
const sample = evenlySample(query, Math.min(query.length, 16))
|
|
709
|
-
|
|
930
|
+
const sample = evenlySample(query, Math.min(query.length, 16)).map((item) => ({
|
|
931
|
+
...item,
|
|
932
|
+
offsetMs: sourceTickToMs(item.range.startTick - range.startTick, range),
|
|
933
|
+
}));
|
|
934
|
+
addOrderedVideoVectorSignals(context, candidates, sample, generation, sourceTickToMs(range.durationTicks, range));
|
|
710
935
|
}
|
|
711
936
|
function addPreparedReferenceSignals(context, candidates, reference, options, active) {
|
|
712
937
|
for (const generation of active) {
|
|
@@ -720,38 +945,62 @@ function addPreparedReferenceSignals(context, candidates, reference, options, ac
|
|
|
720
945
|
continue;
|
|
721
946
|
}
|
|
722
947
|
const samples = preparedVideoSamples(reference, options);
|
|
948
|
+
const startMs = options.range?.startMs ?? 0;
|
|
949
|
+
const durationMs = Math.min(options.range?.durationMs ?? Infinity, preparedVideoDuration(reference) - startMs);
|
|
723
950
|
for (const sample of samples) {
|
|
724
951
|
preparedVector(sample.vector, manifest.dimensions);
|
|
725
952
|
}
|
|
726
953
|
addOrderedVideoVectorSignals(context, candidates, samples.map((sample) => ({
|
|
727
954
|
vector: Float32Array.from(sample.vector),
|
|
728
|
-
|
|
955
|
+
offsetMs: sample.offsetMs - startMs,
|
|
956
|
+
})), generation, durationMs);
|
|
729
957
|
}
|
|
730
958
|
}
|
|
731
|
-
function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
|
|
959
|
+
function addOrderedVideoVectorSignals(context, candidates, sample, generation, durationMs) {
|
|
732
960
|
if (sample.length === 0)
|
|
733
961
|
return;
|
|
734
|
-
const
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
const values = byStream.get(item.range.streamId) ?? [];
|
|
738
|
-
values.push(item);
|
|
739
|
-
byStream.set(item.range.streamId, values);
|
|
740
|
-
}
|
|
962
|
+
const byStream = orderedStreams(context, generation);
|
|
963
|
+
const starts = orderedCandidateStarts(context, candidates, sample, generation, byStream);
|
|
964
|
+
const queryVectors = new Map();
|
|
741
965
|
const windows = [];
|
|
742
|
-
for (const
|
|
743
|
-
values.
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const
|
|
749
|
-
const
|
|
750
|
-
const
|
|
966
|
+
for (const [streamId, positions] of starts) {
|
|
967
|
+
const values = byStream.get(streamId);
|
|
968
|
+
for (const start of [...positions].sort((left, right) => left - right)) {
|
|
969
|
+
const first = values[start];
|
|
970
|
+
const startMs = sourceTickToMs(first.range.startTick, first.range);
|
|
971
|
+
const end = orderedPosition(values, startMs + durationMs);
|
|
972
|
+
const count = end - start;
|
|
973
|
+
const window = Array.from({ length: Math.min(count, 16) }, (_, index) => values[start + (count === 1 ? 0 : Math.round(index * (count - 1) / (Math.min(count, 16) - 1)))]);
|
|
974
|
+
const last = values[end - 1];
|
|
975
|
+
const availableMs = Math.min(durationMs, sourceTickToMs(sourceRangeEndTick(last.range), last.range) - startMs);
|
|
976
|
+
const timedWindow = window.map((item) => ({
|
|
977
|
+
...item,
|
|
978
|
+
offsetMs: sourceTickToMs(item.range.startTick, item.range) - startMs,
|
|
979
|
+
}));
|
|
980
|
+
const offsets = [...new Set([...sample, ...timedWindow]
|
|
981
|
+
.map((item) => item.offsetMs)
|
|
982
|
+
.filter((offset) => offset >= 0 && offset < availableMs))]
|
|
983
|
+
.sort((left, right) => left - right);
|
|
984
|
+
if (offsets.length === 0)
|
|
985
|
+
continue;
|
|
986
|
+
const query = offsets.map((offset) => {
|
|
987
|
+
let vector = queryVectors.get(offset);
|
|
988
|
+
if (!vector) {
|
|
989
|
+
vector = videoVectorAt(sample, offset);
|
|
990
|
+
queryVectors.set(offset, vector);
|
|
991
|
+
}
|
|
992
|
+
return { vector };
|
|
993
|
+
});
|
|
994
|
+
const target = offsets.map((offset) => ({ vector: videoVectorAt(timedWindow, offset) }));
|
|
995
|
+
const aligned = query.reduce((sum, item, index) => sum + cosineSimilarity(item.vector, target[index].vector), 0) / query.length;
|
|
996
|
+
const coherence = transitionCoherence(query, target) * 0.75
|
|
997
|
+
+ temporalContinuity(values, start, end) * 0.25;
|
|
751
998
|
windows.push({
|
|
752
999
|
values: window,
|
|
753
|
-
score: aligned * 0.75 + coherence * 0.25,
|
|
1000
|
+
score: (aligned * 0.75 + coherence * 0.25) * availableMs / durationMs,
|
|
754
1001
|
coherence,
|
|
1002
|
+
durationTicks: Math.max(1, Math.min(sourceRangeEndTick(last.range) - first.range.startTick, Math.round(availableMs * first.range.timeBase.denominator
|
|
1003
|
+
/ (first.range.timeBase.numerator * 1_000)))),
|
|
755
1004
|
});
|
|
756
1005
|
}
|
|
757
1006
|
}
|
|
@@ -763,7 +1012,7 @@ function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
|
|
|
763
1012
|
const last = window.values.at(-1);
|
|
764
1013
|
const combined = {
|
|
765
1014
|
...first.range,
|
|
766
|
-
durationTicks:
|
|
1015
|
+
durationTicks: window.durationTicks,
|
|
767
1016
|
};
|
|
768
1017
|
const segmentId = `ordered:${generation.generation}:${first.segment.segment_id}:${last.segment.segment_id}`;
|
|
769
1018
|
const candidate = {
|
|
@@ -774,6 +1023,14 @@ function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
|
|
|
774
1023
|
representative_tick: first.range.startTick + Math.floor(combined.durationTicks / 2),
|
|
775
1024
|
},
|
|
776
1025
|
signals: [
|
|
1026
|
+
...(isExactOrderedWindow(sample, window.values)
|
|
1027
|
+
? [{
|
|
1028
|
+
kind: "exact",
|
|
1029
|
+
rank: 1,
|
|
1030
|
+
score: 1,
|
|
1031
|
+
explanation: "Exact source bytes and ordered range matched",
|
|
1032
|
+
}]
|
|
1033
|
+
: []),
|
|
777
1034
|
{
|
|
778
1035
|
kind: "visual",
|
|
779
1036
|
rank: index + 1,
|
|
@@ -786,30 +1043,56 @@ function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
|
|
|
786
1043
|
candidates.set(segmentId, candidate);
|
|
787
1044
|
});
|
|
788
1045
|
}
|
|
1046
|
+
function orderedCandidateStarts(context, candidates, sample, generation, streams) {
|
|
1047
|
+
const starts = new Map();
|
|
1048
|
+
const index = vectorIndex(context, generation, "visual");
|
|
1049
|
+
const timedCandidates = new Map([...candidates].filter(([, candidate]) => candidate.segment.source_range_json !== null));
|
|
1050
|
+
const seedCount = Math.max(32, Math.floor(512 / sample.length));
|
|
1051
|
+
for (const querySample of sample) {
|
|
1052
|
+
for (const { value } of index.nearest(querySample.vector, seedCount, timedCandidates)) {
|
|
1053
|
+
if (!value.range)
|
|
1054
|
+
continue;
|
|
1055
|
+
const stream = streams.get(value.range.streamId);
|
|
1056
|
+
const desiredMs = sourceTickToMs(value.range.startTick, value.range) - querySample.offsetMs;
|
|
1057
|
+
const low = orderedPosition(stream, desiredMs);
|
|
1058
|
+
const positions = starts.get(value.range.streamId) ?? new Set();
|
|
1059
|
+
for (const position of [low - 1, low, low + 1]) {
|
|
1060
|
+
const item = stream[position];
|
|
1061
|
+
if (item && candidates.has(item.segment.segment_id))
|
|
1062
|
+
positions.add(position);
|
|
1063
|
+
}
|
|
1064
|
+
starts.set(value.range.streamId, positions);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return starts;
|
|
1068
|
+
}
|
|
1069
|
+
function orderedPosition(stream, milliseconds) {
|
|
1070
|
+
let low = 0;
|
|
1071
|
+
let high = stream.length;
|
|
1072
|
+
while (low < high) {
|
|
1073
|
+
const middle = (low + high) >>> 1;
|
|
1074
|
+
const item = stream[middle];
|
|
1075
|
+
if (sourceTickToMs(item.range.startTick, item.range) < milliseconds)
|
|
1076
|
+
low = middle + 1;
|
|
1077
|
+
else
|
|
1078
|
+
high = middle;
|
|
1079
|
+
}
|
|
1080
|
+
return low;
|
|
1081
|
+
}
|
|
1082
|
+
function videoVectorAt(samples, offsetMs) {
|
|
1083
|
+
const next = samples.findIndex((sample) => sample.offsetMs >= offsetMs);
|
|
1084
|
+
if (next === -1)
|
|
1085
|
+
return samples.at(-1).vector;
|
|
1086
|
+
if (next === 0 || samples[next].offsetMs === offsetMs)
|
|
1087
|
+
return samples[next].vector;
|
|
1088
|
+
const before = samples[next - 1];
|
|
1089
|
+
const after = samples[next];
|
|
1090
|
+
const fraction = (offsetMs - before.offsetMs) / (after.offsetMs - before.offsetMs);
|
|
1091
|
+
return before.vector.map((value, index) => value * (1 - fraction) + after.vector[index] * fraction);
|
|
1092
|
+
}
|
|
789
1093
|
function orderedEmbeddings(context, generation, include) {
|
|
790
|
-
|
|
791
|
-
.
|
|
792
|
-
a.kind AS artifact_kind, a.created_at AS artifact_created_at,
|
|
793
|
-
s.stream_id, s.object_hash, s.source_range_json,
|
|
794
|
-
s.source_path, s.segment_kind, s.representative_tick,
|
|
795
|
-
s.generation, s.manifest_id, e.dimensions, e.vector_blob
|
|
796
|
-
FROM runtime_media_segments s
|
|
797
|
-
JOIN artifacts a ON a.artifact_id=s.artifact_id
|
|
798
|
-
JOIN runtime_segment_embeddings e ON e.segment_id=s.segment_id
|
|
799
|
-
WHERE s.generation=? AND e.embedding_space=?
|
|
800
|
-
AND e.modality='visual' AND s.source_range_json IS NOT NULL
|
|
801
|
-
ORDER BY s.stream_id, s.representative_tick, s.segment_id`)
|
|
802
|
-
.all(generation.generation, generation.embedding_space);
|
|
803
|
-
return rows.flatMap((row) => {
|
|
804
|
-
const range = segmentRange(row);
|
|
805
|
-
return range && include(range)
|
|
806
|
-
? [{
|
|
807
|
-
segment: row,
|
|
808
|
-
range,
|
|
809
|
-
vector: blobToVector(row.vector_blob, row.dimensions),
|
|
810
|
-
}]
|
|
811
|
-
: [];
|
|
812
|
-
});
|
|
1094
|
+
return [...orderedStreams(context, generation).values()]
|
|
1095
|
+
.flatMap((rows) => rows.filter((row) => include(row.range)));
|
|
813
1096
|
}
|
|
814
1097
|
function transitionCoherence(query, candidate) {
|
|
815
1098
|
if (query.length < 2)
|
|
@@ -820,6 +1103,42 @@ function transitionCoherence(query, candidate) {
|
|
|
820
1103
|
}
|
|
821
1104
|
return score / (query.length - 1);
|
|
822
1105
|
}
|
|
1106
|
+
function temporalContinuity(candidate, start, end) {
|
|
1107
|
+
if (end - start < 2)
|
|
1108
|
+
return 1;
|
|
1109
|
+
let prefix = continuityPrefixes.get(candidate);
|
|
1110
|
+
if (!prefix) {
|
|
1111
|
+
prefix = new Float64Array(candidate.length);
|
|
1112
|
+
for (let index = 1; index < candidate.length; index++) {
|
|
1113
|
+
const previous = candidate[index - 1].range;
|
|
1114
|
+
const current = candidate[index].range;
|
|
1115
|
+
const gapMs = Math.max(0, sourceTickToMs(current.startTick, current)
|
|
1116
|
+
- sourceTickToMs(sourceRangeEndTick(previous), previous));
|
|
1117
|
+
const scaleMs = Math.max(1, (sourceTickToMs(previous.durationTicks, previous)
|
|
1118
|
+
+ sourceTickToMs(current.durationTicks, current)) / 2);
|
|
1119
|
+
prefix[index] = prefix[index - 1] + 1 / (1 + gapMs / scaleMs);
|
|
1120
|
+
}
|
|
1121
|
+
continuityPrefixes.set(candidate, prefix);
|
|
1122
|
+
}
|
|
1123
|
+
return (prefix[end - 1] - prefix[start]) / (end - start - 1);
|
|
1124
|
+
}
|
|
1125
|
+
function isExactOrderedWindow(query, candidate) {
|
|
1126
|
+
return query.length === candidate.length && query.every((item, index) => {
|
|
1127
|
+
const reference = item.range;
|
|
1128
|
+
const match = candidate[index]?.range;
|
|
1129
|
+
return Boolean(reference
|
|
1130
|
+
&& match
|
|
1131
|
+
&& reference.objectHash === match.objectHash
|
|
1132
|
+
&& nearlyEqual(sourceTickToMs(reference.startTick, reference), sourceTickToMs(match.startTick, match))
|
|
1133
|
+
&& nearlyEqual(sourceTickToMs(reference.durationTicks, reference), sourceTickToMs(match.durationTicks, match)));
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
function sourceTickToMs(tick, range) {
|
|
1137
|
+
return (tick * range.timeBase.numerator * 1_000) / range.timeBase.denominator;
|
|
1138
|
+
}
|
|
1139
|
+
function nearlyEqual(left, right) {
|
|
1140
|
+
return Math.abs(left - right) <= 1e-6;
|
|
1141
|
+
}
|
|
823
1142
|
function vectorDifference(left, right) {
|
|
824
1143
|
const result = new Float32Array(left.length);
|
|
825
1144
|
for (let index = 0; index < left.length; index += 1) {
|
|
@@ -838,31 +1157,13 @@ function evenlySample(values, count) {
|
|
|
838
1157
|
});
|
|
839
1158
|
}
|
|
840
1159
|
function rankVectorRows(context, candidates, generation, queryVector, modality, explanation) {
|
|
841
|
-
|
|
842
|
-
.
|
|
843
|
-
FROM runtime_segment_embeddings
|
|
844
|
-
WHERE generation=? AND embedding_space=?
|
|
845
|
-
AND modality=?
|
|
846
|
-
ORDER BY segment_id`)
|
|
847
|
-
.all(generation.generation, generation.embedding_space, modality);
|
|
848
|
-
rows
|
|
849
|
-
.map((row) => ({
|
|
850
|
-
row,
|
|
851
|
-
score: cosineSimilarity(queryVector, blobToVector(row.vector_blob, row.dimensions)),
|
|
852
|
-
}))
|
|
853
|
-
.filter((item) => Number.isFinite(item.score))
|
|
854
|
-
.sort((left, right) => right.score - left.score
|
|
855
|
-
|| left.row.segment_id.localeCompare(right.row.segment_id))
|
|
1160
|
+
vectorIndex(context, generation, modality)
|
|
1161
|
+
.nearest(queryVector, VECTOR_CANDIDATES, candidates)
|
|
856
1162
|
.forEach((item, index) => {
|
|
857
|
-
const candidate = candidates.get(item.
|
|
1163
|
+
const candidate = candidates.get(item.key);
|
|
858
1164
|
if (!candidate)
|
|
859
1165
|
return;
|
|
860
|
-
candidate.signals.push({
|
|
861
|
-
kind: modality,
|
|
862
|
-
rank: index + 1,
|
|
863
|
-
score: item.score,
|
|
864
|
-
explanation,
|
|
865
|
-
});
|
|
1166
|
+
candidate.signals.push({ kind: modality, rank: index + 1, score: item.score, explanation });
|
|
866
1167
|
});
|
|
867
1168
|
}
|
|
868
1169
|
function referenceEmbeddingRows(context, reference, generation) {
|
|
@@ -870,13 +1171,14 @@ function referenceEmbeddingRows(context, reference, generation) {
|
|
|
870
1171
|
if (segments.length === 0)
|
|
871
1172
|
return [];
|
|
872
1173
|
const placeholders = segments.map(() => "?").join(",");
|
|
1174
|
+
const modality = reference.kind === "audio" ? "audio" : "visual";
|
|
873
1175
|
return context.store.db
|
|
874
1176
|
.prepare(`SELECT segment_id, embedding_space, modality, dimensions, vector_blob
|
|
875
1177
|
FROM runtime_segment_embeddings
|
|
876
1178
|
WHERE segment_id IN (${placeholders})
|
|
877
|
-
AND embedding_space=?
|
|
1179
|
+
AND embedding_space=? AND modality=?
|
|
878
1180
|
ORDER BY segment_id`)
|
|
879
|
-
.all(...segments, generation.embedding_space);
|
|
1181
|
+
.all(...segments, generation.embedding_space, modality);
|
|
880
1182
|
}
|
|
881
1183
|
function referenceSegments(context, reference, generation) {
|
|
882
1184
|
if (reference.kind === "image") {
|
|
@@ -1073,7 +1375,9 @@ function intervalPriority(interval) {
|
|
|
1073
1375
|
function candidateToHit(candidate) {
|
|
1074
1376
|
return {
|
|
1075
1377
|
artifactId: candidate.segment.artifact_id,
|
|
1076
|
-
|
|
1378
|
+
...(candidate.segment.artifact_label
|
|
1379
|
+
? { artifactLabel: candidate.segment.artifact_label }
|
|
1380
|
+
: {}),
|
|
1077
1381
|
artifactKind: candidate.segment.artifact_kind,
|
|
1078
1382
|
location: segmentLocation(candidate.segment),
|
|
1079
1383
|
...(candidate.segment.representative_tick === null
|
|
@@ -1297,6 +1601,13 @@ function validatePreparedReference(reference, options) {
|
|
|
1297
1601
|
message: "Prepared video references require between 1 and 16 samples",
|
|
1298
1602
|
});
|
|
1299
1603
|
}
|
|
1604
|
+
if (reference.durationMs !== undefined && (!Number.isFinite(reference.durationMs) || reference.durationMs <= 0
|
|
1605
|
+
|| reference.samples.some((sample) => sample.offsetMs >= reference.durationMs))) {
|
|
1606
|
+
throw new EngineFault({
|
|
1607
|
+
code: "INVALID_INPUT",
|
|
1608
|
+
message: "Prepared video duration must be finite, positive, and contain every sample",
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1300
1611
|
let previousOffset = -1;
|
|
1301
1612
|
for (const sample of reference.samples) {
|
|
1302
1613
|
if (!Number.isFinite(sample.offsetMs) || sample.offsetMs < 0) {
|
|
@@ -1334,11 +1645,9 @@ function validatePreparedReference(reference, options) {
|
|
|
1334
1645
|
}
|
|
1335
1646
|
function preparedVideoSamples(reference, options) {
|
|
1336
1647
|
const range = options.range;
|
|
1337
|
-
const endMs = range?.durationMs === undefined
|
|
1338
|
-
? Number.POSITIVE_INFINITY
|
|
1339
|
-
: range.startMs + range.durationMs;
|
|
1648
|
+
const endMs = Math.min(preparedVideoDuration(reference), range?.durationMs === undefined ? Infinity : range.startMs + range.durationMs);
|
|
1340
1649
|
const samples = range
|
|
1341
|
-
? reference.samples.filter((sample) => sample.offsetMs >= range.startMs && sample.offsetMs
|
|
1650
|
+
? reference.samples.filter((sample) => sample.offsetMs >= range.startMs && sample.offsetMs < endMs)
|
|
1342
1651
|
: reference.samples;
|
|
1343
1652
|
if (samples.length === 0) {
|
|
1344
1653
|
throw new EngineFault({
|
|
@@ -1348,6 +1657,13 @@ function preparedVideoSamples(reference, options) {
|
|
|
1348
1657
|
}
|
|
1349
1658
|
return samples;
|
|
1350
1659
|
}
|
|
1660
|
+
function preparedVideoDuration(reference) {
|
|
1661
|
+
if (reference.durationMs !== undefined)
|
|
1662
|
+
return reference.durationMs;
|
|
1663
|
+
const last = reference.samples.at(-1);
|
|
1664
|
+
const intervals = reference.samples.slice(1).map((sample, index) => sample.offsetMs - reference.samples[index].offsetMs).sort((a, b) => a - b);
|
|
1665
|
+
return last.offsetMs + (intervals[Math.floor(intervals.length / 2)] ?? 1);
|
|
1666
|
+
}
|
|
1351
1667
|
function preparedVector(vector, dimensions) {
|
|
1352
1668
|
if (!Array.isArray(vector)
|
|
1353
1669
|
|| vector.length === 0
|