@qaecy/cue-cli 0.0.47 → 0.0.49
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/assets/wasm/dir_scanner_wasm.js +461 -0
- package/main.js +1050 -469
- package/package.json +1 -1
- package/readme.md +1 -0
package/main.js
CHANGED
|
@@ -5141,6 +5141,7 @@ var ENDPOINT_COMMANDS_PROFILE_API_KEYS = "/commands/admin/profile/api-keys";
|
|
|
5141
5141
|
var ENDPOINT_COMMANDS_PROFILE_TERMS = "/commands/admin/profile/terms";
|
|
5142
5142
|
var ENDPOINT_ORG_MEMBERS = (orgId) => `/data-views/admin/organizations/${orgId}/members`;
|
|
5143
5143
|
var ENDPOINT_CREATE_PROJECT = "/commands/admin/project";
|
|
5144
|
+
var ENDPOINT_DELETE_PROJECT = (projectId) => `/commands/admin/project/${projectId}`;
|
|
5144
5145
|
var ENDPOINT_SEARCH = "/assistant/search";
|
|
5145
5146
|
var ENDPOINT_FUSEKI_QUERY = "/triplestore/query";
|
|
5146
5147
|
var ENDPOINT_FUSEKI_UPDATE = "/triplestore/update";
|
|
@@ -5268,6 +5269,11 @@ var CueAuth = class {
|
|
|
5268
5269
|
const result = await (0, import_auth3.signInWithCustomToken)(this._auth, token);
|
|
5269
5270
|
return result.user;
|
|
5270
5271
|
}
|
|
5272
|
+
/** Sign in with a Firebase custom token (e.g. minted server-side for MCP/OAuth flows). */
|
|
5273
|
+
async signInWithCustomToken(token) {
|
|
5274
|
+
const result = await (0, import_auth3.signInWithCustomToken)(this._auth, token);
|
|
5275
|
+
return result.user;
|
|
5276
|
+
}
|
|
5271
5277
|
/** Sign out the current user */
|
|
5272
5278
|
async signOut() {
|
|
5273
5279
|
await (0, import_auth3.signOut)(this._auth);
|
|
@@ -5339,6 +5345,46 @@ var CueAuth = class {
|
|
|
5339
5345
|
}
|
|
5340
5346
|
};
|
|
5341
5347
|
|
|
5348
|
+
// libs/js/cue-sdk/src/lib/storage.ts
|
|
5349
|
+
var CueStorage = class {
|
|
5350
|
+
constructor(_blob) {
|
|
5351
|
+
this._blob = _blob;
|
|
5352
|
+
}
|
|
5353
|
+
/**
|
|
5354
|
+
* Returns a Firebase authenticated download URL for a document stored in Cue.
|
|
5355
|
+
*
|
|
5356
|
+
* The storage path is `{projectId}/{uuid}{suffix}`, e.g. `my-project/abc-123.pdf`.
|
|
5357
|
+
*
|
|
5358
|
+
* @param projectId - The Cue project (space) ID.
|
|
5359
|
+
* @param uuid - The document UUID.
|
|
5360
|
+
* @param suffix - File suffix including the leading dot, e.g. `'.pdf'`, `'.ifc'`.
|
|
5361
|
+
* @param bucket - `'raw'` (default, original uploads) or `'processed'` (derived artefacts).
|
|
5362
|
+
*/
|
|
5363
|
+
async getDownloadUrl(projectId, uuid, suffix, bucket = "raw") {
|
|
5364
|
+
const path = `${projectId}/${uuid}${suffix}`;
|
|
5365
|
+
const url = await this._blob.getDownloadURL(bucket, path);
|
|
5366
|
+
if (!url)
|
|
5367
|
+
throw new Error(`File not found in storage: ${path} (bucket: ${bucket})`);
|
|
5368
|
+
return url;
|
|
5369
|
+
}
|
|
5370
|
+
/**
|
|
5371
|
+
* Returns a Firebase authenticated download URL for an alternative representation
|
|
5372
|
+
* using its full `qcy:remoteRelativePath` stored in the processed bucket.
|
|
5373
|
+
*
|
|
5374
|
+
* Use this instead of `getDownloadUrl` when the document info was obtained via
|
|
5375
|
+
* `fetchAlternativeRepresentations` and carries a `remoteRelativePath`.
|
|
5376
|
+
*
|
|
5377
|
+
* @param remoteRelativePath - The full path in the processed bucket,
|
|
5378
|
+
* e.g. `{projectId}/fragments/{uuid}.fragments`.
|
|
5379
|
+
*/
|
|
5380
|
+
async getAltRepDownloadUrl(remoteRelativePath) {
|
|
5381
|
+
const url = await this._blob.getDownloadURL("processed", remoteRelativePath);
|
|
5382
|
+
if (!url)
|
|
5383
|
+
throw new Error(`Alternative representation not found in storage: ${remoteRelativePath}`);
|
|
5384
|
+
return url;
|
|
5385
|
+
}
|
|
5386
|
+
};
|
|
5387
|
+
|
|
5342
5388
|
// libs/js/cue-sdk/src/lib/tables.ts
|
|
5343
5389
|
var ENDPOINT_DATA_VIEWS_TABLES = "/data-views/tables";
|
|
5344
5390
|
var ENDPOINT_COMMANDS_TABLES = "/commands/tables";
|
|
@@ -5382,6 +5428,55 @@ var CueTables = class {
|
|
|
5382
5428
|
}
|
|
5383
5429
|
};
|
|
5384
5430
|
|
|
5431
|
+
// libs/js/cue-sdk/src/lib/extraction.ts
|
|
5432
|
+
var ENDPOINT_EXTRACTION = "/semantic-extraction/extract";
|
|
5433
|
+
var CueExtraction = class {
|
|
5434
|
+
constructor(_auth, _gatewayUrl) {
|
|
5435
|
+
this._auth = _auth;
|
|
5436
|
+
this._gatewayUrl = _gatewayUrl;
|
|
5437
|
+
}
|
|
5438
|
+
/**
|
|
5439
|
+
* Run semantic extraction on a document page image.
|
|
5440
|
+
*
|
|
5441
|
+
* Sends a multipart/form-data POST to `/semantic-extraction/extract`.
|
|
5442
|
+
* Always requests JSON-LD so the result can be displayed directly in
|
|
5443
|
+
* `cue-rdf-graph` without any further parsing.
|
|
5444
|
+
*/
|
|
5445
|
+
async extract(request) {
|
|
5446
|
+
const fmt = request.rdfFormat ?? "json-ld";
|
|
5447
|
+
const body = new FormData();
|
|
5448
|
+
body.append("file", request.image, "page.png");
|
|
5449
|
+
body.append("template", JSON.stringify(request.template));
|
|
5450
|
+
body.append("space_id", request.projectId);
|
|
5451
|
+
body.append("rdf_format", fmt);
|
|
5452
|
+
if (request.category)
|
|
5453
|
+
body.append("category", request.category);
|
|
5454
|
+
if (request.text)
|
|
5455
|
+
body.append("text", request.text);
|
|
5456
|
+
const response = await this._auth.authenticatedFetch(
|
|
5457
|
+
`${this._gatewayUrl}${ENDPOINT_EXTRACTION}`,
|
|
5458
|
+
{
|
|
5459
|
+
method: "POST",
|
|
5460
|
+
// Do NOT set Content-Type; browser sets it with the correct boundary.
|
|
5461
|
+
headers: {
|
|
5462
|
+
"Accept": "application/ld+json",
|
|
5463
|
+
"x-project-id": request.projectId,
|
|
5464
|
+
"cue-project-id": request.projectId
|
|
5465
|
+
},
|
|
5466
|
+
body
|
|
5467
|
+
}
|
|
5468
|
+
);
|
|
5469
|
+
if (!response.ok) {
|
|
5470
|
+
const msg = await response.text().catch(() => "");
|
|
5471
|
+
throw new Error(
|
|
5472
|
+
`Extraction failed: ${response.status} ${response.statusText}${msg ? " \u2014 " + msg.slice(0, 300) : ""}`
|
|
5473
|
+
);
|
|
5474
|
+
}
|
|
5475
|
+
const jsonld = await response.json();
|
|
5476
|
+
return { jsonld };
|
|
5477
|
+
}
|
|
5478
|
+
};
|
|
5479
|
+
|
|
5385
5480
|
// libs/js/cue-sdk/src/lib/api.ts
|
|
5386
5481
|
var CueApi = class {
|
|
5387
5482
|
constructor(_auth, _gatewayUrl, projects, sync) {
|
|
@@ -5390,8 +5485,11 @@ var CueApi = class {
|
|
|
5390
5485
|
this.projects = projects;
|
|
5391
5486
|
this.sync = sync;
|
|
5392
5487
|
this.tables = new CueTables(_auth, _gatewayUrl);
|
|
5488
|
+
this.extraction = new CueExtraction(_auth, _gatewayUrl);
|
|
5393
5489
|
}
|
|
5394
5490
|
tables;
|
|
5491
|
+
/** Semantic extraction client — call document pages against a SemanticTemplate. */
|
|
5492
|
+
extraction;
|
|
5395
5493
|
/** Active language used for language-sensitive SPARQL queries across all project classes. */
|
|
5396
5494
|
language = "en";
|
|
5397
5495
|
/** Updates the active language. All project classes (`CueProjectSchema`, `CueProjectDocuments`, `CueProjectEntities`) read this at query time. */
|
|
@@ -7354,6 +7452,19 @@ var CueProjects = class {
|
|
|
7354
7452
|
const fn = (0, import_functions2.httpsCallable)(this._functions, "removeUserFromProject");
|
|
7355
7453
|
await fn({ uid, spaceId: projectId });
|
|
7356
7454
|
}
|
|
7455
|
+
/**
|
|
7456
|
+
* Delete a project by ID. Requires superadmin privileges on the server.
|
|
7457
|
+
*/
|
|
7458
|
+
async deleteProject(projectId) {
|
|
7459
|
+
const response = await this._auth.authenticatedFetch(
|
|
7460
|
+
`${this._gatewayUrl}${ENDPOINT_DELETE_PROJECT(projectId)}`,
|
|
7461
|
+
{ method: "DELETE" }
|
|
7462
|
+
);
|
|
7463
|
+
if (!response.ok) {
|
|
7464
|
+
const body = await response.text().catch(() => "");
|
|
7465
|
+
throw new Error(`Failed to delete project: ${response.status} ${response.statusText}${body ? ` \u2014 ${body}` : ""}`);
|
|
7466
|
+
}
|
|
7467
|
+
}
|
|
7357
7468
|
};
|
|
7358
7469
|
|
|
7359
7470
|
// libs/js/cue-sdk/src/lib/profile.ts
|
|
@@ -7527,6 +7638,7 @@ var REQUIRED_ROLES = {
|
|
|
7527
7638
|
createProvider: "superadmin",
|
|
7528
7639
|
changeContentCategories: "syncer",
|
|
7529
7640
|
deleteDocuments: "superadmin",
|
|
7641
|
+
deleteProject: "admin",
|
|
7530
7642
|
deleteUserFromProject: "admin",
|
|
7531
7643
|
downloadDocuments: "member",
|
|
7532
7644
|
editContentCategories: "syncer",
|
|
@@ -7541,8 +7653,10 @@ function defaultPrivileges() {
|
|
|
7541
7653
|
return {
|
|
7542
7654
|
changeContentCategories: false,
|
|
7543
7655
|
createEntities: false,
|
|
7656
|
+
createProject: false,
|
|
7544
7657
|
createProvider: false,
|
|
7545
7658
|
deleteDocuments: false,
|
|
7659
|
+
deleteProject: false,
|
|
7546
7660
|
deleteUserFromProject: false,
|
|
7547
7661
|
downloadDocuments: false,
|
|
7548
7662
|
editContentCategories: false,
|
|
@@ -7558,18 +7672,31 @@ var CuePrivileges = class {
|
|
|
7558
7672
|
constructor(_isSuperAdmin) {
|
|
7559
7673
|
this._isSuperAdmin = _isSuperAdmin;
|
|
7560
7674
|
this._projectRoles = new CueSignal([]);
|
|
7675
|
+
this._orgRole = new CueSignal(null);
|
|
7561
7676
|
this.privileges = cueComputed(
|
|
7562
|
-
[this._projectRoles, _isSuperAdmin],
|
|
7677
|
+
[this._projectRoles, this._orgRole, _isSuperAdmin],
|
|
7563
7678
|
() => this._compute()
|
|
7564
7679
|
);
|
|
7565
7680
|
}
|
|
7566
7681
|
_projectRoles;
|
|
7682
|
+
_orgRole;
|
|
7567
7683
|
/**
|
|
7568
7684
|
* Reactive signal — current user's privileges for the selected project.
|
|
7569
7685
|
* Recomputes automatically when `setProjectRoles()` is called or when
|
|
7570
7686
|
* the `isSuperAdmin` signal changes.
|
|
7571
7687
|
*/
|
|
7572
7688
|
privileges;
|
|
7689
|
+
/**
|
|
7690
|
+
* Set the user's role within the organisation that owns the selected project.
|
|
7691
|
+
* Pass `null` to clear the organisation role (e.g. when deselecting a project).
|
|
7692
|
+
*
|
|
7693
|
+
* Only organisation admins (and superadmins) may create projects.
|
|
7694
|
+
*/
|
|
7695
|
+
setOrgRole(role) {
|
|
7696
|
+
if (this._orgRole.get() !== role) {
|
|
7697
|
+
this._orgRole.set(role);
|
|
7698
|
+
}
|
|
7699
|
+
}
|
|
7573
7700
|
/**
|
|
7574
7701
|
* Set the user's roles for the currently selected project.
|
|
7575
7702
|
*
|
|
@@ -7596,10 +7723,12 @@ var CuePrivileges = class {
|
|
|
7596
7723
|
}
|
|
7597
7724
|
_compute() {
|
|
7598
7725
|
const roles = this._projectRoles.get();
|
|
7726
|
+
const isSuperAdmin = this._isSuperAdmin.get();
|
|
7599
7727
|
const result = defaultPrivileges();
|
|
7600
7728
|
for (const key of Object.keys(REQUIRED_ROLES)) {
|
|
7601
7729
|
result[key] = roles.includes(REQUIRED_ROLES[key]);
|
|
7602
7730
|
}
|
|
7731
|
+
result.createProject = isSuperAdmin || this._orgRole.get() === "admin";
|
|
7603
7732
|
return result;
|
|
7604
7733
|
}
|
|
7605
7734
|
};
|
|
@@ -7852,11 +7981,12 @@ GROUP BY ?iri ?parent`;
|
|
|
7852
7981
|
var OSM_ENDPOINT = "https://qlever.dev/api/osm-planet";
|
|
7853
7982
|
var EXCLUDE_OSM_RELATION = true;
|
|
7854
7983
|
var CueProjectEntities = class {
|
|
7855
|
-
constructor(_api, _projectId, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
|
|
7984
|
+
constructor(_api, _projectId, rdfBase = RESOURCE_BASE, _queryCache, _graphType, _verbose = false) {
|
|
7856
7985
|
this._api = _api;
|
|
7857
7986
|
this._projectId = _projectId;
|
|
7858
7987
|
this._queryCache = _queryCache;
|
|
7859
7988
|
this._graphType = _graphType;
|
|
7989
|
+
this._verbose = _verbose;
|
|
7860
7990
|
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
7861
7991
|
this.entityInfoMap = this._entityInfoMapComputed;
|
|
7862
7992
|
this.entityGraph = this._entityGraph.asReadonly();
|
|
@@ -7876,6 +8006,8 @@ var CueProjectEntities = class {
|
|
|
7876
8006
|
_entityOSMMap = new CueSignal({});
|
|
7877
8007
|
_osmWKTMap = new CueSignal({});
|
|
7878
8008
|
_fetchingOSMIds = /* @__PURE__ */ new Set();
|
|
8009
|
+
/** Cumulative unique entity UUIDs ever passed to request methods (survives cache hits). */
|
|
8010
|
+
_seenIds = /* @__PURE__ */ new Set();
|
|
7879
8011
|
_entityGraph = new CueSignal(void 0);
|
|
7880
8012
|
// ── Derived signals ────────────────────────────────────────────────────────
|
|
7881
8013
|
_entityInfoMapComputed = cueComputed(
|
|
@@ -7900,6 +8032,15 @@ var CueProjectEntities = class {
|
|
|
7900
8032
|
entityIri(uuid) {
|
|
7901
8033
|
return `${this.baseURL}${uuid}`;
|
|
7902
8034
|
}
|
|
8035
|
+
/** @internal Builds a full resource IRI from a UUID without a SPARQL round-trip. */
|
|
8036
|
+
_resourceIri(uuid) {
|
|
8037
|
+
return `${this.baseURL}${uuid}`;
|
|
8038
|
+
}
|
|
8039
|
+
_log(message) {
|
|
8040
|
+
if (this._verbose) {
|
|
8041
|
+
console.debug(`[CueProjectEntities pid=${this._projectId}] ${message}`);
|
|
8042
|
+
}
|
|
8043
|
+
}
|
|
7903
8044
|
/**
|
|
7904
8045
|
* Resets all entity state and re-fetches the entity graph.
|
|
7905
8046
|
* Call when the active project changes.
|
|
@@ -7912,6 +8053,7 @@ var CueProjectEntities = class {
|
|
|
7912
8053
|
this._osmWKTMap.set({});
|
|
7913
8054
|
this._entityGraph.set(void 0);
|
|
7914
8055
|
this._fetchingOSMIds.clear();
|
|
8056
|
+
this._seenIds.clear();
|
|
7915
8057
|
this._fetchEntityGraph().catch(
|
|
7916
8058
|
(err) => console.error(
|
|
7917
8059
|
"[CueProjectEntities] Entity graph fetch failed after reset:",
|
|
@@ -7927,11 +8069,14 @@ var CueProjectEntities = class {
|
|
|
7927
8069
|
* Data is merged into `entityInfoMap` once the SPARQL response arrives.
|
|
7928
8070
|
*/
|
|
7929
8071
|
requestEntityData(uuids, includeMentionCount = false) {
|
|
8072
|
+
for (const id of uuids)
|
|
8073
|
+
this._seenIds.add(id);
|
|
7930
8074
|
const newUUIDs = uuids.filter(
|
|
7931
8075
|
(id) => this._entityDetails.get()[id] === void 0
|
|
7932
8076
|
);
|
|
7933
8077
|
if (newUUIDs.length === 0)
|
|
7934
8078
|
return;
|
|
8079
|
+
this._log(`requestEntityData: ${newUUIDs.length} new / ${uuids.length} requested | cumulative: ${this._seenIds.size} seen`);
|
|
7935
8080
|
const values = newUUIDs.map((id) => `r:${id}`).join(" ");
|
|
7936
8081
|
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
7937
8082
|
PREFIX r: <${this.baseURL}>
|
|
@@ -7961,6 +8106,7 @@ GROUP BY ?id ?mentionCount`;
|
|
|
7961
8106
|
};
|
|
7962
8107
|
});
|
|
7963
8108
|
this._entityDetails.set(updates);
|
|
8109
|
+
this._log(`entityDetails: ${Object.keys(updates).length} with metadata / ${this._seenIds.size} seen (cumulative)`);
|
|
7964
8110
|
}).catch(
|
|
7965
8111
|
(err) => console.error("[CueProjectEntities] requestEntityData failed:", err)
|
|
7966
8112
|
);
|
|
@@ -7978,6 +8124,9 @@ GROUP BY ?id ?mentionCount`;
|
|
|
7978
8124
|
);
|
|
7979
8125
|
if (newUUIDs.length === 0)
|
|
7980
8126
|
return;
|
|
8127
|
+
for (const id of uuids)
|
|
8128
|
+
this._seenIds.add(id);
|
|
8129
|
+
this._log(`requestEntityLocations: ${newUUIDs.length} new / ${uuids.length} requested | cumulative: ${this._seenIds.size} seen`);
|
|
7981
8130
|
const osmInit = { ...this._entityOSMMap.get() };
|
|
7982
8131
|
for (const id of newUUIDs)
|
|
7983
8132
|
osmInit[id] = { direct: [], indirect: [] };
|
|
@@ -8034,6 +8183,7 @@ WHERE {
|
|
|
8034
8183
|
update[id] = entry;
|
|
8035
8184
|
});
|
|
8036
8185
|
this._entityOSMMap.set(update);
|
|
8186
|
+
this._log(`entityOSMMap: ${Object.keys(update).length} with OSM / ${this._seenIds.size} seen (cumulative)`);
|
|
8037
8187
|
}
|
|
8038
8188
|
/**
|
|
8039
8189
|
* Fetches incoming and outgoing relationships for a single entity IRI.
|
|
@@ -8124,7 +8274,51 @@ ORDER BY ${orderByOccurences ? "DESC(?count)" : "ASC(?label)"}`;
|
|
|
8124
8274
|
};
|
|
8125
8275
|
});
|
|
8126
8276
|
}
|
|
8127
|
-
async
|
|
8277
|
+
async entitiesByCategory(categoryIris, includeMetadata = false) {
|
|
8278
|
+
if (categoryIris.length === 0)
|
|
8279
|
+
return [];
|
|
8280
|
+
const ce = CompactExpand.getInstance();
|
|
8281
|
+
const normalise = (iri) => iri.startsWith("http://") || iri.startsWith("https://") ? iri : ce.expandIRI(iri);
|
|
8282
|
+
const values = categoryIris.map((c) => `<${normalise(c)}>`).join(" ");
|
|
8283
|
+
const q = includeMetadata ? `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8284
|
+
SELECT ?id (SAMPLE(?val) AS ?value) (GROUP_CONCAT(DISTINCT STR(?allCat); SEPARATOR=";") AS ?categories)
|
|
8285
|
+
WHERE {
|
|
8286
|
+
VALUES ?filterCat { ${values} }
|
|
8287
|
+
?iri a qcy:CanonicalEntity ;
|
|
8288
|
+
qcy:hasEntityCategory ?filterCat ;
|
|
8289
|
+
qcy:value ?val ;
|
|
8290
|
+
qcy:hasEntityCategory ?allCat .
|
|
8291
|
+
BIND(REPLACE(STR(?iri), "^.*/", "") AS ?id)
|
|
8292
|
+
}
|
|
8293
|
+
GROUP BY ?id` : `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8294
|
+
SELECT DISTINCT ?id
|
|
8295
|
+
WHERE {
|
|
8296
|
+
VALUES ?filterCat { ${values} }
|
|
8297
|
+
?iri a qcy:CanonicalEntity ;
|
|
8298
|
+
qcy:hasEntityCategory ?filterCat .
|
|
8299
|
+
BIND(REPLACE(STR(?iri), "^.*/", "") AS ?id)
|
|
8300
|
+
}`;
|
|
8301
|
+
const data = await this._api.sparql(
|
|
8302
|
+
q,
|
|
8303
|
+
this._projectId,
|
|
8304
|
+
this._graphType
|
|
8305
|
+
);
|
|
8306
|
+
return data.results.bindings.filter((b) => b["id"]).map((b) => {
|
|
8307
|
+
const uuid = b["id"].value;
|
|
8308
|
+
const base = { iri: this._resourceIri(uuid), uuid };
|
|
8309
|
+
if (!includeMetadata)
|
|
8310
|
+
return base;
|
|
8311
|
+
return {
|
|
8312
|
+
...base,
|
|
8313
|
+
value: b["value"]?.value ?? "",
|
|
8314
|
+
categories: b["categories"]?.value?.split(";").filter(Boolean) ?? []
|
|
8315
|
+
};
|
|
8316
|
+
});
|
|
8317
|
+
}
|
|
8318
|
+
async buildSummaryGraph(format, entityIRI) {
|
|
8319
|
+
const ce = CompactExpand.getInstance();
|
|
8320
|
+
const expandedIRI = entityIRI ? entityIRI.includes("://") ? entityIRI : ce.expandIRI(entityIRI) : void 0;
|
|
8321
|
+
const neighbourFilter = expandedIRI ? ` FILTER(?sourceCat = <${expandedIRI}> || ?targetCat = <${expandedIRI}>)` : "";
|
|
8128
8322
|
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8129
8323
|
SELECT
|
|
8130
8324
|
?sourceCat
|
|
@@ -8139,6 +8333,7 @@ WHERE {
|
|
|
8139
8333
|
?s ?predicate ?o .
|
|
8140
8334
|
FILTER(isIRI(?s) && isIRI(?o))
|
|
8141
8335
|
FILTER(?predicate != qcy:relatedEntity)
|
|
8336
|
+
${neighbourFilter}
|
|
8142
8337
|
}
|
|
8143
8338
|
GROUP BY ?sourceCat ?predicate ?targetCat
|
|
8144
8339
|
ORDER BY DESC(?weight)`;
|
|
@@ -8168,7 +8363,6 @@ ORDER BY DESC(?weight)`;
|
|
|
8168
8363
|
};
|
|
8169
8364
|
}
|
|
8170
8365
|
if (format === "md") {
|
|
8171
|
-
const ce = CompactExpand.getInstance();
|
|
8172
8366
|
const rows = bindings.map((b) => ({
|
|
8173
8367
|
src: ce.compactIRI(b["sourceCat"].value),
|
|
8174
8368
|
pred: ce.compactIRI(b["predicate"].value),
|
|
@@ -8413,411 +8607,6 @@ SELECT * WHERE {
|
|
|
8413
8607
|
}
|
|
8414
8608
|
};
|
|
8415
8609
|
|
|
8416
|
-
// libs/js/cue-sdk/src/lib/documents.ts
|
|
8417
|
-
var CueProjectDocuments = class {
|
|
8418
|
-
constructor(_api, _projectId, language, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
|
|
8419
|
-
this._api = _api;
|
|
8420
|
-
this._projectId = _projectId;
|
|
8421
|
-
this._queryCache = _queryCache;
|
|
8422
|
-
this._graphType = _graphType;
|
|
8423
|
-
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
8424
|
-
this._currentLang = language ?? this._api.language;
|
|
8425
|
-
this.documentInfoMap = this._documentInfoMap.asReadonly();
|
|
8426
|
-
this.projectDocumentsData = this._projectDocumentsData.asReadonly();
|
|
8427
|
-
}
|
|
8428
|
-
/** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
|
|
8429
|
-
baseURL;
|
|
8430
|
-
/** Tracks the language for which `_documentInfoMap` is currently populated. */
|
|
8431
|
-
_currentLang;
|
|
8432
|
-
_documentInfoMap = new CueSignal({});
|
|
8433
|
-
_projectDocumentsData = new CueSignal({
|
|
8434
|
-
duplicateCount: 0,
|
|
8435
|
-
documentsBySuffix: {},
|
|
8436
|
-
documentsByContentCategory: {}
|
|
8437
|
-
});
|
|
8438
|
-
/** Lazily populated per-document detail map. */
|
|
8439
|
-
documentInfoMap;
|
|
8440
|
-
/** Project-level document overview (grouped counts + sizes). */
|
|
8441
|
-
projectDocumentsData;
|
|
8442
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
8443
|
-
/**
|
|
8444
|
-
* Resets all document state. Call when the active project changes.
|
|
8445
|
-
* Follow with `fetchOverview()` once the triplestore is ready.
|
|
8446
|
-
*/
|
|
8447
|
-
reset() {
|
|
8448
|
-
this._documentInfoMap.set({});
|
|
8449
|
-
this._projectDocumentsData.set({
|
|
8450
|
-
duplicateCount: 0,
|
|
8451
|
-
documentsBySuffix: {},
|
|
8452
|
-
documentsByContentCategory: {}
|
|
8453
|
-
});
|
|
8454
|
-
}
|
|
8455
|
-
/**
|
|
8456
|
-
* Updates the active language and clears the document info map so that
|
|
8457
|
-
* language-sensitive fields (subject, summary) are re-fetched on the next
|
|
8458
|
-
* `requestDocumentData()` call.
|
|
8459
|
-
*/
|
|
8460
|
-
setLanguage(lang) {
|
|
8461
|
-
if (this._currentLang === lang)
|
|
8462
|
-
return;
|
|
8463
|
-
this._currentLang = lang;
|
|
8464
|
-
this._api.setLanguage(lang);
|
|
8465
|
-
this._documentInfoMap.set({});
|
|
8466
|
-
}
|
|
8467
|
-
// ── Public API ─────────────────────────────────────────────────────────────
|
|
8468
|
-
/**
|
|
8469
|
-
* Fetches the three-part project overview (by suffix, by content category,
|
|
8470
|
-
* duplicate count) in parallel and writes them as a single atomic update to
|
|
8471
|
-
* `projectDocumentsData`. Safe to call again to refresh.
|
|
8472
|
-
*/
|
|
8473
|
-
async fetchOverview() {
|
|
8474
|
-
this._projectDocumentsData.set({
|
|
8475
|
-
duplicateCount: 0,
|
|
8476
|
-
documentsBySuffix: {},
|
|
8477
|
-
documentsByContentCategory: {}
|
|
8478
|
-
});
|
|
8479
|
-
const qSuffix = this._buildDocumentsBySuffixQuery();
|
|
8480
|
-
const qCategory = this._buildDocumentsByContentCategoryQuery();
|
|
8481
|
-
const qDuplicates = this._buildDuplicateCountQuery();
|
|
8482
|
-
await staleWhileRevalidate(
|
|
8483
|
-
qSuffix + qCategory + qDuplicates,
|
|
8484
|
-
async () => {
|
|
8485
|
-
const [bySuffix, byCategory, duplicateCount] = await Promise.all([
|
|
8486
|
-
this._runDocumentsBySuffixQuery(qSuffix),
|
|
8487
|
-
this._runDocumentsByContentCategoryQuery(qCategory),
|
|
8488
|
-
this._runDuplicateCountQuery(qDuplicates)
|
|
8489
|
-
]);
|
|
8490
|
-
return { duplicateCount, documentsBySuffix: bySuffix, documentsByContentCategory: byCategory };
|
|
8491
|
-
},
|
|
8492
|
-
(overview) => this._projectDocumentsData.set(overview),
|
|
8493
|
-
this._queryCache
|
|
8494
|
-
);
|
|
8495
|
-
}
|
|
8496
|
-
/**
|
|
8497
|
-
* Lazily batch-fetches core metadata for the given document UUIDs.
|
|
8498
|
-
* Already-cached UUIDs are skipped. Data is merged into `documentInfoMap`
|
|
8499
|
-
* once the SPARQL response arrives.
|
|
8500
|
-
*/
|
|
8501
|
-
requestDocumentData(uuids) {
|
|
8502
|
-
const newUUIDs = uuids.filter((id) => this._documentInfoMap.get()[id] === void 0);
|
|
8503
|
-
if (newUUIDs.length === 0)
|
|
8504
|
-
return;
|
|
8505
|
-
const values = newUUIDs.map((id) => `r:${id}`).join(" ");
|
|
8506
|
-
const lang = this._api.language;
|
|
8507
|
-
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8508
|
-
PREFIX r: <${this.baseURL}>
|
|
8509
|
-
SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
|
|
8510
|
-
(SAMPLE(?fp) AS ?path)
|
|
8511
|
-
(GROUP_CONCAT(DISTINCT ?tag; SEPARATOR=";") AS ?tags)
|
|
8512
|
-
(GROUP_CONCAT(DISTINCT STR(?cat); SEPARATOR=";") AS ?categories)
|
|
8513
|
-
WHERE {
|
|
8514
|
-
VALUES ?contentIRI { ${values} }
|
|
8515
|
-
?contentIRI qcy:sizeBytes ?size ;
|
|
8516
|
-
qcy:hasFileLocation ?loc .
|
|
8517
|
-
?loc qcy:filePath ?fp ;
|
|
8518
|
-
qcy:suffix ?suffix .
|
|
8519
|
-
OPTIONAL { ?contentIRI qcy:hasContentCategory ?cat }
|
|
8520
|
-
OPTIONAL { ?contentIRI qcy:tag ?tag }
|
|
8521
|
-
OPTIONAL { ?loc qcy:remoteProviderId ?pid }
|
|
8522
|
-
|
|
8523
|
-
OPTIONAL { ?contentIRI qcy:subject ?lang_subj FILTER(LANG(?lang_subj) = "${lang}") }
|
|
8524
|
-
OPTIONAL { ?contentIRI qcy:subject ?no_lang_subj }
|
|
8525
|
-
BIND(COALESCE(?lang_subj, ?no_lang_subj) AS ?subject)
|
|
8526
|
-
|
|
8527
|
-
OPTIONAL { ?contentIRI qcy:textSummary ?lang_summary FILTER(LANG(?lang_summary) = "${lang}") }
|
|
8528
|
-
OPTIONAL { ?contentIRI qcy:textSummary ?no_lang_summary }
|
|
8529
|
-
BIND(COALESCE(?lang_summary, ?no_lang_summary) AS ?summary)
|
|
8530
|
-
|
|
8531
|
-
BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?id)
|
|
8532
|
-
}
|
|
8533
|
-
GROUP BY ?id ?contentIRI ?suffix ?size ?subject ?summary`;
|
|
8534
|
-
this._api.sparql(q, this._projectId).then((data) => {
|
|
8535
|
-
const result = data;
|
|
8536
|
-
const updates = { ...this._documentInfoMap.get() };
|
|
8537
|
-
result.results.bindings.forEach((b) => {
|
|
8538
|
-
if (!b["id"] || !b["contentIRI"])
|
|
8539
|
-
return;
|
|
8540
|
-
const id = b["id"].value;
|
|
8541
|
-
updates[id] = {
|
|
8542
|
-
id,
|
|
8543
|
-
contentIRI: b["contentIRI"].value,
|
|
8544
|
-
path: b["path"]?.value ?? "",
|
|
8545
|
-
suffix: b["suffix"]?.value ?? "",
|
|
8546
|
-
size: b["size"] ? parseInt(b["size"].value, 10) : 0,
|
|
8547
|
-
tags: b["tags"]?.value?.split(";").filter(Boolean) ?? [],
|
|
8548
|
-
categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
|
|
8549
|
-
subject: b["subject"]?.value,
|
|
8550
|
-
summary: b["summary"]?.value,
|
|
8551
|
-
providerId: b["pid"]?.value
|
|
8552
|
-
};
|
|
8553
|
-
});
|
|
8554
|
-
this._documentInfoMap.set(updates);
|
|
8555
|
-
}).catch(
|
|
8556
|
-
(err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
|
|
8557
|
-
);
|
|
8558
|
-
}
|
|
8559
|
-
/**
|
|
8560
|
-
* Returns a single arbitrary file path from the project's triplestore.
|
|
8561
|
-
* Useful for pre-filling path-based query inputs with a realistic example.
|
|
8562
|
-
*/
|
|
8563
|
-
async randomFilePath() {
|
|
8564
|
-
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8565
|
-
SELECT ?path
|
|
8566
|
-
WHERE {
|
|
8567
|
-
?fl a qcy:FileLocation ;
|
|
8568
|
-
qcy:filePath ?path .
|
|
8569
|
-
}
|
|
8570
|
-
LIMIT 1`;
|
|
8571
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8572
|
-
return data.results.bindings[0]?.["path"]?.value ?? null;
|
|
8573
|
-
}
|
|
8574
|
-
// ── Private helpers ────────────────────────────────────────────────────────
|
|
8575
|
-
async _fetchDocumentsBySuffix() {
|
|
8576
|
-
return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
|
|
8577
|
-
}
|
|
8578
|
-
_buildDocumentsBySuffixQuery() {
|
|
8579
|
-
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8580
|
-
SELECT ?ext (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
8581
|
-
WHERE {
|
|
8582
|
-
{
|
|
8583
|
-
SELECT DISTINCT ?fc ?ext ?size
|
|
8584
|
-
WHERE {
|
|
8585
|
-
?fc a qcy:FileContent ;
|
|
8586
|
-
qcy:sizeBytes ?size ;
|
|
8587
|
-
qcy:hasFileLocation/qcy:suffix ?ext .
|
|
8588
|
-
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
8589
|
-
}
|
|
8590
|
-
}
|
|
8591
|
-
}
|
|
8592
|
-
GROUP BY ?ext
|
|
8593
|
-
ORDER BY DESC(?docCount)`;
|
|
8594
|
-
}
|
|
8595
|
-
async _runDocumentsBySuffixQuery(q) {
|
|
8596
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8597
|
-
const result = {};
|
|
8598
|
-
data.results.bindings.forEach((b) => {
|
|
8599
|
-
if (!b["ext"])
|
|
8600
|
-
return;
|
|
8601
|
-
result[b["ext"].value] = {
|
|
8602
|
-
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
8603
|
-
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
8604
|
-
};
|
|
8605
|
-
});
|
|
8606
|
-
return result;
|
|
8607
|
-
}
|
|
8608
|
-
async _fetchDocumentsByContentCategory() {
|
|
8609
|
-
return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
|
|
8610
|
-
}
|
|
8611
|
-
_buildDocumentsByContentCategoryQuery() {
|
|
8612
|
-
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8613
|
-
SELECT ?cat (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
8614
|
-
WHERE {
|
|
8615
|
-
{
|
|
8616
|
-
SELECT DISTINCT ?fc ?cat ?size
|
|
8617
|
-
WHERE {
|
|
8618
|
-
?fc a qcy:FileContent ;
|
|
8619
|
-
qcy:hasContentCategory ?cat ;
|
|
8620
|
-
qcy:sizeBytes ?size .
|
|
8621
|
-
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
8622
|
-
}
|
|
8623
|
-
}
|
|
8624
|
-
}
|
|
8625
|
-
GROUP BY ?cat
|
|
8626
|
-
ORDER BY DESC(?docCount)`;
|
|
8627
|
-
}
|
|
8628
|
-
async _runDocumentsByContentCategoryQuery(q) {
|
|
8629
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8630
|
-
const result = {};
|
|
8631
|
-
data.results.bindings.forEach((b) => {
|
|
8632
|
-
if (!b["cat"])
|
|
8633
|
-
return;
|
|
8634
|
-
result[b["cat"].value] = {
|
|
8635
|
-
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
8636
|
-
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
8637
|
-
};
|
|
8638
|
-
});
|
|
8639
|
-
return result;
|
|
8640
|
-
}
|
|
8641
|
-
async _fetchDuplicateCount() {
|
|
8642
|
-
return this._runDuplicateCountQuery(this._buildDuplicateCountQuery());
|
|
8643
|
-
}
|
|
8644
|
-
_buildDuplicateCountQuery() {
|
|
8645
|
-
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8646
|
-
SELECT (COUNT(*) AS ?count)
|
|
8647
|
-
WHERE {
|
|
8648
|
-
SELECT ?fc
|
|
8649
|
-
WHERE {
|
|
8650
|
-
?fc a qcy:FileContent ;
|
|
8651
|
-
qcy:hasFileLocation ?fl .
|
|
8652
|
-
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
8653
|
-
}
|
|
8654
|
-
GROUP BY ?fc
|
|
8655
|
-
HAVING (COUNT(?fl) > 1)
|
|
8656
|
-
}`;
|
|
8657
|
-
}
|
|
8658
|
-
async _runDuplicateCountQuery(q) {
|
|
8659
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8660
|
-
const first = data.results.bindings[0];
|
|
8661
|
-
return first?.["count"] ? parseInt(first["count"].value, 10) : 0;
|
|
8662
|
-
}
|
|
8663
|
-
};
|
|
8664
|
-
|
|
8665
|
-
// libs/js/cue-sdk/src/lib/project-view.ts
|
|
8666
|
-
var CueProjectView = class {
|
|
8667
|
-
constructor(_api, _projectId, { language, queryCache, rdfBase = RESOURCE_BASE, graphType }) {
|
|
8668
|
-
this._api = _api;
|
|
8669
|
-
this._projectId = _projectId;
|
|
8670
|
-
this.schema = new CueProjectSchema(_api, _projectId, language, queryCache, graphType);
|
|
8671
|
-
this.entities = new CueProjectEntities(_api, _projectId, rdfBase, queryCache, graphType);
|
|
8672
|
-
this.documents = new CueProjectDocuments(_api, _projectId, language, rdfBase, queryCache, graphType);
|
|
8673
|
-
this.availableContentCategories = this.schema.availableContentCategories;
|
|
8674
|
-
this.availableEntityCategories = this.schema.availableEntityCategories;
|
|
8675
|
-
this.availableEntityRelationships = this.schema.availableEntityRelationships;
|
|
8676
|
-
this.schemaReady = this.schema.ready;
|
|
8677
|
-
this.entityInfoMap = this.entities.entityInfoMap;
|
|
8678
|
-
this.entityGraph = this.entities.entityGraph;
|
|
8679
|
-
this.documentInfoMap = this.documents.documentInfoMap;
|
|
8680
|
-
this.projectDocumentsData = this.documents.projectDocumentsData;
|
|
8681
|
-
this.searchResults = this._searchResults.asReadonly();
|
|
8682
|
-
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed:", err));
|
|
8683
|
-
}
|
|
8684
|
-
/** Direct access to the schema data class (available categories / relationships). */
|
|
8685
|
-
schema;
|
|
8686
|
-
/** Direct access to the entity data class. */
|
|
8687
|
-
entities;
|
|
8688
|
-
/** Direct access to the document data class. */
|
|
8689
|
-
documents;
|
|
8690
|
-
// ── Proxied signals ────────────────────────────────────────────────────────
|
|
8691
|
-
/** Available content category definitions for this project. Auto-fetched on init. */
|
|
8692
|
-
availableContentCategories;
|
|
8693
|
-
/** Available entity category definitions for this project. Auto-fetched on init. */
|
|
8694
|
-
availableEntityCategories;
|
|
8695
|
-
/** Available entity relationship types. Auto-fetched on init. */
|
|
8696
|
-
availableEntityRelationships;
|
|
8697
|
-
/**
|
|
8698
|
-
* Resolves when the initial schema load has completed. Await before reading
|
|
8699
|
-
* schema signal values imperatively.
|
|
8700
|
-
*/
|
|
8701
|
-
schemaReady;
|
|
8702
|
-
/** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
|
|
8703
|
-
entityInfoMap;
|
|
8704
|
-
/** Project-level entity co-occurrence graph. Fetched once on init. */
|
|
8705
|
-
entityGraph;
|
|
8706
|
-
/** Per-document info map. Populated lazily via `requestDocumentData()`. */
|
|
8707
|
-
documentInfoMap;
|
|
8708
|
-
/** Project document overview (counts by suffix and category). Fetched on init. */
|
|
8709
|
-
projectDocumentsData;
|
|
8710
|
-
// ── Search state ───────────────────────────────────────────────────────────
|
|
8711
|
-
_searchResults = new CueSignal(void 0);
|
|
8712
|
-
/** The result of the most recent `search()` call. `undefined` before first search. */
|
|
8713
|
-
searchResults;
|
|
8714
|
-
_destroyed = false;
|
|
8715
|
-
// ── Entity methods ─────────────────────────────────────────────────────────
|
|
8716
|
-
/**
|
|
8717
|
-
* Lazily batch-fetch core data (label + categories) for the given entity UUIDs.
|
|
8718
|
-
* Already-fetched UUIDs are skipped. Populates `entityInfoMap`.
|
|
8719
|
-
*/
|
|
8720
|
-
requestEntityData(uuids, includeMentionCount = false) {
|
|
8721
|
-
if (this._destroyed)
|
|
8722
|
-
return;
|
|
8723
|
-
this.entities.requestEntityData(uuids, includeMentionCount);
|
|
8724
|
-
}
|
|
8725
|
-
/**
|
|
8726
|
-
* Lazily fetch OSM location data for the given entity UUIDs.
|
|
8727
|
-
* Already-fetched UUIDs are skipped. Populates `entityInfoMap` geometry fields.
|
|
8728
|
-
*/
|
|
8729
|
-
async requestEntityLocations(uuids) {
|
|
8730
|
-
if (this._destroyed)
|
|
8731
|
-
return;
|
|
8732
|
-
return this.entities.requestEntityLocations(uuids);
|
|
8733
|
-
}
|
|
8734
|
-
/**
|
|
8735
|
-
* Fetch incoming and outgoing relationships for a single entity IRI.
|
|
8736
|
-
* Result is stored in `entityInfoMap[uuid].relationshipData`.
|
|
8737
|
-
*/
|
|
8738
|
-
async fetchEntityRelationships(iri) {
|
|
8739
|
-
if (this._destroyed)
|
|
8740
|
-
throw new Error("CueProjectView is destroyed");
|
|
8741
|
-
return this.entities.fetchEntityRelationships(iri);
|
|
8742
|
-
}
|
|
8743
|
-
/**
|
|
8744
|
-
* Fetch UUIDs of documents that reference the given entity IRI.
|
|
8745
|
-
* Result is stored in `entityInfoMap[uuid].documentRefs`.
|
|
8746
|
-
*/
|
|
8747
|
-
async fetchEntityDocuments(iri) {
|
|
8748
|
-
if (this._destroyed)
|
|
8749
|
-
throw new Error("CueProjectView is destroyed");
|
|
8750
|
-
return this.entities.fetchEntityDocuments(iri);
|
|
8751
|
-
}
|
|
8752
|
-
/** Constructs the full RDF IRI for an entity UUID. */
|
|
8753
|
-
entityIri(uuid) {
|
|
8754
|
-
return this.entities.entityIri(uuid);
|
|
8755
|
-
}
|
|
8756
|
-
// ── Document methods ───────────────────────────────────────────────────────
|
|
8757
|
-
/**
|
|
8758
|
-
* Lazily batch-fetch document info for the given UUIDs.
|
|
8759
|
-
* Already-fetched UUIDs are skipped. Populates `documentInfoMap`.
|
|
8760
|
-
*/
|
|
8761
|
-
requestDocumentData(uuids) {
|
|
8762
|
-
if (this._destroyed)
|
|
8763
|
-
return;
|
|
8764
|
-
this.documents.requestDocumentData(uuids);
|
|
8765
|
-
}
|
|
8766
|
-
// ── Search ─────────────────────────────────────────────────────────────────
|
|
8767
|
-
/**
|
|
8768
|
-
* Run a natural-language search against the project.
|
|
8769
|
-
* The result is stored in `searchResults` and replaces any previous result.
|
|
8770
|
-
*/
|
|
8771
|
-
async search(term, options) {
|
|
8772
|
-
if (this._destroyed)
|
|
8773
|
-
return;
|
|
8774
|
-
const result = await this._api.search({
|
|
8775
|
-
term,
|
|
8776
|
-
projectId: this._projectId,
|
|
8777
|
-
categories: options?.categories
|
|
8778
|
-
});
|
|
8779
|
-
if (!this._destroyed) {
|
|
8780
|
-
this._searchResults.set(result);
|
|
8781
|
-
}
|
|
8782
|
-
}
|
|
8783
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
8784
|
-
/**
|
|
8785
|
-
* Switch the active language for schema labels and document text fields.
|
|
8786
|
-
* Schema responses are cached per language (instant if previously loaded).
|
|
8787
|
-
* The document info map is cleared and lazily re-populated on next access.
|
|
8788
|
-
*/
|
|
8789
|
-
setLanguage(lang) {
|
|
8790
|
-
if (this._destroyed)
|
|
8791
|
-
return;
|
|
8792
|
-
this.schema.setLanguage(lang);
|
|
8793
|
-
this.documents.setLanguage(lang);
|
|
8794
|
-
}
|
|
8795
|
-
/**
|
|
8796
|
-
* Reset all entity and document state and re-fetch the project overview.
|
|
8797
|
-
* Prefer creating a fresh `CueProjectView` when switching projects.
|
|
8798
|
-
* Use `reset()` only when the same project's data needs to be invalidated.
|
|
8799
|
-
*/
|
|
8800
|
-
reset() {
|
|
8801
|
-
if (this._destroyed)
|
|
8802
|
-
return;
|
|
8803
|
-
this.entities.reset();
|
|
8804
|
-
this.documents.reset();
|
|
8805
|
-
this._searchResults.set(void 0);
|
|
8806
|
-
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed after reset:", err));
|
|
8807
|
-
}
|
|
8808
|
-
/**
|
|
8809
|
-
* Tear down this view instance. Clears all reactive state and blocks further
|
|
8810
|
-
* updates. Call from the Angular adapter's `ngOnDestroy` or equivalent.
|
|
8811
|
-
*/
|
|
8812
|
-
destroy() {
|
|
8813
|
-
this._destroyed = true;
|
|
8814
|
-
this._searchResults.set(void 0);
|
|
8815
|
-
}
|
|
8816
|
-
};
|
|
8817
|
-
|
|
8818
|
-
// libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
|
|
8819
|
-
init_src();
|
|
8820
|
-
|
|
8821
8610
|
// libs/js/models/src/lib/file-extensions.ts
|
|
8822
8611
|
var fileExtensionsInfo = {
|
|
8823
8612
|
[".aac" /* AAC */]: {
|
|
@@ -9680,41 +9469,706 @@ var fileExtensionsInfo = {
|
|
|
9680
9469
|
}
|
|
9681
9470
|
};
|
|
9682
9471
|
|
|
9683
|
-
// libs/js/models/src/lib/file-mime-categories.ts
|
|
9684
|
-
var fileTypeToMimeCategory = {
|
|
9685
|
-
["audio" /* AUDIO */]: "AudioFileContent" /* AUDIO */,
|
|
9686
|
-
["video" /* VIDEO */]: "VideoFileContent" /* VIDEO */,
|
|
9687
|
-
["image" /* IMAGE */]: "ImageFileContent" /* IMAGE */,
|
|
9688
|
-
["text" /* TEXT */]: "TextFileContent" /* TEXT */,
|
|
9689
|
-
["markup" /* MARKUP */]: "MarkupFileContent" /* MARKUP */,
|
|
9690
|
-
["script" /* SCRIPT */]: "ScriptFileContent" /* SCRIPT */,
|
|
9691
|
-
["data" /* DATA */]: "DataFileContent" /* DATA */,
|
|
9692
|
-
["archive" /* ARCHIVE */]: "ArchiveFileContent" /* ARCHIVE */,
|
|
9693
|
-
["installer" /* INSTALLER */]: "InstallerFileContent" /* INSTALLER */,
|
|
9694
|
-
["binary" /* BINARY */]: "BinaryFileContent" /* BINARY */,
|
|
9695
|
-
["backup" /* BACKUP */]: "BackupFileContent" /* BACKUP */,
|
|
9696
|
-
["automation" /* AUTOMATION */]: "AutomationFileContent" /* AUTOMATION */,
|
|
9697
|
-
["presentation" /* PRESENTATION */]: "PresentationFileContent" /* PRESENTATION */,
|
|
9698
|
-
["spreadsheet" /* SPREADSHEET */]: "SpreadsheetFileContent" /* SPREADSHEET */,
|
|
9699
|
-
["font" /* FONT */]: "FontFileContent" /* FONT */,
|
|
9700
|
-
["geospatial" /* GEOSPATIAL */]: "GeospatialFileContent" /* GEOSPATIAL */,
|
|
9701
|
-
["3d" /* THREE_D */]: "ThreeDFileContent" /* THREE_D */,
|
|
9702
|
-
["cad" /* CAD */]: "CADFileContent" /* CAD */,
|
|
9703
|
-
["bim" /* BIM */]: "BIMFileContent" /* BIM */,
|
|
9704
|
-
["planning" /* PLANNING */]: "PlanningFileContent" /* PLANNING */,
|
|
9705
|
-
["email" /* EMAIL */]: "EmailFileContent" /* EMAIL */,
|
|
9706
|
-
["multimedia" /* MULTIMEDIA */]: "MultimediaFileContent" /* MULTIMEDIA */,
|
|
9707
|
-
["unknown" /* UNKNOWN */]: "UnknownFileContent" /* UNKNOWN */
|
|
9472
|
+
// libs/js/models/src/lib/file-mime-categories.ts
|
|
9473
|
+
var fileTypeToMimeCategory = {
|
|
9474
|
+
["audio" /* AUDIO */]: "AudioFileContent" /* AUDIO */,
|
|
9475
|
+
["video" /* VIDEO */]: "VideoFileContent" /* VIDEO */,
|
|
9476
|
+
["image" /* IMAGE */]: "ImageFileContent" /* IMAGE */,
|
|
9477
|
+
["text" /* TEXT */]: "TextFileContent" /* TEXT */,
|
|
9478
|
+
["markup" /* MARKUP */]: "MarkupFileContent" /* MARKUP */,
|
|
9479
|
+
["script" /* SCRIPT */]: "ScriptFileContent" /* SCRIPT */,
|
|
9480
|
+
["data" /* DATA */]: "DataFileContent" /* DATA */,
|
|
9481
|
+
["archive" /* ARCHIVE */]: "ArchiveFileContent" /* ARCHIVE */,
|
|
9482
|
+
["installer" /* INSTALLER */]: "InstallerFileContent" /* INSTALLER */,
|
|
9483
|
+
["binary" /* BINARY */]: "BinaryFileContent" /* BINARY */,
|
|
9484
|
+
["backup" /* BACKUP */]: "BackupFileContent" /* BACKUP */,
|
|
9485
|
+
["automation" /* AUTOMATION */]: "AutomationFileContent" /* AUTOMATION */,
|
|
9486
|
+
["presentation" /* PRESENTATION */]: "PresentationFileContent" /* PRESENTATION */,
|
|
9487
|
+
["spreadsheet" /* SPREADSHEET */]: "SpreadsheetFileContent" /* SPREADSHEET */,
|
|
9488
|
+
["font" /* FONT */]: "FontFileContent" /* FONT */,
|
|
9489
|
+
["geospatial" /* GEOSPATIAL */]: "GeospatialFileContent" /* GEOSPATIAL */,
|
|
9490
|
+
["3d" /* THREE_D */]: "ThreeDFileContent" /* THREE_D */,
|
|
9491
|
+
["cad" /* CAD */]: "CADFileContent" /* CAD */,
|
|
9492
|
+
["bim" /* BIM */]: "BIMFileContent" /* BIM */,
|
|
9493
|
+
["planning" /* PLANNING */]: "PlanningFileContent" /* PLANNING */,
|
|
9494
|
+
["email" /* EMAIL */]: "EmailFileContent" /* EMAIL */,
|
|
9495
|
+
["multimedia" /* MULTIMEDIA */]: "MultimediaFileContent" /* MULTIMEDIA */,
|
|
9496
|
+
["unknown" /* UNKNOWN */]: "UnknownFileContent" /* UNKNOWN */
|
|
9497
|
+
};
|
|
9498
|
+
|
|
9499
|
+
// libs/js/models/src/lib/project.ts
|
|
9500
|
+
var import_uuid3 = require("uuid");
|
|
9501
|
+
|
|
9502
|
+
// libs/js/models/src/lib/search-log-entry.ts
|
|
9503
|
+
var import_uuid4 = require("uuid");
|
|
9504
|
+
|
|
9505
|
+
// libs/js/models/src/lib/search-response.ts
|
|
9506
|
+
var import_uuid5 = require("uuid");
|
|
9507
|
+
|
|
9508
|
+
// libs/js/cue-sdk/src/lib/documents.ts
|
|
9509
|
+
var CueProjectDocuments = class {
|
|
9510
|
+
constructor(_api, _projectId, language, rdfBase = RESOURCE_BASE, _queryCache, _graphType, _verbose = false) {
|
|
9511
|
+
this._api = _api;
|
|
9512
|
+
this._projectId = _projectId;
|
|
9513
|
+
this._queryCache = _queryCache;
|
|
9514
|
+
this._graphType = _graphType;
|
|
9515
|
+
this._verbose = _verbose;
|
|
9516
|
+
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
9517
|
+
this._currentLang = language ?? this._api.language;
|
|
9518
|
+
this.documentInfoMap = this._documentInfoMap.asReadonly();
|
|
9519
|
+
this.projectDocumentsData = this._projectDocumentsData.asReadonly();
|
|
9520
|
+
}
|
|
9521
|
+
/** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
|
|
9522
|
+
baseURL;
|
|
9523
|
+
/** Tracks the language for which `_documentInfoMap` is currently populated. */
|
|
9524
|
+
_currentLang;
|
|
9525
|
+
_documentInfoMap = new CueSignal({});
|
|
9526
|
+
/** Cumulative unique document UUIDs ever passed to request methods (survives cache hits). */
|
|
9527
|
+
_seenIds = /* @__PURE__ */ new Set();
|
|
9528
|
+
_projectDocumentsData = new CueSignal({
|
|
9529
|
+
duplicateCount: 0,
|
|
9530
|
+
documentsBySuffix: {},
|
|
9531
|
+
documentsByContentCategory: {}
|
|
9532
|
+
});
|
|
9533
|
+
/** Lazily populated per-document detail map. */
|
|
9534
|
+
documentInfoMap;
|
|
9535
|
+
/** Project-level document overview (grouped counts + sizes). */
|
|
9536
|
+
projectDocumentsData;
|
|
9537
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
9538
|
+
/**
|
|
9539
|
+
* Resets all document state. Call when the active project changes.
|
|
9540
|
+
* Follow with `fetchOverview()` once the triplestore is ready.
|
|
9541
|
+
*/
|
|
9542
|
+
reset() {
|
|
9543
|
+
this._documentInfoMap.set({});
|
|
9544
|
+
this._seenIds.clear();
|
|
9545
|
+
this._projectDocumentsData.set({
|
|
9546
|
+
duplicateCount: 0,
|
|
9547
|
+
documentsBySuffix: {},
|
|
9548
|
+
documentsByContentCategory: {}
|
|
9549
|
+
});
|
|
9550
|
+
}
|
|
9551
|
+
/**
|
|
9552
|
+
* Updates the active language and clears the document info map so that
|
|
9553
|
+
* language-sensitive fields (subject, summary) are re-fetched on the next
|
|
9554
|
+
* `requestDocumentData()` call.
|
|
9555
|
+
*/
|
|
9556
|
+
setLanguage(lang) {
|
|
9557
|
+
if (this._currentLang === lang)
|
|
9558
|
+
return;
|
|
9559
|
+
this._currentLang = lang;
|
|
9560
|
+
this._api.setLanguage(lang);
|
|
9561
|
+
this._documentInfoMap.set({});
|
|
9562
|
+
}
|
|
9563
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
9564
|
+
/**
|
|
9565
|
+
* Fetches the three-part project overview (by suffix, by content category,
|
|
9566
|
+
* duplicate count) in parallel and writes them as a single atomic update to
|
|
9567
|
+
* `projectDocumentsData`. Safe to call again to refresh.
|
|
9568
|
+
*/
|
|
9569
|
+
async fetchOverview() {
|
|
9570
|
+
this._projectDocumentsData.set({
|
|
9571
|
+
duplicateCount: 0,
|
|
9572
|
+
documentsBySuffix: {},
|
|
9573
|
+
documentsByContentCategory: {}
|
|
9574
|
+
});
|
|
9575
|
+
const qSuffix = this._buildDocumentsBySuffixQuery();
|
|
9576
|
+
const qCategory = this._buildDocumentsByContentCategoryQuery();
|
|
9577
|
+
const qDuplicates = this._buildDuplicateCountQuery();
|
|
9578
|
+
await staleWhileRevalidate(
|
|
9579
|
+
qSuffix + qCategory + qDuplicates,
|
|
9580
|
+
async () => {
|
|
9581
|
+
const [bySuffix, byCategory, duplicateCount] = await Promise.all([
|
|
9582
|
+
this._runDocumentsBySuffixQuery(qSuffix),
|
|
9583
|
+
this._runDocumentsByContentCategoryQuery(qCategory),
|
|
9584
|
+
this._runDuplicateCountQuery(qDuplicates)
|
|
9585
|
+
]);
|
|
9586
|
+
return { duplicateCount, documentsBySuffix: bySuffix, documentsByContentCategory: byCategory };
|
|
9587
|
+
},
|
|
9588
|
+
(overview) => this._projectDocumentsData.set(overview),
|
|
9589
|
+
this._queryCache
|
|
9590
|
+
);
|
|
9591
|
+
}
|
|
9592
|
+
/**
|
|
9593
|
+
* Lazily batch-fetches core metadata for the given document UUIDs.
|
|
9594
|
+
* Already-cached UUIDs are skipped. Data is merged into `documentInfoMap`
|
|
9595
|
+
* once the SPARQL response arrives.
|
|
9596
|
+
*/
|
|
9597
|
+
requestDocumentData(uuids) {
|
|
9598
|
+
for (const id of uuids)
|
|
9599
|
+
this._seenIds.add(id);
|
|
9600
|
+
const newUUIDs = uuids.filter((id) => this._documentInfoMap.get()[id] === void 0);
|
|
9601
|
+
if (newUUIDs.length === 0)
|
|
9602
|
+
return;
|
|
9603
|
+
this._log(`requestDocumentData: ${newUUIDs.length} new / ${uuids.length} requested | cumulative: ${this._seenIds.size} seen`);
|
|
9604
|
+
this._fetchDocumentInfoBatch(newUUIDs).catch(
|
|
9605
|
+
(err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
|
|
9606
|
+
);
|
|
9607
|
+
}
|
|
9608
|
+
/**
|
|
9609
|
+
* Promise-based alternative to {@link requestDocumentData} for non-reactive contexts.
|
|
9610
|
+
*
|
|
9611
|
+
* Resolves with the `DocumentInfo` entries for every requested UUID once the
|
|
9612
|
+
* SPARQL response arrives. UUIDs already present in the cache are returned
|
|
9613
|
+
* immediately without a network request. The result is also written into
|
|
9614
|
+
* `documentInfoMap` so reactive consumers stay in sync.
|
|
9615
|
+
*
|
|
9616
|
+
* UUIDs not found in the triplestore are omitted from the returned map.
|
|
9617
|
+
*
|
|
9618
|
+
* @example
|
|
9619
|
+
* ```ts
|
|
9620
|
+
* const docs = await cueProjectDocs.fetchDocumentData(['uuid1', 'uuid2']);
|
|
9621
|
+
* console.log(docs['uuid1'].subject);
|
|
9622
|
+
* ```
|
|
9623
|
+
*/
|
|
9624
|
+
async fetchDocumentData(uuids) {
|
|
9625
|
+
const current = this._documentInfoMap.get();
|
|
9626
|
+
const newUUIDs = uuids.filter((id) => current[id] === void 0);
|
|
9627
|
+
for (const id of uuids)
|
|
9628
|
+
this._seenIds.add(id);
|
|
9629
|
+
if (newUUIDs.length > 0) {
|
|
9630
|
+
await this._fetchDocumentInfoBatch(newUUIDs);
|
|
9631
|
+
}
|
|
9632
|
+
const updated = this._documentInfoMap.get();
|
|
9633
|
+
return Object.fromEntries(
|
|
9634
|
+
uuids.filter((id) => updated[id] !== void 0).map((id) => [id, updated[id]])
|
|
9635
|
+
);
|
|
9636
|
+
}
|
|
9637
|
+
/**
|
|
9638
|
+
* Fetches a lightweight document metadata shape (id/path/suffix/size) for
|
|
9639
|
+
* the given UUIDs and merges the results into `documentInfoMap`.
|
|
9640
|
+
*
|
|
9641
|
+
* This is useful for list/table contexts that do not need language-tagged
|
|
9642
|
+
* fields (`subject`, `summary`) or category/tag enrichment.
|
|
9643
|
+
*
|
|
9644
|
+
* UUIDs already present in `documentInfoMap` are skipped.
|
|
9645
|
+
*/
|
|
9646
|
+
async fetchDocumentDataSimple(uuids) {
|
|
9647
|
+
const current = this._documentInfoMap.get();
|
|
9648
|
+
const newUUIDs = uuids.filter((id) => current[id] === void 0);
|
|
9649
|
+
for (const id of uuids)
|
|
9650
|
+
this._seenIds.add(id);
|
|
9651
|
+
if (newUUIDs.length > 0) {
|
|
9652
|
+
await this._fetchSimpleDocumentInfoBatch(newUUIDs);
|
|
9653
|
+
}
|
|
9654
|
+
const updated = this._documentInfoMap.get();
|
|
9655
|
+
return Object.fromEntries(
|
|
9656
|
+
uuids.filter((id) => updated[id] !== void 0).map((id) => [id, updated[id]])
|
|
9657
|
+
);
|
|
9658
|
+
}
|
|
9659
|
+
/**
|
|
9660
|
+
* Returns the alternative representations of the given document UUID.
|
|
9661
|
+
*
|
|
9662
|
+
* Alternative representations are derived artefacts stored under
|
|
9663
|
+
* `qcy:alternativeRepresentation` in the triplestore — for example a
|
|
9664
|
+
* `.fragments` BIM tile derived from an `.ifc` source file.
|
|
9665
|
+
*
|
|
9666
|
+
* The returned `DocumentInfo` entries are also merged into
|
|
9667
|
+
* `documentInfoMap` so reactive consumers stay in sync.
|
|
9668
|
+
*
|
|
9669
|
+
* @example
|
|
9670
|
+
* ```ts
|
|
9671
|
+
* const alts = await docs.fetchAlternativeRepresentations('abc-123');
|
|
9672
|
+
* // alts[0].suffix => '.fragments'
|
|
9673
|
+
* ```
|
|
9674
|
+
*/
|
|
9675
|
+
async fetchAlternativeRepresentations(uuid) {
|
|
9676
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9677
|
+
PREFIX r: <${this.baseURL}>
|
|
9678
|
+
SELECT ?altId ?contentIRI ?suffix ?rrp
|
|
9679
|
+
WHERE {
|
|
9680
|
+
r:${uuid} qcy:alternativeRepresentation ?contentIRI .
|
|
9681
|
+
BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?altId)
|
|
9682
|
+
?contentIRI qcy:hasFileLocation ?loc .
|
|
9683
|
+
?loc qcy:suffix ?suffix .
|
|
9684
|
+
OPTIONAL { ?loc qcy:remoteRelativePath ?rrp }
|
|
9685
|
+
}`;
|
|
9686
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9687
|
+
const updates = { ...this._documentInfoMap.get() };
|
|
9688
|
+
const alts = [];
|
|
9689
|
+
data.results.bindings.forEach((b) => {
|
|
9690
|
+
if (!b["altId"] || !b["contentIRI"])
|
|
9691
|
+
return;
|
|
9692
|
+
const id = b["altId"].value;
|
|
9693
|
+
const info = {
|
|
9694
|
+
id,
|
|
9695
|
+
contentIRI: b["contentIRI"].value,
|
|
9696
|
+
path: "",
|
|
9697
|
+
suffix: b["suffix"]?.value ?? "",
|
|
9698
|
+
size: 0,
|
|
9699
|
+
tags: [],
|
|
9700
|
+
categories: [],
|
|
9701
|
+
remoteRelativePath: b["rrp"]?.value
|
|
9702
|
+
};
|
|
9703
|
+
updates[id] = info;
|
|
9704
|
+
alts.push(info);
|
|
9705
|
+
});
|
|
9706
|
+
this._documentInfoMap.set(updates);
|
|
9707
|
+
return alts;
|
|
9708
|
+
}
|
|
9709
|
+
/**
|
|
9710
|
+
* Returns a single arbitrary file path from the project's triplestore.
|
|
9711
|
+
* Useful for pre-filling path-based query inputs with a realistic example.
|
|
9712
|
+
*/
|
|
9713
|
+
async randomFilePath() {
|
|
9714
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9715
|
+
SELECT ?path
|
|
9716
|
+
WHERE {
|
|
9717
|
+
?fl a qcy:FileLocation ;
|
|
9718
|
+
qcy:filePath ?path .
|
|
9719
|
+
}
|
|
9720
|
+
LIMIT 1`;
|
|
9721
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9722
|
+
return data.results.bindings[0]?.["path"]?.value ?? null;
|
|
9723
|
+
}
|
|
9724
|
+
async documentsBySuffix(suffixes, includeMetadata = false) {
|
|
9725
|
+
if (suffixes.length === 0)
|
|
9726
|
+
return [];
|
|
9727
|
+
const normalised = suffixes.map(
|
|
9728
|
+
(s) => (s.startsWith(".") ? s : `.${s}`).toLowerCase()
|
|
9729
|
+
);
|
|
9730
|
+
const values = normalised.map((s) => `"${s}"`).join(" ");
|
|
9731
|
+
const q = includeMetadata ? `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9732
|
+
SELECT ?id (SAMPLE(?fp) AS ?path) ?suffix (MAX(?sz) AS ?size)
|
|
9733
|
+
WHERE {
|
|
9734
|
+
VALUES ?suffix { ${values} }
|
|
9735
|
+
?iri a qcy:FileContent ;
|
|
9736
|
+
qcy:sizeBytes ?sz ;
|
|
9737
|
+
qcy:hasFileLocation ?loc .
|
|
9738
|
+
?loc qcy:suffix ?suffix ;
|
|
9739
|
+
qcy:filePath ?fp .
|
|
9740
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?iri }
|
|
9741
|
+
BIND(REPLACE(STR(?iri), "^.*/", "") AS ?id)
|
|
9742
|
+
}
|
|
9743
|
+
GROUP BY ?id ?suffix` : `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9744
|
+
SELECT DISTINCT ?id
|
|
9745
|
+
WHERE {
|
|
9746
|
+
VALUES ?suffix { ${values} }
|
|
9747
|
+
?iri a qcy:FileContent ;
|
|
9748
|
+
qcy:hasFileLocation/qcy:suffix ?suffix .
|
|
9749
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?iri }
|
|
9750
|
+
BIND(REPLACE(STR(?iri), "^.*/", "") AS ?id)
|
|
9751
|
+
}`;
|
|
9752
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9753
|
+
return data.results.bindings.filter((b) => b["id"]).map((b) => {
|
|
9754
|
+
const uuid = b["id"].value;
|
|
9755
|
+
const base = { iri: this._resourceIri(uuid), uuid };
|
|
9756
|
+
if (!includeMetadata)
|
|
9757
|
+
return base;
|
|
9758
|
+
return {
|
|
9759
|
+
...base,
|
|
9760
|
+
path: b["path"]?.value ?? "",
|
|
9761
|
+
suffix: b["suffix"]?.value ?? "",
|
|
9762
|
+
size: b["size"] ? parseInt(b["size"].value, 10) : 0
|
|
9763
|
+
};
|
|
9764
|
+
});
|
|
9765
|
+
}
|
|
9766
|
+
documentsByFileType(fileTypes, includeMetadata = false) {
|
|
9767
|
+
const typeSet = new Set(fileTypes);
|
|
9768
|
+
const suffixes = Object.values(fileExtensionsInfo).filter((info) => typeSet.has(info.type)).map((info) => info.suffix);
|
|
9769
|
+
return this.documentsBySuffix(suffixes, includeMetadata);
|
|
9770
|
+
}
|
|
9771
|
+
async documentsByContentCategory(categoryIRIs, includeMetadata = false) {
|
|
9772
|
+
if (categoryIRIs.length === 0)
|
|
9773
|
+
return [];
|
|
9774
|
+
const values = categoryIRIs.map((iri) => `<${iri}>`).join(" ");
|
|
9775
|
+
const q = includeMetadata ? `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9776
|
+
SELECT ?id (SAMPLE(?fp) AS ?path) ?suffix (MAX(?sz) AS ?size)
|
|
9777
|
+
WHERE {
|
|
9778
|
+
VALUES ?cat { ${values} }
|
|
9779
|
+
?iri a qcy:FileContent ;
|
|
9780
|
+
qcy:hasContentCategory ?cat ;
|
|
9781
|
+
qcy:sizeBytes ?sz ;
|
|
9782
|
+
qcy:hasFileLocation ?loc .
|
|
9783
|
+
?loc qcy:suffix ?suffix ;
|
|
9784
|
+
qcy:filePath ?fp .
|
|
9785
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?iri }
|
|
9786
|
+
BIND(REPLACE(STR(?iri), "^.*/", "") AS ?id)
|
|
9787
|
+
}
|
|
9788
|
+
GROUP BY ?id ?suffix` : `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9789
|
+
SELECT DISTINCT ?id
|
|
9790
|
+
WHERE {
|
|
9791
|
+
VALUES ?cat { ${values} }
|
|
9792
|
+
?iri a qcy:FileContent ;
|
|
9793
|
+
qcy:hasContentCategory ?cat .
|
|
9794
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?iri }
|
|
9795
|
+
BIND(REPLACE(STR(?iri), "^.*/", "") AS ?id)
|
|
9796
|
+
}`;
|
|
9797
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9798
|
+
return data.results.bindings.filter((b) => b["id"]).map((b) => {
|
|
9799
|
+
const uuid = b["id"].value;
|
|
9800
|
+
const base = { iri: this._resourceIri(uuid), uuid };
|
|
9801
|
+
if (!includeMetadata)
|
|
9802
|
+
return base;
|
|
9803
|
+
return {
|
|
9804
|
+
...base,
|
|
9805
|
+
path: b["path"]?.value ?? "",
|
|
9806
|
+
suffix: b["suffix"]?.value ?? "",
|
|
9807
|
+
size: b["size"] ? parseInt(b["size"].value, 10) : 0
|
|
9808
|
+
};
|
|
9809
|
+
});
|
|
9810
|
+
}
|
|
9811
|
+
documentsByMime(mimeTypes, includeMetadata = false) {
|
|
9812
|
+
const mimeSet = new Set(mimeTypes);
|
|
9813
|
+
const suffixes = Object.values(fileExtensionsInfo).filter((info) => mimeSet.has(info.mime)).map((info) => info.suffix);
|
|
9814
|
+
return this.documentsBySuffix(suffixes, includeMetadata);
|
|
9815
|
+
}
|
|
9816
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
9817
|
+
/** Builds a full resource IRI from a UUID without a SPARQL round-trip. */
|
|
9818
|
+
_resourceIri(uuid) {
|
|
9819
|
+
return `${this.baseURL}${uuid}`;
|
|
9820
|
+
}
|
|
9821
|
+
_log(message) {
|
|
9822
|
+
if (this._verbose) {
|
|
9823
|
+
console.debug(`[CueProjectDocuments pid=${this._projectId}] ${message}`);
|
|
9824
|
+
}
|
|
9825
|
+
}
|
|
9826
|
+
/** Executes the document-info SPARQL query for the given UUIDs, merges results
|
|
9827
|
+
* into `documentInfoMap`, and returns the newly fetched entries. */
|
|
9828
|
+
async _fetchDocumentInfoBatch(uuids) {
|
|
9829
|
+
const values = uuids.map((id) => `r:${id}`).join(" ");
|
|
9830
|
+
const lang = this._api.language;
|
|
9831
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9832
|
+
PREFIX r: <${this.baseURL}>
|
|
9833
|
+
SELECT ?id ?suffix ?size ?subject ?summary
|
|
9834
|
+
(SAMPLE(?fp) AS ?path)
|
|
9835
|
+
(GROUP_CONCAT(DISTINCT ?tag; SEPARATOR=";") AS ?tags)
|
|
9836
|
+
(GROUP_CONCAT(DISTINCT STR(?cat); SEPARATOR=";") AS ?categories)
|
|
9837
|
+
WHERE {
|
|
9838
|
+
VALUES ?contentIRI { ${values} }
|
|
9839
|
+
?contentIRI qcy:sizeBytes ?size ;
|
|
9840
|
+
qcy:hasFileLocation ?loc .
|
|
9841
|
+
?loc qcy:filePath ?fp ;
|
|
9842
|
+
qcy:suffix ?suffix .
|
|
9843
|
+
OPTIONAL { ?contentIRI qcy:hasContentCategory ?cat }
|
|
9844
|
+
OPTIONAL { ?contentIRI qcy:tag ?tag }
|
|
9845
|
+
OPTIONAL { ?loc qcy:remoteProviderId ?pid }
|
|
9846
|
+
|
|
9847
|
+
OPTIONAL { ?contentIRI qcy:subject ?lang_subj FILTER(LANG(?lang_subj) = "${lang}") }
|
|
9848
|
+
OPTIONAL { ?contentIRI qcy:subject ?no_lang_subj }
|
|
9849
|
+
BIND(COALESCE(?lang_subj, ?no_lang_subj) AS ?subject)
|
|
9850
|
+
|
|
9851
|
+
OPTIONAL { ?contentIRI qcy:textSummary ?lang_summary FILTER(LANG(?lang_summary) = "${lang}") }
|
|
9852
|
+
OPTIONAL { ?contentIRI qcy:textSummary ?no_lang_summary }
|
|
9853
|
+
BIND(COALESCE(?lang_summary, ?no_lang_summary) AS ?summary)
|
|
9854
|
+
|
|
9855
|
+
BIND(REPLACE(STR(?contentIRI), "^.*/", "") AS ?id)
|
|
9856
|
+
}
|
|
9857
|
+
GROUP BY ?id ?suffix ?size ?subject ?summary`;
|
|
9858
|
+
const result = await this._api.sparql(q, this._projectId);
|
|
9859
|
+
const updates = { ...this._documentInfoMap.get() };
|
|
9860
|
+
const fetched = {};
|
|
9861
|
+
result.results.bindings.forEach((b) => {
|
|
9862
|
+
if (!b["id"])
|
|
9863
|
+
return;
|
|
9864
|
+
const id = b["id"].value;
|
|
9865
|
+
const info = {
|
|
9866
|
+
id,
|
|
9867
|
+
contentIRI: this._resourceIri(id),
|
|
9868
|
+
path: b["path"]?.value ?? "",
|
|
9869
|
+
suffix: b["suffix"]?.value ?? "",
|
|
9870
|
+
size: b["size"] ? parseInt(b["size"].value, 10) : 0,
|
|
9871
|
+
tags: b["tags"]?.value?.split(";").filter(Boolean) ?? [],
|
|
9872
|
+
categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
|
|
9873
|
+
subject: b["subject"]?.value,
|
|
9874
|
+
summary: b["summary"]?.value,
|
|
9875
|
+
providerId: b["pid"]?.value
|
|
9876
|
+
};
|
|
9877
|
+
updates[id] = info;
|
|
9878
|
+
fetched[id] = info;
|
|
9879
|
+
});
|
|
9880
|
+
this._documentInfoMap.set(updates);
|
|
9881
|
+
this._log(`fetchDocumentInfoBatch: ${Object.keys(fetched).length} fetched | cumulative: ${Object.keys(updates).length} with metadata / ${this._seenIds.size} seen`);
|
|
9882
|
+
return fetched;
|
|
9883
|
+
}
|
|
9884
|
+
/** Executes a reduced document-info query (id/path/suffix/size only), merges
|
|
9885
|
+
* into `documentInfoMap`, and returns newly fetched entries. */
|
|
9886
|
+
async _fetchSimpleDocumentInfoBatch(uuids) {
|
|
9887
|
+
const values = uuids.map((id) => `r:${id}`).join(" ");
|
|
9888
|
+
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9889
|
+
PREFIX r: <${this.baseURL}>
|
|
9890
|
+
SELECT ?id ?suffix ?size (SAMPLE(?fp) AS ?path)
|
|
9891
|
+
WHERE {
|
|
9892
|
+
VALUES ?contentIRI { ${values} }
|
|
9893
|
+
?contentIRI qcy:sizeBytes ?size ;
|
|
9894
|
+
qcy:hasFileLocation ?loc .
|
|
9895
|
+
?loc qcy:filePath ?fp ;
|
|
9896
|
+
qcy:suffix ?suffix .
|
|
9897
|
+
BIND(REPLACE(STR(?contentIRI), "^.*/", "") AS ?id)
|
|
9898
|
+
}
|
|
9899
|
+
GROUP BY ?id ?suffix ?size`;
|
|
9900
|
+
const result = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9901
|
+
const updates = { ...this._documentInfoMap.get() };
|
|
9902
|
+
const fetched = {};
|
|
9903
|
+
result.results.bindings.forEach((b) => {
|
|
9904
|
+
if (!b["id"])
|
|
9905
|
+
return;
|
|
9906
|
+
const id = b["id"].value;
|
|
9907
|
+
const existing = updates[id];
|
|
9908
|
+
const info = {
|
|
9909
|
+
id,
|
|
9910
|
+
contentIRI: this._resourceIri(id),
|
|
9911
|
+
path: b["path"]?.value ?? existing?.path ?? id,
|
|
9912
|
+
suffix: b["suffix"]?.value ?? existing?.suffix ?? "",
|
|
9913
|
+
size: b["size"] ? parseInt(b["size"].value, 10) : existing?.size ?? 0,
|
|
9914
|
+
tags: existing?.tags ?? [],
|
|
9915
|
+
categories: existing?.categories ?? [],
|
|
9916
|
+
subject: existing?.subject,
|
|
9917
|
+
summary: existing?.summary,
|
|
9918
|
+
providerId: existing?.providerId
|
|
9919
|
+
};
|
|
9920
|
+
updates[id] = info;
|
|
9921
|
+
fetched[id] = info;
|
|
9922
|
+
});
|
|
9923
|
+
this._documentInfoMap.set(updates);
|
|
9924
|
+
this._log(`fetchSimpleDocumentInfoBatch: ${Object.keys(fetched).length} fetched | cumulative: ${Object.keys(updates).length} with metadata / ${this._seenIds.size} seen`);
|
|
9925
|
+
return fetched;
|
|
9926
|
+
}
|
|
9927
|
+
async _fetchDocumentsBySuffix() {
|
|
9928
|
+
return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
|
|
9929
|
+
}
|
|
9930
|
+
_buildDocumentsBySuffixQuery() {
|
|
9931
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9932
|
+
SELECT ?ext (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
9933
|
+
WHERE {
|
|
9934
|
+
{
|
|
9935
|
+
SELECT DISTINCT ?fc ?ext ?size
|
|
9936
|
+
WHERE {
|
|
9937
|
+
?fc a qcy:FileContent ;
|
|
9938
|
+
qcy:sizeBytes ?size ;
|
|
9939
|
+
qcy:hasFileLocation/qcy:suffix ?ext .
|
|
9940
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
9941
|
+
}
|
|
9942
|
+
}
|
|
9943
|
+
}
|
|
9944
|
+
GROUP BY ?ext
|
|
9945
|
+
ORDER BY DESC(?docCount)`;
|
|
9946
|
+
}
|
|
9947
|
+
async _runDocumentsBySuffixQuery(q) {
|
|
9948
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9949
|
+
const result = {};
|
|
9950
|
+
data.results.bindings.forEach((b) => {
|
|
9951
|
+
if (!b["ext"])
|
|
9952
|
+
return;
|
|
9953
|
+
result[b["ext"].value] = {
|
|
9954
|
+
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
9955
|
+
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
9956
|
+
};
|
|
9957
|
+
});
|
|
9958
|
+
return result;
|
|
9959
|
+
}
|
|
9960
|
+
async _fetchDocumentsByContentCategory() {
|
|
9961
|
+
return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
|
|
9962
|
+
}
|
|
9963
|
+
_buildDocumentsByContentCategoryQuery() {
|
|
9964
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9965
|
+
SELECT ?cat (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
9966
|
+
WHERE {
|
|
9967
|
+
{
|
|
9968
|
+
SELECT DISTINCT ?fc ?cat ?size
|
|
9969
|
+
WHERE {
|
|
9970
|
+
?fc a qcy:FileContent ;
|
|
9971
|
+
qcy:hasContentCategory ?cat ;
|
|
9972
|
+
qcy:sizeBytes ?size .
|
|
9973
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9976
|
+
}
|
|
9977
|
+
GROUP BY ?cat
|
|
9978
|
+
ORDER BY DESC(?docCount)`;
|
|
9979
|
+
}
|
|
9980
|
+
async _runDocumentsByContentCategoryQuery(q) {
|
|
9981
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
9982
|
+
const result = {};
|
|
9983
|
+
data.results.bindings.forEach((b) => {
|
|
9984
|
+
if (!b["cat"])
|
|
9985
|
+
return;
|
|
9986
|
+
result[b["cat"].value] = {
|
|
9987
|
+
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
9988
|
+
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
9989
|
+
};
|
|
9990
|
+
});
|
|
9991
|
+
return result;
|
|
9992
|
+
}
|
|
9993
|
+
async _fetchDuplicateCount() {
|
|
9994
|
+
return this._runDuplicateCountQuery(this._buildDuplicateCountQuery());
|
|
9995
|
+
}
|
|
9996
|
+
_buildDuplicateCountQuery() {
|
|
9997
|
+
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
9998
|
+
SELECT (COUNT(*) AS ?count)
|
|
9999
|
+
WHERE {
|
|
10000
|
+
SELECT ?fc
|
|
10001
|
+
WHERE {
|
|
10002
|
+
?fc a qcy:FileContent ;
|
|
10003
|
+
qcy:hasFileLocation ?fl .
|
|
10004
|
+
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
10005
|
+
}
|
|
10006
|
+
GROUP BY ?fc
|
|
10007
|
+
HAVING (COUNT(?fl) > 1)
|
|
10008
|
+
}`;
|
|
10009
|
+
}
|
|
10010
|
+
async _runDuplicateCountQuery(q) {
|
|
10011
|
+
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
10012
|
+
const first = data.results.bindings[0];
|
|
10013
|
+
return first?.["count"] ? parseInt(first["count"].value, 10) : 0;
|
|
10014
|
+
}
|
|
10015
|
+
};
|
|
10016
|
+
|
|
10017
|
+
// libs/js/cue-sdk/src/lib/project-view.ts
|
|
10018
|
+
var CueProjectView = class {
|
|
10019
|
+
constructor(_api, _projectId, { language, queryCache, rdfBase = RESOURCE_BASE, graphType, verbose }) {
|
|
10020
|
+
this._api = _api;
|
|
10021
|
+
this._projectId = _projectId;
|
|
10022
|
+
this.schema = new CueProjectSchema(_api, _projectId, language, queryCache, graphType);
|
|
10023
|
+
this.entities = new CueProjectEntities(_api, _projectId, rdfBase, queryCache, graphType, verbose);
|
|
10024
|
+
this.documents = new CueProjectDocuments(_api, _projectId, language, rdfBase, queryCache, graphType, verbose);
|
|
10025
|
+
this.availableContentCategories = this.schema.availableContentCategories;
|
|
10026
|
+
this.availableEntityCategories = this.schema.availableEntityCategories;
|
|
10027
|
+
this.availableEntityRelationships = this.schema.availableEntityRelationships;
|
|
10028
|
+
this.schemaReady = this.schema.ready;
|
|
10029
|
+
this.entityInfoMap = this.entities.entityInfoMap;
|
|
10030
|
+
this.entityGraph = this.entities.entityGraph;
|
|
10031
|
+
this.documentInfoMap = this.documents.documentInfoMap;
|
|
10032
|
+
this.projectDocumentsData = this.documents.projectDocumentsData;
|
|
10033
|
+
this.searchResults = this._searchResults.asReadonly();
|
|
10034
|
+
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed:", err));
|
|
10035
|
+
}
|
|
10036
|
+
/** Direct access to the schema data class (available categories / relationships). */
|
|
10037
|
+
schema;
|
|
10038
|
+
/** Direct access to the entity data class. */
|
|
10039
|
+
entities;
|
|
10040
|
+
/** Direct access to the document data class. */
|
|
10041
|
+
documents;
|
|
10042
|
+
// ── Proxied signals ────────────────────────────────────────────────────────
|
|
10043
|
+
/** Available content category definitions for this project. Auto-fetched on init. */
|
|
10044
|
+
availableContentCategories;
|
|
10045
|
+
/** Available entity category definitions for this project. Auto-fetched on init. */
|
|
10046
|
+
availableEntityCategories;
|
|
10047
|
+
/** Available entity relationship types. Auto-fetched on init. */
|
|
10048
|
+
availableEntityRelationships;
|
|
10049
|
+
/**
|
|
10050
|
+
* Resolves when the initial schema load has completed. Await before reading
|
|
10051
|
+
* schema signal values imperatively.
|
|
10052
|
+
*/
|
|
10053
|
+
schemaReady;
|
|
10054
|
+
/** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
|
|
10055
|
+
entityInfoMap;
|
|
10056
|
+
/** Project-level entity co-occurrence graph. Fetched once on init. */
|
|
10057
|
+
entityGraph;
|
|
10058
|
+
/** Per-document info map. Populated lazily via `requestDocumentData()`. */
|
|
10059
|
+
documentInfoMap;
|
|
10060
|
+
/** Project document overview (counts by suffix and category). Fetched on init. */
|
|
10061
|
+
projectDocumentsData;
|
|
10062
|
+
// ── Search state ───────────────────────────────────────────────────────────
|
|
10063
|
+
_searchResults = new CueSignal(void 0);
|
|
10064
|
+
/** The result of the most recent `search()` call. `undefined` before first search. */
|
|
10065
|
+
searchResults;
|
|
10066
|
+
_destroyed = false;
|
|
10067
|
+
// ── Entity methods ─────────────────────────────────────────────────────────
|
|
10068
|
+
/**
|
|
10069
|
+
* Lazily batch-fetch core data (label + categories) for the given entity UUIDs.
|
|
10070
|
+
* Already-fetched UUIDs are skipped. Populates `entityInfoMap`.
|
|
10071
|
+
*/
|
|
10072
|
+
requestEntityData(uuids, includeMentionCount = false) {
|
|
10073
|
+
if (this._destroyed)
|
|
10074
|
+
return;
|
|
10075
|
+
this.entities.requestEntityData(uuids, includeMentionCount);
|
|
10076
|
+
}
|
|
10077
|
+
/**
|
|
10078
|
+
* Lazily fetch OSM location data for the given entity UUIDs.
|
|
10079
|
+
* Already-fetched UUIDs are skipped. Populates `entityInfoMap` geometry fields.
|
|
10080
|
+
*/
|
|
10081
|
+
async requestEntityLocations(uuids) {
|
|
10082
|
+
if (this._destroyed)
|
|
10083
|
+
return;
|
|
10084
|
+
return this.entities.requestEntityLocations(uuids);
|
|
10085
|
+
}
|
|
10086
|
+
/**
|
|
10087
|
+
* Fetch incoming and outgoing relationships for a single entity IRI.
|
|
10088
|
+
* Result is stored in `entityInfoMap[uuid].relationshipData`.
|
|
10089
|
+
*/
|
|
10090
|
+
async fetchEntityRelationships(iri) {
|
|
10091
|
+
if (this._destroyed)
|
|
10092
|
+
throw new Error("CueProjectView is destroyed");
|
|
10093
|
+
return this.entities.fetchEntityRelationships(iri);
|
|
10094
|
+
}
|
|
10095
|
+
/**
|
|
10096
|
+
* Fetch UUIDs of documents that reference the given entity IRI.
|
|
10097
|
+
* Result is stored in `entityInfoMap[uuid].documentRefs`.
|
|
10098
|
+
*/
|
|
10099
|
+
async fetchEntityDocuments(iri) {
|
|
10100
|
+
if (this._destroyed)
|
|
10101
|
+
throw new Error("CueProjectView is destroyed");
|
|
10102
|
+
return this.entities.fetchEntityDocuments(iri);
|
|
10103
|
+
}
|
|
10104
|
+
/** Constructs the full RDF IRI for an entity UUID. */
|
|
10105
|
+
entityIri(uuid) {
|
|
10106
|
+
return this.entities.entityIri(uuid);
|
|
10107
|
+
}
|
|
10108
|
+
// ── Document methods ───────────────────────────────────────────────────────
|
|
10109
|
+
/**
|
|
10110
|
+
* Lazily batch-fetch document info for the given UUIDs.
|
|
10111
|
+
* Already-fetched UUIDs are skipped. Populates `documentInfoMap`.
|
|
10112
|
+
*/
|
|
10113
|
+
requestDocumentData(uuids) {
|
|
10114
|
+
if (this._destroyed)
|
|
10115
|
+
return;
|
|
10116
|
+
this.documents.requestDocumentData(uuids);
|
|
10117
|
+
}
|
|
10118
|
+
// ── Search ─────────────────────────────────────────────────────────────────
|
|
10119
|
+
/**
|
|
10120
|
+
* Run a natural-language search against the project.
|
|
10121
|
+
* The result is stored in `searchResults` and replaces any previous result.
|
|
10122
|
+
*/
|
|
10123
|
+
async search(term, options) {
|
|
10124
|
+
if (this._destroyed)
|
|
10125
|
+
return;
|
|
10126
|
+
const result = await this._api.search({
|
|
10127
|
+
term,
|
|
10128
|
+
projectId: this._projectId,
|
|
10129
|
+
categories: options?.categories
|
|
10130
|
+
});
|
|
10131
|
+
if (!this._destroyed) {
|
|
10132
|
+
this._searchResults.set(result);
|
|
10133
|
+
}
|
|
10134
|
+
}
|
|
10135
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
10136
|
+
/**
|
|
10137
|
+
* Switch the active language for schema labels and document text fields.
|
|
10138
|
+
* Schema responses are cached per language (instant if previously loaded).
|
|
10139
|
+
* The document info map is cleared and lazily re-populated on next access.
|
|
10140
|
+
*/
|
|
10141
|
+
setLanguage(lang) {
|
|
10142
|
+
if (this._destroyed)
|
|
10143
|
+
return;
|
|
10144
|
+
this.schema.setLanguage(lang);
|
|
10145
|
+
this.documents.setLanguage(lang);
|
|
10146
|
+
}
|
|
10147
|
+
/**
|
|
10148
|
+
* Reset all entity and document state and re-fetch the project overview.
|
|
10149
|
+
* Prefer creating a fresh `CueProjectView` when switching projects.
|
|
10150
|
+
* Use `reset()` only when the same project's data needs to be invalidated.
|
|
10151
|
+
*/
|
|
10152
|
+
reset() {
|
|
10153
|
+
if (this._destroyed)
|
|
10154
|
+
return;
|
|
10155
|
+
this.entities.reset();
|
|
10156
|
+
this.documents.reset();
|
|
10157
|
+
this._searchResults.set(void 0);
|
|
10158
|
+
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed after reset:", err));
|
|
10159
|
+
}
|
|
10160
|
+
/**
|
|
10161
|
+
* Tear down this view instance. Clears all reactive state and blocks further
|
|
10162
|
+
* updates. Call from the Angular adapter's `ngOnDestroy` or equivalent.
|
|
10163
|
+
*/
|
|
10164
|
+
destroy() {
|
|
10165
|
+
this._destroyed = true;
|
|
10166
|
+
this._searchResults.set(void 0);
|
|
10167
|
+
}
|
|
9708
10168
|
};
|
|
9709
10169
|
|
|
9710
|
-
// libs/js/
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
// libs/js/models/src/lib/search-log-entry.ts
|
|
9714
|
-
var import_uuid4 = require("uuid");
|
|
9715
|
-
|
|
9716
|
-
// libs/js/models/src/lib/search-response.ts
|
|
9717
|
-
var import_uuid5 = require("uuid");
|
|
10170
|
+
// libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
|
|
10171
|
+
init_src();
|
|
9718
10172
|
|
|
9719
10173
|
// libs/js/rdf-document-writers/src/lib/alternative-representation.ts
|
|
9720
10174
|
var import_n33 = require("n3");
|
|
@@ -10506,10 +10960,15 @@ var Cue = class _Cue {
|
|
|
10506
10960
|
profile;
|
|
10507
10961
|
privileges;
|
|
10508
10962
|
cache;
|
|
10963
|
+
storage;
|
|
10509
10964
|
_app;
|
|
10510
10965
|
_endpoints;
|
|
10511
10966
|
_isEmulator;
|
|
10967
|
+
_storageRaw;
|
|
10968
|
+
_storageProcessed;
|
|
10512
10969
|
_gis = null;
|
|
10970
|
+
_projectDocuments = /* @__PURE__ */ new Map();
|
|
10971
|
+
_verbose;
|
|
10513
10972
|
/**
|
|
10514
10973
|
* Reactive GIS service. Lazily constructed on first access.
|
|
10515
10974
|
*
|
|
@@ -10543,6 +11002,7 @@ var Cue = class _Cue {
|
|
|
10543
11002
|
const env = config.environment ?? "production";
|
|
10544
11003
|
this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
|
|
10545
11004
|
this._isEmulator = env === "emulator";
|
|
11005
|
+
this._verbose = config.verbose ?? false;
|
|
10546
11006
|
this._app = (0, import_app2.getApps)().find((a5) => a5.name === "[DEFAULT]") ?? (0, import_app2.initializeApp)({
|
|
10547
11007
|
apiKey,
|
|
10548
11008
|
appId,
|
|
@@ -10553,7 +11013,26 @@ var Cue = class _Cue {
|
|
|
10553
11013
|
});
|
|
10554
11014
|
this.auth = new CueAuth(this._app, this._isEmulator, this._endpoints);
|
|
10555
11015
|
this.projects = new CueProjects(this.auth, this._app, this._isEmulator, this._endpoints);
|
|
11016
|
+
this._storageRaw = (0, import_storage5.getStorage)(this._app, BUCKET_RAW2);
|
|
11017
|
+
this._storageProcessed = (0, import_storage5.getStorage)(this._app, BUCKET_PROCESSED2);
|
|
11018
|
+
const storagePublic = (0, import_storage5.getStorage)(this._app, BUCKET_PUBLIC2);
|
|
11019
|
+
const storageLogs = (0, import_storage5.getStorage)(this._app, BUCKET_LOGS2);
|
|
11020
|
+
const storageChatSessions = (0, import_storage5.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
|
|
11021
|
+
const storagePersistence = (0, import_storage5.getStorage)(this._app, BUCKET_PERSISTENCE2);
|
|
11022
|
+
if (this._isEmulator) {
|
|
11023
|
+
(0, import_storage5.connectStorageEmulator)(this._storageRaw, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
|
|
11024
|
+
(0, import_storage5.connectStorageEmulator)(this._storageProcessed, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
|
|
11025
|
+
}
|
|
10556
11026
|
this.api = this._buildApi(this.projects);
|
|
11027
|
+
const blob = new CueBlobStorage({
|
|
11028
|
+
storageRaw: this._storageRaw,
|
|
11029
|
+
storageProcessed: this._storageProcessed,
|
|
11030
|
+
storagePublic,
|
|
11031
|
+
storageLogs,
|
|
11032
|
+
storageChatSessions,
|
|
11033
|
+
storagePersistence
|
|
11034
|
+
});
|
|
11035
|
+
this.storage = new CueStorage(blob);
|
|
10557
11036
|
this.profile = new CueProfile(
|
|
10558
11037
|
this.auth,
|
|
10559
11038
|
this._app,
|
|
@@ -10561,7 +11040,6 @@ var Cue = class _Cue {
|
|
|
10561
11040
|
this._endpoints.gatewayUrl
|
|
10562
11041
|
);
|
|
10563
11042
|
this.privileges = new CuePrivileges(this.auth.isSuperAdmin);
|
|
10564
|
-
const storagePersistence = (0, import_storage5.getStorage)(this._app, BUCKET_PERSISTENCE2);
|
|
10565
11043
|
if (this._isEmulator) {
|
|
10566
11044
|
(0, import_storage5.connectStorageEmulator)(storagePersistence, this._endpoints.storageEmulatorHost, this._endpoints.storageEmulatorPort);
|
|
10567
11045
|
}
|
|
@@ -10609,16 +11087,20 @@ var Cue = class _Cue {
|
|
|
10609
11087
|
const instance = Object.create(_Cue.prototype);
|
|
10610
11088
|
const privileges = new CuePrivileges(auth.isSuperAdmin);
|
|
10611
11089
|
const cache = new CueCache(storagePersistence);
|
|
11090
|
+
const storage = new CueStorage(blob);
|
|
10612
11091
|
Object.assign(instance, {
|
|
10613
11092
|
_app: app,
|
|
10614
11093
|
_endpoints: endpoints,
|
|
10615
11094
|
_isEmulator: env === "emulator",
|
|
11095
|
+
_storageRaw: storageRaw,
|
|
11096
|
+
_storageProcessed: storageProcessed,
|
|
10616
11097
|
auth,
|
|
10617
11098
|
api,
|
|
10618
11099
|
projects,
|
|
10619
11100
|
profile,
|
|
10620
11101
|
privileges,
|
|
10621
|
-
cache
|
|
11102
|
+
cache,
|
|
11103
|
+
storage
|
|
10622
11104
|
});
|
|
10623
11105
|
return instance;
|
|
10624
11106
|
}
|
|
@@ -10649,29 +11131,110 @@ var Cue = class _Cue {
|
|
|
10649
11131
|
get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
|
|
10650
11132
|
set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
|
|
10651
11133
|
};
|
|
10652
|
-
return new CueProjectView(this.api, projectId, { ...opts, queryCache });
|
|
11134
|
+
return new CueProjectView(this.api, projectId, { verbose: this._verbose, ...opts, queryCache });
|
|
11135
|
+
}
|
|
11136
|
+
/**
|
|
11137
|
+
* Creates a `CueProjectEntities` instance for the given project, with the
|
|
11138
|
+
* SDK query cache wired automatically.
|
|
11139
|
+
*
|
|
11140
|
+
* Prefer this over `new CueProjectEntities(cue.api, projectId)` — it avoids
|
|
11141
|
+
* the manual cache setup and keeps the instance bound to the correct API.
|
|
11142
|
+
*
|
|
11143
|
+
* @example
|
|
11144
|
+
* ```ts
|
|
11145
|
+
* const entities = cue.createProjectEntities('my-project');
|
|
11146
|
+
* entities.requestEntityData(['uuid1']);
|
|
11147
|
+
* const info = entities.entityInfoMap.get()['uuid1'];
|
|
11148
|
+
*
|
|
11149
|
+
* // Promise-based (no signal polling needed):
|
|
11150
|
+
* const graph = await entities.buildSummaryGraph('graph');
|
|
11151
|
+
* ```
|
|
11152
|
+
*/
|
|
11153
|
+
createProjectEntities(projectId, opts) {
|
|
11154
|
+
const queryCache = opts?.queryCache ?? {
|
|
11155
|
+
get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
|
|
11156
|
+
set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
|
|
11157
|
+
};
|
|
11158
|
+
return new CueProjectEntities(
|
|
11159
|
+
this.api,
|
|
11160
|
+
projectId,
|
|
11161
|
+
opts?.rdfBase,
|
|
11162
|
+
queryCache,
|
|
11163
|
+
opts?.graphType,
|
|
11164
|
+
opts?.verbose ?? this._verbose
|
|
11165
|
+
);
|
|
11166
|
+
}
|
|
11167
|
+
/**
|
|
11168
|
+
* Creates a `CueProjectDocuments` instance for the given project, with the
|
|
11169
|
+
* SDK query cache wired automatically.
|
|
11170
|
+
*
|
|
11171
|
+
* Prefer this over `new CueProjectDocuments(cue.api, projectId)` — it avoids
|
|
11172
|
+
* the manual cache setup and keeps the instance bound to the correct API.
|
|
11173
|
+
*
|
|
11174
|
+
* @example
|
|
11175
|
+
* ```ts
|
|
11176
|
+
* const docs = cue.createProjectDocuments('my-project');
|
|
11177
|
+
* await docs.fetchOverview();
|
|
11178
|
+
* const data = await docs.fetchDocumentData(['uuid1', 'uuid2']);
|
|
11179
|
+
* ```
|
|
11180
|
+
*/
|
|
11181
|
+
createProjectDocuments(projectId, opts) {
|
|
11182
|
+
const hasCustomOptions = opts?.language !== void 0 || opts?.rdfBase !== void 0 || opts?.graphType !== void 0 || opts?.queryCache !== void 0;
|
|
11183
|
+
if (!hasCustomOptions) {
|
|
11184
|
+
const existing = this._projectDocuments.get(projectId);
|
|
11185
|
+
if (existing) {
|
|
11186
|
+
existing.setLanguage(this.api.language);
|
|
11187
|
+
return existing;
|
|
11188
|
+
}
|
|
11189
|
+
const queryCache2 = {
|
|
11190
|
+
get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
|
|
11191
|
+
set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
|
|
11192
|
+
};
|
|
11193
|
+
const docs = new CueProjectDocuments(
|
|
11194
|
+
this.api,
|
|
11195
|
+
projectId,
|
|
11196
|
+
this.api.language,
|
|
11197
|
+
void 0,
|
|
11198
|
+
queryCache2,
|
|
11199
|
+
void 0,
|
|
11200
|
+
opts?.verbose ?? this._verbose
|
|
11201
|
+
);
|
|
11202
|
+
this._projectDocuments.set(projectId, docs);
|
|
11203
|
+
return docs;
|
|
11204
|
+
}
|
|
11205
|
+
const queryCache = opts?.queryCache ?? {
|
|
11206
|
+
get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
|
|
11207
|
+
set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
|
|
11208
|
+
};
|
|
11209
|
+
return new CueProjectDocuments(
|
|
11210
|
+
this.api,
|
|
11211
|
+
projectId,
|
|
11212
|
+
opts?.language ?? this.api.language,
|
|
11213
|
+
opts?.rdfBase,
|
|
11214
|
+
queryCache,
|
|
11215
|
+
opts?.graphType,
|
|
11216
|
+
opts?.verbose ?? this._verbose
|
|
11217
|
+
);
|
|
10653
11218
|
}
|
|
10654
11219
|
};
|
|
10655
11220
|
|
|
10656
11221
|
// libs/js/cue-sdk/src/lib/cue-node.ts
|
|
10657
|
-
var
|
|
11222
|
+
var import_storage8 = require("firebase/storage");
|
|
10658
11223
|
var CueNode = class extends Cue {
|
|
10659
11224
|
constructor(config) {
|
|
10660
11225
|
super(config);
|
|
10661
11226
|
}
|
|
10662
11227
|
_buildApi(projects) {
|
|
10663
|
-
const storageChatSessions = (0,
|
|
10664
|
-
const storageLogs = (0,
|
|
10665
|
-
const storageRaw =
|
|
10666
|
-
const storagePersistence = (0,
|
|
10667
|
-
const storageProcessed =
|
|
10668
|
-
const storagePublic = (0,
|
|
11228
|
+
const storageChatSessions = (0, import_storage8.getStorage)(this._app, BUCKET_CHAT_SESSIONS2);
|
|
11229
|
+
const storageLogs = (0, import_storage8.getStorage)(this._app, BUCKET_LOGS2);
|
|
11230
|
+
const storageRaw = this._storageRaw;
|
|
11231
|
+
const storagePersistence = (0, import_storage8.getStorage)(this._app, BUCKET_PERSISTENCE2);
|
|
11232
|
+
const storageProcessed = this._storageProcessed;
|
|
11233
|
+
const storagePublic = (0, import_storage8.getStorage)(this._app, BUCKET_PUBLIC2);
|
|
10669
11234
|
if (this._isEmulator) {
|
|
10670
11235
|
const storageHost = this._endpoints.storageEmulatorHost;
|
|
10671
11236
|
const storagePort = this._endpoints.storageEmulatorPort;
|
|
10672
|
-
(0,
|
|
10673
|
-
(0, import_storage6.connectStorageEmulator)(storageProcessed, storageHost, storagePort);
|
|
10674
|
-
(0, import_storage6.connectStorageEmulator)(storagePublic, storageHost, storagePort);
|
|
11237
|
+
(0, import_storage8.connectStorageEmulator)(storagePublic, storageHost, storagePort);
|
|
10675
11238
|
}
|
|
10676
11239
|
const blob = new CueBlobStorage({
|
|
10677
11240
|
storageChatSessions,
|
|
@@ -10834,21 +11397,21 @@ async function compareHandler(options) {
|
|
|
10834
11397
|
}
|
|
10835
11398
|
|
|
10836
11399
|
// apps/desktop/cue-cli/src/helpers/get-files-containing-substring.ts
|
|
10837
|
-
var
|
|
11400
|
+
var import_storage9 = require("firebase/storage");
|
|
10838
11401
|
var import_path2 = require("path");
|
|
10839
11402
|
var import_promises = require("fs/promises");
|
|
10840
11403
|
async function getFilesContainingSubstring(bucket, subDir = "", subString = "") {
|
|
10841
11404
|
const firebase = CueFirebase.getInstance();
|
|
10842
11405
|
const storage = bucket === "raw" ? firebase.storageRaw : firebase.storageProcessed;
|
|
10843
11406
|
console.log("Fetching files from storage bucket:", bucket, "in subdir:", subDir, "containing substring:", subString);
|
|
10844
|
-
const listResult = await (0,
|
|
11407
|
+
const listResult = await (0, import_storage9.listAll)((0, import_storage9.ref)(storage, subDir));
|
|
10845
11408
|
const outputDir = (0, import_path2.join)(process.cwd(), "downloaded_blobs", bucket, subDir);
|
|
10846
11409
|
await (0, import_promises.mkdir)(outputDir, { recursive: true });
|
|
10847
11410
|
for (const fileRef of listResult.items) {
|
|
10848
11411
|
console.log(fileRef.name);
|
|
10849
11412
|
if (subDir && !fileRef.name.includes(subString))
|
|
10850
11413
|
continue;
|
|
10851
|
-
const bytes = await (0,
|
|
11414
|
+
const bytes = await (0, import_storage9.getBytes)(fileRef);
|
|
10852
11415
|
const outputPath = (0, import_path2.join)(outputDir, fileRef.name);
|
|
10853
11416
|
await (0, import_promises.writeFile)(outputPath, Buffer.from(bytes));
|
|
10854
11417
|
console.log(`Downloaded ${fileRef.name} to ${outputPath} \u2705`);
|
|
@@ -11242,16 +11805,16 @@ async function dumpHandler(options) {
|
|
|
11242
11805
|
}
|
|
11243
11806
|
|
|
11244
11807
|
// apps/desktop/cue-cli/src/helpers/repair-remote-ttl.ts
|
|
11245
|
-
var
|
|
11808
|
+
var import_storage10 = require("firebase/storage");
|
|
11246
11809
|
async function repairRemoteTTL(space, subString, regex, substituteString) {
|
|
11247
11810
|
const firebase = CueFirebase.getInstance();
|
|
11248
11811
|
const storage = firebase.storageProcessed;
|
|
11249
11812
|
console.log("Fetching files from storage bucket 'triples' containing substring:", subString);
|
|
11250
|
-
const listResult = await (0,
|
|
11813
|
+
const listResult = await (0, import_storage10.listAll)((0, import_storage10.ref)(storage, `${space}/triples`));
|
|
11251
11814
|
for (const fileRef of listResult.items) {
|
|
11252
11815
|
if (subString && !fileRef.name.match(subString))
|
|
11253
11816
|
continue;
|
|
11254
|
-
const stream = await (0,
|
|
11817
|
+
const stream = await (0, import_storage10.getStream)(fileRef);
|
|
11255
11818
|
const reader = stream.getReader();
|
|
11256
11819
|
const chunks = [];
|
|
11257
11820
|
let done = false;
|
|
@@ -11275,13 +11838,13 @@ async function repairRemoteTTL(space, subString, regex, substituteString) {
|
|
|
11275
11838
|
const buffer = Buffer.from(fileContent, "utf8");
|
|
11276
11839
|
let existingMetadata = {};
|
|
11277
11840
|
try {
|
|
11278
|
-
existingMetadata = await (0,
|
|
11841
|
+
existingMetadata = await (0, import_storage10.getMetadata)(fileRef);
|
|
11279
11842
|
} catch (err) {
|
|
11280
11843
|
console.warn(`Could not fetch metadata for ${fileRef.name}, proceeding with default.`);
|
|
11281
11844
|
}
|
|
11282
11845
|
const customMetadata = { ...existingMetadata.customMetadata || {}, stored: "False" };
|
|
11283
11846
|
const metadata = { customMetadata };
|
|
11284
|
-
await (0,
|
|
11847
|
+
await (0, import_storage10.uploadBytesResumable)((0, import_storage10.ref)(storage, `${space}/triples/${fileRef.name}`), buffer, metadata);
|
|
11285
11848
|
console.log(`Fixed ${fileRef.name} \u2705`);
|
|
11286
11849
|
} else {
|
|
11287
11850
|
console.log(`No changes for ${fileRef.name}`);
|
|
@@ -12011,6 +12574,10 @@ async function buildCueClient(options) {
|
|
|
12011
12574
|
console.error("API key is required. Provide it via --key or CUE_API_KEY env variable.");
|
|
12012
12575
|
process.exit(1);
|
|
12013
12576
|
}
|
|
12577
|
+
if (options.verbose) {
|
|
12578
|
+
const source = options.key ? "--key flag" : "CUE_API_KEY env variable";
|
|
12579
|
+
console.error(`Authenticating with API key from ${source}...`);
|
|
12580
|
+
}
|
|
12014
12581
|
const cue = new CueNode({
|
|
12015
12582
|
apiKey: FIREBASE_CONFIG().apiKey,
|
|
12016
12583
|
appId: FIREBASE_CONFIG().appId,
|
|
@@ -12036,6 +12603,7 @@ async function listProjectsHandler(options) {
|
|
|
12036
12603
|
lastSync: p.lastSync
|
|
12037
12604
|
}));
|
|
12038
12605
|
console.log(JSON.stringify(output, null, 2));
|
|
12606
|
+
process.exit(0);
|
|
12039
12607
|
} catch (err) {
|
|
12040
12608
|
console.error("Error:", err instanceof Error ? err.message : String(err));
|
|
12041
12609
|
process.exit(1);
|
|
@@ -12045,20 +12613,32 @@ async function entitySummaryGraphHandler(options) {
|
|
|
12045
12613
|
try {
|
|
12046
12614
|
const cue = await buildCueClient(options);
|
|
12047
12615
|
if (options.verbose)
|
|
12048
|
-
console.error(`Fetching entity summary graph for project ${options.space}...`);
|
|
12616
|
+
console.error(`Fetching entity summary graph for project ${options.space}${options.entity ? ` (neighbourhood of ${options.entity})` : ""}...`);
|
|
12049
12617
|
const entities = new CueProjectEntities(cue.api, options.space);
|
|
12050
12618
|
if (options.format === "graph") {
|
|
12051
|
-
const graph = await entities.buildSummaryGraph("graph");
|
|
12619
|
+
const graph = await entities.buildSummaryGraph("graph", options.entity);
|
|
12052
12620
|
console.log(JSON.stringify(graph, null, 2));
|
|
12053
12621
|
} else if (options.format === "md") {
|
|
12054
|
-
const md = await entities.buildSummaryGraph("md");
|
|
12622
|
+
const md = await entities.buildSummaryGraph("md", options.entity);
|
|
12055
12623
|
console.log(md);
|
|
12056
12624
|
} else {
|
|
12057
|
-
const raw = await entities.buildSummaryGraph();
|
|
12625
|
+
const raw = await entities.buildSummaryGraph(void 0, options.entity);
|
|
12058
12626
|
console.log(JSON.stringify(raw, null, 2));
|
|
12059
12627
|
}
|
|
12628
|
+
process.exit(0);
|
|
12060
12629
|
} catch (err) {
|
|
12061
|
-
|
|
12630
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12631
|
+
if (msg.includes("403")) {
|
|
12632
|
+
console.error(`Error: ${msg}`);
|
|
12633
|
+
console.error(
|
|
12634
|
+
`Hint: 403 Forbidden usually means the API key does not have access to project "${options.space}".
|
|
12635
|
+
- If targeting a local environment, add the -e / --emulators flag.
|
|
12636
|
+
- If using a production project, verify the API key has access.
|
|
12637
|
+
- API key source: ${options.key ? "--key flag" : "CUE_API_KEY env variable"}`
|
|
12638
|
+
);
|
|
12639
|
+
} else {
|
|
12640
|
+
console.error("Error:", msg);
|
|
12641
|
+
}
|
|
12062
12642
|
process.exit(1);
|
|
12063
12643
|
}
|
|
12064
12644
|
}
|
|
@@ -12084,6 +12664,7 @@ async function sparqlHandler(options) {
|
|
|
12084
12664
|
console.error(`Executing SPARQL query against project ${options.space}...`);
|
|
12085
12665
|
const result = await cue.api.sparql(sparqlQuery, options.space);
|
|
12086
12666
|
console.log(JSON.stringify(result, null, 2));
|
|
12667
|
+
process.exit(0);
|
|
12087
12668
|
} catch (err) {
|
|
12088
12669
|
console.error("Error:", err instanceof Error ? err.message : String(err));
|
|
12089
12670
|
process.exit(1);
|
|
@@ -12091,7 +12672,7 @@ async function sparqlHandler(options) {
|
|
|
12091
12672
|
}
|
|
12092
12673
|
var appBuilderToolsCommand = new import_commander.Command("app-builder-tools").description("Tools for agent-assisted Cue app development. Outputs JSON for machine consumption.");
|
|
12093
12674
|
appBuilderToolsCommand.command("list-projects").description("List all projects accessible to the authenticated user").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-v, --verbose", "Enable verbose output", false).action(listProjectsHandler);
|
|
12094
|
-
appBuilderToolsCommand.command("entity-summary-graph").description("Fetch the entity category relationship summary graph for a project").requiredOption("-s, --space <id>", "Project ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-f, --format <format>", "Output format: md (aligned text), graph (JSON nodes+edges), json (raw SPARQL)", "md").option("-v, --verbose", "Enable verbose output", false).action(entitySummaryGraphHandler);
|
|
12675
|
+
appBuilderToolsCommand.command("entity-summary-graph").description("Fetch the entity category relationship summary graph for a project").requiredOption("-s, --space <id>", "Project ID (required)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-f, --format <format>", "Output format: md (aligned text), graph (JSON nodes+edges), json (raw SPARQL)", "md").option("--entity <iri>", "Filter to the one-hop neighbourhood of this category IRI (prefixed or full, e.g. qcy:Building)").option("-v, --verbose", "Enable verbose output", false).action(entitySummaryGraphHandler);
|
|
12095
12676
|
appBuilderToolsCommand.command("sparql").description("Execute a SPARQL SELECT query against a project triplestore. Query via --query or stdin.").requiredOption("-s, --space <id>", "Project ID (required)").option("-q, --query <sparql>", "SPARQL query string (or pipe query via stdin)").option("-k, --key <api-key>", "Specify the API key (or set CUE_API_KEY env variable)").option("-e, --emulators", "Use emulators", false).option("-v, --verbose", "Enable verbose output", false).action(sparqlHandler);
|
|
12096
12677
|
|
|
12097
12678
|
// apps/desktop/cue-cli/src/main.ts
|