@blinkk/root-cms 3.0.1-alpha.1 → 3.0.1-beta.3
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 +12 -6
- package/dist/chunk-CRK7N6RR.js +298 -0
- package/dist/{chunk-SNZ4S4IC.js → chunk-F4SODS5S.js} +87 -297
- package/dist/{chunk-R4LKO3EZ.js → chunk-MAZA5B27.js} +16 -18
- package/dist/chunk-NXEXOHBD.js +982 -0
- package/dist/{chunk-UTSL2E2P.js → chunk-TRM4MQHU.js} +270 -11
- package/dist/{chunk-KDXHFMIH.js → chunk-XLX37FRL.js} +3 -0
- package/dist/cli.js +3 -2
- package/dist/{client-DdB4xpM6.d.ts → client-1puwLgNx.d.ts} +194 -10
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -1
- package/dist/core.d.ts +3 -3
- package/dist/core.js +2 -1
- package/dist/functions.js +3 -2
- package/dist/{generate-types-MHWSSOWV.js → generate-types-SPV7I3A5.js} +1 -1
- package/dist/plugin.d.ts +2 -2
- package/dist/plugin.js +287 -78
- package/dist/project.d.ts +15 -2
- package/dist/project.js +3 -1
- package/dist/richtext.d.ts +25 -18
- package/dist/richtext.js +98 -43
- package/dist/{schema-rjBOZk-j.d.ts → schema-Db_xODoi.d.ts} +5 -0
- package/dist/ui/signin.css +1 -1
- package/dist/ui/signin.js +3 -3
- package/dist/ui/ui.css +1 -1
- package/dist/ui/ui.js +287 -177
- package/dist/ui/ui.js.LEGAL.txt +2 -1
- package/package.json +10 -7
- package/dist/ai-OZY3JXDH.js +0 -19
- package/dist/altText-RDKJNVGH.js +0 -7
- package/dist/chunk-BBOESYH7.js +0 -192
- package/dist/chunk-T5UK2H24.js +0 -419
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
// core/ai.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { promises as fs } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
6
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
7
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
8
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
9
|
+
import {
|
|
10
|
+
convertToModelMessages,
|
|
11
|
+
generateImage as generateImageSdk,
|
|
12
|
+
generateText,
|
|
13
|
+
stepCountIs,
|
|
14
|
+
streamText
|
|
15
|
+
} from "ai";
|
|
16
|
+
import { Timestamp } from "firebase-admin/firestore";
|
|
17
|
+
|
|
18
|
+
// core/ai-tools.ts
|
|
19
|
+
import { tool } from "ai";
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
var READ_ONLY_CMS_TOOL_NAMES = [
|
|
22
|
+
"collections_list",
|
|
23
|
+
"docs_list",
|
|
24
|
+
"docs_search",
|
|
25
|
+
"doc_get",
|
|
26
|
+
"doc_getVersion",
|
|
27
|
+
"doc_listVersions",
|
|
28
|
+
"schema_get"
|
|
29
|
+
];
|
|
30
|
+
function createReadOnlyCmsTools() {
|
|
31
|
+
const all = createCmsTools();
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const name of READ_ONLY_CMS_TOOL_NAMES) {
|
|
34
|
+
if (all[name]) {
|
|
35
|
+
out[name] = all[name];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function createCmsTools() {
|
|
41
|
+
return {
|
|
42
|
+
collections_list: tool({
|
|
43
|
+
description: "List all CMS collections defined in the project. Returns each collection id along with optional name/description metadata.",
|
|
44
|
+
inputSchema: z.object({})
|
|
45
|
+
}),
|
|
46
|
+
docs_list: tool({
|
|
47
|
+
description: "List documents inside a CMS collection. Returns up to `limit` docs (default 25, max 100). Use this to explore content before reading individual docs.",
|
|
48
|
+
inputSchema: z.object({
|
|
49
|
+
collectionId: z.string().describe('Collection id, e.g. "Pages" or "BlogPosts".'),
|
|
50
|
+
mode: z.enum(["draft", "published"]).default("draft").describe("Whether to read draft or published versions."),
|
|
51
|
+
limit: z.number().int().min(1).max(100).default(25)
|
|
52
|
+
})
|
|
53
|
+
}),
|
|
54
|
+
docs_search: tool({
|
|
55
|
+
description: "Run a full-text search across all indexed CMS docs. Returns the top matching doc ids ordered by relevance.",
|
|
56
|
+
inputSchema: z.object({
|
|
57
|
+
query: z.string().min(1),
|
|
58
|
+
limit: z.number().int().min(1).max(50).default(10)
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
doc_get: tool({
|
|
62
|
+
description: "Read a single CMS document. Returns the doc fields plus system metadata. Use this when you need the full content of a doc.",
|
|
63
|
+
inputSchema: z.object({
|
|
64
|
+
docId: z.string().describe(
|
|
65
|
+
'Full doc id in the form "Collection/slug" (e.g. "Pages/home").'
|
|
66
|
+
),
|
|
67
|
+
mode: z.enum(["draft", "published"]).default("draft")
|
|
68
|
+
})
|
|
69
|
+
}),
|
|
70
|
+
doc_getVersion: tool({
|
|
71
|
+
description: 'Read a specific version of a CMS document. Use versionId "draft" or "published" for the current draft/published state, or a numeric timestamp for a historical version (from `doc_listVersions`).',
|
|
72
|
+
inputSchema: z.object({
|
|
73
|
+
docId: z.string().describe(
|
|
74
|
+
'Full doc id in the form "Collection/slug" (e.g. "Pages/home").'
|
|
75
|
+
),
|
|
76
|
+
versionId: z.string().describe(
|
|
77
|
+
'Version identifier: "draft", "published", or a numeric timestamp.'
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
}),
|
|
81
|
+
doc_set: tool({
|
|
82
|
+
description: "Replace the entire draft fields payload of a CMS document. Pass the full JSON object that should become the new draft contents \u2014 any fields omitted will be removed. The payload is validated against the collection schema and the call is rejected on validation errors. Prefer `doc_updateField` for targeted edits. Only writes the draft version; users must publish separately. Always confirm with the user before calling.",
|
|
83
|
+
inputSchema: z.object({
|
|
84
|
+
docId: z.string().describe(
|
|
85
|
+
'Full doc id in the form "Collection/slug" (e.g. "Pages/home").'
|
|
86
|
+
),
|
|
87
|
+
fields: z.record(z.string(), z.any()).describe(
|
|
88
|
+
"New fields object. Replaces the existing draft fields entirely."
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
}),
|
|
92
|
+
doc_create: tool({
|
|
93
|
+
description: "Create a new draft CMS document with the given slug. Fails if the doc already exists. Pass optional initial fields (validated against the collection schema). Always confirm with the user before calling.",
|
|
94
|
+
inputSchema: z.object({
|
|
95
|
+
docId: z.string().describe(
|
|
96
|
+
'Full doc id in the form "Collection/slug" (e.g. "BlogPosts/my-new-post").'
|
|
97
|
+
),
|
|
98
|
+
fields: z.record(z.string(), z.any()).optional().describe("Optional initial fields for the new doc.")
|
|
99
|
+
})
|
|
100
|
+
}),
|
|
101
|
+
doc_updateField: tool({
|
|
102
|
+
description: 'Update a single field on a draft CMS document by JSON path. Paths are relative to the doc fields object; do not prefix them with "fields.". Use dotted paths (e.g. "hero.title") and zero-based array indices. For example, update the first module title with path "content.modules.0.title". To append or remove array items, first read the current doc, then set the whole array path (e.g. "content.modules") to the updated array. The value is validated against the field schema and the call is rejected if the shape is wrong (e.g. passing a string to a richtext field). Only updates the draft version; users must publish separately. Always confirm with the user before calling.',
|
|
103
|
+
inputSchema: z.object({
|
|
104
|
+
docId: z.string(),
|
|
105
|
+
path: z.string().describe(
|
|
106
|
+
'Dotted JSON path within the fields object, e.g. "content.modules.0.title".'
|
|
107
|
+
),
|
|
108
|
+
value: z.any().describe("JSON value to set at the path.")
|
|
109
|
+
})
|
|
110
|
+
}),
|
|
111
|
+
// `doc_publish` and `doc_delete` are intentionally omitted — see the
|
|
112
|
+
// safety policy comment on `createCmsTools`. Users must run these from
|
|
113
|
+
// the CMS UI themselves.
|
|
114
|
+
doc_duplicate: tool({
|
|
115
|
+
description: "Duplicate an existing CMS document to a new slug. Copies all draft fields to the target doc id. Fails if the target already exists. Always confirm with the user before calling.",
|
|
116
|
+
inputSchema: z.object({
|
|
117
|
+
fromDocId: z.string().describe('Source doc id to copy from (e.g. "Pages/home").'),
|
|
118
|
+
toDocId: z.string().describe('Target doc id for the copy (e.g. "Pages/home-copy").')
|
|
119
|
+
})
|
|
120
|
+
}),
|
|
121
|
+
// `doc_revertDraft` is intentionally omitted — see the safety policy
|
|
122
|
+
// comment on `createCmsTools`. Discarding draft edits is destructive
|
|
123
|
+
// and must be triggered by the user from the CMS UI.
|
|
124
|
+
doc_listVersions: tool({
|
|
125
|
+
description: "List version history for a CMS document. Returns versions ordered by most recent first. Use the versionId from the results with `doc_getVersion` to read a specific version.",
|
|
126
|
+
inputSchema: z.object({
|
|
127
|
+
docId: z.string().describe(
|
|
128
|
+
'Full doc id in the form "Collection/slug" (e.g. "Pages/home").'
|
|
129
|
+
),
|
|
130
|
+
limit: z.number().int().min(1).max(50).default(10)
|
|
131
|
+
})
|
|
132
|
+
}),
|
|
133
|
+
doc_translateField: tool({
|
|
134
|
+
description: "Translate a text value into one or more target locales using AI. Returns the translated strings keyed by locale. Use this to help with localization workflows.",
|
|
135
|
+
inputSchema: z.object({
|
|
136
|
+
sourceText: z.string().min(1).describe("The source text to translate."),
|
|
137
|
+
targetLocales: z.array(z.string()).min(1).describe(
|
|
138
|
+
'Array of locale codes to translate into (e.g. ["es", "fr", "de"]).'
|
|
139
|
+
),
|
|
140
|
+
description: z.string().optional().describe(
|
|
141
|
+
"Optional context about the text to improve translation quality."
|
|
142
|
+
)
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
schema_get: tool({
|
|
146
|
+
description: "Get the field schema for a CMS collection. Returns the full field definitions including types, labels, and validation rules. Use this to understand what fields a collection supports before creating or editing docs.",
|
|
147
|
+
inputSchema: z.object({
|
|
148
|
+
collectionId: z.string().describe('Collection id, e.g. "Pages" or "BlogPosts".')
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// core/ai.ts
|
|
155
|
+
var ROOT_MD_FILENAME = "ROOT.md";
|
|
156
|
+
function resolveLanguageModel(model) {
|
|
157
|
+
const modelId = model.modelId || model.id;
|
|
158
|
+
switch (model.provider) {
|
|
159
|
+
case "openai": {
|
|
160
|
+
const provider = createOpenAI({
|
|
161
|
+
apiKey: model.apiKey,
|
|
162
|
+
baseURL: model.baseURL,
|
|
163
|
+
headers: model.headers
|
|
164
|
+
});
|
|
165
|
+
return provider(modelId);
|
|
166
|
+
}
|
|
167
|
+
case "openai-compatible": {
|
|
168
|
+
if (!model.baseURL) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`model "${model.id}" requires a baseURL for provider "openai-compatible"`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const provider = createOpenAICompatible({
|
|
174
|
+
name: model.id,
|
|
175
|
+
baseURL: model.baseURL,
|
|
176
|
+
apiKey: model.apiKey,
|
|
177
|
+
headers: model.headers
|
|
178
|
+
});
|
|
179
|
+
return provider(modelId);
|
|
180
|
+
}
|
|
181
|
+
case "anthropic": {
|
|
182
|
+
const provider = createAnthropic({
|
|
183
|
+
apiKey: model.apiKey,
|
|
184
|
+
baseURL: model.baseURL,
|
|
185
|
+
headers: model.headers
|
|
186
|
+
});
|
|
187
|
+
return provider(modelId);
|
|
188
|
+
}
|
|
189
|
+
case "google": {
|
|
190
|
+
const provider = createGoogleGenerativeAI({
|
|
191
|
+
apiKey: model.apiKey,
|
|
192
|
+
baseURL: model.baseURL,
|
|
193
|
+
headers: model.headers
|
|
194
|
+
});
|
|
195
|
+
return provider(modelId);
|
|
196
|
+
}
|
|
197
|
+
default: {
|
|
198
|
+
throw new Error(`unknown ai provider: ${model.provider}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function resolveImageModel(model) {
|
|
203
|
+
const modelId = model.modelId || model.id;
|
|
204
|
+
switch (model.provider) {
|
|
205
|
+
case "openai": {
|
|
206
|
+
const provider = createOpenAI({
|
|
207
|
+
apiKey: model.apiKey,
|
|
208
|
+
baseURL: model.baseURL,
|
|
209
|
+
headers: model.headers
|
|
210
|
+
});
|
|
211
|
+
return provider.image(modelId);
|
|
212
|
+
}
|
|
213
|
+
case "google": {
|
|
214
|
+
const provider = createGoogleGenerativeAI({
|
|
215
|
+
apiKey: model.apiKey,
|
|
216
|
+
baseURL: model.baseURL,
|
|
217
|
+
headers: model.headers
|
|
218
|
+
});
|
|
219
|
+
return provider.image(modelId);
|
|
220
|
+
}
|
|
221
|
+
default: {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`provider "${model.provider}" does not support image generation`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function serializeAiConfig(config) {
|
|
229
|
+
return {
|
|
230
|
+
defaultModel: config.defaultModel || config.models[0]?.id,
|
|
231
|
+
models: config.models.map((m) => ({
|
|
232
|
+
id: m.id,
|
|
233
|
+
label: m.label || m.id,
|
|
234
|
+
description: m.description,
|
|
235
|
+
provider: m.provider,
|
|
236
|
+
capabilities: {
|
|
237
|
+
tools: m.capabilities?.tools !== false,
|
|
238
|
+
reasoning: m.capabilities?.reasoning ?? false,
|
|
239
|
+
attachments: m.capabilities?.attachments ?? false
|
|
240
|
+
}
|
|
241
|
+
})),
|
|
242
|
+
imageGenerationEnabled: !!config.imageModels?.length
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function getAiConfig(rootConfig) {
|
|
246
|
+
const cmsPlugin = rootConfig.plugins?.find((p) => p.name === "root-cms");
|
|
247
|
+
const ai = cmsPlugin?.getConfig().ai;
|
|
248
|
+
if (!ai || !Array.isArray(ai.models) || ai.models.length === 0) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return ai;
|
|
252
|
+
}
|
|
253
|
+
function findModel(config, modelId) {
|
|
254
|
+
if (modelId) {
|
|
255
|
+
const match = config.models.find((m) => m.id === modelId);
|
|
256
|
+
if (match) {
|
|
257
|
+
return match;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const defaultId = config.defaultModel || config.models[0]?.id;
|
|
261
|
+
return config.models.find((m) => m.id === defaultId) || null;
|
|
262
|
+
}
|
|
263
|
+
function findImageModel(config, modelId) {
|
|
264
|
+
const imageModels = config.imageModels || [];
|
|
265
|
+
if (imageModels.length === 0) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
if (modelId) {
|
|
269
|
+
const match = imageModels.find((m) => m.id === modelId);
|
|
270
|
+
if (match) {
|
|
271
|
+
return match;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const defaultId = config.defaultImageModel || imageModels[0]?.id;
|
|
275
|
+
return imageModels.find((m) => m.id === defaultId) || null;
|
|
276
|
+
}
|
|
277
|
+
function normalizeExecutionMode(value) {
|
|
278
|
+
if (value === "read" || value === "suggest" || value === "approve" || value === "auto") {
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
return "approve";
|
|
282
|
+
}
|
|
283
|
+
function requireDefaultModel(rootConfig) {
|
|
284
|
+
const config = getAiConfig(rootConfig);
|
|
285
|
+
if (!config) {
|
|
286
|
+
throw new Error("AI is not configured. Set `ai` on the cmsPlugin config.");
|
|
287
|
+
}
|
|
288
|
+
const model = findModel(config);
|
|
289
|
+
if (!model) {
|
|
290
|
+
throw new Error("No AI chat model configured.");
|
|
291
|
+
}
|
|
292
|
+
return { config, model };
|
|
293
|
+
}
|
|
294
|
+
var ChatStore = class {
|
|
295
|
+
constructor(cmsClient, user) {
|
|
296
|
+
this.cmsClient = cmsClient;
|
|
297
|
+
this.user = user;
|
|
298
|
+
}
|
|
299
|
+
collection() {
|
|
300
|
+
return this.cmsClient.db.collection(
|
|
301
|
+
`Projects/${this.cmsClient.projectId}/AiChats`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
async createChat(options) {
|
|
305
|
+
const id = options?.id || crypto.randomUUID();
|
|
306
|
+
const now = Timestamp.now();
|
|
307
|
+
const record = {
|
|
308
|
+
id,
|
|
309
|
+
createdBy: this.user,
|
|
310
|
+
createdAt: now,
|
|
311
|
+
modifiedAt: now,
|
|
312
|
+
modelId: options?.modelId,
|
|
313
|
+
messages: []
|
|
314
|
+
};
|
|
315
|
+
await this.collection().doc(id).set(record);
|
|
316
|
+
return record;
|
|
317
|
+
}
|
|
318
|
+
async getChat(id) {
|
|
319
|
+
const snap = await this.collection().doc(id).get();
|
|
320
|
+
if (!snap.exists) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
const data = snap.data();
|
|
324
|
+
if (data.createdBy !== this.user) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return data;
|
|
328
|
+
}
|
|
329
|
+
async listChats(options) {
|
|
330
|
+
const limit = options?.limit ?? 50;
|
|
331
|
+
const res = await this.collection().where("createdBy", "==", this.user).get();
|
|
332
|
+
const records = res.docs.map((d) => d.data());
|
|
333
|
+
records.sort((a, b) => {
|
|
334
|
+
const aMs = a.modifiedAt?.toMillis?.() ?? 0;
|
|
335
|
+
const bMs = b.modifiedAt?.toMillis?.() ?? 0;
|
|
336
|
+
return bMs - aMs;
|
|
337
|
+
});
|
|
338
|
+
return records.slice(0, limit);
|
|
339
|
+
}
|
|
340
|
+
async deleteChat(id) {
|
|
341
|
+
const chat = await this.getChat(id);
|
|
342
|
+
if (!chat) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
await this.collection().doc(id).delete();
|
|
346
|
+
}
|
|
347
|
+
async updateMessages(id, messages, options) {
|
|
348
|
+
const updates = {
|
|
349
|
+
messages,
|
|
350
|
+
modifiedAt: Timestamp.now()
|
|
351
|
+
};
|
|
352
|
+
if (options?.modelId) {
|
|
353
|
+
updates.modelId = options.modelId;
|
|
354
|
+
}
|
|
355
|
+
if (options?.title) {
|
|
356
|
+
updates.title = options.title;
|
|
357
|
+
}
|
|
358
|
+
await this.collection().doc(id).update(updates);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
async function readRootMd(rootDir) {
|
|
362
|
+
const filePath = path.join(rootDir, ROOT_MD_FILENAME);
|
|
363
|
+
try {
|
|
364
|
+
const contents = await fs.readFile(filePath, "utf8");
|
|
365
|
+
const trimmed = contents.trim();
|
|
366
|
+
return trimmed || null;
|
|
367
|
+
} catch (err) {
|
|
368
|
+
if (err && err.code === "ENOENT") {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
console.error(`failed to read ${ROOT_MD_FILENAME}:`, err);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function buildSystemPrompt(basePrompt, rootMd) {
|
|
376
|
+
if (!rootMd) {
|
|
377
|
+
return basePrompt;
|
|
378
|
+
}
|
|
379
|
+
return [
|
|
380
|
+
basePrompt,
|
|
381
|
+
"",
|
|
382
|
+
`The project includes a \`${ROOT_MD_FILENAME}\` file with site-specific`,
|
|
383
|
+
"instructions, conventions and context provided by the developer. Treat",
|
|
384
|
+
"these instructions as authoritative for this project and follow them",
|
|
385
|
+
"when responding or calling tools.",
|
|
386
|
+
"",
|
|
387
|
+
`<${ROOT_MD_FILENAME}>`,
|
|
388
|
+
rootMd,
|
|
389
|
+
`</${ROOT_MD_FILENAME}>`
|
|
390
|
+
].join("\n");
|
|
391
|
+
}
|
|
392
|
+
function deriveChatTitle(messages) {
|
|
393
|
+
const first = messages.find((m) => m.role === "user");
|
|
394
|
+
if (!first) {
|
|
395
|
+
return "New chat";
|
|
396
|
+
}
|
|
397
|
+
const text = first.parts.filter((p) => p.type === "text").map((p) => p.text).join(" ").trim();
|
|
398
|
+
if (!text) {
|
|
399
|
+
return "New chat";
|
|
400
|
+
}
|
|
401
|
+
return text.length > 60 ? `${text.slice(0, 57)}\u2026` : text;
|
|
402
|
+
}
|
|
403
|
+
async function generateChatTitle(model, messages) {
|
|
404
|
+
const fallback = deriveChatTitle(messages);
|
|
405
|
+
if (fallback === "New chat") {
|
|
406
|
+
return fallback;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
const result = await generateText({
|
|
410
|
+
model,
|
|
411
|
+
system: "Generate a short title (max 50 characters) summarizing the following conversation. Return only the title text, no quotes or punctuation at the end.",
|
|
412
|
+
prompt: messages.slice(0, 6).map((m) => {
|
|
413
|
+
const text = m.parts.filter((p) => p.type === "text").map((p) => p.text).join(" ");
|
|
414
|
+
return `${m.role}: ${text}`;
|
|
415
|
+
}).join("\n"),
|
|
416
|
+
maxOutputTokens: 30
|
|
417
|
+
});
|
|
418
|
+
const title = result.text.trim().replace(/["']+$/g, "").replace(/^["']+/g, "");
|
|
419
|
+
if (title) {
|
|
420
|
+
return title.length > 80 ? `${title.slice(0, 77)}\u2026` : title;
|
|
421
|
+
}
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error("failed to generate chat title:", err);
|
|
424
|
+
}
|
|
425
|
+
return fallback;
|
|
426
|
+
}
|
|
427
|
+
function buildExecutionModePrompt(mode) {
|
|
428
|
+
const common = [
|
|
429
|
+
"Root AI execution workflow:",
|
|
430
|
+
"- For content-changing tasks, first gather the relevant context with read tools.",
|
|
431
|
+
"- Before the first write, briefly state a plan that names the target docs, fields, intended changes, assumptions, and validation checks.",
|
|
432
|
+
"- Never claim a draft change was applied until the matching tool output reports success.",
|
|
433
|
+
"- After write tools finish, provide a short receipt with changed docs, changed fields, validation result, and a reminder that publishing remains manual."
|
|
434
|
+
];
|
|
435
|
+
if (mode === "read") {
|
|
436
|
+
common.push(
|
|
437
|
+
"",
|
|
438
|
+
"Current execution mode: Read only.",
|
|
439
|
+
"- You only have read-only CMS tools.",
|
|
440
|
+
"- Do not propose tool writes or ask for approval to write. Answer from the context you can read."
|
|
441
|
+
);
|
|
442
|
+
} else if (mode === "suggest") {
|
|
443
|
+
common.push(
|
|
444
|
+
"",
|
|
445
|
+
"Current execution mode: Suggest changes.",
|
|
446
|
+
"- You only have read-only CMS tools.",
|
|
447
|
+
"- Do not call write tools. Provide proposed edits, field paths, and rationale for the user to review."
|
|
448
|
+
);
|
|
449
|
+
} else if (mode === "approve") {
|
|
450
|
+
common.push(
|
|
451
|
+
"",
|
|
452
|
+
"Current execution mode: Ask before writing.",
|
|
453
|
+
"- Write tools are available, but the UI will pause each draft write for user approval with a diff.",
|
|
454
|
+
"- Call write tools only after you have enough context to make a specific, reviewable change.",
|
|
455
|
+
"- If the user rejects a write, revise the plan instead of retrying the same write."
|
|
456
|
+
);
|
|
457
|
+
} else {
|
|
458
|
+
common.push(
|
|
459
|
+
"",
|
|
460
|
+
"Current execution mode: Auto-apply draft edits.",
|
|
461
|
+
"- Draft-only write tools may run without an approval pause.",
|
|
462
|
+
"- Keep edits narrowly scoped to the user request and summarize the exact draft changes afterward."
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return common.join("\n");
|
|
466
|
+
}
|
|
467
|
+
async function runChatStream(options) {
|
|
468
|
+
const {
|
|
469
|
+
rootConfig,
|
|
470
|
+
model,
|
|
471
|
+
config,
|
|
472
|
+
messages,
|
|
473
|
+
cmsClient,
|
|
474
|
+
user,
|
|
475
|
+
chatId,
|
|
476
|
+
executionMode = "approve"
|
|
477
|
+
} = options;
|
|
478
|
+
const languageModel = resolveLanguageModel(model);
|
|
479
|
+
const tools = model.capabilities?.tools === false ? {} : executionMode === "read" || executionMode === "suggest" ? createReadOnlyCmsTools() : createCmsTools();
|
|
480
|
+
const basePrompt = config.systemPrompt || [
|
|
481
|
+
"You are an assistant embedded in the Root CMS admin UI.",
|
|
482
|
+
"Help the user explore and edit content, answer questions about",
|
|
483
|
+
"the project, and use the provided tools to read and write CMS docs.",
|
|
484
|
+
"Be concise and use markdown for rich responses."
|
|
485
|
+
].join(" ");
|
|
486
|
+
const rootMd = await readRootMd(rootConfig.rootDir);
|
|
487
|
+
const systemPrompt = buildSystemPrompt(
|
|
488
|
+
[basePrompt, "", buildExecutionModePrompt(executionMode)].join("\n"),
|
|
489
|
+
rootMd
|
|
490
|
+
);
|
|
491
|
+
const modelMessages = await convertToModelMessages(messages, { tools });
|
|
492
|
+
const result = streamText({
|
|
493
|
+
model: languageModel,
|
|
494
|
+
system: systemPrompt,
|
|
495
|
+
messages: modelMessages,
|
|
496
|
+
tools,
|
|
497
|
+
stopWhen: stepCountIs(config.maxSteps ?? 10)
|
|
498
|
+
});
|
|
499
|
+
return result.toUIMessageStreamResponse({
|
|
500
|
+
sendReasoning: model.capabilities?.reasoning ?? false,
|
|
501
|
+
originalMessages: messages,
|
|
502
|
+
onFinish: async ({ messages: finalMessages }) => {
|
|
503
|
+
const store = new ChatStore(cmsClient, user);
|
|
504
|
+
const title = await generateChatTitle(languageModel, finalMessages);
|
|
505
|
+
const updates = {
|
|
506
|
+
modelId: model.id,
|
|
507
|
+
title
|
|
508
|
+
};
|
|
509
|
+
try {
|
|
510
|
+
await store.updateMessages(
|
|
511
|
+
chatId,
|
|
512
|
+
stripUndefined(finalMessages),
|
|
513
|
+
updates
|
|
514
|
+
);
|
|
515
|
+
} catch (err) {
|
|
516
|
+
console.error("failed to persist chat history:", err);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
async function runEditObjectStream(options) {
|
|
522
|
+
const { rootConfig, model, config, messages, editData } = options;
|
|
523
|
+
const languageModel = resolveLanguageModel(model);
|
|
524
|
+
const tools = model.capabilities?.tools === false ? {} : createReadOnlyCmsTools();
|
|
525
|
+
const editPromptText = (await import("./edit-XX3LAGK6.js")).default;
|
|
526
|
+
const promptParts = [
|
|
527
|
+
stripLegacyEditOutputSpec(editPromptText),
|
|
528
|
+
"",
|
|
529
|
+
"Output format:",
|
|
530
|
+
"- Begin with a brief 1-2 sentence message describing what you changed.",
|
|
531
|
+
"- Then emit a single fenced code block tagged ```json containing the",
|
|
532
|
+
" COMPLETE proposed new JSON object (including any unmodified fields).",
|
|
533
|
+
"- The code block must be the LAST content in your response. The CMS UI",
|
|
534
|
+
" parses it and shows the result in a diff viewer for the user to",
|
|
535
|
+
" approve before saving.",
|
|
536
|
+
"",
|
|
537
|
+
"Tool policy:",
|
|
538
|
+
"- The available tools are READ-ONLY. Use them only when you need extra",
|
|
539
|
+
" context (e.g. inspecting the schema or referencing other CMS docs).",
|
|
540
|
+
"- You MUST NOT attempt to call write tools (e.g. doc_set, doc_create,",
|
|
541
|
+
" doc_updateField). The user approves and saves changes manually via",
|
|
542
|
+
" the modal's Save button."
|
|
543
|
+
];
|
|
544
|
+
try {
|
|
545
|
+
const rootCmsDefsPath = path.join(rootConfig.rootDir, "root-cms.d.ts");
|
|
546
|
+
const rootCmsDefs = await fs.readFile(rootCmsDefsPath, "utf8");
|
|
547
|
+
if (rootCmsDefs.trim()) {
|
|
548
|
+
promptParts.push(
|
|
549
|
+
"",
|
|
550
|
+
"Here is the `root-cms.d.ts` file for this project:",
|
|
551
|
+
"```",
|
|
552
|
+
rootCmsDefs,
|
|
553
|
+
"```"
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
} catch (err) {
|
|
557
|
+
if (err?.code !== "ENOENT") {
|
|
558
|
+
console.error("failed to read root-cms.d.ts:", err);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
promptParts.push(
|
|
562
|
+
"",
|
|
563
|
+
"The JSON you must edit is:",
|
|
564
|
+
"```json",
|
|
565
|
+
JSON.stringify(editData ?? {}, null, 2),
|
|
566
|
+
"```"
|
|
567
|
+
);
|
|
568
|
+
const basePrompt = promptParts.join("\n");
|
|
569
|
+
const rootMd = await readRootMd(rootConfig.rootDir);
|
|
570
|
+
const systemPrompt = buildSystemPrompt(basePrompt, rootMd);
|
|
571
|
+
const modelMessages = await convertToModelMessages(messages, { tools });
|
|
572
|
+
const result = streamText({
|
|
573
|
+
model: languageModel,
|
|
574
|
+
system: systemPrompt,
|
|
575
|
+
messages: modelMessages,
|
|
576
|
+
tools,
|
|
577
|
+
stopWhen: stepCountIs(config.maxSteps ?? 10)
|
|
578
|
+
});
|
|
579
|
+
return result.toUIMessageStreamResponse({
|
|
580
|
+
sendReasoning: model.capabilities?.reasoning ?? false,
|
|
581
|
+
originalMessages: messages
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
function stripLegacyEditOutputSpec(prompt) {
|
|
585
|
+
const marker = "Finally, when you provide your response, it MUST be structured";
|
|
586
|
+
const idx = prompt.indexOf(marker);
|
|
587
|
+
if (idx === -1) {
|
|
588
|
+
return prompt;
|
|
589
|
+
}
|
|
590
|
+
return prompt.slice(0, idx).trimEnd();
|
|
591
|
+
}
|
|
592
|
+
function stripUndefined(value) {
|
|
593
|
+
if (Array.isArray(value)) {
|
|
594
|
+
return value.filter((v) => v !== void 0).map((v) => stripUndefined(v));
|
|
595
|
+
}
|
|
596
|
+
if (value && typeof value === "object") {
|
|
597
|
+
const out = {};
|
|
598
|
+
for (const [k, v] of Object.entries(value)) {
|
|
599
|
+
if (v === void 0) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
out[k] = stripUndefined(v);
|
|
603
|
+
}
|
|
604
|
+
return out;
|
|
605
|
+
}
|
|
606
|
+
return value;
|
|
607
|
+
}
|
|
608
|
+
async function summarizeDiff(rootConfig, options) {
|
|
609
|
+
const { model } = requireDefaultModel(rootConfig);
|
|
610
|
+
const languageModel = resolveLanguageModel(model);
|
|
611
|
+
const beforeJson = JSON.stringify(options.before ?? null, null, 2);
|
|
612
|
+
const afterJson = JSON.stringify(options.after ?? null, null, 2);
|
|
613
|
+
const system = [
|
|
614
|
+
"Summarize CMS document changes in 2-4 bullet points.",
|
|
615
|
+
"Focus on content changes only. Ignore metadata like timestamps.",
|
|
616
|
+
'If no meaningful changes, say "No significant changes."'
|
|
617
|
+
].join("\n");
|
|
618
|
+
const prompt = [
|
|
619
|
+
"Before:",
|
|
620
|
+
beforeJson,
|
|
621
|
+
"",
|
|
622
|
+
"After:",
|
|
623
|
+
afterJson,
|
|
624
|
+
"",
|
|
625
|
+
"What changed?"
|
|
626
|
+
].join("\n");
|
|
627
|
+
const result = await generateText({
|
|
628
|
+
model: languageModel,
|
|
629
|
+
system,
|
|
630
|
+
prompt,
|
|
631
|
+
// Respond more quickly with less creativity.
|
|
632
|
+
temperature: 0.3
|
|
633
|
+
});
|
|
634
|
+
return result.text?.trim() || "";
|
|
635
|
+
}
|
|
636
|
+
async function generatePublishMessage(rootConfig, options) {
|
|
637
|
+
const { model } = requireDefaultModel(rootConfig);
|
|
638
|
+
const languageModel = resolveLanguageModel(model);
|
|
639
|
+
const beforeJson = JSON.stringify(options.before ?? null, null, 2);
|
|
640
|
+
const afterJson = JSON.stringify(options.after ?? null, null, 2);
|
|
641
|
+
const system = [
|
|
642
|
+
"You are an assistant that generates concise commit-style messages for CMS document changes.",
|
|
643
|
+
"Generate a single short sentence (maximum 60 characters) describing the most important change.",
|
|
644
|
+
'Use imperative mood like "Add feature" or "Update content" or "Fix typo".',
|
|
645
|
+
"Focus on the key content change, ignore structural metadata changes.",
|
|
646
|
+
"Do not use punctuation at the end.",
|
|
647
|
+
'Examples: "Add new hero image", "Update pricing details", "Fix typo in headline"'
|
|
648
|
+
].join("\n");
|
|
649
|
+
const prompt = [
|
|
650
|
+
"Previous version JSON:",
|
|
651
|
+
"```json",
|
|
652
|
+
beforeJson,
|
|
653
|
+
"```",
|
|
654
|
+
"",
|
|
655
|
+
"Updated version JSON:",
|
|
656
|
+
"```json",
|
|
657
|
+
afterJson,
|
|
658
|
+
"```",
|
|
659
|
+
"",
|
|
660
|
+
"Generate a commit message for these changes."
|
|
661
|
+
].join("\n");
|
|
662
|
+
const result = await generateText({
|
|
663
|
+
model: languageModel,
|
|
664
|
+
system,
|
|
665
|
+
prompt
|
|
666
|
+
});
|
|
667
|
+
return result.text?.trim() || "";
|
|
668
|
+
}
|
|
669
|
+
async function translateString(rootConfig, options) {
|
|
670
|
+
const { model } = requireDefaultModel(rootConfig);
|
|
671
|
+
const languageModel = resolveLanguageModel(model);
|
|
672
|
+
const system = [
|
|
673
|
+
"You are a professional translator assistant.",
|
|
674
|
+
"Translate the given source text into the requested target languages.",
|
|
675
|
+
"Maintain the tone, style, and intent of the original text.",
|
|
676
|
+
"Return ONLY a valid JSON object with locale codes as keys and translations as values.",
|
|
677
|
+
"Do not include any markdown formatting, code blocks, or explanatory text."
|
|
678
|
+
].join("\n");
|
|
679
|
+
const userPromptParts = [
|
|
680
|
+
`Source text: "${options.sourceText}"`,
|
|
681
|
+
""
|
|
682
|
+
];
|
|
683
|
+
if (options.description) {
|
|
684
|
+
userPromptParts.push(`Context/Description: ${options.description}`, "");
|
|
685
|
+
}
|
|
686
|
+
if (options.existingTranslations && Object.keys(options.existingTranslations).length > 0) {
|
|
687
|
+
userPromptParts.push("Existing translations for reference:");
|
|
688
|
+
Object.entries(options.existingTranslations).forEach(
|
|
689
|
+
([locale, translation]) => {
|
|
690
|
+
if (translation) {
|
|
691
|
+
userPromptParts.push(`- ${locale}: "${translation}"`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
);
|
|
695
|
+
userPromptParts.push("");
|
|
696
|
+
}
|
|
697
|
+
userPromptParts.push(
|
|
698
|
+
`Target locales: ${options.targetLocales.join(", ")}`,
|
|
699
|
+
"",
|
|
700
|
+
"Provide translations as a JSON object with locale codes as keys."
|
|
701
|
+
);
|
|
702
|
+
const result = await generateText({
|
|
703
|
+
model: languageModel,
|
|
704
|
+
system,
|
|
705
|
+
prompt: userPromptParts.join("\n")
|
|
706
|
+
});
|
|
707
|
+
const responseText = result.text || "{}";
|
|
708
|
+
const jsonText = extractJsonFromResponse(responseText);
|
|
709
|
+
try {
|
|
710
|
+
return JSON.parse(jsonText);
|
|
711
|
+
} catch (err) {
|
|
712
|
+
console.error("failed to parse AI translation response:", responseText);
|
|
713
|
+
throw new Error("Invalid response format from AI translation");
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async function generateAltText(rootConfig, options) {
|
|
717
|
+
const { model } = requireDefaultModel(rootConfig);
|
|
718
|
+
const languageModel = resolveLanguageModel(model);
|
|
719
|
+
const system = [
|
|
720
|
+
"Create a descriptive and concise alt text for the attached image.",
|
|
721
|
+
"",
|
|
722
|
+
"- The alt text should be a brief but comprehensive description of the image, including key subjects, the setting, and any relevant details or actions.",
|
|
723
|
+
"- The alt text should not exceed 125 characters.",
|
|
724
|
+
"- Only provide one generation, and include only that data in the response. No surrounding text, clarifications, etc. Just the alt text."
|
|
725
|
+
].join("\n");
|
|
726
|
+
const result = await generateText({
|
|
727
|
+
model: languageModel,
|
|
728
|
+
system,
|
|
729
|
+
messages: [
|
|
730
|
+
{
|
|
731
|
+
role: "user",
|
|
732
|
+
content: [
|
|
733
|
+
{ type: "text", text: "Generate alt text for the image above." },
|
|
734
|
+
{ type: "image", image: new URL(options.imageUrl) }
|
|
735
|
+
]
|
|
736
|
+
}
|
|
737
|
+
]
|
|
738
|
+
});
|
|
739
|
+
return result.text?.trim() || "";
|
|
740
|
+
}
|
|
741
|
+
async function generateImage(rootConfig, options) {
|
|
742
|
+
const config = getAiConfig(rootConfig);
|
|
743
|
+
if (!config) {
|
|
744
|
+
throw new Error("AI is not configured. Set `ai` on the cmsPlugin config.");
|
|
745
|
+
}
|
|
746
|
+
const imageModelConfig = findImageModel(config, options.modelId);
|
|
747
|
+
if (!imageModelConfig) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
"No image model configured. Set `ai.imageModels` on the cmsPlugin config."
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const imageModel = resolveImageModel(imageModelConfig);
|
|
753
|
+
const result = await generateImageSdk({
|
|
754
|
+
model: imageModel,
|
|
755
|
+
prompt: options.prompt,
|
|
756
|
+
aspectRatio: options.aspectRatio
|
|
757
|
+
});
|
|
758
|
+
if (!result.image) {
|
|
759
|
+
throw new Error("No image generated");
|
|
760
|
+
}
|
|
761
|
+
const mediaType = result.image.mediaType || "image/png";
|
|
762
|
+
return { imageUrl: `data:${mediaType};base64,${result.image.base64}` };
|
|
763
|
+
}
|
|
764
|
+
function extractJsonFromResponse(responseText) {
|
|
765
|
+
let jsonText = responseText.trim();
|
|
766
|
+
if (jsonText.startsWith("```")) {
|
|
767
|
+
const lines = jsonText.split("\n");
|
|
768
|
+
jsonText = lines.slice(1, -1).join("\n");
|
|
769
|
+
if (jsonText.startsWith("json")) {
|
|
770
|
+
jsonText = jsonText.substring(4).trim();
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return jsonText;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// package.json
|
|
777
|
+
var package_default = {
|
|
778
|
+
name: "@blinkk/root-cms",
|
|
779
|
+
version: "3.0.1-beta.3",
|
|
780
|
+
author: "s@blinkk.com",
|
|
781
|
+
license: "MIT",
|
|
782
|
+
engines: {
|
|
783
|
+
node: ">=16.0.0"
|
|
784
|
+
},
|
|
785
|
+
repository: {
|
|
786
|
+
type: "git",
|
|
787
|
+
url: "git+https://github.com/blinkk/rootjs.git",
|
|
788
|
+
directory: "packages/root-cms"
|
|
789
|
+
},
|
|
790
|
+
files: [
|
|
791
|
+
"dist/**/*"
|
|
792
|
+
],
|
|
793
|
+
bin: {
|
|
794
|
+
"root-cms": "./bin/root-cms.js"
|
|
795
|
+
},
|
|
796
|
+
type: "module",
|
|
797
|
+
module: "./dist/index.js",
|
|
798
|
+
types: "./dist/index.d.ts",
|
|
799
|
+
exports: {
|
|
800
|
+
".": {
|
|
801
|
+
types: "./dist/core.d.ts",
|
|
802
|
+
import: "./dist/core.js"
|
|
803
|
+
},
|
|
804
|
+
"./client": {
|
|
805
|
+
types: "./dist/client.d.ts",
|
|
806
|
+
import: "./dist/client.js"
|
|
807
|
+
},
|
|
808
|
+
"./core": {
|
|
809
|
+
types: "./dist/core.d.ts",
|
|
810
|
+
import: "./dist/core.js"
|
|
811
|
+
},
|
|
812
|
+
"./functions": {
|
|
813
|
+
types: "./dist/functions.d.ts",
|
|
814
|
+
import: "./dist/functions.js"
|
|
815
|
+
},
|
|
816
|
+
"./plugin": {
|
|
817
|
+
types: "./dist/plugin.d.ts",
|
|
818
|
+
import: "./dist/plugin.js"
|
|
819
|
+
},
|
|
820
|
+
"./project": {
|
|
821
|
+
types: "./dist/project.d.ts",
|
|
822
|
+
import: "./dist/project.js"
|
|
823
|
+
},
|
|
824
|
+
"./richtext": {
|
|
825
|
+
types: "./dist/richtext.d.ts",
|
|
826
|
+
import: "./dist/richtext.js"
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
scripts: {
|
|
830
|
+
build: 'rm -rf dist && concurrently -n "core,signin,ui" npm:build:core npm:build:signin npm:build:ui',
|
|
831
|
+
"build:core": "tsup-node --config=./core/tsup.config.ts",
|
|
832
|
+
"//": "NOTE: esbuild is used here because tsup doesn't currently support aliases.",
|
|
833
|
+
"build:ui": "esbuild ui/ui.tsx --bundle --minify --alias:react=@preact/compat --alias:react-dom=@preact/compat --tsconfig=ui/tsconfig.json --outdir=dist/ui --legal-comments=external",
|
|
834
|
+
"build:signin": "esbuild signin/signin.tsx --bundle --minify --tsconfig=signin/tsconfig.json --outdir=dist/ui --legal-comments=external",
|
|
835
|
+
dev: 'rm -rf dist && concurrently -k -n "core,ui" npm:dev:core npm:dev:signin npm:dev:ui',
|
|
836
|
+
"dev:core": "pnpm build:core --watch",
|
|
837
|
+
"dev:signin": "pnpm build:signin --watch",
|
|
838
|
+
"dev:ui": "pnpm build:ui --watch",
|
|
839
|
+
test: "pnpm build && firebase emulators:exec 'vitest run --exclude=**/*.visual.test.tsx'",
|
|
840
|
+
"test:visual": "vitest run --config=vitest.config.visual.ts",
|
|
841
|
+
"test:watch": "pnpm build && firebase emulators:exec 'vitest'"
|
|
842
|
+
},
|
|
843
|
+
dependencies: {
|
|
844
|
+
"@ag-grid-community/client-side-row-model": "32.3.9",
|
|
845
|
+
"@ag-grid-community/core": "32.3.9",
|
|
846
|
+
"@ag-grid-community/react": "32.3.9",
|
|
847
|
+
"@ag-grid-community/styles": "32.3.9",
|
|
848
|
+
"@ai-sdk/anthropic": "3.0.74",
|
|
849
|
+
"@ai-sdk/google": "3.0.67",
|
|
850
|
+
"@ai-sdk/openai": "3.0.60",
|
|
851
|
+
"@ai-sdk/openai-compatible": "2.0.46",
|
|
852
|
+
"@ai-sdk/react": "3.0.177",
|
|
853
|
+
ai: "6.0.175",
|
|
854
|
+
zod: "4.4.3",
|
|
855
|
+
"@google-cloud/firestore": "7.11.3",
|
|
856
|
+
"@hello-pangea/dnd": "18.0.1",
|
|
857
|
+
"@types/cli-progress": "3.11.6",
|
|
858
|
+
"body-parser": "1.20.2",
|
|
859
|
+
"cli-progress": "3.12.0",
|
|
860
|
+
commander: "11.0.0",
|
|
861
|
+
"csv-parse": "5.5.2",
|
|
862
|
+
"csv-stringify": "6.4.4",
|
|
863
|
+
"date-fns": "4.1.0",
|
|
864
|
+
"date-fns-tz": "3.2.0",
|
|
865
|
+
diff: "8.0.2",
|
|
866
|
+
"dts-dom": "3.7.0",
|
|
867
|
+
"@mantine/spotlight": "4.2.12",
|
|
868
|
+
"fnv-plus": "1.3.1",
|
|
869
|
+
jsonwebtoken: "9.0.2",
|
|
870
|
+
kleur: "4.1.5",
|
|
871
|
+
minisearch: "7.2.0",
|
|
872
|
+
sirv: "2.0.3",
|
|
873
|
+
"tiny-glob": "0.2.9"
|
|
874
|
+
},
|
|
875
|
+
"//": "NOTE(stevenle): due to compat issues with mantine and preact, mantine is pinned to v4.2.12",
|
|
876
|
+
devDependencies: {
|
|
877
|
+
"@babel/core": "7.17.9",
|
|
878
|
+
"@blinkk/root": "workspace:*",
|
|
879
|
+
"@editorjs/editorjs": "2.30.8",
|
|
880
|
+
"@editorjs/header": "2.8.8",
|
|
881
|
+
"@editorjs/image": "2.10.2",
|
|
882
|
+
"@editorjs/list": "2.0.6",
|
|
883
|
+
"@editorjs/nested-list": "1.4.3",
|
|
884
|
+
"@editorjs/raw": "2.5.1",
|
|
885
|
+
"@editorjs/table": "2.4.4",
|
|
886
|
+
"@editorjs/underline": "1.2.1",
|
|
887
|
+
"@emotion/react": "11.10.5",
|
|
888
|
+
"@firebase/app-compat": "0.5.2",
|
|
889
|
+
"@firebase/app-types": "0.9.3",
|
|
890
|
+
"@firebase/rules-unit-testing": "5.0.0",
|
|
891
|
+
"@lexical/code": "0.33.1",
|
|
892
|
+
"@lexical/html": "0.33.1",
|
|
893
|
+
"@lexical/link": "0.33.1",
|
|
894
|
+
"@lexical/list": "0.33.1",
|
|
895
|
+
"@lexical/markdown": "0.33.1",
|
|
896
|
+
"@lexical/react": "0.33.1",
|
|
897
|
+
"@lexical/rich-text": "0.33.1",
|
|
898
|
+
"@lexical/selection": "0.33.1",
|
|
899
|
+
"@lexical/table": "0.33.1",
|
|
900
|
+
"@lexical/utils": "0.33.1",
|
|
901
|
+
"@mantine/core": "4.2.12",
|
|
902
|
+
"@mantine/hooks": "4.2.12",
|
|
903
|
+
"@mantine/modals": "4.2.12",
|
|
904
|
+
"@mantine/notifications": "4.2.12",
|
|
905
|
+
"@preact/compat": "18.3.1",
|
|
906
|
+
"@tabler/icons-preact": "3.35.0",
|
|
907
|
+
"@testing-library/preact": "3.2.4",
|
|
908
|
+
"@testing-library/user-event": "14.6.1",
|
|
909
|
+
"@types/body-parser": "1.19.3",
|
|
910
|
+
"@types/fnv-plus": "1.3.2",
|
|
911
|
+
"@types/gapi": "0.0.47",
|
|
912
|
+
"@types/gapi.client.drive-v3": "0.0.4",
|
|
913
|
+
"@types/gapi.client.sheets-v4": "0.0.4",
|
|
914
|
+
"@types/google.accounts": "0.0.14",
|
|
915
|
+
"@types/jsonwebtoken": "9.0.1",
|
|
916
|
+
"@types/node": "24.3.1",
|
|
917
|
+
"@vitest/browser": "4.1.2",
|
|
918
|
+
"@vitest/browser-playwright": "4.1.2",
|
|
919
|
+
concurrently: "7.6.0",
|
|
920
|
+
esbuild: "0.25.9",
|
|
921
|
+
firebase: "12.2.1",
|
|
922
|
+
"firebase-admin": "13.5.0",
|
|
923
|
+
"firebase-functions": "6.4.0",
|
|
924
|
+
"firebase-tools": "14.15.2",
|
|
925
|
+
"highlight.js": "11.6.0",
|
|
926
|
+
jsdom: "27.2.0",
|
|
927
|
+
"json-diff-kit": "1.0.29",
|
|
928
|
+
lexical: "0.33.1",
|
|
929
|
+
marked: "9.1.1",
|
|
930
|
+
"mdast-util-from-markdown": "2.0.1",
|
|
931
|
+
"mdast-util-gfm": "3.0.0",
|
|
932
|
+
"micromark-extension-gfm": "3.0.0",
|
|
933
|
+
playwright: "1.56.1",
|
|
934
|
+
preact: "10.27.1",
|
|
935
|
+
"preact-iso": "2.11.1",
|
|
936
|
+
"preact-render-to-string": "6.6.7",
|
|
937
|
+
react: "npm:@preact/compat@18.3.1",
|
|
938
|
+
"react-dom": "npm:@preact/compat@18.3.1",
|
|
939
|
+
"react-easy-crop": "5.5.6",
|
|
940
|
+
"react-json-view-compare": "2.0.2",
|
|
941
|
+
tsup: "8.5.0",
|
|
942
|
+
typescript: "5.9.2",
|
|
943
|
+
vite: "8.0.3",
|
|
944
|
+
vitest: "4.1.2",
|
|
945
|
+
yjs: "13.6.27"
|
|
946
|
+
},
|
|
947
|
+
peerDependencies: {
|
|
948
|
+
"@blinkk/root": "3.0.1-beta.3",
|
|
949
|
+
"firebase-admin": ">=11",
|
|
950
|
+
"firebase-functions": ">=4"
|
|
951
|
+
},
|
|
952
|
+
peerDependenciesMeta: {
|
|
953
|
+
"firebase-functions": {
|
|
954
|
+
optional: true
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// core/server-version.ts
|
|
960
|
+
var SERVER_STARTUP_TS = String(Math.floor((/* @__PURE__ */ new Date()).getTime() / 1e3));
|
|
961
|
+
function getServerVersion() {
|
|
962
|
+
if (process.env.NODE_ENV === "development") {
|
|
963
|
+
return SERVER_STARTUP_TS;
|
|
964
|
+
}
|
|
965
|
+
return package_default?.version || "root-3.0.0";
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export {
|
|
969
|
+
serializeAiConfig,
|
|
970
|
+
getAiConfig,
|
|
971
|
+
findModel,
|
|
972
|
+
normalizeExecutionMode,
|
|
973
|
+
ChatStore,
|
|
974
|
+
runChatStream,
|
|
975
|
+
runEditObjectStream,
|
|
976
|
+
summarizeDiff,
|
|
977
|
+
generatePublishMessage,
|
|
978
|
+
translateString,
|
|
979
|
+
generateAltText,
|
|
980
|
+
generateImage,
|
|
981
|
+
getServerVersion
|
|
982
|
+
};
|