videobook-engine 5.0.1 → 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 +73 -13
- package/dist/artifacts.d.ts +2 -2
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +42 -11
- package/dist/artifacts.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 +1 -1
- package/dist/catalog-metadata.js +1 -1
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +7 -1
- package/dist/context.js.map +1 -1
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +2 -2
- 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 +97 -12
- package/dist/engine-types.d.ts.map +1 -1
- package/dist/engine-types.js +2 -0
- package/dist/engine-types.js.map +1 -1
- package/dist/engine.d.ts +11 -3
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +43 -1
- 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 +48 -1
- package/dist/files.js.map +1 -1
- package/dist/fork.d.ts +11 -28
- package/dist/fork.d.ts.map +1 -1
- package/dist/fork.js +5 -7
- package/dist/fork.js.map +1 -1
- package/dist/history.js +10 -9
- package/dist/history.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- 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/merge-policy.d.ts +6 -10
- package/dist/merge-policy.d.ts.map +1 -1
- package/dist/merge-policy.js +5 -10
- 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 +11 -0
- 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 +188 -117
- 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-contracts.d.ts +31 -7
- 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-grid.d.ts +13 -4
- package/dist/notebook-grid.d.ts.map +1 -1
- package/dist/notebook-grid.js +35 -8
- package/dist/notebook-grid.js.map +1 -1
- package/dist/notebook-mentions.d.ts +1 -1
- package/dist/notebook-mentions.d.ts.map +1 -1
- package/dist/notebook-mentions.js +2 -1
- package/dist/notebook-mentions.js.map +1 -1
- package/dist/schema.d.ts +2 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -4
- 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 +165 -145
- package/dist/similarity.js.map +1 -1
- package/dist/store.d.ts +15 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +168 -14
- 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 +404 -132
- 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 +42 -31
- 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/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 +3 -3
- package/package.json +19 -13
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
|
-
|
|
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;
|
|
556
781
|
}
|
|
557
|
-
if (query.orientations
|
|
558
|
-
|
|
559
|
-
|
|
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;
|
|
560
787
|
}
|
|
561
|
-
if (query.indexingStates
|
|
562
|
-
|
|
563
|
-
|
|
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;
|
|
564
794
|
}
|
|
565
|
-
|
|
566
|
-
const range = segmentRange(row);
|
|
567
|
-
if (range) {
|
|
568
|
-
const durationMs = (range.durationTicks * range.timeBase.numerator * 1_000)
|
|
569
|
-
/ range.timeBase.denominator;
|
|
570
|
-
if ((query.durationMs.min !== undefined
|
|
571
|
-
&& durationMs < query.durationMs.min)
|
|
572
|
-
|| (query.durationMs.max !== undefined
|
|
573
|
-
&& durationMs > query.durationMs.max)) {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
return true;
|
|
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,39 +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
|
|
751
|
-
|
|
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;
|
|
752
998
|
windows.push({
|
|
753
999
|
values: window,
|
|
754
|
-
score: aligned * 0.75 + coherence * 0.25,
|
|
1000
|
+
score: (aligned * 0.75 + coherence * 0.25) * availableMs / durationMs,
|
|
755
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)))),
|
|
756
1004
|
});
|
|
757
1005
|
}
|
|
758
1006
|
}
|
|
@@ -764,7 +1012,7 @@ function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
|
|
|
764
1012
|
const last = window.values.at(-1);
|
|
765
1013
|
const combined = {
|
|
766
1014
|
...first.range,
|
|
767
|
-
durationTicks:
|
|
1015
|
+
durationTicks: window.durationTicks,
|
|
768
1016
|
};
|
|
769
1017
|
const segmentId = `ordered:${generation.generation}:${first.segment.segment_id}:${last.segment.segment_id}`;
|
|
770
1018
|
const candidate = {
|
|
@@ -795,30 +1043,56 @@ function addOrderedVideoVectorSignals(context, candidates, sample, generation) {
|
|
|
795
1043
|
candidates.set(segmentId, candidate);
|
|
796
1044
|
});
|
|
797
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
|
+
}
|
|
798
1093
|
function orderedEmbeddings(context, generation, include) {
|
|
799
|
-
|
|
800
|
-
.
|
|
801
|
-
a.kind AS artifact_kind, a.created_at AS artifact_created_at,
|
|
802
|
-
s.stream_id, s.object_hash, s.source_range_json,
|
|
803
|
-
s.source_path, s.segment_kind, s.representative_tick,
|
|
804
|
-
s.generation, s.manifest_id, e.dimensions, e.vector_blob
|
|
805
|
-
FROM runtime_media_segments s
|
|
806
|
-
JOIN artifacts a ON a.artifact_id=s.artifact_id
|
|
807
|
-
JOIN runtime_segment_embeddings e ON e.segment_id=s.segment_id
|
|
808
|
-
WHERE s.generation=? AND e.embedding_space=?
|
|
809
|
-
AND e.modality='visual' AND s.source_range_json IS NOT NULL
|
|
810
|
-
ORDER BY s.stream_id, s.representative_tick, s.segment_id`)
|
|
811
|
-
.all(generation.generation, generation.embedding_space);
|
|
812
|
-
return rows.flatMap((row) => {
|
|
813
|
-
const range = segmentRange(row);
|
|
814
|
-
return range && include(range)
|
|
815
|
-
? [{
|
|
816
|
-
segment: row,
|
|
817
|
-
range,
|
|
818
|
-
vector: blobToVector(row.vector_blob, row.dimensions),
|
|
819
|
-
}]
|
|
820
|
-
: [];
|
|
821
|
-
});
|
|
1094
|
+
return [...orderedStreams(context, generation).values()]
|
|
1095
|
+
.flatMap((rows) => rows.filter((row) => include(row.range)));
|
|
822
1096
|
}
|
|
823
1097
|
function transitionCoherence(query, candidate) {
|
|
824
1098
|
if (query.length < 2)
|
|
@@ -829,20 +1103,24 @@ function transitionCoherence(query, candidate) {
|
|
|
829
1103
|
}
|
|
830
1104
|
return score / (query.length - 1);
|
|
831
1105
|
}
|
|
832
|
-
function temporalContinuity(candidate) {
|
|
833
|
-
if (
|
|
1106
|
+
function temporalContinuity(candidate, start, end) {
|
|
1107
|
+
if (end - start < 2)
|
|
834
1108
|
return 1;
|
|
835
|
-
let
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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);
|
|
844
1122
|
}
|
|
845
|
-
return
|
|
1123
|
+
return (prefix[end - 1] - prefix[start]) / (end - start - 1);
|
|
846
1124
|
}
|
|
847
1125
|
function isExactOrderedWindow(query, candidate) {
|
|
848
1126
|
return query.length === candidate.length && query.every((item, index) => {
|
|
@@ -879,31 +1157,13 @@ function evenlySample(values, count) {
|
|
|
879
1157
|
});
|
|
880
1158
|
}
|
|
881
1159
|
function rankVectorRows(context, candidates, generation, queryVector, modality, explanation) {
|
|
882
|
-
|
|
883
|
-
.
|
|
884
|
-
FROM runtime_segment_embeddings
|
|
885
|
-
WHERE generation=? AND embedding_space=?
|
|
886
|
-
AND modality=?
|
|
887
|
-
ORDER BY segment_id`)
|
|
888
|
-
.all(generation.generation, generation.embedding_space, modality);
|
|
889
|
-
rows
|
|
890
|
-
.map((row) => ({
|
|
891
|
-
row,
|
|
892
|
-
score: cosineSimilarity(queryVector, blobToVector(row.vector_blob, row.dimensions)),
|
|
893
|
-
}))
|
|
894
|
-
.filter((item) => Number.isFinite(item.score))
|
|
895
|
-
.sort((left, right) => right.score - left.score
|
|
896
|
-
|| left.row.segment_id.localeCompare(right.row.segment_id))
|
|
1160
|
+
vectorIndex(context, generation, modality)
|
|
1161
|
+
.nearest(queryVector, VECTOR_CANDIDATES, candidates)
|
|
897
1162
|
.forEach((item, index) => {
|
|
898
|
-
const candidate = candidates.get(item.
|
|
1163
|
+
const candidate = candidates.get(item.key);
|
|
899
1164
|
if (!candidate)
|
|
900
1165
|
return;
|
|
901
|
-
candidate.signals.push({
|
|
902
|
-
kind: modality,
|
|
903
|
-
rank: index + 1,
|
|
904
|
-
score: item.score,
|
|
905
|
-
explanation,
|
|
906
|
-
});
|
|
1166
|
+
candidate.signals.push({ kind: modality, rank: index + 1, score: item.score, explanation });
|
|
907
1167
|
});
|
|
908
1168
|
}
|
|
909
1169
|
function referenceEmbeddingRows(context, reference, generation) {
|
|
@@ -1341,6 +1601,13 @@ function validatePreparedReference(reference, options) {
|
|
|
1341
1601
|
message: "Prepared video references require between 1 and 16 samples",
|
|
1342
1602
|
});
|
|
1343
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
|
+
}
|
|
1344
1611
|
let previousOffset = -1;
|
|
1345
1612
|
for (const sample of reference.samples) {
|
|
1346
1613
|
if (!Number.isFinite(sample.offsetMs) || sample.offsetMs < 0) {
|
|
@@ -1378,11 +1645,9 @@ function validatePreparedReference(reference, options) {
|
|
|
1378
1645
|
}
|
|
1379
1646
|
function preparedVideoSamples(reference, options) {
|
|
1380
1647
|
const range = options.range;
|
|
1381
|
-
const endMs = range?.durationMs === undefined
|
|
1382
|
-
? Number.POSITIVE_INFINITY
|
|
1383
|
-
: range.startMs + range.durationMs;
|
|
1648
|
+
const endMs = Math.min(preparedVideoDuration(reference), range?.durationMs === undefined ? Infinity : range.startMs + range.durationMs);
|
|
1384
1649
|
const samples = range
|
|
1385
|
-
? reference.samples.filter((sample) => sample.offsetMs >= range.startMs && sample.offsetMs
|
|
1650
|
+
? reference.samples.filter((sample) => sample.offsetMs >= range.startMs && sample.offsetMs < endMs)
|
|
1386
1651
|
: reference.samples;
|
|
1387
1652
|
if (samples.length === 0) {
|
|
1388
1653
|
throw new EngineFault({
|
|
@@ -1392,6 +1657,13 @@ function preparedVideoSamples(reference, options) {
|
|
|
1392
1657
|
}
|
|
1393
1658
|
return samples;
|
|
1394
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
|
+
}
|
|
1395
1667
|
function preparedVector(vector, dimensions) {
|
|
1396
1668
|
if (!Array.isArray(vector)
|
|
1397
1669
|
|| vector.length === 0
|