@qaecy/cue-cli 0.0.48 → 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/main.js +886 -598
- 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);
|
|
@@ -5422,6 +5428,55 @@ var CueTables = class {
|
|
|
5422
5428
|
}
|
|
5423
5429
|
};
|
|
5424
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
|
+
|
|
5425
5480
|
// libs/js/cue-sdk/src/lib/api.ts
|
|
5426
5481
|
var CueApi = class {
|
|
5427
5482
|
constructor(_auth, _gatewayUrl, projects, sync) {
|
|
@@ -5430,8 +5485,11 @@ var CueApi = class {
|
|
|
5430
5485
|
this.projects = projects;
|
|
5431
5486
|
this.sync = sync;
|
|
5432
5487
|
this.tables = new CueTables(_auth, _gatewayUrl);
|
|
5488
|
+
this.extraction = new CueExtraction(_auth, _gatewayUrl);
|
|
5433
5489
|
}
|
|
5434
5490
|
tables;
|
|
5491
|
+
/** Semantic extraction client — call document pages against a SemanticTemplate. */
|
|
5492
|
+
extraction;
|
|
5435
5493
|
/** Active language used for language-sensitive SPARQL queries across all project classes. */
|
|
5436
5494
|
language = "en";
|
|
5437
5495
|
/** Updates the active language. All project classes (`CueProjectSchema`, `CueProjectDocuments`, `CueProjectEntities`) read this at query time. */
|
|
@@ -7394,6 +7452,19 @@ var CueProjects = class {
|
|
|
7394
7452
|
const fn = (0, import_functions2.httpsCallable)(this._functions, "removeUserFromProject");
|
|
7395
7453
|
await fn({ uid, spaceId: projectId });
|
|
7396
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
|
+
}
|
|
7397
7468
|
};
|
|
7398
7469
|
|
|
7399
7470
|
// libs/js/cue-sdk/src/lib/profile.ts
|
|
@@ -7567,6 +7638,7 @@ var REQUIRED_ROLES = {
|
|
|
7567
7638
|
createProvider: "superadmin",
|
|
7568
7639
|
changeContentCategories: "syncer",
|
|
7569
7640
|
deleteDocuments: "superadmin",
|
|
7641
|
+
deleteProject: "admin",
|
|
7570
7642
|
deleteUserFromProject: "admin",
|
|
7571
7643
|
downloadDocuments: "member",
|
|
7572
7644
|
editContentCategories: "syncer",
|
|
@@ -7581,8 +7653,10 @@ function defaultPrivileges() {
|
|
|
7581
7653
|
return {
|
|
7582
7654
|
changeContentCategories: false,
|
|
7583
7655
|
createEntities: false,
|
|
7656
|
+
createProject: false,
|
|
7584
7657
|
createProvider: false,
|
|
7585
7658
|
deleteDocuments: false,
|
|
7659
|
+
deleteProject: false,
|
|
7586
7660
|
deleteUserFromProject: false,
|
|
7587
7661
|
downloadDocuments: false,
|
|
7588
7662
|
editContentCategories: false,
|
|
@@ -7598,18 +7672,31 @@ var CuePrivileges = class {
|
|
|
7598
7672
|
constructor(_isSuperAdmin) {
|
|
7599
7673
|
this._isSuperAdmin = _isSuperAdmin;
|
|
7600
7674
|
this._projectRoles = new CueSignal([]);
|
|
7675
|
+
this._orgRole = new CueSignal(null);
|
|
7601
7676
|
this.privileges = cueComputed(
|
|
7602
|
-
[this._projectRoles, _isSuperAdmin],
|
|
7677
|
+
[this._projectRoles, this._orgRole, _isSuperAdmin],
|
|
7603
7678
|
() => this._compute()
|
|
7604
7679
|
);
|
|
7605
7680
|
}
|
|
7606
7681
|
_projectRoles;
|
|
7682
|
+
_orgRole;
|
|
7607
7683
|
/**
|
|
7608
7684
|
* Reactive signal — current user's privileges for the selected project.
|
|
7609
7685
|
* Recomputes automatically when `setProjectRoles()` is called or when
|
|
7610
7686
|
* the `isSuperAdmin` signal changes.
|
|
7611
7687
|
*/
|
|
7612
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
|
+
}
|
|
7613
7700
|
/**
|
|
7614
7701
|
* Set the user's roles for the currently selected project.
|
|
7615
7702
|
*
|
|
@@ -7636,10 +7723,12 @@ var CuePrivileges = class {
|
|
|
7636
7723
|
}
|
|
7637
7724
|
_compute() {
|
|
7638
7725
|
const roles = this._projectRoles.get();
|
|
7726
|
+
const isSuperAdmin = this._isSuperAdmin.get();
|
|
7639
7727
|
const result = defaultPrivileges();
|
|
7640
7728
|
for (const key of Object.keys(REQUIRED_ROLES)) {
|
|
7641
7729
|
result[key] = roles.includes(REQUIRED_ROLES[key]);
|
|
7642
7730
|
}
|
|
7731
|
+
result.createProject = isSuperAdmin || this._orgRole.get() === "admin";
|
|
7643
7732
|
return result;
|
|
7644
7733
|
}
|
|
7645
7734
|
};
|
|
@@ -7892,11 +7981,12 @@ GROUP BY ?iri ?parent`;
|
|
|
7892
7981
|
var OSM_ENDPOINT = "https://qlever.dev/api/osm-planet";
|
|
7893
7982
|
var EXCLUDE_OSM_RELATION = true;
|
|
7894
7983
|
var CueProjectEntities = class {
|
|
7895
|
-
constructor(_api, _projectId, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
|
|
7984
|
+
constructor(_api, _projectId, rdfBase = RESOURCE_BASE, _queryCache, _graphType, _verbose = false) {
|
|
7896
7985
|
this._api = _api;
|
|
7897
7986
|
this._projectId = _projectId;
|
|
7898
7987
|
this._queryCache = _queryCache;
|
|
7899
7988
|
this._graphType = _graphType;
|
|
7989
|
+
this._verbose = _verbose;
|
|
7900
7990
|
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
7901
7991
|
this.entityInfoMap = this._entityInfoMapComputed;
|
|
7902
7992
|
this.entityGraph = this._entityGraph.asReadonly();
|
|
@@ -7916,6 +8006,8 @@ var CueProjectEntities = class {
|
|
|
7916
8006
|
_entityOSMMap = new CueSignal({});
|
|
7917
8007
|
_osmWKTMap = new CueSignal({});
|
|
7918
8008
|
_fetchingOSMIds = /* @__PURE__ */ new Set();
|
|
8009
|
+
/** Cumulative unique entity UUIDs ever passed to request methods (survives cache hits). */
|
|
8010
|
+
_seenIds = /* @__PURE__ */ new Set();
|
|
7919
8011
|
_entityGraph = new CueSignal(void 0);
|
|
7920
8012
|
// ── Derived signals ────────────────────────────────────────────────────────
|
|
7921
8013
|
_entityInfoMapComputed = cueComputed(
|
|
@@ -7940,6 +8032,15 @@ var CueProjectEntities = class {
|
|
|
7940
8032
|
entityIri(uuid) {
|
|
7941
8033
|
return `${this.baseURL}${uuid}`;
|
|
7942
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
|
+
}
|
|
7943
8044
|
/**
|
|
7944
8045
|
* Resets all entity state and re-fetches the entity graph.
|
|
7945
8046
|
* Call when the active project changes.
|
|
@@ -7952,6 +8053,7 @@ var CueProjectEntities = class {
|
|
|
7952
8053
|
this._osmWKTMap.set({});
|
|
7953
8054
|
this._entityGraph.set(void 0);
|
|
7954
8055
|
this._fetchingOSMIds.clear();
|
|
8056
|
+
this._seenIds.clear();
|
|
7955
8057
|
this._fetchEntityGraph().catch(
|
|
7956
8058
|
(err) => console.error(
|
|
7957
8059
|
"[CueProjectEntities] Entity graph fetch failed after reset:",
|
|
@@ -7967,11 +8069,14 @@ var CueProjectEntities = class {
|
|
|
7967
8069
|
* Data is merged into `entityInfoMap` once the SPARQL response arrives.
|
|
7968
8070
|
*/
|
|
7969
8071
|
requestEntityData(uuids, includeMentionCount = false) {
|
|
8072
|
+
for (const id of uuids)
|
|
8073
|
+
this._seenIds.add(id);
|
|
7970
8074
|
const newUUIDs = uuids.filter(
|
|
7971
8075
|
(id) => this._entityDetails.get()[id] === void 0
|
|
7972
8076
|
);
|
|
7973
8077
|
if (newUUIDs.length === 0)
|
|
7974
8078
|
return;
|
|
8079
|
+
this._log(`requestEntityData: ${newUUIDs.length} new / ${uuids.length} requested | cumulative: ${this._seenIds.size} seen`);
|
|
7975
8080
|
const values = newUUIDs.map((id) => `r:${id}`).join(" ");
|
|
7976
8081
|
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
7977
8082
|
PREFIX r: <${this.baseURL}>
|
|
@@ -8001,6 +8106,7 @@ GROUP BY ?id ?mentionCount`;
|
|
|
8001
8106
|
};
|
|
8002
8107
|
});
|
|
8003
8108
|
this._entityDetails.set(updates);
|
|
8109
|
+
this._log(`entityDetails: ${Object.keys(updates).length} with metadata / ${this._seenIds.size} seen (cumulative)`);
|
|
8004
8110
|
}).catch(
|
|
8005
8111
|
(err) => console.error("[CueProjectEntities] requestEntityData failed:", err)
|
|
8006
8112
|
);
|
|
@@ -8018,6 +8124,9 @@ GROUP BY ?id ?mentionCount`;
|
|
|
8018
8124
|
);
|
|
8019
8125
|
if (newUUIDs.length === 0)
|
|
8020
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`);
|
|
8021
8130
|
const osmInit = { ...this._entityOSMMap.get() };
|
|
8022
8131
|
for (const id of newUUIDs)
|
|
8023
8132
|
osmInit[id] = { direct: [], indirect: [] };
|
|
@@ -8074,6 +8183,7 @@ WHERE {
|
|
|
8074
8183
|
update[id] = entry;
|
|
8075
8184
|
});
|
|
8076
8185
|
this._entityOSMMap.set(update);
|
|
8186
|
+
this._log(`entityOSMMap: ${Object.keys(update).length} with OSM / ${this._seenIds.size} seen (cumulative)`);
|
|
8077
8187
|
}
|
|
8078
8188
|
/**
|
|
8079
8189
|
* Fetches incoming and outgoing relationships for a single entity IRI.
|
|
@@ -8164,7 +8274,51 @@ ORDER BY ${orderByOccurences ? "DESC(?count)" : "ASC(?label)"}`;
|
|
|
8164
8274
|
};
|
|
8165
8275
|
});
|
|
8166
8276
|
}
|
|
8167
|
-
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}>)` : "";
|
|
8168
8322
|
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8169
8323
|
SELECT
|
|
8170
8324
|
?sourceCat
|
|
@@ -8179,6 +8333,7 @@ WHERE {
|
|
|
8179
8333
|
?s ?predicate ?o .
|
|
8180
8334
|
FILTER(isIRI(?s) && isIRI(?o))
|
|
8181
8335
|
FILTER(?predicate != qcy:relatedEntity)
|
|
8336
|
+
${neighbourFilter}
|
|
8182
8337
|
}
|
|
8183
8338
|
GROUP BY ?sourceCat ?predicate ?targetCat
|
|
8184
8339
|
ORDER BY DESC(?weight)`;
|
|
@@ -8208,7 +8363,6 @@ ORDER BY DESC(?weight)`;
|
|
|
8208
8363
|
};
|
|
8209
8364
|
}
|
|
8210
8365
|
if (format === "md") {
|
|
8211
|
-
const ce = CompactExpand.getInstance();
|
|
8212
8366
|
const rows = bindings.map((b) => ({
|
|
8213
8367
|
src: ce.compactIRI(b["sourceCat"].value),
|
|
8214
8368
|
pred: ce.compactIRI(b["predicate"].value),
|
|
@@ -8453,557 +8607,6 @@ SELECT * WHERE {
|
|
|
8453
8607
|
}
|
|
8454
8608
|
};
|
|
8455
8609
|
|
|
8456
|
-
// libs/js/cue-sdk/src/lib/documents.ts
|
|
8457
|
-
var CueProjectDocuments = class {
|
|
8458
|
-
constructor(_api, _projectId, language, rdfBase = RESOURCE_BASE, _queryCache, _graphType) {
|
|
8459
|
-
this._api = _api;
|
|
8460
|
-
this._projectId = _projectId;
|
|
8461
|
-
this._queryCache = _queryCache;
|
|
8462
|
-
this._graphType = _graphType;
|
|
8463
|
-
this.baseURL = `${rdfBase}${_projectId}/`;
|
|
8464
|
-
this._currentLang = language ?? this._api.language;
|
|
8465
|
-
this.documentInfoMap = this._documentInfoMap.asReadonly();
|
|
8466
|
-
this.projectDocumentsData = this._projectDocumentsData.asReadonly();
|
|
8467
|
-
}
|
|
8468
|
-
/** Full RDF base URL for this project, e.g. `https://cue.qaecy.com/r/{pid}/` */
|
|
8469
|
-
baseURL;
|
|
8470
|
-
/** Tracks the language for which `_documentInfoMap` is currently populated. */
|
|
8471
|
-
_currentLang;
|
|
8472
|
-
_documentInfoMap = new CueSignal({});
|
|
8473
|
-
_projectDocumentsData = new CueSignal({
|
|
8474
|
-
duplicateCount: 0,
|
|
8475
|
-
documentsBySuffix: {},
|
|
8476
|
-
documentsByContentCategory: {}
|
|
8477
|
-
});
|
|
8478
|
-
/** Lazily populated per-document detail map. */
|
|
8479
|
-
documentInfoMap;
|
|
8480
|
-
/** Project-level document overview (grouped counts + sizes). */
|
|
8481
|
-
projectDocumentsData;
|
|
8482
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
8483
|
-
/**
|
|
8484
|
-
* Resets all document state. Call when the active project changes.
|
|
8485
|
-
* Follow with `fetchOverview()` once the triplestore is ready.
|
|
8486
|
-
*/
|
|
8487
|
-
reset() {
|
|
8488
|
-
this._documentInfoMap.set({});
|
|
8489
|
-
this._projectDocumentsData.set({
|
|
8490
|
-
duplicateCount: 0,
|
|
8491
|
-
documentsBySuffix: {},
|
|
8492
|
-
documentsByContentCategory: {}
|
|
8493
|
-
});
|
|
8494
|
-
}
|
|
8495
|
-
/**
|
|
8496
|
-
* Updates the active language and clears the document info map so that
|
|
8497
|
-
* language-sensitive fields (subject, summary) are re-fetched on the next
|
|
8498
|
-
* `requestDocumentData()` call.
|
|
8499
|
-
*/
|
|
8500
|
-
setLanguage(lang) {
|
|
8501
|
-
if (this._currentLang === lang)
|
|
8502
|
-
return;
|
|
8503
|
-
this._currentLang = lang;
|
|
8504
|
-
this._api.setLanguage(lang);
|
|
8505
|
-
this._documentInfoMap.set({});
|
|
8506
|
-
}
|
|
8507
|
-
// ── Public API ─────────────────────────────────────────────────────────────
|
|
8508
|
-
/**
|
|
8509
|
-
* Fetches the three-part project overview (by suffix, by content category,
|
|
8510
|
-
* duplicate count) in parallel and writes them as a single atomic update to
|
|
8511
|
-
* `projectDocumentsData`. Safe to call again to refresh.
|
|
8512
|
-
*/
|
|
8513
|
-
async fetchOverview() {
|
|
8514
|
-
this._projectDocumentsData.set({
|
|
8515
|
-
duplicateCount: 0,
|
|
8516
|
-
documentsBySuffix: {},
|
|
8517
|
-
documentsByContentCategory: {}
|
|
8518
|
-
});
|
|
8519
|
-
const qSuffix = this._buildDocumentsBySuffixQuery();
|
|
8520
|
-
const qCategory = this._buildDocumentsByContentCategoryQuery();
|
|
8521
|
-
const qDuplicates = this._buildDuplicateCountQuery();
|
|
8522
|
-
await staleWhileRevalidate(
|
|
8523
|
-
qSuffix + qCategory + qDuplicates,
|
|
8524
|
-
async () => {
|
|
8525
|
-
const [bySuffix, byCategory, duplicateCount] = await Promise.all([
|
|
8526
|
-
this._runDocumentsBySuffixQuery(qSuffix),
|
|
8527
|
-
this._runDocumentsByContentCategoryQuery(qCategory),
|
|
8528
|
-
this._runDuplicateCountQuery(qDuplicates)
|
|
8529
|
-
]);
|
|
8530
|
-
return { duplicateCount, documentsBySuffix: bySuffix, documentsByContentCategory: byCategory };
|
|
8531
|
-
},
|
|
8532
|
-
(overview) => this._projectDocumentsData.set(overview),
|
|
8533
|
-
this._queryCache
|
|
8534
|
-
);
|
|
8535
|
-
}
|
|
8536
|
-
/**
|
|
8537
|
-
* Lazily batch-fetches core metadata for the given document UUIDs.
|
|
8538
|
-
* Already-cached UUIDs are skipped. Data is merged into `documentInfoMap`
|
|
8539
|
-
* once the SPARQL response arrives.
|
|
8540
|
-
*/
|
|
8541
|
-
requestDocumentData(uuids) {
|
|
8542
|
-
const newUUIDs = uuids.filter((id) => this._documentInfoMap.get()[id] === void 0);
|
|
8543
|
-
if (newUUIDs.length === 0)
|
|
8544
|
-
return;
|
|
8545
|
-
this._fetchDocumentInfoBatch(newUUIDs).catch(
|
|
8546
|
-
(err) => console.error("[CueProjectDocuments] requestDocumentData failed:", err)
|
|
8547
|
-
);
|
|
8548
|
-
}
|
|
8549
|
-
/**
|
|
8550
|
-
* Promise-based alternative to {@link requestDocumentData} for non-reactive contexts.
|
|
8551
|
-
*
|
|
8552
|
-
* Resolves with the `DocumentInfo` entries for every requested UUID once the
|
|
8553
|
-
* SPARQL response arrives. UUIDs already present in the cache are returned
|
|
8554
|
-
* immediately without a network request. The result is also written into
|
|
8555
|
-
* `documentInfoMap` so reactive consumers stay in sync.
|
|
8556
|
-
*
|
|
8557
|
-
* UUIDs not found in the triplestore are omitted from the returned map.
|
|
8558
|
-
*
|
|
8559
|
-
* @example
|
|
8560
|
-
* ```ts
|
|
8561
|
-
* const docs = await cueProjectDocs.fetchDocumentData(['uuid1', 'uuid2']);
|
|
8562
|
-
* console.log(docs['uuid1'].subject);
|
|
8563
|
-
* ```
|
|
8564
|
-
*/
|
|
8565
|
-
async fetchDocumentData(uuids) {
|
|
8566
|
-
const current = this._documentInfoMap.get();
|
|
8567
|
-
const newUUIDs = uuids.filter((id) => current[id] === void 0);
|
|
8568
|
-
if (newUUIDs.length > 0) {
|
|
8569
|
-
await this._fetchDocumentInfoBatch(newUUIDs);
|
|
8570
|
-
}
|
|
8571
|
-
const updated = this._documentInfoMap.get();
|
|
8572
|
-
return Object.fromEntries(
|
|
8573
|
-
uuids.filter((id) => updated[id] !== void 0).map((id) => [id, updated[id]])
|
|
8574
|
-
);
|
|
8575
|
-
}
|
|
8576
|
-
/**
|
|
8577
|
-
* Fetches a lightweight document metadata shape (id/path/suffix/size) for
|
|
8578
|
-
* the given UUIDs and merges the results into `documentInfoMap`.
|
|
8579
|
-
*
|
|
8580
|
-
* This is useful for list/table contexts that do not need language-tagged
|
|
8581
|
-
* fields (`subject`, `summary`) or category/tag enrichment.
|
|
8582
|
-
*
|
|
8583
|
-
* UUIDs already present in `documentInfoMap` are skipped.
|
|
8584
|
-
*/
|
|
8585
|
-
async fetchDocumentDataSimple(uuids) {
|
|
8586
|
-
const current = this._documentInfoMap.get();
|
|
8587
|
-
const newUUIDs = uuids.filter((id) => current[id] === void 0);
|
|
8588
|
-
if (newUUIDs.length > 0) {
|
|
8589
|
-
await this._fetchSimpleDocumentInfoBatch(newUUIDs);
|
|
8590
|
-
}
|
|
8591
|
-
const updated = this._documentInfoMap.get();
|
|
8592
|
-
return Object.fromEntries(
|
|
8593
|
-
uuids.filter((id) => updated[id] !== void 0).map((id) => [id, updated[id]])
|
|
8594
|
-
);
|
|
8595
|
-
}
|
|
8596
|
-
/**
|
|
8597
|
-
* Returns the alternative representations of the given document UUID.
|
|
8598
|
-
*
|
|
8599
|
-
* Alternative representations are derived artefacts stored under
|
|
8600
|
-
* `qcy:alternativeRepresentation` in the triplestore — for example a
|
|
8601
|
-
* `.fragments` BIM tile derived from an `.ifc` source file.
|
|
8602
|
-
*
|
|
8603
|
-
* The returned `DocumentInfo` entries are also merged into
|
|
8604
|
-
* `documentInfoMap` so reactive consumers stay in sync.
|
|
8605
|
-
*
|
|
8606
|
-
* @example
|
|
8607
|
-
* ```ts
|
|
8608
|
-
* const alts = await docs.fetchAlternativeRepresentations('abc-123');
|
|
8609
|
-
* // alts[0].suffix => '.fragments'
|
|
8610
|
-
* ```
|
|
8611
|
-
*/
|
|
8612
|
-
async fetchAlternativeRepresentations(uuid) {
|
|
8613
|
-
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8614
|
-
PREFIX r: <${this.baseURL}>
|
|
8615
|
-
SELECT ?altId ?contentIRI ?suffix ?rrp
|
|
8616
|
-
WHERE {
|
|
8617
|
-
r:${uuid} qcy:alternativeRepresentation ?contentIRI .
|
|
8618
|
-
BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?altId)
|
|
8619
|
-
?contentIRI qcy:hasFileLocation ?loc .
|
|
8620
|
-
?loc qcy:suffix ?suffix .
|
|
8621
|
-
OPTIONAL { ?loc qcy:remoteRelativePath ?rrp }
|
|
8622
|
-
}`;
|
|
8623
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8624
|
-
const updates = { ...this._documentInfoMap.get() };
|
|
8625
|
-
const alts = [];
|
|
8626
|
-
data.results.bindings.forEach((b) => {
|
|
8627
|
-
if (!b["altId"] || !b["contentIRI"])
|
|
8628
|
-
return;
|
|
8629
|
-
const id = b["altId"].value;
|
|
8630
|
-
const info = {
|
|
8631
|
-
id,
|
|
8632
|
-
contentIRI: b["contentIRI"].value,
|
|
8633
|
-
path: "",
|
|
8634
|
-
suffix: b["suffix"]?.value ?? "",
|
|
8635
|
-
size: 0,
|
|
8636
|
-
tags: [],
|
|
8637
|
-
categories: [],
|
|
8638
|
-
remoteRelativePath: b["rrp"]?.value
|
|
8639
|
-
};
|
|
8640
|
-
updates[id] = info;
|
|
8641
|
-
alts.push(info);
|
|
8642
|
-
});
|
|
8643
|
-
this._documentInfoMap.set(updates);
|
|
8644
|
-
return alts;
|
|
8645
|
-
}
|
|
8646
|
-
/**
|
|
8647
|
-
* Returns a single arbitrary file path from the project's triplestore.
|
|
8648
|
-
* Useful for pre-filling path-based query inputs with a realistic example.
|
|
8649
|
-
*/
|
|
8650
|
-
async randomFilePath() {
|
|
8651
|
-
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8652
|
-
SELECT ?path
|
|
8653
|
-
WHERE {
|
|
8654
|
-
?fl a qcy:FileLocation ;
|
|
8655
|
-
qcy:filePath ?path .
|
|
8656
|
-
}
|
|
8657
|
-
LIMIT 1`;
|
|
8658
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8659
|
-
return data.results.bindings[0]?.["path"]?.value ?? null;
|
|
8660
|
-
}
|
|
8661
|
-
// ── Private helpers ────────────────────────────────────────────────────────
|
|
8662
|
-
/** Executes the document-info SPARQL query for the given UUIDs, merges results
|
|
8663
|
-
* into `documentInfoMap`, and returns the newly fetched entries. */
|
|
8664
|
-
async _fetchDocumentInfoBatch(uuids) {
|
|
8665
|
-
const values = uuids.map((id) => `r:${id}`).join(" ");
|
|
8666
|
-
const lang = this._api.language;
|
|
8667
|
-
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8668
|
-
PREFIX r: <${this.baseURL}>
|
|
8669
|
-
SELECT ?id ?contentIRI ?suffix ?size ?subject ?summary
|
|
8670
|
-
(SAMPLE(?fp) AS ?path)
|
|
8671
|
-
(GROUP_CONCAT(DISTINCT ?tag; SEPARATOR=";") AS ?tags)
|
|
8672
|
-
(GROUP_CONCAT(DISTINCT STR(?cat); SEPARATOR=";") AS ?categories)
|
|
8673
|
-
WHERE {
|
|
8674
|
-
VALUES ?contentIRI { ${values} }
|
|
8675
|
-
?contentIRI qcy:sizeBytes ?size ;
|
|
8676
|
-
qcy:hasFileLocation ?loc .
|
|
8677
|
-
?loc qcy:filePath ?fp ;
|
|
8678
|
-
qcy:suffix ?suffix .
|
|
8679
|
-
OPTIONAL { ?contentIRI qcy:hasContentCategory ?cat }
|
|
8680
|
-
OPTIONAL { ?contentIRI qcy:tag ?tag }
|
|
8681
|
-
OPTIONAL { ?loc qcy:remoteProviderId ?pid }
|
|
8682
|
-
|
|
8683
|
-
OPTIONAL { ?contentIRI qcy:subject ?lang_subj FILTER(LANG(?lang_subj) = "${lang}") }
|
|
8684
|
-
OPTIONAL { ?contentIRI qcy:subject ?no_lang_subj }
|
|
8685
|
-
BIND(COALESCE(?lang_subj, ?no_lang_subj) AS ?subject)
|
|
8686
|
-
|
|
8687
|
-
OPTIONAL { ?contentIRI qcy:textSummary ?lang_summary FILTER(LANG(?lang_summary) = "${lang}") }
|
|
8688
|
-
OPTIONAL { ?contentIRI qcy:textSummary ?no_lang_summary }
|
|
8689
|
-
BIND(COALESCE(?lang_summary, ?no_lang_summary) AS ?summary)
|
|
8690
|
-
|
|
8691
|
-
BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?id)
|
|
8692
|
-
}
|
|
8693
|
-
GROUP BY ?id ?contentIRI ?suffix ?size ?subject ?summary`;
|
|
8694
|
-
const result = await this._api.sparql(q, this._projectId);
|
|
8695
|
-
const updates = { ...this._documentInfoMap.get() };
|
|
8696
|
-
const fetched = {};
|
|
8697
|
-
result.results.bindings.forEach((b) => {
|
|
8698
|
-
if (!b["id"] || !b["contentIRI"])
|
|
8699
|
-
return;
|
|
8700
|
-
const id = b["id"].value;
|
|
8701
|
-
const info = {
|
|
8702
|
-
id,
|
|
8703
|
-
contentIRI: b["contentIRI"].value,
|
|
8704
|
-
path: b["path"]?.value ?? "",
|
|
8705
|
-
suffix: b["suffix"]?.value ?? "",
|
|
8706
|
-
size: b["size"] ? parseInt(b["size"].value, 10) : 0,
|
|
8707
|
-
tags: b["tags"]?.value?.split(";").filter(Boolean) ?? [],
|
|
8708
|
-
categories: b["categories"]?.value?.split(";").filter(Boolean) ?? [],
|
|
8709
|
-
subject: b["subject"]?.value,
|
|
8710
|
-
summary: b["summary"]?.value,
|
|
8711
|
-
providerId: b["pid"]?.value
|
|
8712
|
-
};
|
|
8713
|
-
updates[id] = info;
|
|
8714
|
-
fetched[id] = info;
|
|
8715
|
-
});
|
|
8716
|
-
this._documentInfoMap.set(updates);
|
|
8717
|
-
return fetched;
|
|
8718
|
-
}
|
|
8719
|
-
/** Executes a reduced document-info query (id/path/suffix/size only), merges
|
|
8720
|
-
* into `documentInfoMap`, and returns newly fetched entries. */
|
|
8721
|
-
async _fetchSimpleDocumentInfoBatch(uuids) {
|
|
8722
|
-
const values = uuids.map((id) => `r:${id}`).join(" ");
|
|
8723
|
-
const q = `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8724
|
-
PREFIX r: <${this.baseURL}>
|
|
8725
|
-
SELECT ?id ?contentIRI ?suffix ?size (SAMPLE(?fp) AS ?path)
|
|
8726
|
-
WHERE {
|
|
8727
|
-
VALUES ?contentIRI { ${values} }
|
|
8728
|
-
?contentIRI qcy:sizeBytes ?size ;
|
|
8729
|
-
qcy:hasFileLocation ?loc .
|
|
8730
|
-
?loc qcy:filePath ?fp ;
|
|
8731
|
-
qcy:suffix ?suffix .
|
|
8732
|
-
BIND(REPLACE(STR(?contentIRI), "^.*/([^/]*)$", "$1") AS ?id)
|
|
8733
|
-
}
|
|
8734
|
-
GROUP BY ?id ?contentIRI ?suffix ?size`;
|
|
8735
|
-
const result = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8736
|
-
const updates = { ...this._documentInfoMap.get() };
|
|
8737
|
-
const fetched = {};
|
|
8738
|
-
result.results.bindings.forEach((b) => {
|
|
8739
|
-
if (!b["id"])
|
|
8740
|
-
return;
|
|
8741
|
-
const id = b["id"].value;
|
|
8742
|
-
const existing = updates[id];
|
|
8743
|
-
const info = {
|
|
8744
|
-
id,
|
|
8745
|
-
contentIRI: b["contentIRI"]?.value ?? existing?.contentIRI ?? id,
|
|
8746
|
-
path: b["path"]?.value ?? existing?.path ?? id,
|
|
8747
|
-
suffix: b["suffix"]?.value ?? existing?.suffix ?? "",
|
|
8748
|
-
size: b["size"] ? parseInt(b["size"].value, 10) : existing?.size ?? 0,
|
|
8749
|
-
tags: existing?.tags ?? [],
|
|
8750
|
-
categories: existing?.categories ?? [],
|
|
8751
|
-
subject: existing?.subject,
|
|
8752
|
-
summary: existing?.summary,
|
|
8753
|
-
providerId: existing?.providerId
|
|
8754
|
-
};
|
|
8755
|
-
updates[id] = info;
|
|
8756
|
-
fetched[id] = info;
|
|
8757
|
-
});
|
|
8758
|
-
this._documentInfoMap.set(updates);
|
|
8759
|
-
return fetched;
|
|
8760
|
-
}
|
|
8761
|
-
async _fetchDocumentsBySuffix() {
|
|
8762
|
-
return this._runDocumentsBySuffixQuery(this._buildDocumentsBySuffixQuery());
|
|
8763
|
-
}
|
|
8764
|
-
_buildDocumentsBySuffixQuery() {
|
|
8765
|
-
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8766
|
-
SELECT ?ext (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
8767
|
-
WHERE {
|
|
8768
|
-
{
|
|
8769
|
-
SELECT DISTINCT ?fc ?ext ?size
|
|
8770
|
-
WHERE {
|
|
8771
|
-
?fc a qcy:FileContent ;
|
|
8772
|
-
qcy:sizeBytes ?size ;
|
|
8773
|
-
qcy:hasFileLocation/qcy:suffix ?ext .
|
|
8774
|
-
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
8775
|
-
}
|
|
8776
|
-
}
|
|
8777
|
-
}
|
|
8778
|
-
GROUP BY ?ext
|
|
8779
|
-
ORDER BY DESC(?docCount)`;
|
|
8780
|
-
}
|
|
8781
|
-
async _runDocumentsBySuffixQuery(q) {
|
|
8782
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8783
|
-
const result = {};
|
|
8784
|
-
data.results.bindings.forEach((b) => {
|
|
8785
|
-
if (!b["ext"])
|
|
8786
|
-
return;
|
|
8787
|
-
result[b["ext"].value] = {
|
|
8788
|
-
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
8789
|
-
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
8790
|
-
};
|
|
8791
|
-
});
|
|
8792
|
-
return result;
|
|
8793
|
-
}
|
|
8794
|
-
async _fetchDocumentsByContentCategory() {
|
|
8795
|
-
return this._runDocumentsByContentCategoryQuery(this._buildDocumentsByContentCategoryQuery());
|
|
8796
|
-
}
|
|
8797
|
-
_buildDocumentsByContentCategoryQuery() {
|
|
8798
|
-
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8799
|
-
SELECT ?cat (SUM(?size) AS ?totalSize) (COUNT(*) AS ?docCount)
|
|
8800
|
-
WHERE {
|
|
8801
|
-
{
|
|
8802
|
-
SELECT DISTINCT ?fc ?cat ?size
|
|
8803
|
-
WHERE {
|
|
8804
|
-
?fc a qcy:FileContent ;
|
|
8805
|
-
qcy:hasContentCategory ?cat ;
|
|
8806
|
-
qcy:sizeBytes ?size .
|
|
8807
|
-
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
8808
|
-
}
|
|
8809
|
-
}
|
|
8810
|
-
}
|
|
8811
|
-
GROUP BY ?cat
|
|
8812
|
-
ORDER BY DESC(?docCount)`;
|
|
8813
|
-
}
|
|
8814
|
-
async _runDocumentsByContentCategoryQuery(q) {
|
|
8815
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8816
|
-
const result = {};
|
|
8817
|
-
data.results.bindings.forEach((b) => {
|
|
8818
|
-
if (!b["cat"])
|
|
8819
|
-
return;
|
|
8820
|
-
result[b["cat"].value] = {
|
|
8821
|
-
size: b["totalSize"] ? parseInt(b["totalSize"].value, 10) : 0,
|
|
8822
|
-
count: b["docCount"] ? parseInt(b["docCount"].value, 10) : 0
|
|
8823
|
-
};
|
|
8824
|
-
});
|
|
8825
|
-
return result;
|
|
8826
|
-
}
|
|
8827
|
-
async _fetchDuplicateCount() {
|
|
8828
|
-
return this._runDuplicateCountQuery(this._buildDuplicateCountQuery());
|
|
8829
|
-
}
|
|
8830
|
-
_buildDuplicateCountQuery() {
|
|
8831
|
-
return `PREFIX qcy: <${qaecyPrefixes["qcy"]}>
|
|
8832
|
-
SELECT (COUNT(*) AS ?count)
|
|
8833
|
-
WHERE {
|
|
8834
|
-
SELECT ?fc
|
|
8835
|
-
WHERE {
|
|
8836
|
-
?fc a qcy:FileContent ;
|
|
8837
|
-
qcy:hasFileLocation ?fl .
|
|
8838
|
-
FILTER NOT EXISTS { ?x qcy:alternativeRepresentation ?fc }
|
|
8839
|
-
}
|
|
8840
|
-
GROUP BY ?fc
|
|
8841
|
-
HAVING (COUNT(?fl) > 1)
|
|
8842
|
-
}`;
|
|
8843
|
-
}
|
|
8844
|
-
async _runDuplicateCountQuery(q) {
|
|
8845
|
-
const data = await this._api.sparql(q, this._projectId, this._graphType);
|
|
8846
|
-
const first = data.results.bindings[0];
|
|
8847
|
-
return first?.["count"] ? parseInt(first["count"].value, 10) : 0;
|
|
8848
|
-
}
|
|
8849
|
-
};
|
|
8850
|
-
|
|
8851
|
-
// libs/js/cue-sdk/src/lib/project-view.ts
|
|
8852
|
-
var CueProjectView = class {
|
|
8853
|
-
constructor(_api, _projectId, { language, queryCache, rdfBase = RESOURCE_BASE, graphType }) {
|
|
8854
|
-
this._api = _api;
|
|
8855
|
-
this._projectId = _projectId;
|
|
8856
|
-
this.schema = new CueProjectSchema(_api, _projectId, language, queryCache, graphType);
|
|
8857
|
-
this.entities = new CueProjectEntities(_api, _projectId, rdfBase, queryCache, graphType);
|
|
8858
|
-
this.documents = new CueProjectDocuments(_api, _projectId, language, rdfBase, queryCache, graphType);
|
|
8859
|
-
this.availableContentCategories = this.schema.availableContentCategories;
|
|
8860
|
-
this.availableEntityCategories = this.schema.availableEntityCategories;
|
|
8861
|
-
this.availableEntityRelationships = this.schema.availableEntityRelationships;
|
|
8862
|
-
this.schemaReady = this.schema.ready;
|
|
8863
|
-
this.entityInfoMap = this.entities.entityInfoMap;
|
|
8864
|
-
this.entityGraph = this.entities.entityGraph;
|
|
8865
|
-
this.documentInfoMap = this.documents.documentInfoMap;
|
|
8866
|
-
this.projectDocumentsData = this.documents.projectDocumentsData;
|
|
8867
|
-
this.searchResults = this._searchResults.asReadonly();
|
|
8868
|
-
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed:", err));
|
|
8869
|
-
}
|
|
8870
|
-
/** Direct access to the schema data class (available categories / relationships). */
|
|
8871
|
-
schema;
|
|
8872
|
-
/** Direct access to the entity data class. */
|
|
8873
|
-
entities;
|
|
8874
|
-
/** Direct access to the document data class. */
|
|
8875
|
-
documents;
|
|
8876
|
-
// ── Proxied signals ────────────────────────────────────────────────────────
|
|
8877
|
-
/** Available content category definitions for this project. Auto-fetched on init. */
|
|
8878
|
-
availableContentCategories;
|
|
8879
|
-
/** Available entity category definitions for this project. Auto-fetched on init. */
|
|
8880
|
-
availableEntityCategories;
|
|
8881
|
-
/** Available entity relationship types. Auto-fetched on init. */
|
|
8882
|
-
availableEntityRelationships;
|
|
8883
|
-
/**
|
|
8884
|
-
* Resolves when the initial schema load has completed. Await before reading
|
|
8885
|
-
* schema signal values imperatively.
|
|
8886
|
-
*/
|
|
8887
|
-
schemaReady;
|
|
8888
|
-
/** Merged per-entity detail map. Populated lazily via `requestEntityData()` etc. */
|
|
8889
|
-
entityInfoMap;
|
|
8890
|
-
/** Project-level entity co-occurrence graph. Fetched once on init. */
|
|
8891
|
-
entityGraph;
|
|
8892
|
-
/** Per-document info map. Populated lazily via `requestDocumentData()`. */
|
|
8893
|
-
documentInfoMap;
|
|
8894
|
-
/** Project document overview (counts by suffix and category). Fetched on init. */
|
|
8895
|
-
projectDocumentsData;
|
|
8896
|
-
// ── Search state ───────────────────────────────────────────────────────────
|
|
8897
|
-
_searchResults = new CueSignal(void 0);
|
|
8898
|
-
/** The result of the most recent `search()` call. `undefined` before first search. */
|
|
8899
|
-
searchResults;
|
|
8900
|
-
_destroyed = false;
|
|
8901
|
-
// ── Entity methods ─────────────────────────────────────────────────────────
|
|
8902
|
-
/**
|
|
8903
|
-
* Lazily batch-fetch core data (label + categories) for the given entity UUIDs.
|
|
8904
|
-
* Already-fetched UUIDs are skipped. Populates `entityInfoMap`.
|
|
8905
|
-
*/
|
|
8906
|
-
requestEntityData(uuids, includeMentionCount = false) {
|
|
8907
|
-
if (this._destroyed)
|
|
8908
|
-
return;
|
|
8909
|
-
this.entities.requestEntityData(uuids, includeMentionCount);
|
|
8910
|
-
}
|
|
8911
|
-
/**
|
|
8912
|
-
* Lazily fetch OSM location data for the given entity UUIDs.
|
|
8913
|
-
* Already-fetched UUIDs are skipped. Populates `entityInfoMap` geometry fields.
|
|
8914
|
-
*/
|
|
8915
|
-
async requestEntityLocations(uuids) {
|
|
8916
|
-
if (this._destroyed)
|
|
8917
|
-
return;
|
|
8918
|
-
return this.entities.requestEntityLocations(uuids);
|
|
8919
|
-
}
|
|
8920
|
-
/**
|
|
8921
|
-
* Fetch incoming and outgoing relationships for a single entity IRI.
|
|
8922
|
-
* Result is stored in `entityInfoMap[uuid].relationshipData`.
|
|
8923
|
-
*/
|
|
8924
|
-
async fetchEntityRelationships(iri) {
|
|
8925
|
-
if (this._destroyed)
|
|
8926
|
-
throw new Error("CueProjectView is destroyed");
|
|
8927
|
-
return this.entities.fetchEntityRelationships(iri);
|
|
8928
|
-
}
|
|
8929
|
-
/**
|
|
8930
|
-
* Fetch UUIDs of documents that reference the given entity IRI.
|
|
8931
|
-
* Result is stored in `entityInfoMap[uuid].documentRefs`.
|
|
8932
|
-
*/
|
|
8933
|
-
async fetchEntityDocuments(iri) {
|
|
8934
|
-
if (this._destroyed)
|
|
8935
|
-
throw new Error("CueProjectView is destroyed");
|
|
8936
|
-
return this.entities.fetchEntityDocuments(iri);
|
|
8937
|
-
}
|
|
8938
|
-
/** Constructs the full RDF IRI for an entity UUID. */
|
|
8939
|
-
entityIri(uuid) {
|
|
8940
|
-
return this.entities.entityIri(uuid);
|
|
8941
|
-
}
|
|
8942
|
-
// ── Document methods ───────────────────────────────────────────────────────
|
|
8943
|
-
/**
|
|
8944
|
-
* Lazily batch-fetch document info for the given UUIDs.
|
|
8945
|
-
* Already-fetched UUIDs are skipped. Populates `documentInfoMap`.
|
|
8946
|
-
*/
|
|
8947
|
-
requestDocumentData(uuids) {
|
|
8948
|
-
if (this._destroyed)
|
|
8949
|
-
return;
|
|
8950
|
-
this.documents.requestDocumentData(uuids);
|
|
8951
|
-
}
|
|
8952
|
-
// ── Search ─────────────────────────────────────────────────────────────────
|
|
8953
|
-
/**
|
|
8954
|
-
* Run a natural-language search against the project.
|
|
8955
|
-
* The result is stored in `searchResults` and replaces any previous result.
|
|
8956
|
-
*/
|
|
8957
|
-
async search(term, options) {
|
|
8958
|
-
if (this._destroyed)
|
|
8959
|
-
return;
|
|
8960
|
-
const result = await this._api.search({
|
|
8961
|
-
term,
|
|
8962
|
-
projectId: this._projectId,
|
|
8963
|
-
categories: options?.categories
|
|
8964
|
-
});
|
|
8965
|
-
if (!this._destroyed) {
|
|
8966
|
-
this._searchResults.set(result);
|
|
8967
|
-
}
|
|
8968
|
-
}
|
|
8969
|
-
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
8970
|
-
/**
|
|
8971
|
-
* Switch the active language for schema labels and document text fields.
|
|
8972
|
-
* Schema responses are cached per language (instant if previously loaded).
|
|
8973
|
-
* The document info map is cleared and lazily re-populated on next access.
|
|
8974
|
-
*/
|
|
8975
|
-
setLanguage(lang) {
|
|
8976
|
-
if (this._destroyed)
|
|
8977
|
-
return;
|
|
8978
|
-
this.schema.setLanguage(lang);
|
|
8979
|
-
this.documents.setLanguage(lang);
|
|
8980
|
-
}
|
|
8981
|
-
/**
|
|
8982
|
-
* Reset all entity and document state and re-fetch the project overview.
|
|
8983
|
-
* Prefer creating a fresh `CueProjectView` when switching projects.
|
|
8984
|
-
* Use `reset()` only when the same project's data needs to be invalidated.
|
|
8985
|
-
*/
|
|
8986
|
-
reset() {
|
|
8987
|
-
if (this._destroyed)
|
|
8988
|
-
return;
|
|
8989
|
-
this.entities.reset();
|
|
8990
|
-
this.documents.reset();
|
|
8991
|
-
this._searchResults.set(void 0);
|
|
8992
|
-
this.documents.fetchOverview().catch((err) => console.error("[CueProjectView] fetchOverview failed after reset:", err));
|
|
8993
|
-
}
|
|
8994
|
-
/**
|
|
8995
|
-
* Tear down this view instance. Clears all reactive state and blocks further
|
|
8996
|
-
* updates. Call from the Angular adapter's `ngOnDestroy` or equivalent.
|
|
8997
|
-
*/
|
|
8998
|
-
destroy() {
|
|
8999
|
-
this._destroyed = true;
|
|
9000
|
-
this._searchResults.set(void 0);
|
|
9001
|
-
}
|
|
9002
|
-
};
|
|
9003
|
-
|
|
9004
|
-
// libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
|
|
9005
|
-
init_src();
|
|
9006
|
-
|
|
9007
8610
|
// libs/js/models/src/lib/file-extensions.ts
|
|
9008
8611
|
var fileExtensionsInfo = {
|
|
9009
8612
|
[".aac" /* AAC */]: {
|
|
@@ -9866,41 +9469,706 @@ var fileExtensionsInfo = {
|
|
|
9866
9469
|
}
|
|
9867
9470
|
};
|
|
9868
9471
|
|
|
9869
|
-
// libs/js/models/src/lib/file-mime-categories.ts
|
|
9870
|
-
var fileTypeToMimeCategory = {
|
|
9871
|
-
["audio" /* AUDIO */]: "AudioFileContent" /* AUDIO */,
|
|
9872
|
-
["video" /* VIDEO */]: "VideoFileContent" /* VIDEO */,
|
|
9873
|
-
["image" /* IMAGE */]: "ImageFileContent" /* IMAGE */,
|
|
9874
|
-
["text" /* TEXT */]: "TextFileContent" /* TEXT */,
|
|
9875
|
-
["markup" /* MARKUP */]: "MarkupFileContent" /* MARKUP */,
|
|
9876
|
-
["script" /* SCRIPT */]: "ScriptFileContent" /* SCRIPT */,
|
|
9877
|
-
["data" /* DATA */]: "DataFileContent" /* DATA */,
|
|
9878
|
-
["archive" /* ARCHIVE */]: "ArchiveFileContent" /* ARCHIVE */,
|
|
9879
|
-
["installer" /* INSTALLER */]: "InstallerFileContent" /* INSTALLER */,
|
|
9880
|
-
["binary" /* BINARY */]: "BinaryFileContent" /* BINARY */,
|
|
9881
|
-
["backup" /* BACKUP */]: "BackupFileContent" /* BACKUP */,
|
|
9882
|
-
["automation" /* AUTOMATION */]: "AutomationFileContent" /* AUTOMATION */,
|
|
9883
|
-
["presentation" /* PRESENTATION */]: "PresentationFileContent" /* PRESENTATION */,
|
|
9884
|
-
["spreadsheet" /* SPREADSHEET */]: "SpreadsheetFileContent" /* SPREADSHEET */,
|
|
9885
|
-
["font" /* FONT */]: "FontFileContent" /* FONT */,
|
|
9886
|
-
["geospatial" /* GEOSPATIAL */]: "GeospatialFileContent" /* GEOSPATIAL */,
|
|
9887
|
-
["3d" /* THREE_D */]: "ThreeDFileContent" /* THREE_D */,
|
|
9888
|
-
["cad" /* CAD */]: "CADFileContent" /* CAD */,
|
|
9889
|
-
["bim" /* BIM */]: "BIMFileContent" /* BIM */,
|
|
9890
|
-
["planning" /* PLANNING */]: "PlanningFileContent" /* PLANNING */,
|
|
9891
|
-
["email" /* EMAIL */]: "EmailFileContent" /* EMAIL */,
|
|
9892
|
-
["multimedia" /* MULTIMEDIA */]: "MultimediaFileContent" /* MULTIMEDIA */,
|
|
9893
|
-
["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
|
+
}
|
|
9894
10168
|
};
|
|
9895
10169
|
|
|
9896
|
-
// libs/js/
|
|
9897
|
-
|
|
9898
|
-
|
|
9899
|
-
// libs/js/models/src/lib/search-log-entry.ts
|
|
9900
|
-
var import_uuid4 = require("uuid");
|
|
9901
|
-
|
|
9902
|
-
// libs/js/models/src/lib/search-response.ts
|
|
9903
|
-
var import_uuid5 = require("uuid");
|
|
10170
|
+
// libs/js/file-metadata-helpers/src/lib/js-file-metadata-helpers.ts
|
|
10171
|
+
init_src();
|
|
9904
10172
|
|
|
9905
10173
|
// libs/js/rdf-document-writers/src/lib/alternative-representation.ts
|
|
9906
10174
|
var import_n33 = require("n3");
|
|
@@ -10700,6 +10968,7 @@ var Cue = class _Cue {
|
|
|
10700
10968
|
_storageProcessed;
|
|
10701
10969
|
_gis = null;
|
|
10702
10970
|
_projectDocuments = /* @__PURE__ */ new Map();
|
|
10971
|
+
_verbose;
|
|
10703
10972
|
/**
|
|
10704
10973
|
* Reactive GIS service. Lazily constructed on first access.
|
|
10705
10974
|
*
|
|
@@ -10733,6 +11002,7 @@ var Cue = class _Cue {
|
|
|
10733
11002
|
const env = config.environment ?? "production";
|
|
10734
11003
|
this._endpoints = { ...ENDPOINTS[env], ...config.endpoints };
|
|
10735
11004
|
this._isEmulator = env === "emulator";
|
|
11005
|
+
this._verbose = config.verbose ?? false;
|
|
10736
11006
|
this._app = (0, import_app2.getApps)().find((a5) => a5.name === "[DEFAULT]") ?? (0, import_app2.initializeApp)({
|
|
10737
11007
|
apiKey,
|
|
10738
11008
|
appId,
|
|
@@ -10861,7 +11131,7 @@ var Cue = class _Cue {
|
|
|
10861
11131
|
get: (key) => this.cache.getQueryCache(projectId, key).then((entry) => entry?.results),
|
|
10862
11132
|
set: (key, data) => this.cache.setQueryCache(projectId, key, { query: key, results: data })
|
|
10863
11133
|
};
|
|
10864
|
-
return new CueProjectView(this.api, projectId, { ...opts, queryCache });
|
|
11134
|
+
return new CueProjectView(this.api, projectId, { verbose: this._verbose, ...opts, queryCache });
|
|
10865
11135
|
}
|
|
10866
11136
|
/**
|
|
10867
11137
|
* Creates a `CueProjectEntities` instance for the given project, with the
|
|
@@ -10890,7 +11160,8 @@ var Cue = class _Cue {
|
|
|
10890
11160
|
projectId,
|
|
10891
11161
|
opts?.rdfBase,
|
|
10892
11162
|
queryCache,
|
|
10893
|
-
opts?.graphType
|
|
11163
|
+
opts?.graphType,
|
|
11164
|
+
opts?.verbose ?? this._verbose
|
|
10894
11165
|
);
|
|
10895
11166
|
}
|
|
10896
11167
|
/**
|
|
@@ -10925,7 +11196,8 @@ var Cue = class _Cue {
|
|
|
10925
11196
|
this.api.language,
|
|
10926
11197
|
void 0,
|
|
10927
11198
|
queryCache2,
|
|
10928
|
-
void 0
|
|
11199
|
+
void 0,
|
|
11200
|
+
opts?.verbose ?? this._verbose
|
|
10929
11201
|
);
|
|
10930
11202
|
this._projectDocuments.set(projectId, docs);
|
|
10931
11203
|
return docs;
|
|
@@ -10940,7 +11212,8 @@ var Cue = class _Cue {
|
|
|
10940
11212
|
opts?.language ?? this.api.language,
|
|
10941
11213
|
opts?.rdfBase,
|
|
10942
11214
|
queryCache,
|
|
10943
|
-
opts?.graphType
|
|
11215
|
+
opts?.graphType,
|
|
11216
|
+
opts?.verbose ?? this._verbose
|
|
10944
11217
|
);
|
|
10945
11218
|
}
|
|
10946
11219
|
};
|
|
@@ -12301,6 +12574,10 @@ async function buildCueClient(options) {
|
|
|
12301
12574
|
console.error("API key is required. Provide it via --key or CUE_API_KEY env variable.");
|
|
12302
12575
|
process.exit(1);
|
|
12303
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
|
+
}
|
|
12304
12581
|
const cue = new CueNode({
|
|
12305
12582
|
apiKey: FIREBASE_CONFIG().apiKey,
|
|
12306
12583
|
appId: FIREBASE_CONFIG().appId,
|
|
@@ -12336,21 +12613,32 @@ async function entitySummaryGraphHandler(options) {
|
|
|
12336
12613
|
try {
|
|
12337
12614
|
const cue = await buildCueClient(options);
|
|
12338
12615
|
if (options.verbose)
|
|
12339
|
-
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})` : ""}...`);
|
|
12340
12617
|
const entities = new CueProjectEntities(cue.api, options.space);
|
|
12341
12618
|
if (options.format === "graph") {
|
|
12342
|
-
const graph = await entities.buildSummaryGraph("graph");
|
|
12619
|
+
const graph = await entities.buildSummaryGraph("graph", options.entity);
|
|
12343
12620
|
console.log(JSON.stringify(graph, null, 2));
|
|
12344
12621
|
} else if (options.format === "md") {
|
|
12345
|
-
const md = await entities.buildSummaryGraph("md");
|
|
12622
|
+
const md = await entities.buildSummaryGraph("md", options.entity);
|
|
12346
12623
|
console.log(md);
|
|
12347
12624
|
} else {
|
|
12348
|
-
const raw = await entities.buildSummaryGraph();
|
|
12625
|
+
const raw = await entities.buildSummaryGraph(void 0, options.entity);
|
|
12349
12626
|
console.log(JSON.stringify(raw, null, 2));
|
|
12350
12627
|
}
|
|
12351
12628
|
process.exit(0);
|
|
12352
12629
|
} catch (err) {
|
|
12353
|
-
|
|
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
|
+
}
|
|
12354
12642
|
process.exit(1);
|
|
12355
12643
|
}
|
|
12356
12644
|
}
|
|
@@ -12384,7 +12672,7 @@ async function sparqlHandler(options) {
|
|
|
12384
12672
|
}
|
|
12385
12673
|
var appBuilderToolsCommand = new import_commander.Command("app-builder-tools").description("Tools for agent-assisted Cue app development. Outputs JSON for machine consumption.");
|
|
12386
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);
|
|
12387
|
-
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);
|
|
12388
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);
|
|
12389
12677
|
|
|
12390
12678
|
// apps/desktop/cue-cli/src/main.ts
|