@blinkk/root-cms 3.1.3 → 3.1.4
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/dist/app.js +1 -4
- package/dist/chunk-MYN76V3L.js +764 -0
- package/dist/{chunk-4VZTYUQ7.js → chunk-NC3CRIFA.js} +191 -13
- package/dist/core/ai-tools.d.ts +82 -42
- package/dist/core/ai.d.ts +54 -218
- package/dist/core/plugin.d.ts +10 -4
- package/dist/functions.js +1 -2
- package/dist/plugin.js +42 -208
- package/dist/shared/ai/models.d.ts +110 -0
- package/dist/shared/ai/prompt-utils.d.ts +59 -0
- package/dist/ui/AIPage-4W4MFELY.js +1 -0
- package/dist/ui/AssetsPage-QUL2NBWK.js +1 -0
- package/dist/ui/CollectionPage-7VU6SUUP.js +1 -0
- package/dist/ui/ComparePage-DCP5QHDE.js +1 -0
- package/dist/ui/DataPage-TUVFBALT.js +1 -0
- package/dist/ui/DataSourcePage-YE2ZOHYI.js +1 -0
- package/dist/ui/DocTranslationsPage-3ILTQOP2.js +1 -0
- package/dist/ui/DocumentPage-JFQ3Y2GH.js +1 -0
- package/dist/ui/EditDataSourcePage-WHPZDMD4.js +1 -0
- package/dist/ui/EditReleasePage-QKWHIURV.js +1 -0
- package/dist/ui/EmbeddedAIPage-SQLOV4IT.js +1 -0
- package/dist/ui/EmbeddedDocumentPage-6FYHUHTE.js +1 -0
- package/dist/ui/{ImageEditorDialog-GGS4VFEA.js → ImageEditorDialog-Q44YTLB5.js} +1 -1
- package/dist/ui/LogsPage-RC5O722W.js +1 -0
- package/dist/ui/NewDataSourcePage-QXW54CHQ.js +1 -0
- package/dist/ui/NewReleasePage-TKYWVTOH.js +1 -0
- package/dist/ui/{NotFoundPage-WXEZQZK6.js → NotFoundPage-RMNPVSD3.js} +1 -1
- package/dist/ui/ProjectPage-VY4EJX6J.js +1 -0
- package/dist/ui/ReleasePage-VZAL4KUI.js +1 -0
- package/dist/ui/ReleasesPage-A4BLUNJE.js +1 -0
- package/dist/ui/SettingsPage-MP3YHQDP.js +1 -0
- package/dist/ui/{SidebarToolsPage-API53RRR.js → SidebarToolsPage-S5DMIEQ6.js} +1 -1
- package/dist/ui/TaskPage-LCBDMA4I.js +1 -0
- package/dist/ui/TasksPage-72KVSUQY.js +1 -0
- package/dist/ui/TranslationsArbPage-B7TIRREV.js +2 -0
- package/dist/ui/TranslationsEditPage-VQG4YHA6.js +1 -0
- package/dist/ui/{TranslationsPage-NGGZB4L6.js → TranslationsPage-6VCVMNXI.js} +1 -1
- package/dist/ui/ai-tools-7S4URTMT.js +1 -0
- package/dist/ui/chunk-2PDMFZBF.js +1 -0
- package/dist/ui/{chunk-NSI5JSVV.js → chunk-3WQCVKK4.js} +1 -1
- package/dist/ui/{chunk-FXIR2RBE.js → chunk-4N6CPIOQ.js} +1 -1
- package/dist/ui/{chunk-NJMNOITA.js → chunk-5SY2SVEX.js} +1 -1
- package/dist/ui/chunk-62VPTZJL.js +1 -0
- package/dist/ui/{chunk-GT5EQSEX.js → chunk-6P75AZ3B.js} +1 -1
- package/dist/ui/{chunk-CAY5NM4Z.js → chunk-74OB6FM4.js} +1 -1
- package/dist/ui/{chunk-DIFPCF7I.js → chunk-76PG55CM.js} +1 -1
- package/dist/ui/chunk-AAR7DBEN.js +110 -0
- package/dist/ui/{chunk-JS6TK4OP.js → chunk-ATTW36EM.js} +1 -1
- package/dist/ui/chunk-AZ2SK2ZK.js +1 -0
- package/dist/ui/chunk-CSUKZZK5.js +1 -0
- package/dist/ui/chunk-DZYYWCHX.js +3 -0
- package/dist/ui/{chunk-NSOTG64N.js → chunk-EIMRPYCD.js} +1 -1
- package/dist/ui/{chunk-HARRKJFZ.js → chunk-ELKXEMOU.js} +1 -1
- package/dist/ui/{chunk-LROR6N3B.js → chunk-ETPZ3TXX.js} +2 -2
- package/dist/ui/{chunk-LROR6N3B.js.LEGAL.txt → chunk-ETPZ3TXX.js.LEGAL.txt} +1 -0
- package/dist/ui/{chunk-J5E457OI.js → chunk-EYVQCOAD.js} +1 -1
- package/dist/ui/{chunk-T77VOWHP.js → chunk-F4F5MH7A.js} +1 -1
- package/dist/ui/{chunk-UPDL6UXI.js → chunk-FF4CLAZS.js} +1 -1
- package/dist/ui/{chunk-W6LP22IK.js → chunk-GCMX5V3P.js} +1 -1
- package/dist/ui/{chunk-FJLNXXE3.js → chunk-H3PYPGNR.js} +1 -1
- package/dist/ui/{chunk-WFQU7V3W.js → chunk-HVYNDEA7.js} +1 -1
- package/dist/ui/{chunk-TVYAZN2Q.js → chunk-IMXZTYAA.js} +1 -1
- package/dist/ui/{chunk-PPYM4O5N.js → chunk-J5KDO4RM.js} +1 -1
- package/dist/ui/chunk-JSWRHHTZ.js +1 -0
- package/dist/ui/{chunk-MSYTAJBV.js → chunk-K3UM5OQF.js} +1 -1
- package/dist/ui/{chunk-BW2MKGNP.js → chunk-KBJ4AK5O.js} +1 -1
- package/dist/ui/{chunk-4FGASIIX.js → chunk-L3O3CLWO.js} +1 -1
- package/dist/ui/{chunk-JTIQBW33.js → chunk-LC6WLVRS.js} +1 -1
- package/dist/ui/{chunk-WYJJZTCI.js → chunk-MAZJRNAL.js} +1 -1
- package/dist/ui/{chunk-3E6CASIN.js → chunk-MYKJ6VAM.js} +1 -1
- package/dist/ui/chunk-NEA3XU6N.js +19 -0
- package/dist/ui/{chunk-UUVAD4U4.js → chunk-PR7NB5VK.js} +1 -1
- package/dist/ui/{chunk-QF3GPZTI.js → chunk-QHOZXW7Y.js} +1 -1
- package/dist/ui/chunk-RN4XVVWD.js +1 -0
- package/dist/ui/{chunk-DS27MO63.js → chunk-SLLTYDVX.js} +1 -1
- package/dist/ui/chunk-SWTNOWNE.js +1 -0
- package/dist/ui/{chunk-BOER3EAW.js → chunk-SYDHFYRC.js} +1 -1
- package/dist/ui/{chunk-ZMS6NIGG.js → chunk-UHK5N75L.js} +1 -1
- package/dist/ui/chunk-V3G3554T.js +31 -0
- package/dist/ui/chunk-VB5VMEN7.js +1 -0
- package/dist/ui/{chunk-2EPQJUGM.js → chunk-VRRRFAIV.js} +1 -1
- package/dist/ui/{chunk-Y3MCFLQU.js → chunk-W2ZAVRAT.js} +53 -53
- package/dist/ui/{chunk-P2Z66DHV.js → chunk-W3ICOHMF.js} +1 -1
- package/dist/ui/{chunk-ENQKSLGJ.js → chunk-WAT22UHZ.js} +1 -1
- package/dist/ui/{chunk-G5ZZDHWA.js → chunk-WN7HUDGY.js} +1 -1
- package/dist/ui/{chunk-BUNR5EU3.js → chunk-WS37MDM3.js} +1 -1
- package/dist/ui/chunk-XACLTYBB.js +5 -0
- package/dist/ui/chunk-XJUQOX4E.js +1 -0
- package/dist/ui/{chunk-QHJOTXYB.js → chunk-XM35ZJBQ.js} +1 -1
- package/dist/ui/{chunk-VD6G5NV4.js → chunk-XOWWPE7X.js} +12 -12
- package/dist/ui/chunk-YV5SGR24.js +1 -0
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-4G7S2LJH.js +0 -1411
- package/dist/chunk-AJDMNTZQ.js +0 -187
- package/dist/ui/AIPage-VOTAEVJE.js +0 -1
- package/dist/ui/AssetsPage-EWGRMRPJ.js +0 -1
- package/dist/ui/CollectionPage-N5JIAUDS.js +0 -1
- package/dist/ui/ComparePage-JDPVU2VA.js +0 -1
- package/dist/ui/DataPage-37D4CMLZ.js +0 -1
- package/dist/ui/DataSourcePage-LX5ASYQN.js +0 -1
- package/dist/ui/DocTranslationsPage-V5M4KB7X.js +0 -1
- package/dist/ui/DocumentPage-SURTQJPG.js +0 -1
- package/dist/ui/EditDataSourcePage-O7DOV5LX.js +0 -1
- package/dist/ui/EditReleasePage-NDYTKGXY.js +0 -1
- package/dist/ui/EmbeddedAIPage-RJOUIPF2.js +0 -1
- package/dist/ui/EmbeddedDocumentPage-SLEULNNI.js +0 -1
- package/dist/ui/LogsPage-ZGBP3I35.js +0 -1
- package/dist/ui/NewDataSourcePage-GQONTMP4.js +0 -1
- package/dist/ui/NewReleasePage-7ZKXHDTD.js +0 -1
- package/dist/ui/ProjectPage-F7W7X6YO.js +0 -1
- package/dist/ui/ReleasePage-ZKMNYPML.js +0 -1
- package/dist/ui/ReleasesPage-JHCE6AEJ.js +0 -1
- package/dist/ui/SettingsPage-MWCPZPGE.js +0 -1
- package/dist/ui/TaskPage-73NLAR4J.js +0 -1
- package/dist/ui/TasksPage-IKPW2OQO.js +0 -1
- package/dist/ui/TranslationsArbPage-QVN66DIW.js +0 -2
- package/dist/ui/TranslationsEditPage-ZOELF5PK.js +0 -1
- package/dist/ui/ai-tools-4DWMFQS6.js +0 -1
- package/dist/ui/chunk-2RAVKX2Z.js +0 -19
- package/dist/ui/chunk-344GZOGG.js +0 -84
- package/dist/ui/chunk-4ISFTE2G.js +0 -5
- package/dist/ui/chunk-BT2NTAW2.js +0 -1
- package/dist/ui/chunk-C2BLMTPR.js +0 -3
- package/dist/ui/chunk-D3UIZHRK.js +0 -1
- package/dist/ui/chunk-HO3W4RVG.js +0 -1
- package/dist/ui/chunk-IOR72US3.js +0 -1
- package/dist/ui/chunk-KRNQ6L35.js +0 -1
- package/dist/ui/chunk-N3JSLTMV.js +0 -1
- package/dist/ui/chunk-TOGLZDK3.js +0 -1
- package/dist/ui/chunk-TPJCPCRR.js +0 -1
- package/dist/ui/chunk-VPHBKJ3I.js +0 -1
- package/dist/ui/chunk-YUU6LWQK.js +0 -1
- package/dist/ui/chunk-Z6DBYLIM.js +0 -1
- /package/dist/ui/{ImageEditorDialog-GGS4VFEA.js.LEGAL.txt → ImageEditorDialog-Q44YTLB5.js.LEGAL.txt} +0 -0
- /package/dist/ui/{chunk-NJMNOITA.js.LEGAL.txt → chunk-5SY2SVEX.js.LEGAL.txt} +0 -0
- /package/dist/ui/{chunk-Y3MCFLQU.js.LEGAL.txt → chunk-W2ZAVRAT.js.LEGAL.txt} +0 -0
- /package/dist/ui/{chunk-VD6G5NV4.js.LEGAL.txt → chunk-XOWWPE7X.js.LEGAL.txt} +0 -0
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
+
RootCMSClient,
|
|
2
3
|
getCmsPlugin
|
|
3
4
|
} from "./chunk-AFBSITEO.js";
|
|
4
5
|
|
|
6
|
+
// core/cron.ts
|
|
7
|
+
import { Timestamp as Timestamp3 } from "firebase-admin/firestore";
|
|
8
|
+
|
|
5
9
|
// core/search-index.ts
|
|
6
10
|
import fs from "node:fs";
|
|
7
11
|
import path from "node:path";
|
|
@@ -673,8 +677,8 @@ var SearchIndexService = class {
|
|
|
673
677
|
async listAllDocs(collectionIds) {
|
|
674
678
|
const all = [];
|
|
675
679
|
for (const collectionId of collectionIds) {
|
|
676
|
-
const
|
|
677
|
-
const snap = await this.db.collection(
|
|
680
|
+
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
681
|
+
const snap = await this.db.collection(path3).get();
|
|
678
682
|
snap.forEach((d) => {
|
|
679
683
|
const docId = `${collectionId}/${d.id}`;
|
|
680
684
|
if (!this.isDocIndexable(docId)) {
|
|
@@ -696,8 +700,8 @@ var SearchIndexService = class {
|
|
|
696
700
|
async listChangedDocs(collectionIds, lastRun) {
|
|
697
701
|
const all = [];
|
|
698
702
|
for (const collectionId of collectionIds) {
|
|
699
|
-
const
|
|
700
|
-
const q = this.db.collection(
|
|
703
|
+
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
704
|
+
const q = this.db.collection(path3).where("sys.modifiedAt", ">=", lastRun);
|
|
701
705
|
const snap = await q.get();
|
|
702
706
|
snap.forEach((d) => {
|
|
703
707
|
const docId = `${collectionId}/${d.id}`;
|
|
@@ -721,8 +725,8 @@ var SearchIndexService = class {
|
|
|
721
725
|
async hasChangesSince(lastRun, docMap = null) {
|
|
722
726
|
const collectionIds = await this.listCollectionIds();
|
|
723
727
|
for (const collectionId of collectionIds) {
|
|
724
|
-
const
|
|
725
|
-
const q = this.db.collection(
|
|
728
|
+
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
729
|
+
const q = this.db.collection(path3).where("sys.modifiedAt", ">=", lastRun).limit(1);
|
|
726
730
|
const snap = await q.get();
|
|
727
731
|
if (!snap.empty) {
|
|
728
732
|
return true;
|
|
@@ -735,8 +739,8 @@ var SearchIndexService = class {
|
|
|
735
739
|
}
|
|
736
740
|
const liveIds = /* @__PURE__ */ new Set();
|
|
737
741
|
for (const collectionId of collectionIds) {
|
|
738
|
-
const
|
|
739
|
-
const snap = await this.db.collection(
|
|
742
|
+
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
743
|
+
const snap = await this.db.collection(path3).select().get();
|
|
740
744
|
snap.forEach((d) => {
|
|
741
745
|
const docId = `${collectionId}/${d.id}`;
|
|
742
746
|
if (this.isDocIndexable(docId)) {
|
|
@@ -803,8 +807,8 @@ var SearchIndexService = class {
|
|
|
803
807
|
);
|
|
804
808
|
continue;
|
|
805
809
|
}
|
|
806
|
-
const
|
|
807
|
-
const snap = await this.db.collection(
|
|
810
|
+
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
811
|
+
const snap = await this.db.collection(path3).get();
|
|
808
812
|
snap.forEach((d) => {
|
|
809
813
|
const data = d.data() || {};
|
|
810
814
|
const slug = data.slug || d.id;
|
|
@@ -880,8 +884,8 @@ var SearchIndexService = class {
|
|
|
880
884
|
}
|
|
881
885
|
const liveIds = /* @__PURE__ */ new Set();
|
|
882
886
|
for (const collectionId of collectionIds) {
|
|
883
|
-
const
|
|
884
|
-
const snap = await this.db.collection(
|
|
887
|
+
const path3 = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
888
|
+
const snap = await this.db.collection(path3).select().get();
|
|
885
889
|
snap.forEach((d) => {
|
|
886
890
|
const docId = `${collectionId}/${d.id}`;
|
|
887
891
|
if (this.isDocIndexable(docId)) {
|
|
@@ -997,6 +1001,180 @@ var SearchIndexService = class {
|
|
|
997
1001
|
}
|
|
998
1002
|
};
|
|
999
1003
|
|
|
1004
|
+
// core/versions.ts
|
|
1005
|
+
import fs2 from "node:fs";
|
|
1006
|
+
import path2 from "node:path";
|
|
1007
|
+
import { Timestamp as Timestamp2 } from "firebase-admin/firestore";
|
|
1008
|
+
import glob2 from "tiny-glob";
|
|
1009
|
+
var DOCUMENT_SAVE_OFFSET = 5 * 60 * 1e3;
|
|
1010
|
+
var VersionsService = class {
|
|
1011
|
+
constructor(rootConfig) {
|
|
1012
|
+
this.rootConfig = rootConfig;
|
|
1013
|
+
const cmsPlugin = getCmsPlugin(rootConfig);
|
|
1014
|
+
const cmsPluginOptions = cmsPlugin.getConfig();
|
|
1015
|
+
const projectId = cmsPluginOptions.id || "default";
|
|
1016
|
+
this.projectId = projectId;
|
|
1017
|
+
this.db = cmsPlugin.getFirestore();
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Saves a version of all documents that have been edited since the last run.
|
|
1021
|
+
*/
|
|
1022
|
+
async saveVersions() {
|
|
1023
|
+
const lastRun = await this.getLastRun();
|
|
1024
|
+
const lastRunWithOffset = lastRun === 0 ? lastRun : lastRun - DOCUMENT_SAVE_OFFSET;
|
|
1025
|
+
const changedDocs = await this.getDocsModifiedAfter(lastRunWithOffset);
|
|
1026
|
+
const now = Timestamp2.now().toMillis();
|
|
1027
|
+
const versions = changedDocs.filter((doc) => {
|
|
1028
|
+
const modifiedAt = doc.sys.modifiedAt.toMillis();
|
|
1029
|
+
if (modifiedAt > now - DOCUMENT_SAVE_OFFSET) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
const publishedAt = doc.sys.publishedAt?.toMillis?.();
|
|
1033
|
+
if (publishedAt && Math.abs(publishedAt - modifiedAt) < 5e3) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
return true;
|
|
1037
|
+
});
|
|
1038
|
+
if (versions.length > 0) {
|
|
1039
|
+
this.saveVersionsToFirestore(versions);
|
|
1040
|
+
}
|
|
1041
|
+
this.saveLastRun(now);
|
|
1042
|
+
}
|
|
1043
|
+
async saveVersionsToFirestore(versions) {
|
|
1044
|
+
const batch = this.db.batch();
|
|
1045
|
+
versions.forEach((version) => {
|
|
1046
|
+
if (!version.collection || !version.slug || !version.sys?.modifiedAt) {
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
const modifiedAtMillis = version.sys.modifiedAt.toMillis();
|
|
1050
|
+
const versionPath = `Projects/${this.projectId}/Collections/${version.collection}/Drafts/${version.slug}/Versions/${modifiedAtMillis}`;
|
|
1051
|
+
console.log(versionPath);
|
|
1052
|
+
const versionRef = this.db.doc(versionPath);
|
|
1053
|
+
batch.set(versionRef, version);
|
|
1054
|
+
});
|
|
1055
|
+
await batch.commit();
|
|
1056
|
+
console.log(`versions: saved ${versions.length} versions`);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Returns the last time (in millis) saveVersions() was run, or 0 if has
|
|
1060
|
+
* never been run.
|
|
1061
|
+
*/
|
|
1062
|
+
async getLastRun() {
|
|
1063
|
+
const projectDocRef = this.db.collection("Projects").doc(this.projectId);
|
|
1064
|
+
const projectDoc = await projectDocRef.get();
|
|
1065
|
+
if (projectDoc.exists) {
|
|
1066
|
+
const data = projectDoc.data() || {};
|
|
1067
|
+
const ts = data.versionsLastRun;
|
|
1068
|
+
if (ts) {
|
|
1069
|
+
return ts.toMillis();
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return 0;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Saves {versionLastRun: <timestamp>} to the Projects/<projectId> doc.
|
|
1076
|
+
*/
|
|
1077
|
+
async saveLastRun(millis) {
|
|
1078
|
+
const ts = Timestamp2.fromMillis(millis);
|
|
1079
|
+
const projectDocRef = this.db.collection("Projects").doc(this.projectId);
|
|
1080
|
+
await projectDocRef.set({ versionsLastRun: ts }, { merge: true });
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Returns a list of all docs that were edited after a certain time.
|
|
1084
|
+
*/
|
|
1085
|
+
async getDocsModifiedAfter(millis) {
|
|
1086
|
+
const ts = Timestamp2.fromMillis(millis);
|
|
1087
|
+
const results = [];
|
|
1088
|
+
const collectionIds = await this.listCollections();
|
|
1089
|
+
for (const collectionId of collectionIds) {
|
|
1090
|
+
const collectionPath = `Projects/${this.projectId}/Collections/${collectionId}/Drafts`;
|
|
1091
|
+
const query = this.db.collection(collectionPath).where("sys.modifiedAt", ">=", ts);
|
|
1092
|
+
const querySnapshot = await query.get();
|
|
1093
|
+
querySnapshot.forEach((doc) => {
|
|
1094
|
+
results.push(doc.data());
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
return results;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* Returns a list of collection ids for the Root project.
|
|
1101
|
+
*/
|
|
1102
|
+
async listCollections() {
|
|
1103
|
+
const collectionsDir = path2.join(this.rootConfig.rootDir, "collections");
|
|
1104
|
+
if (!fs2.existsSync(collectionsDir)) {
|
|
1105
|
+
return [];
|
|
1106
|
+
}
|
|
1107
|
+
const collectionIds = [];
|
|
1108
|
+
const collectionFileNames = await glob2("*.schema.ts", {
|
|
1109
|
+
cwd: collectionsDir
|
|
1110
|
+
});
|
|
1111
|
+
collectionFileNames.forEach((filename) => {
|
|
1112
|
+
collectionIds.push(filename.slice(0, -10));
|
|
1113
|
+
});
|
|
1114
|
+
return collectionIds;
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
// core/cron.ts
|
|
1119
|
+
var SEARCH_INDEX_MIN_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1120
|
+
async function runCronJobs(rootConfig, options = {}) {
|
|
1121
|
+
await Promise.all([
|
|
1122
|
+
runCronJob("publishScheduledDocs", runPublishScheduledDocs, rootConfig),
|
|
1123
|
+
runCronJob(
|
|
1124
|
+
"syncScheduledDataSources",
|
|
1125
|
+
runSyncScheduledDataSources,
|
|
1126
|
+
rootConfig
|
|
1127
|
+
),
|
|
1128
|
+
runCronJob("saveVersions", runSaveVersions, rootConfig),
|
|
1129
|
+
runCronJob(
|
|
1130
|
+
"incrementalSearchIndex",
|
|
1131
|
+
runIncrementalSearchIndex,
|
|
1132
|
+
rootConfig,
|
|
1133
|
+
options.loadSchema
|
|
1134
|
+
)
|
|
1135
|
+
]);
|
|
1136
|
+
}
|
|
1137
|
+
async function runCronJob(name, fn, ...args) {
|
|
1138
|
+
try {
|
|
1139
|
+
await fn(...args);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
console.log(`cron failed: ${name}`);
|
|
1142
|
+
console.error(String(err.stack || err));
|
|
1143
|
+
throw err;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async function runPublishScheduledDocs(rootConfig) {
|
|
1147
|
+
const cmsClient = new RootCMSClient(rootConfig);
|
|
1148
|
+
await cmsClient.publishScheduledDocs();
|
|
1149
|
+
await cmsClient.publishScheduledReleases();
|
|
1150
|
+
}
|
|
1151
|
+
async function runSyncScheduledDataSources(rootConfig) {
|
|
1152
|
+
const cmsClient = new RootCMSClient(rootConfig);
|
|
1153
|
+
await cmsClient.syncScheduledDataSources();
|
|
1154
|
+
}
|
|
1155
|
+
async function runSaveVersions(rootConfig) {
|
|
1156
|
+
const service = new VersionsService(rootConfig);
|
|
1157
|
+
await service.saveVersions();
|
|
1158
|
+
}
|
|
1159
|
+
async function runIncrementalSearchIndex(rootConfig, loadSchema) {
|
|
1160
|
+
const service = new SearchIndexService(rootConfig, loadSchema);
|
|
1161
|
+
const status = await service.getStatus();
|
|
1162
|
+
if (status.lastRun !== null) {
|
|
1163
|
+
const elapsed = Date.now() - status.lastRun;
|
|
1164
|
+
if (elapsed < SEARCH_INDEX_MIN_INTERVAL_MS) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const hasChanges = await service.hasChangesSince(
|
|
1168
|
+
Timestamp3.fromMillis(status.lastRun)
|
|
1169
|
+
);
|
|
1170
|
+
if (!hasChanges) {
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
await service.rebuildIndex({ force: false });
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1000
1177
|
export {
|
|
1001
|
-
SearchIndexService
|
|
1178
|
+
SearchIndexService,
|
|
1179
|
+
runCronJobs
|
|
1002
1180
|
};
|
package/dist/core/ai-tools.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in CMS tools exposed to the chat model on the `/cms/ai` page.
|
|
3
3
|
*
|
|
4
|
-
* Read tools (`doc_get`, `docs_list`, etc.) execute
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Read tools (`doc_get`, `docs_list`, etc.) run their `execute` against a
|
|
5
|
+
* pluggable `CmsToolReadBackend`. The `/cms/ai` chat now streams directly from
|
|
6
|
+
* the browser, so the client supplies a Firebase-web-SDK backend (see
|
|
7
|
+
* `ui/components/RootAIChat/clientCmsTools.ts`). The factory itself is
|
|
8
|
+
* data-source agnostic.
|
|
9
9
|
*
|
|
10
10
|
* Write tools (`doc_set`, `doc_create`, `doc_updateField`, `doc_duplicate`)
|
|
11
11
|
* stay schema-only here — the browser executes them via `onToolCall` so the
|
|
@@ -15,16 +15,12 @@
|
|
|
15
15
|
* Validation helpers (`validateFields`, `validateValueAtPath`) live here
|
|
16
16
|
* too so both browser and server can share the same checks.
|
|
17
17
|
*
|
|
18
|
-
* Browser-import safety: this module is
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* are referenced as types only; the actual instance is passed in at call
|
|
22
|
-
* time via `CmsToolContext`.
|
|
18
|
+
* Browser-import safety: this module is imported by the browser bundle, so it
|
|
19
|
+
* must not pull server-only modules (`firebase-admin`, `node:*`) into runtime
|
|
20
|
+
* imports. The backend implementation is injected at call time.
|
|
23
21
|
*/
|
|
24
|
-
import type { RootConfig } from '@blinkk/root';
|
|
25
22
|
import { ToolSet } from 'ai';
|
|
26
|
-
import
|
|
27
|
-
import { Collection } from './schema.js';
|
|
23
|
+
import { Collection, Field } from './schema.js';
|
|
28
24
|
import { validateFields } from './validation.js';
|
|
29
25
|
/** Tool ids handled client-side. Kept in sync with the schemas below. */
|
|
30
26
|
export declare const CMS_TOOL_NAMES: readonly ["collections_list", "docs_list", "doc_get", "doc_getVersion", "doc_set", "doc_create", "doc_updateField", "doc_edit", "doc_duplicate", "doc_listVersions", "doc_translateField", "schema_get"];
|
|
@@ -35,48 +31,83 @@ export type CmsToolName = (typeof CMS_TOOL_NAMES)[number];
|
|
|
35
31
|
* and the model must not write to Firestore directly.
|
|
36
32
|
*/
|
|
37
33
|
export declare const READ_ONLY_CMS_TOOL_NAMES: readonly CmsToolName[];
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
34
|
+
/** A CMS document shaped for the model (fields/sys already unmarshalled). */
|
|
35
|
+
export interface CmsToolDoc {
|
|
36
|
+
id: string;
|
|
37
|
+
collection: string;
|
|
38
|
+
slug: string;
|
|
39
|
+
sys: Record<string, unknown>;
|
|
40
|
+
fields: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/** Lightweight doc summary returned by `docs_list`. */
|
|
43
|
+
export interface CmsToolDocSummary {
|
|
44
|
+
id: string;
|
|
45
|
+
slug: string;
|
|
46
|
+
sys: Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
/** Version summary returned by `doc_listVersions`. */
|
|
49
|
+
export interface CmsToolVersionSummary {
|
|
50
|
+
versionId: string;
|
|
51
|
+
sys: Record<string, unknown>;
|
|
52
|
+
tags: string[];
|
|
53
|
+
publishMessage?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Collection summary returned by `collections_list`. */
|
|
56
|
+
export interface CmsToolCollectionSummary {
|
|
57
|
+
id: string;
|
|
58
|
+
name?: string;
|
|
59
|
+
description?: string;
|
|
47
60
|
}
|
|
48
61
|
/**
|
|
49
|
-
*
|
|
50
|
-
* the
|
|
51
|
-
*
|
|
62
|
+
* Data-source backing the read tools. Implemented server-side (Firebase Admin)
|
|
63
|
+
* or in the browser (Firebase web SDK — see `clientCmsTools.ts`). The factory
|
|
64
|
+
* is agnostic to where the reads run.
|
|
65
|
+
*
|
|
66
|
+
* Implementations own docId parsing and unmarshalling so the tool `execute`
|
|
67
|
+
* blocks stay thin and the returned shapes are model-ready.
|
|
52
68
|
*/
|
|
53
|
-
export interface
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
export interface CmsToolReadBackend {
|
|
70
|
+
/** Lists all collections with optional name/description metadata. */
|
|
71
|
+
listCollections(): Promise<CmsToolCollectionSummary[]>;
|
|
72
|
+
/** Lists docs in a collection (draft or published). */
|
|
73
|
+
listDocs(collectionId: string, options: {
|
|
74
|
+
mode: 'draft' | 'published';
|
|
75
|
+
limit: number;
|
|
76
|
+
}): Promise<CmsToolDocSummary[]>;
|
|
77
|
+
/** Reads a single doc, or `null` if it does not exist. */
|
|
78
|
+
getDoc(docId: string, options: {
|
|
79
|
+
mode: 'draft' | 'published';
|
|
80
|
+
}): Promise<CmsToolDoc | null>;
|
|
81
|
+
/**
|
|
82
|
+
* Reads a specific version of a doc. `versionId` is "draft", "published" or
|
|
83
|
+
* a numeric timestamp. Returns `null` if it does not exist.
|
|
84
|
+
*/
|
|
85
|
+
getDocVersion(docId: string, versionId: string): Promise<CmsToolDoc | null>;
|
|
86
|
+
/** Lists a doc's version history, most recent first. */
|
|
87
|
+
listVersions(docId: string, options: {
|
|
60
88
|
limit: number;
|
|
61
|
-
})
|
|
89
|
+
}): Promise<CmsToolVersionSummary[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Returns the simplified field definitions for a collection (see
|
|
92
|
+
* `simplifyFields`), or `null` if the collection is unknown.
|
|
93
|
+
*/
|
|
94
|
+
getSchemaFields(collectionId: string): Promise<unknown[] | null>;
|
|
62
95
|
}
|
|
63
96
|
/**
|
|
64
97
|
* Returns a `ToolSet` filtered to only the read-only CMS tools. Use this in
|
|
65
98
|
* flows where the AI assists with proposing changes that the user reviews
|
|
66
99
|
* and saves manually (so the model can read context but cannot mutate data).
|
|
67
100
|
*/
|
|
68
|
-
export declare function createReadOnlyCmsTools(
|
|
101
|
+
export declare function createReadOnlyCmsTools(backend: CmsToolReadBackend): ToolSet;
|
|
69
102
|
/**
|
|
70
103
|
* Builds the full tool set advertised to the model.
|
|
71
104
|
*
|
|
72
105
|
* Read tools (`collections_list`, `docs_list`, `docs_search`, `doc_get`,
|
|
73
|
-
* `doc_getVersion`, `doc_listVersions`, `schema_get`) carry
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* browser
|
|
77
|
-
*
|
|
78
|
-
* executes them via `onToolCall` so the user can approve diffs before
|
|
79
|
-
* persistence.
|
|
106
|
+
* `doc_getVersion`, `doc_listVersions`, `schema_get`) carry an `execute` that
|
|
107
|
+
* delegates to the supplied `CmsToolReadBackend`. Write tools (`doc_set`,
|
|
108
|
+
* `doc_create`, `doc_updateField`, `doc_duplicate`, `doc_translateField`)
|
|
109
|
+
* remain schema-only — the browser executes them via `onToolCall` so the user
|
|
110
|
+
* can approve diffs before persistence.
|
|
80
111
|
*
|
|
81
112
|
* Safety policy: this tool set is intentionally read + draft-write only.
|
|
82
113
|
* The following operations are deliberately NOT exposed to the model
|
|
@@ -97,7 +128,16 @@ export declare function createReadOnlyCmsTools(ctx: CmsToolContext): ToolSet;
|
|
|
97
128
|
* If you add new write tools here, keep them limited to draft-mode edits
|
|
98
129
|
* the user can easily review before publishing.
|
|
99
130
|
*/
|
|
100
|
-
export declare function createCmsTools(
|
|
131
|
+
export declare function createCmsTools(backend: CmsToolReadBackend): ToolSet;
|
|
132
|
+
/**
|
|
133
|
+
* Reduces a collection's field definitions down to the bits the model needs
|
|
134
|
+
* (id, type, label, options, nested shape). Drops Root-internal metadata
|
|
135
|
+
* (preview, conditional visibility, default UI widget hints, etc.) since
|
|
136
|
+
* those would bloat the model context without adding decision-useful info.
|
|
137
|
+
*
|
|
138
|
+
* Exported so the browser backend can shape `schema_get` results the same way.
|
|
139
|
+
*/
|
|
140
|
+
export declare function simplifyFields(fields: Field[]): unknown[];
|
|
101
141
|
/**
|
|
102
142
|
* Validates `value` against the field schema located at `path` within
|
|
103
143
|
* `collection`. Returns validation errors if the field can be resolved and
|