plugin-build-guide-block 1.0.11 → 1.1.2
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/client/components/SpaceSelect.d.ts +2 -0
- package/dist/client/index.js +9 -1
- package/dist/client/locale.d.ts +3 -0
- package/dist/client/models/UserGuideBlockModel.d.ts +3 -3
- package/dist/client/schemaSettings.d.ts +2 -0
- package/dist/client/schemas/spacesSchema.d.ts +118 -0
- package/dist/externalVersion.js +8 -8
- package/dist/locale/en-US.json +12 -1
- package/dist/locale/namespace.d.ts +6 -0
- package/dist/locale/namespace.js +36 -0
- package/dist/locale/vi-VN.json +3 -0
- package/dist/locale/zh-CN.json +3 -0
- package/dist/node_modules/marked/bin/main.js +279 -0
- package/dist/node_modules/marked/bin/marked.js +15 -0
- package/dist/node_modules/marked/lib/marked.cjs +1 -0
- package/dist/node_modules/marked/lib/marked.d.cts +657 -0
- package/dist/node_modules/marked/lib/marked.d.ts +657 -0
- package/dist/node_modules/marked/lib/marked.esm.js +2432 -0
- package/dist/node_modules/marked/lib/marked.umd.js +2456 -0
- package/dist/node_modules/marked/man/marked.1 +111 -0
- package/dist/node_modules/marked/marked.min.js +6 -0
- package/dist/node_modules/marked/package.json +1 -0
- package/dist/server/actions/build.js +383 -101
- package/dist/server/actions/getMarkdown.d.ts +2 -0
- package/dist/server/actions/getMarkdown.js +53 -0
- package/dist/server/collections/ai-build-guide-pages.d.ts +2 -0
- package/dist/server/collections/ai-build-guide-pages.js +90 -0
- package/dist/server/collections/ai-build-guide-spaces.js +42 -0
- package/dist/server/plugin.d.ts +3 -0
- package/dist/server/plugin.js +58 -13
- package/package.json +51 -31
- package/src/client/UserGuideBlock.tsx +368 -53
- package/src/client/UserGuideBlockProvider.tsx +9 -8
- package/src/client/components/SpaceSelect.tsx +37 -0
- package/src/client/locale.ts +18 -0
- package/src/client/models/UserGuideBlockModel.ts +19 -29
- package/src/client/plugin.tsx +53 -30
- package/src/client/schemaSettings.ts +65 -0
- package/src/client/schemas/spacesSchema.ts +434 -316
- package/src/locale/en-US.json +12 -1
- package/src/locale/namespace.ts +6 -0
- package/src/locale/vi-VN.json +3 -0
- package/src/locale/zh-CN.json +3 -0
- package/src/server/actions/build.ts +497 -176
- package/src/server/actions/getMarkdown.ts +26 -0
- package/src/server/collections/ai-build-guide-pages.ts +60 -0
- package/src/server/collections/ai-build-guide-spaces.ts +57 -15
- package/src/server/plugin.ts +130 -76
- package/src/server/collections/.gitkeep +0 -0
|
@@ -44,6 +44,61 @@ var import_messages = require("@langchain/core/messages");
|
|
|
44
44
|
var import_axios = __toESM(require("axios"));
|
|
45
45
|
var import_fs = __toESM(require("fs"));
|
|
46
46
|
var import_path = __toESM(require("path"));
|
|
47
|
+
var import_crypto = __toESM(require("crypto"));
|
|
48
|
+
var import_marked = require("marked");
|
|
49
|
+
const MAX_SOURCE_CHARS = 9e4;
|
|
50
|
+
const MIN_CHAPTERS = 3;
|
|
51
|
+
const MAX_CHAPTERS = 12;
|
|
52
|
+
const DEFAULT_TARGET_CHAPTERS = 5;
|
|
53
|
+
const SANITIZE_OPTIONS = {
|
|
54
|
+
allowedTags: [
|
|
55
|
+
"div",
|
|
56
|
+
"p",
|
|
57
|
+
"h1",
|
|
58
|
+
"h2",
|
|
59
|
+
"h3",
|
|
60
|
+
"h4",
|
|
61
|
+
"h5",
|
|
62
|
+
"h6",
|
|
63
|
+
"ul",
|
|
64
|
+
"ol",
|
|
65
|
+
"li",
|
|
66
|
+
"table",
|
|
67
|
+
"thead",
|
|
68
|
+
"tbody",
|
|
69
|
+
"tr",
|
|
70
|
+
"td",
|
|
71
|
+
"th",
|
|
72
|
+
"a",
|
|
73
|
+
"img",
|
|
74
|
+
"span",
|
|
75
|
+
"strong",
|
|
76
|
+
"em",
|
|
77
|
+
"code",
|
|
78
|
+
"pre",
|
|
79
|
+
"blockquote",
|
|
80
|
+
"br",
|
|
81
|
+
"hr"
|
|
82
|
+
],
|
|
83
|
+
allowedAttributes: {
|
|
84
|
+
a: ["href", "target"],
|
|
85
|
+
img: ["src", "alt", "width", "height"],
|
|
86
|
+
"*": ["style", "class", "id"]
|
|
87
|
+
},
|
|
88
|
+
allowedStyles: {
|
|
89
|
+
"*": {
|
|
90
|
+
color: [/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, /^rgb/, /^rgba/],
|
|
91
|
+
"background-color": [/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, /^rgb/, /^rgba/],
|
|
92
|
+
"text-align": [/^left$/, /^right$/, /^center$/, /^justify$/],
|
|
93
|
+
"font-size": [/^\d+(?:px|em|%)$/]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
function clampChapterCount(value) {
|
|
98
|
+
const count = Number(value);
|
|
99
|
+
if (!Number.isFinite(count)) return DEFAULT_TARGET_CHAPTERS;
|
|
100
|
+
return Math.max(MIN_CHAPTERS, Math.min(MAX_CHAPTERS, Math.round(count)));
|
|
101
|
+
}
|
|
47
102
|
async function fetchFileContent(app, file) {
|
|
48
103
|
const fileManager = app.pm.get("file-manager");
|
|
49
104
|
if (!fileManager) return "";
|
|
@@ -52,20 +107,334 @@ async function fetchFileContent(app, file) {
|
|
|
52
107
|
if (url.startsWith("http")) {
|
|
53
108
|
const response = await import_axios.default.get(url, { responseType: "text", timeout: 15e3 });
|
|
54
109
|
return response.data;
|
|
55
|
-
} else {
|
|
56
|
-
let localPath = url;
|
|
57
|
-
if (process.env.APP_PUBLIC_PATH && localPath.startsWith(process.env.APP_PUBLIC_PATH)) {
|
|
58
|
-
localPath = localPath.slice(process.env.APP_PUBLIC_PATH.length);
|
|
59
|
-
}
|
|
60
|
-
localPath = import_path.default.join(process.cwd(), localPath);
|
|
61
|
-
const data = await import_fs.default.promises.readFile(localPath, "utf8");
|
|
62
|
-
return data;
|
|
63
110
|
}
|
|
111
|
+
let localPath = url;
|
|
112
|
+
if (process.env.APP_PUBLIC_PATH && localPath.startsWith(process.env.APP_PUBLIC_PATH)) {
|
|
113
|
+
localPath = localPath.slice(process.env.APP_PUBLIC_PATH.length);
|
|
114
|
+
}
|
|
115
|
+
localPath = import_path.default.join(process.cwd(), localPath);
|
|
116
|
+
return await import_fs.default.promises.readFile(localPath, "utf8");
|
|
64
117
|
} catch (err) {
|
|
65
118
|
app.log.error(`Failed to read file content for document ${file.id}`, err);
|
|
66
119
|
return `[Failed to read document: ${file.filename}]`;
|
|
67
120
|
}
|
|
68
121
|
}
|
|
122
|
+
function toPlainText(value) {
|
|
123
|
+
if (typeof value === "string") return value;
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
return value.map((item) => {
|
|
126
|
+
if (typeof item === "string") return item;
|
|
127
|
+
if (typeof (item == null ? void 0 : item.text) === "string") return item.text;
|
|
128
|
+
if (typeof (item == null ? void 0 : item.content) === "string") return item.content;
|
|
129
|
+
return "";
|
|
130
|
+
}).filter(Boolean).join("\n");
|
|
131
|
+
}
|
|
132
|
+
if (value && typeof value === "object") {
|
|
133
|
+
const text = value.text || value.content;
|
|
134
|
+
if (typeof text === "string") return text;
|
|
135
|
+
}
|
|
136
|
+
return JSON.stringify(value);
|
|
137
|
+
}
|
|
138
|
+
function stripFence(text) {
|
|
139
|
+
return text.replace(/^```(?:json|markdown|md|html)?\s*/i, "").replace(/```\s*$/i, "").trim();
|
|
140
|
+
}
|
|
141
|
+
function slugify(text, fallback) {
|
|
142
|
+
const slug = text.toLowerCase().normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
143
|
+
return slug || fallback;
|
|
144
|
+
}
|
|
145
|
+
function createFallbackPlan(guideTitle, targetChapterCount) {
|
|
146
|
+
const chapterTemplates = [
|
|
147
|
+
{
|
|
148
|
+
title: "Overview and scope",
|
|
149
|
+
goal: "Explain what the guide covers, who it is for, and the expected outcome."
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
title: "Prerequisites and preparation",
|
|
153
|
+
goal: "List required access, tools, input data, setup steps, and assumptions before starting."
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
title: "Initial setup",
|
|
157
|
+
goal: "Guide the user through the first required configuration or installation flow."
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
title: "Core workflow",
|
|
161
|
+
goal: "Describe the main task flow users need to complete successfully."
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
title: "Advanced usage and configuration",
|
|
165
|
+
goal: "Cover optional settings, variations, and deeper operational procedures."
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
title: "Verification and validation",
|
|
169
|
+
goal: "Show how to confirm that the setup or workflow is working correctly."
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
title: "Troubleshooting",
|
|
173
|
+
goal: "Document common problems, likely causes, and recovery steps."
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
title: "Reference and next steps",
|
|
177
|
+
goal: "Summarize related tasks, reference information, and recommended follow-up actions."
|
|
178
|
+
}
|
|
179
|
+
];
|
|
180
|
+
return {
|
|
181
|
+
title: guideTitle,
|
|
182
|
+
chapters: Array.from({ length: targetChapterCount }, (_, index) => {
|
|
183
|
+
const template = chapterTemplates[index] || {
|
|
184
|
+
title: `Additional topic ${index + 1}`,
|
|
185
|
+
goal: "Expand an important topic from the source documents into a focused guide section."
|
|
186
|
+
};
|
|
187
|
+
return {
|
|
188
|
+
...template,
|
|
189
|
+
sourceHints: []
|
|
190
|
+
};
|
|
191
|
+
})
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function normalizePlan(rawText, guideTitle, targetChapterCount) {
|
|
195
|
+
const cleanText = stripFence(rawText);
|
|
196
|
+
const jsonStart = cleanText.indexOf("{");
|
|
197
|
+
const jsonEnd = cleanText.lastIndexOf("}");
|
|
198
|
+
const jsonText = jsonStart >= 0 && jsonEnd > jsonStart ? cleanText.slice(jsonStart, jsonEnd + 1) : cleanText;
|
|
199
|
+
let parsed;
|
|
200
|
+
try {
|
|
201
|
+
parsed = JSON.parse(jsonText);
|
|
202
|
+
} catch {
|
|
203
|
+
return createFallbackPlan(guideTitle, targetChapterCount);
|
|
204
|
+
}
|
|
205
|
+
const rawChapters = Array.isArray(parsed == null ? void 0 : parsed.chapters) ? parsed.chapters : [];
|
|
206
|
+
const chapters = rawChapters.slice(0, MAX_CHAPTERS).map((item, index) => ({
|
|
207
|
+
title: String((item == null ? void 0 : item.title) || `Section ${index + 1}`),
|
|
208
|
+
goal: (item == null ? void 0 : item.goal) ? String(item.goal) : "",
|
|
209
|
+
sourceHints: Array.isArray(item == null ? void 0 : item.sourceHints) ? item.sourceHints.map((hint) => String(hint)) : []
|
|
210
|
+
})).filter((item) => item.title);
|
|
211
|
+
if (chapters.length < MIN_CHAPTERS) {
|
|
212
|
+
return createFallbackPlan((parsed == null ? void 0 : parsed.title) ? String(parsed.title) : guideTitle, targetChapterCount);
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
title: (parsed == null ? void 0 : parsed.title) ? String(parsed.title) : guideTitle,
|
|
216
|
+
chapters
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
async function getLLMProvider(app, llmService, model) {
|
|
220
|
+
const aiPlugin = app.pm.get("ai");
|
|
221
|
+
if (!aiPlugin) {
|
|
222
|
+
throw new Error("Plugin AI is not available");
|
|
223
|
+
}
|
|
224
|
+
const serviceData = await aiPlugin.aiManager.getLLMService({ llmService, model });
|
|
225
|
+
return serviceData.provider;
|
|
226
|
+
}
|
|
227
|
+
async function buildPlan(provider, space, documentsText) {
|
|
228
|
+
const { title, systemPrompt, targetChapterCount, chapterGuidance } = space.get();
|
|
229
|
+
const targetCount = clampChapterCount(targetChapterCount);
|
|
230
|
+
const messages = [];
|
|
231
|
+
if (systemPrompt) {
|
|
232
|
+
messages.push(new import_messages.SystemMessage(systemPrompt));
|
|
233
|
+
}
|
|
234
|
+
messages.push(
|
|
235
|
+
new import_messages.HumanMessage(`Create a concise breakdown plan for a multi-page user guide.
|
|
236
|
+
|
|
237
|
+
Return ONLY valid JSON with this shape:
|
|
238
|
+
{
|
|
239
|
+
"title": "Guide title",
|
|
240
|
+
"chapters": [
|
|
241
|
+
{
|
|
242
|
+
"title": "Chapter title",
|
|
243
|
+
"goal": "What this chapter should teach",
|
|
244
|
+
"sourceHints": ["Relevant topics, file names, or keywords"]
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
Rules:
|
|
250
|
+
- Create exactly ${targetCount} chapters.
|
|
251
|
+
- Each chapter must cover a distinct user goal. Do not collapse the guide into a single chapter.
|
|
252
|
+
- Keep chapter titles user-facing and action-oriented.
|
|
253
|
+
- Do not include markdown fences or explanations.
|
|
254
|
+
|
|
255
|
+
Guide title: ${title || "User guide"}
|
|
256
|
+
|
|
257
|
+
Chapter guidance:
|
|
258
|
+
${chapterGuidance || "Use the source document structure and split the guide into logical user-facing tasks."}
|
|
259
|
+
|
|
260
|
+
Source documents:
|
|
261
|
+
${documentsText.slice(0, MAX_SOURCE_CHARS)}`)
|
|
262
|
+
);
|
|
263
|
+
const response = await provider.chatModel.invoke(messages);
|
|
264
|
+
return normalizePlan(toPlainText(response.content), title || "User guide", targetCount);
|
|
265
|
+
}
|
|
266
|
+
async function buildPageMarkdown(provider, space, plan, chapter, documentsText) {
|
|
267
|
+
const { title, systemPrompt } = space.get();
|
|
268
|
+
const messages = [];
|
|
269
|
+
if (systemPrompt) {
|
|
270
|
+
messages.push(new import_messages.SystemMessage(systemPrompt));
|
|
271
|
+
}
|
|
272
|
+
messages.push(
|
|
273
|
+
new import_messages.HumanMessage(`Write one chapter for a user guide in pure Markdown.
|
|
274
|
+
|
|
275
|
+
Output rules:
|
|
276
|
+
- Output ONLY Markdown content for this chapter.
|
|
277
|
+
- Start with a level-2 heading using the chapter title.
|
|
278
|
+
- Use clear steps, tables, and callouts when useful.
|
|
279
|
+
- Do not wrap the whole response in a code fence.
|
|
280
|
+
- Do not mention that this was generated by AI.
|
|
281
|
+
|
|
282
|
+
Guide title: ${plan.title || title || "User guide"}
|
|
283
|
+
|
|
284
|
+
Full chapter plan:
|
|
285
|
+
${JSON.stringify(plan, null, 2)}
|
|
286
|
+
|
|
287
|
+
Chapter to write:
|
|
288
|
+
${JSON.stringify(chapter, null, 2)}
|
|
289
|
+
|
|
290
|
+
Source documents:
|
|
291
|
+
${documentsText.slice(0, MAX_SOURCE_CHARS)}`)
|
|
292
|
+
);
|
|
293
|
+
const response = await provider.chatModel.invoke(messages);
|
|
294
|
+
return stripFence(toPlainText(response.content));
|
|
295
|
+
}
|
|
296
|
+
async function markdownToCleanHtml(markdown) {
|
|
297
|
+
const renderedHtml = await import_marked.marked.parse(markdown, { async: true });
|
|
298
|
+
return (0, import_sanitize_html.default)(renderedHtml, SANITIZE_OPTIONS);
|
|
299
|
+
}
|
|
300
|
+
async function readDocuments(app, space) {
|
|
301
|
+
const documents = await space.getDocuments();
|
|
302
|
+
if (!documents || documents.length === 0) {
|
|
303
|
+
return "";
|
|
304
|
+
}
|
|
305
|
+
const texts = await Promise.all(
|
|
306
|
+
documents.map(async (doc) => {
|
|
307
|
+
const content = await fetchFileContent(app, doc);
|
|
308
|
+
return `--- Document: ${doc.filename || doc.id} ---
|
|
309
|
+
${content}
|
|
310
|
+
`;
|
|
311
|
+
})
|
|
312
|
+
);
|
|
313
|
+
return texts.join("\n");
|
|
314
|
+
}
|
|
315
|
+
async function runBuild(app, db, filterByTk) {
|
|
316
|
+
const spaceRepo = db.getRepository("aiBuildGuideSpaces");
|
|
317
|
+
const pageRepo = db.getRepository("aiBuildGuidePages");
|
|
318
|
+
const space = await spaceRepo.findById(filterByTk);
|
|
319
|
+
if (!space) {
|
|
320
|
+
throw new Error("Space not found");
|
|
321
|
+
}
|
|
322
|
+
const { llmService, model } = space.get();
|
|
323
|
+
if (!llmService || !model) {
|
|
324
|
+
throw new Error("LLM Service or model is missing in space configuration");
|
|
325
|
+
}
|
|
326
|
+
await pageRepo.destroy({
|
|
327
|
+
filter: {
|
|
328
|
+
spaceId: filterByTk
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
await spaceRepo.update({
|
|
332
|
+
filterByTk,
|
|
333
|
+
values: {
|
|
334
|
+
buildPhase: "reading",
|
|
335
|
+
buildLog: "Reading source documents",
|
|
336
|
+
generatedHtml: null,
|
|
337
|
+
generatedMarkdown: null,
|
|
338
|
+
planJson: null,
|
|
339
|
+
pageCount: 0
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
const documentsText = await readDocuments(app, space);
|
|
343
|
+
const sourceHash = import_crypto.default.createHash("sha256").update(documentsText).digest("hex");
|
|
344
|
+
const provider = await getLLMProvider(app, llmService, model);
|
|
345
|
+
await spaceRepo.update({
|
|
346
|
+
filterByTk,
|
|
347
|
+
values: {
|
|
348
|
+
buildPhase: "planning",
|
|
349
|
+
buildLog: "Creating guide breakdown plan",
|
|
350
|
+
sourceHash
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
const plan = await buildPlan(provider, space, documentsText);
|
|
354
|
+
await spaceRepo.update({
|
|
355
|
+
filterByTk,
|
|
356
|
+
values: {
|
|
357
|
+
planJson: plan,
|
|
358
|
+
pageCount: plan.chapters.length,
|
|
359
|
+
buildPhase: "building_pages",
|
|
360
|
+
buildLog: `Plan created with ${plan.chapters.length} chapters`
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
const pageRecords = [];
|
|
364
|
+
for (const [index, chapter] of plan.chapters.entries()) {
|
|
365
|
+
const page = await pageRepo.create({
|
|
366
|
+
values: {
|
|
367
|
+
spaceId: filterByTk,
|
|
368
|
+
sort: index + 1,
|
|
369
|
+
title: chapter.title,
|
|
370
|
+
slug: slugify(chapter.title, `chapter-${index + 1}`),
|
|
371
|
+
goal: chapter.goal,
|
|
372
|
+
planItem: chapter,
|
|
373
|
+
status: "pending"
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
pageRecords.push(page);
|
|
377
|
+
}
|
|
378
|
+
for (const [index, page] of pageRecords.entries()) {
|
|
379
|
+
const chapter = plan.chapters[index];
|
|
380
|
+
const pageId = page.get("id");
|
|
381
|
+
await pageRepo.update({
|
|
382
|
+
filterByTk: pageId,
|
|
383
|
+
values: {
|
|
384
|
+
status: "building",
|
|
385
|
+
buildLog: "Building chapter with LLM"
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
await spaceRepo.update({
|
|
389
|
+
filterByTk,
|
|
390
|
+
values: {
|
|
391
|
+
buildPhase: "building_pages",
|
|
392
|
+
buildLog: `Building chapter ${index + 1}/${pageRecords.length}: ${chapter.title}`
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
try {
|
|
396
|
+
const markdown = await buildPageMarkdown(provider, space, plan, chapter, documentsText);
|
|
397
|
+
const html = await markdownToCleanHtml(markdown);
|
|
398
|
+
await pageRepo.update({
|
|
399
|
+
filterByTk: pageId,
|
|
400
|
+
values: {
|
|
401
|
+
status: "completed",
|
|
402
|
+
generatedMarkdown: markdown,
|
|
403
|
+
generatedHtml: html,
|
|
404
|
+
buildLog: null
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
} catch (error) {
|
|
408
|
+
await pageRepo.update({
|
|
409
|
+
filterByTk: pageId,
|
|
410
|
+
values: {
|
|
411
|
+
status: "error",
|
|
412
|
+
buildLog: error.message || String(error)
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
throw error;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const completedPages = await pageRepo.find({
|
|
419
|
+
filter: {
|
|
420
|
+
spaceId: filterByTk,
|
|
421
|
+
status: "completed"
|
|
422
|
+
},
|
|
423
|
+
sort: ["sort"]
|
|
424
|
+
});
|
|
425
|
+
const combinedMarkdown = completedPages.map((page) => page.get("generatedMarkdown")).filter(Boolean).join("\n\n---\n\n");
|
|
426
|
+
const combinedHtml = await markdownToCleanHtml(combinedMarkdown);
|
|
427
|
+
await spaceRepo.update({
|
|
428
|
+
filterByTk,
|
|
429
|
+
values: {
|
|
430
|
+
status: "completed",
|
|
431
|
+
buildPhase: "completed",
|
|
432
|
+
buildLog: `Built ${completedPages.length} chapters successfully`,
|
|
433
|
+
generatedMarkdown: combinedMarkdown,
|
|
434
|
+
generatedHtml: combinedHtml
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
69
438
|
async function build(ctx, next) {
|
|
70
439
|
const { filterByTk } = ctx.action.params;
|
|
71
440
|
const repository = ctx.db.getRepository("aiBuildGuideSpaces");
|
|
@@ -83,106 +452,18 @@ async function build(ctx, next) {
|
|
|
83
452
|
filterByTk,
|
|
84
453
|
values: {
|
|
85
454
|
status: "building",
|
|
86
|
-
|
|
455
|
+
buildPhase: "queued",
|
|
456
|
+
buildLog: "Build queued"
|
|
87
457
|
}
|
|
88
458
|
});
|
|
89
|
-
|
|
90
|
-
const bgRepo = db.getRepository("aiBuildGuideSpaces");
|
|
91
|
-
const documents = await space.getDocuments();
|
|
92
|
-
let documentsText = "";
|
|
93
|
-
if (documents && documents.length > 0) {
|
|
94
|
-
const texts = await Promise.all(
|
|
95
|
-
documents.map(async (doc) => {
|
|
96
|
-
const content = await fetchFileContent(app, doc);
|
|
97
|
-
return `--- Document: ${doc.filename} ---
|
|
98
|
-
${content}
|
|
99
|
-
`;
|
|
100
|
-
})
|
|
101
|
-
);
|
|
102
|
-
documentsText = texts.join("\n");
|
|
103
|
-
}
|
|
104
|
-
const aiPlugin = app.pm.get("ai");
|
|
105
|
-
if (!aiPlugin) {
|
|
106
|
-
throw new Error("Plugin AI is not available");
|
|
107
|
-
}
|
|
108
|
-
const { llmService, model, systemPrompt } = space.get();
|
|
109
|
-
if (!llmService || !model) {
|
|
110
|
-
throw new Error("LLM Service or model is missing in space configuration");
|
|
111
|
-
}
|
|
112
|
-
const serviceData = await aiPlugin.aiManager.getLLMService({ llmService, model });
|
|
113
|
-
const provider = serviceData.provider;
|
|
114
|
-
const messages = [];
|
|
115
|
-
if (systemPrompt) {
|
|
116
|
-
messages.push(new import_messages.SystemMessage(systemPrompt));
|
|
117
|
-
}
|
|
118
|
-
const instruction = `Please generate an HTML user guide based on the following documents. Output ONLY valid HTML without Markdown blocks.
|
|
119
|
-
|
|
120
|
-
Documents:
|
|
121
|
-
${documentsText}`;
|
|
122
|
-
messages.push(new import_messages.HumanMessage(instruction));
|
|
123
|
-
const response = await provider.chatModel.invoke(messages);
|
|
124
|
-
let rawHtml = typeof response.content === "string" ? response.content : JSON.stringify(response.content);
|
|
125
|
-
rawHtml = rawHtml.replace(/^```html\s*/, "").replace(/```\s*$/, "");
|
|
126
|
-
const cleanHtml = (0, import_sanitize_html.default)(rawHtml, {
|
|
127
|
-
allowedTags: [
|
|
128
|
-
"div",
|
|
129
|
-
"p",
|
|
130
|
-
"h1",
|
|
131
|
-
"h2",
|
|
132
|
-
"h3",
|
|
133
|
-
"h4",
|
|
134
|
-
"h5",
|
|
135
|
-
"h6",
|
|
136
|
-
"ul",
|
|
137
|
-
"ol",
|
|
138
|
-
"li",
|
|
139
|
-
"table",
|
|
140
|
-
"thead",
|
|
141
|
-
"tbody",
|
|
142
|
-
"tr",
|
|
143
|
-
"td",
|
|
144
|
-
"th",
|
|
145
|
-
"a",
|
|
146
|
-
"img",
|
|
147
|
-
"span",
|
|
148
|
-
"strong",
|
|
149
|
-
"em",
|
|
150
|
-
"code",
|
|
151
|
-
"pre",
|
|
152
|
-
"blockquote",
|
|
153
|
-
"br",
|
|
154
|
-
"hr"
|
|
155
|
-
],
|
|
156
|
-
allowedAttributes: {
|
|
157
|
-
"a": ["href", "target"],
|
|
158
|
-
"img": ["src", "alt", "width", "height"],
|
|
159
|
-
"*": ["style", "class"]
|
|
160
|
-
},
|
|
161
|
-
allowedStyles: {
|
|
162
|
-
"*": {
|
|
163
|
-
"color": [/^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, /^rgb/, /^rgba/],
|
|
164
|
-
"background-color": [/^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/, /^rgb/, /^rgba/],
|
|
165
|
-
"text-align": [/^left$/, /^right$/, /^center$/, /^justify$/],
|
|
166
|
-
"font-size": [/^\d+(?:px|em|%)$/]
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
await bgRepo.update({
|
|
171
|
-
filterByTk,
|
|
172
|
-
values: {
|
|
173
|
-
generatedHtml: cleanHtml,
|
|
174
|
-
status: "completed"
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
})();
|
|
178
|
-
bgPromise.catch(async (error) => {
|
|
459
|
+
runBuild(app, db, filterByTk).catch(async (error) => {
|
|
179
460
|
app.log.error("Build Guide Background Error", error);
|
|
180
461
|
try {
|
|
181
|
-
|
|
182
|
-
await bgRepo.update({
|
|
462
|
+
await repository.update({
|
|
183
463
|
filterByTk,
|
|
184
464
|
values: {
|
|
185
465
|
status: "error",
|
|
466
|
+
buildPhase: "error",
|
|
186
467
|
buildLog: error.message || String(error)
|
|
187
468
|
}
|
|
188
469
|
});
|
|
@@ -197,6 +478,7 @@ ${documentsText}`;
|
|
|
197
478
|
filterByTk,
|
|
198
479
|
values: {
|
|
199
480
|
status: "error",
|
|
481
|
+
buildPhase: "error",
|
|
200
482
|
buildLog: error.message || String(error)
|
|
201
483
|
}
|
|
202
484
|
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var getMarkdown_exports = {};
|
|
28
|
+
__export(getMarkdown_exports, {
|
|
29
|
+
getMarkdown: () => getMarkdown
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(getMarkdown_exports);
|
|
32
|
+
async function getMarkdown(ctx, next) {
|
|
33
|
+
const { filterByTk } = ctx.action.params;
|
|
34
|
+
const { resourceName } = ctx.action;
|
|
35
|
+
const repository = ctx.db.getRepository(resourceName);
|
|
36
|
+
const model = await repository.findById(filterByTk);
|
|
37
|
+
if (!model) {
|
|
38
|
+
ctx.throw(404, "User Guide not found");
|
|
39
|
+
}
|
|
40
|
+
if (model.get("status") !== "completed") {
|
|
41
|
+
ctx.throw(400, "User Guide is not ready yet");
|
|
42
|
+
}
|
|
43
|
+
ctx.body = model.get("generatedMarkdown") || "";
|
|
44
|
+
ctx.withoutDataWrapping = true;
|
|
45
|
+
ctx.set({
|
|
46
|
+
"Content-Type": "text/markdown; charset=UTF-8"
|
|
47
|
+
});
|
|
48
|
+
await next();
|
|
49
|
+
}
|
|
50
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
51
|
+
0 && (module.exports = {
|
|
52
|
+
getMarkdown
|
|
53
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var ai_build_guide_pages_exports = {};
|
|
28
|
+
__export(ai_build_guide_pages_exports, {
|
|
29
|
+
default: () => ai_build_guide_pages_default
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(ai_build_guide_pages_exports);
|
|
32
|
+
var import_database = require("@nocobase/database");
|
|
33
|
+
var ai_build_guide_pages_default = (0, import_database.defineCollection)({
|
|
34
|
+
name: "aiBuildGuidePages",
|
|
35
|
+
shared: true,
|
|
36
|
+
dumpRules: "required",
|
|
37
|
+
migrationRules: ["overwrite", "schema-only"],
|
|
38
|
+
timestamps: true,
|
|
39
|
+
fields: [
|
|
40
|
+
{
|
|
41
|
+
type: "uid",
|
|
42
|
+
name: "id",
|
|
43
|
+
primaryKey: true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: "belongsTo",
|
|
47
|
+
name: "space",
|
|
48
|
+
target: "aiBuildGuideSpaces",
|
|
49
|
+
foreignKey: "spaceId"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "integer",
|
|
53
|
+
name: "sort",
|
|
54
|
+
defaultValue: 0
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "string",
|
|
58
|
+
name: "title"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "string",
|
|
62
|
+
name: "slug"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
name: "goal"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: "json",
|
|
70
|
+
name: "planItem"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "string",
|
|
74
|
+
name: "status",
|
|
75
|
+
defaultValue: "pending"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
type: "text",
|
|
79
|
+
name: "generatedHtml"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
name: "generatedMarkdown"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
name: "buildLog"
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
});
|