@blinkk/root-cms 2.4.2 → 2.4.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 +15 -4
- package/dist/{client-DK623LL2.d.ts → client-DYO2WWlf.d.ts} +13 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +3 -0
- package/dist/core.d.ts +3 -3
- package/dist/core.js +3 -0
- package/dist/functions.js +3 -0
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +349 -240
- package/dist/project.d.ts +1 -1
- package/dist/richtext.d.ts +25 -1
- package/dist/richtext.js +31 -0
- package/dist/{schema-sKTRRRSq.d.ts → schema-BxPmMeRc.d.ts} +9 -0
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +327 -178
- package/dist/ui/ui.js.LEGAL.txt +194 -191
- package/package.json +15 -5
package/dist/plugin.js
CHANGED
|
@@ -45,6 +45,287 @@ var require_altText = __commonJS({
|
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
// core/ai.ts
|
|
49
|
+
var ai_exports = {};
|
|
50
|
+
__export(ai_exports, {
|
|
51
|
+
Chat: () => Chat,
|
|
52
|
+
ChatClient: () => ChatClient,
|
|
53
|
+
generateImage: () => generateImage,
|
|
54
|
+
summarizeDiff: () => summarizeDiff
|
|
55
|
+
});
|
|
56
|
+
import crypto from "crypto";
|
|
57
|
+
import fs from "fs";
|
|
58
|
+
import path from "path";
|
|
59
|
+
import { vertexAI } from "@genkit-ai/vertexai";
|
|
60
|
+
import { Timestamp } from "firebase-admin/firestore";
|
|
61
|
+
import { genkit } from "genkit";
|
|
62
|
+
import { logger } from "genkit/logging";
|
|
63
|
+
async function summarizeDiff(cmsClient, options) {
|
|
64
|
+
const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
|
|
65
|
+
const firebaseConfig = cmsPluginOptions.firebaseConfig;
|
|
66
|
+
const model = (typeof cmsPluginOptions.experiments?.ai === "object" ? cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
|
|
67
|
+
const ai = genkit({
|
|
68
|
+
plugins: [
|
|
69
|
+
vertexAI({
|
|
70
|
+
projectId: firebaseConfig.projectId,
|
|
71
|
+
location: firebaseConfig.location || "us-central1"
|
|
72
|
+
})
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
const beforeJson = JSON.stringify(options.before ?? null, null, 2);
|
|
76
|
+
const afterJson = JSON.stringify(options.after ?? null, null, 2);
|
|
77
|
+
const systemPrompt = [
|
|
78
|
+
"You are an assistant that summarizes changes made to CMS documents stored as JSON.",
|
|
79
|
+
"Provide a concise description of the most important updates using short bullet points.",
|
|
80
|
+
'If there are no meaningful differences, respond with "No significant changes."',
|
|
81
|
+
`Focus on just the content changes, ignore insignificant changes to richtext blocks and structure, such as updates to the richtext block's "timestamp" and "version" fields.`
|
|
82
|
+
].join("\n");
|
|
83
|
+
const diffPrompt = [
|
|
84
|
+
"Previous version JSON:",
|
|
85
|
+
"```json",
|
|
86
|
+
beforeJson,
|
|
87
|
+
"```",
|
|
88
|
+
"",
|
|
89
|
+
"Updated version JSON:",
|
|
90
|
+
"```json",
|
|
91
|
+
afterJson,
|
|
92
|
+
"```",
|
|
93
|
+
"",
|
|
94
|
+
"Summarize the differences between the two payloads."
|
|
95
|
+
].join("\n");
|
|
96
|
+
const res = await ai.generate({
|
|
97
|
+
model,
|
|
98
|
+
messages: [
|
|
99
|
+
{
|
|
100
|
+
role: "system",
|
|
101
|
+
content: [{ text: systemPrompt }]
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
prompt: [{ text: diffPrompt }]
|
|
105
|
+
});
|
|
106
|
+
return res.text?.trim() || "";
|
|
107
|
+
}
|
|
108
|
+
async function generateImage(cmsClient, options) {
|
|
109
|
+
const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
|
|
110
|
+
const firebaseConfig = cmsPluginOptions.firebaseConfig;
|
|
111
|
+
const model = "vertexai/gemini-2.5-flash-image";
|
|
112
|
+
const ai = genkit({
|
|
113
|
+
plugins: [
|
|
114
|
+
vertexAI({
|
|
115
|
+
projectId: firebaseConfig.projectId,
|
|
116
|
+
location: firebaseConfig.location || "us-central1"
|
|
117
|
+
})
|
|
118
|
+
]
|
|
119
|
+
});
|
|
120
|
+
const res = await ai.generate({
|
|
121
|
+
model,
|
|
122
|
+
prompt: [
|
|
123
|
+
{
|
|
124
|
+
text: options.prompt
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
config: {
|
|
128
|
+
imageConfig: {
|
|
129
|
+
aspectRatio: options.aspectRatio
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
output: {
|
|
133
|
+
format: "media"
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
if (!res.media) {
|
|
137
|
+
throw new Error("No image generated");
|
|
138
|
+
}
|
|
139
|
+
return res.media.url;
|
|
140
|
+
}
|
|
141
|
+
var DEFAULT_MODEL, Chat, ChatClient;
|
|
142
|
+
var init_ai = __esm({
|
|
143
|
+
"core/ai.ts"() {
|
|
144
|
+
"use strict";
|
|
145
|
+
logger.setLogLevel("warn");
|
|
146
|
+
DEFAULT_MODEL = "vertexai/gemini-2.5-flash";
|
|
147
|
+
Chat = class {
|
|
148
|
+
constructor(chatClient, id, options) {
|
|
149
|
+
this.chatClient = chatClient;
|
|
150
|
+
this.cmsClient = chatClient.cmsClient;
|
|
151
|
+
this.cmsPluginOptions = this.cmsClient.cmsPlugin.getConfig();
|
|
152
|
+
this.id = id;
|
|
153
|
+
this.history = options?.history ?? [];
|
|
154
|
+
this.model = options?.model || (typeof this.cmsPluginOptions.experiments?.ai === "object" ? this.cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
|
|
155
|
+
const firebaseConfig = this.cmsPluginOptions.firebaseConfig;
|
|
156
|
+
this.ai = genkit({
|
|
157
|
+
plugins: [
|
|
158
|
+
vertexAI({
|
|
159
|
+
projectId: firebaseConfig.projectId,
|
|
160
|
+
location: firebaseConfig.location || "us-central1"
|
|
161
|
+
})
|
|
162
|
+
]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** Builds the messages for the AI request. */
|
|
166
|
+
async buildMessages(options) {
|
|
167
|
+
const messages = this.history;
|
|
168
|
+
const hasSystemPrompt = messages.some(
|
|
169
|
+
(msg) => msg.role === "system" && msg.content.length > 0
|
|
170
|
+
);
|
|
171
|
+
if (!hasSystemPrompt) {
|
|
172
|
+
messages.push({
|
|
173
|
+
role: "system",
|
|
174
|
+
content: [{ text: await this.buildSystemPrompt(options) }]
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (options.mode === "edit") {
|
|
178
|
+
messages.push({
|
|
179
|
+
role: "user",
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
text: [
|
|
183
|
+
"The JSON you must edit is:",
|
|
184
|
+
"",
|
|
185
|
+
JSON.stringify(options.editData || {}, null, 2)
|
|
186
|
+
].join("")
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
return messages;
|
|
192
|
+
}
|
|
193
|
+
/** Builds the request sent to the AI based on the `ChatMode`. */
|
|
194
|
+
async buildGenerateRequest(prompt, options) {
|
|
195
|
+
if (options.mode === "edit") {
|
|
196
|
+
return {
|
|
197
|
+
messages: await this.buildMessages(options),
|
|
198
|
+
model: this.model,
|
|
199
|
+
prompt
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
messages: await this.buildMessages(options),
|
|
204
|
+
model: this.model,
|
|
205
|
+
prompt
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** Sends the request to the AI and stores the history in the session and the database. */
|
|
209
|
+
async sendPrompt(prompt, options = {}) {
|
|
210
|
+
const chatRequest = await this.buildGenerateRequest(prompt, options);
|
|
211
|
+
const res = await this.ai.generate({
|
|
212
|
+
model: chatRequest.model,
|
|
213
|
+
messages: chatRequest.messages,
|
|
214
|
+
prompt: Array.isArray(prompt) ? prompt.flat() : prompt
|
|
215
|
+
});
|
|
216
|
+
this.history = res.messages;
|
|
217
|
+
await this.dbDoc().update({
|
|
218
|
+
history: this.history,
|
|
219
|
+
modifiedAt: Timestamp.now()
|
|
220
|
+
});
|
|
221
|
+
if (options.mode === "edit") {
|
|
222
|
+
const aiResponse = res.output;
|
|
223
|
+
if (aiResponse?.data && !aiResponse.message) {
|
|
224
|
+
aiResponse.message = "";
|
|
225
|
+
}
|
|
226
|
+
return aiResponse;
|
|
227
|
+
}
|
|
228
|
+
return { message: res.text, data: null };
|
|
229
|
+
}
|
|
230
|
+
dbDoc() {
|
|
231
|
+
return this.chatClient.dbCollection().doc(this.id);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Builds the system prompt sent to the AI, based on the `ChatMode` and
|
|
235
|
+
* supplied `SendPromptOptions`. `SendPromptOptions` may contain data or
|
|
236
|
+
* references to information needed to construct the prompt.
|
|
237
|
+
*/
|
|
238
|
+
async buildSystemPrompt(options) {
|
|
239
|
+
const serializedRootConfig = JSON.stringify(
|
|
240
|
+
this.cmsClient.rootConfig,
|
|
241
|
+
null,
|
|
242
|
+
2
|
|
243
|
+
);
|
|
244
|
+
if (options.mode === "edit") {
|
|
245
|
+
const rootDir = process.cwd();
|
|
246
|
+
const rootCmsDefsPath = path.resolve(rootDir, "root-cms.d.ts");
|
|
247
|
+
const rootCmsDefs = fs.existsSync(rootCmsDefsPath) ? fs.readFileSync(rootCmsDefsPath, {
|
|
248
|
+
encoding: "utf8"
|
|
249
|
+
}) : null;
|
|
250
|
+
const text = [(await Promise.resolve().then(() => __toESM(require_edit(), 1))).default];
|
|
251
|
+
if (rootCmsDefs) {
|
|
252
|
+
text.push(
|
|
253
|
+
"Here is the `root-cms.d.ts` file for this project:",
|
|
254
|
+
"```",
|
|
255
|
+
rootCmsDefs,
|
|
256
|
+
"```"
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return text.join("\n");
|
|
260
|
+
}
|
|
261
|
+
if (options.mode === "altText") {
|
|
262
|
+
return (await Promise.resolve().then(() => __toESM(require_altText(), 1))).default;
|
|
263
|
+
}
|
|
264
|
+
const systemText = [
|
|
265
|
+
`You are an assistant for a headless CMS called Root CMS which is used on a website called ${this.cmsPluginOptions.name || this.cmsPluginOptions.id}. Your job is to answer questions about the docs in the system, and if requested, help suggest changes to the JSON data in the docs. If you don't know the answer, just say that you don't know, don't try to make up an answer. Be friendly and playful with your messaging.`,
|
|
266
|
+
"",
|
|
267
|
+
"Here is the root.config.ts file for the site:",
|
|
268
|
+
"```",
|
|
269
|
+
serializedRootConfig,
|
|
270
|
+
"```",
|
|
271
|
+
"",
|
|
272
|
+
"Here are the docs that exist in the system:"
|
|
273
|
+
];
|
|
274
|
+
const pages = await this.cmsClient.listDocs("Pages", { mode: "draft" });
|
|
275
|
+
pages.docs.forEach((doc) => {
|
|
276
|
+
systemText.push(JSON.stringify(doc));
|
|
277
|
+
});
|
|
278
|
+
return systemText.join("\n");
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
ChatClient = class {
|
|
282
|
+
constructor(cmsClient, user) {
|
|
283
|
+
this.cmsClient = cmsClient;
|
|
284
|
+
this.user = user;
|
|
285
|
+
}
|
|
286
|
+
async createChat() {
|
|
287
|
+
const chatId = crypto.randomUUID();
|
|
288
|
+
const docRef = this.dbCollection().doc(chatId);
|
|
289
|
+
const chat = new Chat(this, chatId);
|
|
290
|
+
await docRef.set({
|
|
291
|
+
id: chatId,
|
|
292
|
+
createdBy: this.user,
|
|
293
|
+
createdAt: Timestamp.now(),
|
|
294
|
+
modifiedAt: Timestamp.now(),
|
|
295
|
+
model: chat.model
|
|
296
|
+
});
|
|
297
|
+
return chat;
|
|
298
|
+
}
|
|
299
|
+
async getOrCreateChat(chatId) {
|
|
300
|
+
return chatId ? this.getChat(chatId) : this.createChat();
|
|
301
|
+
}
|
|
302
|
+
async getChat(chatId) {
|
|
303
|
+
const docRef = this.dbCollection().doc(chatId);
|
|
304
|
+
const chatDoc = await docRef.get();
|
|
305
|
+
if (!chatDoc.exists) {
|
|
306
|
+
throw new Error(`${chatId} does not exist`);
|
|
307
|
+
}
|
|
308
|
+
const chatData = chatDoc.data() || {};
|
|
309
|
+
return new Chat(this, chatId, {
|
|
310
|
+
history: chatData.history,
|
|
311
|
+
model: chatData.model
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async listChats(options) {
|
|
315
|
+
const limit = options?.limit || 20;
|
|
316
|
+
const query = this.dbCollection().where("createdBy", "==", this.user).limit(limit).orderBy("createdAt", "desc");
|
|
317
|
+
const res = await query.get();
|
|
318
|
+
return res.docs.map((doc) => doc.data());
|
|
319
|
+
}
|
|
320
|
+
dbCollection() {
|
|
321
|
+
return this.cmsClient.db.collection(
|
|
322
|
+
`Projects/${this.cmsClient.projectId}/Experiments/ai/Chat`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
48
329
|
// cli/generate-types.ts
|
|
49
330
|
var generate_types_exports = {};
|
|
50
331
|
__export(generate_types_exports, {
|
|
@@ -383,245 +664,11 @@ var SSEEvent = {
|
|
|
383
664
|
};
|
|
384
665
|
|
|
385
666
|
// core/api.ts
|
|
667
|
+
init_ai();
|
|
386
668
|
import { promises as fs2 } from "fs";
|
|
387
669
|
import path3 from "path";
|
|
388
670
|
import { multipartMiddleware } from "@blinkk/root/middleware";
|
|
389
671
|
|
|
390
|
-
// core/ai.ts
|
|
391
|
-
import crypto from "crypto";
|
|
392
|
-
import fs from "fs";
|
|
393
|
-
import path from "path";
|
|
394
|
-
import { vertexAI } from "@genkit-ai/vertexai";
|
|
395
|
-
import { Timestamp } from "firebase-admin/firestore";
|
|
396
|
-
import { genkit } from "genkit";
|
|
397
|
-
import { logger } from "genkit/logging";
|
|
398
|
-
logger.setLogLevel("warn");
|
|
399
|
-
var DEFAULT_MODEL = "vertexai/gemini-2.5-flash";
|
|
400
|
-
async function summarizeDiff(cmsClient, options) {
|
|
401
|
-
const cmsPluginOptions = cmsClient.cmsPlugin.getConfig();
|
|
402
|
-
const firebaseConfig = cmsPluginOptions.firebaseConfig;
|
|
403
|
-
const model = (typeof cmsPluginOptions.experiments?.ai === "object" ? cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
|
|
404
|
-
const ai = genkit({
|
|
405
|
-
plugins: [
|
|
406
|
-
vertexAI({
|
|
407
|
-
projectId: firebaseConfig.projectId,
|
|
408
|
-
location: firebaseConfig.location || "us-central1"
|
|
409
|
-
})
|
|
410
|
-
]
|
|
411
|
-
});
|
|
412
|
-
const beforeJson = JSON.stringify(options.before ?? null, null, 2);
|
|
413
|
-
const afterJson = JSON.stringify(options.after ?? null, null, 2);
|
|
414
|
-
const systemPrompt = [
|
|
415
|
-
"You are an assistant that summarizes changes made to CMS documents stored as JSON.",
|
|
416
|
-
"Provide a concise description of the most important updates using short bullet points.",
|
|
417
|
-
'If there are no meaningful differences, respond with "No significant changes."',
|
|
418
|
-
`Focus on just the content changes, ignore insignificant changes to richtext blocks and structure, such as updates to the richtext block's "timestamp" and "version" fields.`
|
|
419
|
-
].join("\n");
|
|
420
|
-
const diffPrompt = [
|
|
421
|
-
"Previous version JSON:",
|
|
422
|
-
"```json",
|
|
423
|
-
beforeJson,
|
|
424
|
-
"```",
|
|
425
|
-
"",
|
|
426
|
-
"Updated version JSON:",
|
|
427
|
-
"```json",
|
|
428
|
-
afterJson,
|
|
429
|
-
"```",
|
|
430
|
-
"",
|
|
431
|
-
"Summarize the differences between the two payloads."
|
|
432
|
-
].join("\n");
|
|
433
|
-
const res = await ai.generate({
|
|
434
|
-
model,
|
|
435
|
-
messages: [
|
|
436
|
-
{
|
|
437
|
-
role: "system",
|
|
438
|
-
content: [{ text: systemPrompt }]
|
|
439
|
-
}
|
|
440
|
-
],
|
|
441
|
-
prompt: [{ text: diffPrompt }]
|
|
442
|
-
});
|
|
443
|
-
return res.text?.trim() || "";
|
|
444
|
-
}
|
|
445
|
-
var Chat = class {
|
|
446
|
-
constructor(chatClient, id, options) {
|
|
447
|
-
this.chatClient = chatClient;
|
|
448
|
-
this.cmsClient = chatClient.cmsClient;
|
|
449
|
-
this.cmsPluginOptions = this.cmsClient.cmsPlugin.getConfig();
|
|
450
|
-
this.id = id;
|
|
451
|
-
this.history = options?.history ?? [];
|
|
452
|
-
this.model = options?.model || (typeof this.cmsPluginOptions.experiments?.ai === "object" ? this.cmsPluginOptions.experiments.ai.model : void 0) || DEFAULT_MODEL;
|
|
453
|
-
const firebaseConfig = this.cmsPluginOptions.firebaseConfig;
|
|
454
|
-
this.ai = genkit({
|
|
455
|
-
plugins: [
|
|
456
|
-
vertexAI({
|
|
457
|
-
projectId: firebaseConfig.projectId,
|
|
458
|
-
location: firebaseConfig.location || "us-central1"
|
|
459
|
-
})
|
|
460
|
-
]
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
/** Builds the messages for the AI request. */
|
|
464
|
-
async buildMessages(options) {
|
|
465
|
-
const messages = this.history;
|
|
466
|
-
const hasSystemPrompt = messages.some(
|
|
467
|
-
(msg) => msg.role === "system" && msg.content.length > 0
|
|
468
|
-
);
|
|
469
|
-
if (!hasSystemPrompt) {
|
|
470
|
-
messages.push({
|
|
471
|
-
role: "system",
|
|
472
|
-
content: [{ text: await this.buildSystemPrompt(options) }]
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
if (options.mode === "edit") {
|
|
476
|
-
messages.push({
|
|
477
|
-
role: "user",
|
|
478
|
-
content: [
|
|
479
|
-
{
|
|
480
|
-
text: [
|
|
481
|
-
"The JSON you must edit is:",
|
|
482
|
-
"",
|
|
483
|
-
JSON.stringify(options.editData || {}, null, 2)
|
|
484
|
-
].join("")
|
|
485
|
-
}
|
|
486
|
-
]
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
return messages;
|
|
490
|
-
}
|
|
491
|
-
/** Builds the request sent to the AI based on the `ChatMode`. */
|
|
492
|
-
async buildGenerateRequest(prompt, options) {
|
|
493
|
-
if (options.mode === "edit") {
|
|
494
|
-
return {
|
|
495
|
-
messages: await this.buildMessages(options),
|
|
496
|
-
model: this.model,
|
|
497
|
-
prompt
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
return {
|
|
501
|
-
messages: await this.buildMessages(options),
|
|
502
|
-
model: this.model,
|
|
503
|
-
prompt
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
/** Sends the request to the AI and stores the history in the session and the database. */
|
|
507
|
-
async sendPrompt(prompt, options = {}) {
|
|
508
|
-
const chatRequest = await this.buildGenerateRequest(prompt, options);
|
|
509
|
-
const res = await this.ai.generate({
|
|
510
|
-
model: chatRequest.model,
|
|
511
|
-
messages: chatRequest.messages,
|
|
512
|
-
prompt: Array.isArray(prompt) ? prompt.flat() : prompt
|
|
513
|
-
});
|
|
514
|
-
this.history = res.messages;
|
|
515
|
-
await this.dbDoc().update({
|
|
516
|
-
history: this.history,
|
|
517
|
-
modifiedAt: Timestamp.now()
|
|
518
|
-
});
|
|
519
|
-
if (options.mode === "edit") {
|
|
520
|
-
const aiResponse = res.output;
|
|
521
|
-
if (aiResponse?.data && !aiResponse.message) {
|
|
522
|
-
aiResponse.message = "";
|
|
523
|
-
}
|
|
524
|
-
return aiResponse;
|
|
525
|
-
}
|
|
526
|
-
return { message: res.text, data: null };
|
|
527
|
-
}
|
|
528
|
-
dbDoc() {
|
|
529
|
-
return this.chatClient.dbCollection().doc(this.id);
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Builds the system prompt sent to the AI, based on the `ChatMode` and
|
|
533
|
-
* supplied `SendPromptOptions`. `SendPromptOptions` may contain data or
|
|
534
|
-
* references to information needed to construct the prompt.
|
|
535
|
-
*/
|
|
536
|
-
async buildSystemPrompt(options) {
|
|
537
|
-
const serializedRootConfig = JSON.stringify(
|
|
538
|
-
this.cmsClient.rootConfig,
|
|
539
|
-
null,
|
|
540
|
-
2
|
|
541
|
-
);
|
|
542
|
-
if (options.mode === "edit") {
|
|
543
|
-
const rootDir = process.cwd();
|
|
544
|
-
const rootCmsDefsPath = path.resolve(rootDir, "root-cms.d.ts");
|
|
545
|
-
const rootCmsDefs = fs.existsSync(rootCmsDefsPath) ? fs.readFileSync(rootCmsDefsPath, {
|
|
546
|
-
encoding: "utf8"
|
|
547
|
-
}) : null;
|
|
548
|
-
const text = [(await Promise.resolve().then(() => __toESM(require_edit(), 1))).default];
|
|
549
|
-
if (rootCmsDefs) {
|
|
550
|
-
text.push(
|
|
551
|
-
"Here is the `root-cms.d.ts` file for this project:",
|
|
552
|
-
"```",
|
|
553
|
-
rootCmsDefs,
|
|
554
|
-
"```"
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
return text.join("\n");
|
|
558
|
-
}
|
|
559
|
-
if (options.mode === "altText") {
|
|
560
|
-
return (await Promise.resolve().then(() => __toESM(require_altText(), 1))).default;
|
|
561
|
-
}
|
|
562
|
-
const systemText = [
|
|
563
|
-
`You are an assistant for a headless CMS called Root CMS which is used on a website called ${this.cmsPluginOptions.name || this.cmsPluginOptions.id}. Your job is to answer questions about the docs in the system, and if requested, help suggest changes to the JSON data in the docs. If you don't know the answer, just say that you don't know, don't try to make up an answer. Be friendly and playful with your messaging.`,
|
|
564
|
-
"",
|
|
565
|
-
"Here is the root.config.ts file for the site:",
|
|
566
|
-
"```",
|
|
567
|
-
serializedRootConfig,
|
|
568
|
-
"```",
|
|
569
|
-
"",
|
|
570
|
-
"Here are the docs that exist in the system:"
|
|
571
|
-
];
|
|
572
|
-
const pages = await this.cmsClient.listDocs("Pages", { mode: "draft" });
|
|
573
|
-
pages.docs.forEach((doc) => {
|
|
574
|
-
systemText.push(JSON.stringify(doc));
|
|
575
|
-
});
|
|
576
|
-
return systemText.join("\n");
|
|
577
|
-
}
|
|
578
|
-
};
|
|
579
|
-
var ChatClient = class {
|
|
580
|
-
constructor(cmsClient, user) {
|
|
581
|
-
this.cmsClient = cmsClient;
|
|
582
|
-
this.user = user;
|
|
583
|
-
}
|
|
584
|
-
async createChat() {
|
|
585
|
-
const chatId = crypto.randomUUID();
|
|
586
|
-
const docRef = this.dbCollection().doc(chatId);
|
|
587
|
-
const chat = new Chat(this, chatId);
|
|
588
|
-
await docRef.set({
|
|
589
|
-
id: chatId,
|
|
590
|
-
createdBy: this.user,
|
|
591
|
-
createdAt: Timestamp.now(),
|
|
592
|
-
modifiedAt: Timestamp.now(),
|
|
593
|
-
model: chat.model
|
|
594
|
-
});
|
|
595
|
-
return chat;
|
|
596
|
-
}
|
|
597
|
-
async getOrCreateChat(chatId) {
|
|
598
|
-
return chatId ? this.getChat(chatId) : this.createChat();
|
|
599
|
-
}
|
|
600
|
-
async getChat(chatId) {
|
|
601
|
-
const docRef = this.dbCollection().doc(chatId);
|
|
602
|
-
const chatDoc = await docRef.get();
|
|
603
|
-
if (!chatDoc.exists) {
|
|
604
|
-
throw new Error(`${chatId} does not exist`);
|
|
605
|
-
}
|
|
606
|
-
const chatData = chatDoc.data() || {};
|
|
607
|
-
return new Chat(this, chatId, {
|
|
608
|
-
history: chatData.history,
|
|
609
|
-
model: chatData.model
|
|
610
|
-
});
|
|
611
|
-
}
|
|
612
|
-
async listChats(options) {
|
|
613
|
-
const limit = options?.limit || 20;
|
|
614
|
-
const query = this.dbCollection().where("createdBy", "==", this.user).limit(limit).orderBy("createdAt", "desc");
|
|
615
|
-
const res = await query.get();
|
|
616
|
-
return res.docs.map((doc) => doc.data());
|
|
617
|
-
}
|
|
618
|
-
dbCollection() {
|
|
619
|
-
return this.cmsClient.db.collection(
|
|
620
|
-
`Projects/${this.cmsClient.projectId}/Experiments/ai/Chat`
|
|
621
|
-
);
|
|
622
|
-
}
|
|
623
|
-
};
|
|
624
|
-
|
|
625
672
|
// core/client.ts
|
|
626
673
|
import crypto2 from "crypto";
|
|
627
674
|
import {
|
|
@@ -1760,6 +1807,9 @@ var RootCMSClient = class {
|
|
|
1760
1807
|
by: options?.by || "system",
|
|
1761
1808
|
metadata: options?.metadata || {}
|
|
1762
1809
|
};
|
|
1810
|
+
if (options?.links) {
|
|
1811
|
+
data.links = options.links;
|
|
1812
|
+
}
|
|
1763
1813
|
const colRef = this.db.collection(`Projects/${this.projectId}/ActionLogs`);
|
|
1764
1814
|
await colRef.add(data);
|
|
1765
1815
|
const metaStr = options?.metadata ? stringifyObj(options.metadata) : "";
|
|
@@ -2448,7 +2498,8 @@ function api(server, options) {
|
|
|
2448
2498
|
try {
|
|
2449
2499
|
await cmsClient.logAction(action, {
|
|
2450
2500
|
by: req.user?.email,
|
|
2451
|
-
metadata
|
|
2501
|
+
metadata,
|
|
2502
|
+
links: reqBody.links
|
|
2452
2503
|
});
|
|
2453
2504
|
res.status(200).json({ success: true });
|
|
2454
2505
|
} catch (err) {
|
|
@@ -2551,12 +2602,44 @@ function api(server, options) {
|
|
|
2551
2602
|
res.status(500).json({ success: false, error: "UNKNOWN" });
|
|
2552
2603
|
}
|
|
2553
2604
|
});
|
|
2605
|
+
server.use(
|
|
2606
|
+
"/cms/api/ai.generate_image",
|
|
2607
|
+
async (req, res) => {
|
|
2608
|
+
if (req.method !== "POST" || !String(req.get("content-type")).startsWith("application/json")) {
|
|
2609
|
+
res.status(400).json({ success: false, error: "BAD_REQUEST" });
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
if (!req.user?.email) {
|
|
2613
|
+
res.status(401).json({ success: false, error: "UNAUTHORIZED" });
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const reqBody = req.body || {};
|
|
2617
|
+
const prompt = reqBody.prompt;
|
|
2618
|
+
const aspectRatio = reqBody.aspectRatio;
|
|
2619
|
+
if (!prompt || !aspectRatio) {
|
|
2620
|
+
res.status(400).json({ success: false, error: "MISSING_REQUIRED_FIELD" });
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
try {
|
|
2624
|
+
const cmsClient = new RootCMSClient(req.rootConfig);
|
|
2625
|
+
const { generateImage: generateImage2 } = await Promise.resolve().then(() => (init_ai(), ai_exports));
|
|
2626
|
+
const imageUrl = await generateImage2(cmsClient, {
|
|
2627
|
+
prompt,
|
|
2628
|
+
aspectRatio
|
|
2629
|
+
});
|
|
2630
|
+
res.status(200).json({ success: true, image: imageUrl });
|
|
2631
|
+
} catch (err) {
|
|
2632
|
+
console.error(err.stack || err);
|
|
2633
|
+
res.status(500).json({ success: false, error: err.message || "UNKNOWN" });
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
);
|
|
2554
2637
|
}
|
|
2555
2638
|
|
|
2556
2639
|
// package.json
|
|
2557
2640
|
var package_default = {
|
|
2558
2641
|
name: "@blinkk/root-cms",
|
|
2559
|
-
version: "2.4.
|
|
2642
|
+
version: "2.4.4",
|
|
2560
2643
|
author: "s@blinkk.com",
|
|
2561
2644
|
license: "MIT",
|
|
2562
2645
|
engines: {
|
|
@@ -2616,10 +2699,15 @@ var package_default = {
|
|
|
2616
2699
|
"dev:core": "pnpm build:core --watch",
|
|
2617
2700
|
"dev:signin": "pnpm build:signin --watch",
|
|
2618
2701
|
"dev:ui": "pnpm build:ui --watch",
|
|
2619
|
-
test: "pnpm build && firebase emulators:exec 'vitest run'",
|
|
2702
|
+
test: "pnpm build && firebase emulators:exec 'vitest run --exclude=**/*.visual.test.tsx'",
|
|
2703
|
+
"test:visual": "vitest run --config=vitest.config.visual.ts",
|
|
2620
2704
|
"test:watch": "pnpm build && firebase emulators:exec 'vitest'"
|
|
2621
2705
|
},
|
|
2622
2706
|
dependencies: {
|
|
2707
|
+
"@ag-grid-community/client-side-row-model": "32.3.9",
|
|
2708
|
+
"@ag-grid-community/core": "32.3.9",
|
|
2709
|
+
"@ag-grid-community/react": "32.3.9",
|
|
2710
|
+
"@ag-grid-community/styles": "32.3.9",
|
|
2623
2711
|
"@genkit-ai/ai": "1.18.0",
|
|
2624
2712
|
"@genkit-ai/core": "1.18.0",
|
|
2625
2713
|
"@genkit-ai/vertexai": "1.18.0",
|
|
@@ -2663,6 +2751,7 @@ var package_default = {
|
|
|
2663
2751
|
"@lexical/react": "0.33.1",
|
|
2664
2752
|
"@lexical/rich-text": "0.33.1",
|
|
2665
2753
|
"@lexical/selection": "0.33.1",
|
|
2754
|
+
"@lexical/table": "0.33.1",
|
|
2666
2755
|
"@lexical/utils": "0.33.1",
|
|
2667
2756
|
"@mantine/core": "4.2.12",
|
|
2668
2757
|
"@mantine/hooks": "4.2.12",
|
|
@@ -2671,6 +2760,7 @@ var package_default = {
|
|
|
2671
2760
|
"@mantine/spotlight": "4.2.12",
|
|
2672
2761
|
"@preact/compat": "18.3.1",
|
|
2673
2762
|
"@tabler/icons-preact": "3.35.0",
|
|
2763
|
+
"@testing-library/preact": "3.2.4",
|
|
2674
2764
|
"@types/body-parser": "1.19.3",
|
|
2675
2765
|
"@types/fnv-plus": "1.3.2",
|
|
2676
2766
|
"@types/gapi": "0.0.47",
|
|
@@ -2679,6 +2769,8 @@ var package_default = {
|
|
|
2679
2769
|
"@types/google.accounts": "0.0.14",
|
|
2680
2770
|
"@types/jsonwebtoken": "9.0.1",
|
|
2681
2771
|
"@types/node": "24.3.1",
|
|
2772
|
+
"@vitest/browser": "4.0.10",
|
|
2773
|
+
"@vitest/browser-playwright": "4.0.10",
|
|
2682
2774
|
concurrently: "7.6.0",
|
|
2683
2775
|
esbuild: "0.25.9",
|
|
2684
2776
|
firebase: "12.2.1",
|
|
@@ -2692,6 +2784,7 @@ var package_default = {
|
|
|
2692
2784
|
"mdast-util-from-markdown": "2.0.1",
|
|
2693
2785
|
"mdast-util-gfm": "3.0.0",
|
|
2694
2786
|
"micromark-extension-gfm": "3.0.0",
|
|
2787
|
+
playwright: "1.56.1",
|
|
2695
2788
|
preact: "10.27.1",
|
|
2696
2789
|
"preact-render-to-string": "6.6.1",
|
|
2697
2790
|
"preact-router": "4.1.2",
|
|
@@ -2701,11 +2794,11 @@ var package_default = {
|
|
|
2701
2794
|
tsup: "8.5.0",
|
|
2702
2795
|
typescript: "5.9.2",
|
|
2703
2796
|
vite: "7.1.4",
|
|
2704
|
-
vitest: "
|
|
2797
|
+
vitest: "4.0.10",
|
|
2705
2798
|
yjs: "13.6.27"
|
|
2706
2799
|
},
|
|
2707
2800
|
peerDependencies: {
|
|
2708
|
-
"@blinkk/root": "2.4.
|
|
2801
|
+
"@blinkk/root": "2.4.4",
|
|
2709
2802
|
"firebase-admin": ">=11",
|
|
2710
2803
|
"firebase-functions": ">=4",
|
|
2711
2804
|
preact: ">=10",
|
|
@@ -3129,6 +3222,22 @@ function cmsPlugin(options) {
|
|
|
3129
3222
|
res.status(500).send("UNKNOWN SERVER ERROR");
|
|
3130
3223
|
}
|
|
3131
3224
|
});
|
|
3225
|
+
process.on("unhandledRejection", (reason) => {
|
|
3226
|
+
const err = reason;
|
|
3227
|
+
if (err?.message?.includes("reauth related error") || err?.message?.includes("invalid_grant")) {
|
|
3228
|
+
console.log("\n");
|
|
3229
|
+
console.log("==================================================");
|
|
3230
|
+
console.log("Google Cloud Reauthentication Required");
|
|
3231
|
+
console.log("==================================================");
|
|
3232
|
+
console.log(
|
|
3233
|
+
"Your Google Cloud credentials have expired or require reauthentication."
|
|
3234
|
+
);
|
|
3235
|
+
console.log("Please run the following command to reauthenticate:");
|
|
3236
|
+
console.log("\n gcloud auth application-default login\n");
|
|
3237
|
+
console.log("==================================================");
|
|
3238
|
+
console.log("\n");
|
|
3239
|
+
}
|
|
3240
|
+
});
|
|
3132
3241
|
}
|
|
3133
3242
|
};
|
|
3134
3243
|
if (process.env.NODE_ENV === "development" && options.watch !== false) {
|
package/dist/project.d.ts
CHANGED